diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ecb139 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# python-app-with-azure-kubernetes +A demo python application used in blog Application Deployment on Azure Kubernetes Services. + +This repo is used in the Medium blog [Application Deployment with Azure Kubernetes Service and Azure Pipelines](https://medium.com/faun/application-deployment-with-azure-kubernetes-service-and-azure-pipelines-a0bf43916746) + +### Steps + +**Create a resource group in Azure Subscription**
+`az group create -l southcentralus -n cloudorbit-resource-grp --subscription pay-as-you-go` + +**Create Azure k8s cluster**
+`az aks create -g cloudorbit-resource-grp -n cloudobrit-cluster --node-vm-size Standard_B2s --node-count 2 --generate-ssh-keys` + +**Create Azure Container Registry**
+`az acr create -g cloudorbit-resource-grp -n cloudorbit --sku Standard` + +**Create Azure Pipelines**
+Follow the steps in the blog to create and setup Azure Pipeline via Azure DevOps console
+ +Once all done, we need to check the running pod, services and horizontal pod autoscaler
+ +**Authenticate your k8s cluster with cloud-shell**
+`az aks get-credentials -g cloudorbit-resource-grp -n cloudobrit-cluster` + +**Get deployments**
+`kubectl -n dev get deployments` + +**Get pods**
+`kubectl -n dev get pods` + +**Get services**
+`kubectl -n dev get svc` + +**Get Horizontal Pod Autoscaler**
+`kubectl -n dev get hpa` + +**To check the app**
+`curl http://[external-ip]:5000` + +**To describe pods specification and check logs**
+`kubectl -n dev describe pods [pod-name]` +`kubectl -n dev logs [pod-name]` + +*We can scale our deployment manually as well as automatically. For automatic scaling, we have defined HPA in YAML which increases one replica of pod whenever the average traffic on the running pod goes beyond 80%. It will increase replica up to the max number we defined in YAML manifest that’s 3 in our case.* + +**To manual scale**
+`kubectl -n dev scale deployment python-app --replicas=3` +`kubectl -n dev get deployment python-app` + +**For interactive UI console of k8s cluster**
+`az aks browse -g cloudorbit-resource-grp -n cloudorbit-cluster` + +**For entering into pods**
+`kubectl -n dev exec -it [pod-name] -- /bin/bash` diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..7dc4796 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,78 @@ +# Deploy to Azure Kubernetes Service +# Build and push image to Azure Container Registry; Deploy to Azure Kubernetes Service +# https://docs.microsoft.com/azure/devops/pipelines/languages/docker + +trigger: +- master + +resources: +- repo: self + +variables: + + # Container registry service connection established during pipeline creation + dockerRegistryServiceConnection: '43322574-036c-4786-83bd-e924f31a2a52' + imageRepository: 'pythonapp' + containerRegistry: 'cloudorbit.azurecr.io' + dockerfilePath: '**/Dockerfile' + tag: '$(Build.BuildId)' + imagePullSecret: 'cloudorbit429779af-auth' + + # Agent VM image name + vmImageName: 'ubuntu-latest' + + +stages: +- stage: Build + displayName: Build stage + jobs: + - job: Build + displayName: Build + pool: + vmImage: $(vmImageName) + steps: + - task: Docker@2 + displayName: Build and push an image to azure container registry + inputs: + command: buildAndPush + repository: $(imageRepository) + dockerfile: $(dockerfilePath) + containerRegistry: $(dockerRegistryServiceConnection) + tags: | + $(tag) + + - upload: manifests + artifact: manifests + +- stage: Deploy + displayName: Deploy stage + dependsOn: Build + + jobs: + - deployment: Deploy + displayName: Deploy + pool: + vmImage: $(vmImageName) + environment: 'r4rohanpythonappwithazurekubernetes.dev' + strategy: + runOnce: + deploy: + steps: + - task: KubernetesManifest@0 + displayName: Create imagePullSecret + inputs: + action: createSecret + secretName: $(imagePullSecret) + dockerRegistryEndpoint: $(dockerRegistryServiceConnection) + + - task: KubernetesManifest@0 + displayName: Deploy to Kubernetes cluster + inputs: + action: deploy + manifests: | + $(Pipeline.Workspace)/manifests/app.yml + imagePullSecrets: | + $(imagePullSecret) + containers: | + $(containerRegistry)/$(imageRepository):$(tag) + diff --git a/manifests/app.yml b/manifests/app.yml new file mode 100644 index 0000000..db6cb68 --- /dev/null +++ b/manifests/app.yml @@ -0,0 +1,52 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: python-app + labels: + name: python-app +spec: + replicas: 1 + minReadySeconds: 60 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + name: python-app + spec: + containers: + - name: test + image: cloudorbit.azurecr.io/pythonapp + imagePullPolicy: Always + +--- +kind: Service +apiVersion: v1 +metadata: + name: python-app +spec: + selector: + name: python-app + ports: + - name: port1 + protocol: TCP + port: 5000 + targetPort: 5000 + type: LoadBalancer + +--- +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: python-app +spec: + maxReplicas: 3 + minReplicas: 1 + scaleTargetRef: + apiVersion: extensions/v1beta1 + kind: Deployment + name: python-app + targetCPUUtilizationPercentage: 80 diff --git a/manifests/deployment.yml b/manifests/deployment.yml new file mode 100644 index 0000000..5530c66 --- /dev/null +++ b/manifests/deployment.yml @@ -0,0 +1,16 @@ +apiVersion : apps/v1beta1 +kind: Deployment +metadata: + name: pythonapp +spec: + replicas: 1 + template: + metadata: + labels: + app: pythonapp + spec: + containers: + - name: pythonapp + image: cloudorbit.azurecr.io/pythonapp + ports: + - containerPort: 5000 \ No newline at end of file diff --git a/manifests/nginx-ingress.yml b/manifests/nginx-ingress.yml new file mode 100644 index 0000000..b2a7bab --- /dev/null +++ b/manifests/nginx-ingress.yml @@ -0,0 +1,17 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: nginx-ingress + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 25m +spec: + rules: + - host: domain-name.com + http: + paths: + - path: / + backend: + serviceName: python-app + servicePort: 5000 diff --git a/manifests/service.yml b/manifests/service.yml new file mode 100644 index 0000000..c6761a7 --- /dev/null +++ b/manifests/service.yml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: pythonapp +spec: + type: LoadBalancer + ports: + - port: 5000 + selector: + app: pythonapp \ No newline at end of file diff --git a/python-application/Dockerfile b/python-application/Dockerfile new file mode 100644 index 0000000..4d88ddc --- /dev/null +++ b/python-application/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.6 +WORKDIR /app +COPY requirements.txt ./ +RUN pip install -r requirements.txt +COPY . /app +EXPOSE 5000 +CMD [ "python", "app.py" ] diff --git a/python-application/app.py b/python-application/app.py new file mode 100644 index 0000000..6ea23b5 --- /dev/null +++ b/python-application/app.py @@ -0,0 +1,11 @@ +from flask import Flask +app = Flask(__name__) + + +@app.route('/') +def hello(): + return "Stay inside, stay safe and keep social distancing." + +if __name__ == '__main__': + app.run(debug=True,host='0.0.0.0',port=5000) +app.run(host='0.0.0.0') diff --git a/python-application/requirements.txt b/python-application/requirements.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/python-application/requirements.txt @@ -0,0 +1 @@ +flask