{"id":20113555,"url":"https://github.com/cloudacademy/aks-voteapp-demo","last_synced_at":"2025-05-06T12:30:33.109Z","repository":{"id":77791311,"uuid":"259497610","full_name":"cloudacademy/aks-voteapp-demo","owner":"cloudacademy","description":"Azure AKS VoteApp Demo","archived":false,"fork":false,"pushed_at":"2024-02-15T21:26:18.000Z","size":655,"stargazers_count":9,"open_issues_count":1,"forks_count":37,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T12:21:34.015Z","etag":null,"topics":["aks","azure","cloudacademy","devops","kubernetes","microservices"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudacademy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-28T01:19:48.000Z","updated_at":"2024-01-13T13:34:01.000Z","dependencies_parsed_at":"2024-11-13T18:29:27.598Z","dependency_job_id":"7e03b07b-12fe-4556-b26a-86eb7da5498a","html_url":"https://github.com/cloudacademy/aks-voteapp-demo","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudacademy%2Faks-voteapp-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudacademy%2Faks-voteapp-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudacademy%2Faks-voteapp-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudacademy%2Faks-voteapp-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudacademy","download_url":"https://codeload.github.com/cloudacademy/aks-voteapp-demo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252683370,"owners_count":21788026,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aks","azure","cloudacademy","devops","kubernetes","microservices"],"created_at":"2024-11-13T18:24:57.315Z","updated_at":"2025-05-06T12:30:32.808Z","avatar_url":"https://github.com/cloudacademy.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# AKS VoteApp Deployment Instructions\n\nThe following instructions are used to demonstrate how to provision an AKS cluster on Azure and deploy a cloud native application into it.\n\n:metal: \n\nThe cloud native application is architected using microservices and is presented to the user as a web application. The application frontend provides the end-user with the ability to vote on one of 6 programming languages: C#, Python, JavaScript, Go, Java, and/or NodeJS. Voting results in AJAX calls being made from the browser to an API which in turn then saves the results into a MongoDB database.\n\n![AKSDeployment](./doc/aks-deployment.png)\n\n# Updates/Changelog\nThu 15 Feb 2024\n* Removed `--docker-bridge-address` option on `az aks create` as this has been deprecated\n* Corrected name of nginx svc from `aks-nginx-ingress-nginx-ingress` to `aks-nginx-ingress-controller`\n* Increased Kubernetes version to 1.27.7 to align with current Azure default\n* Updated network policy selectors to use correct `podSelector` labels for nginx\n* Added commands to delete the service principle\n\nMon 16 Jan 2023 11:57:20 NZDT\n* Updated instructions and retested end-to-end\n* Added instruction to create resource group\n* Added ```--location``` parameter to commands that use it\n* Removed deprecated ```--skip-assignment``` parameter\n* Upgraded cluster version to **1.25.2**\n* Updated DNS testing to use the container image ```cloudacademydevops/networkutils:v2```\n* Updated the Ingress resources to **networking.k8s.io/v1**\n* Updated networkpolicy curl commands to include ```--max-time 5``` to control connection timeout\n\nTue  3 Nov 2020 20:51:59 NZDT\n* Updated instructions and retested end-to-end\n* Upgraded cluster version to 1.18.8\n* Mongo deployment now pinned down to use 4.2 to ensure replication setup initiates\n* Helm nginx-ingress deployment updated\n* Minor fixes to various ```kubectl``` commands\n\n# Client Tools\nTested with the following client tool versions\n* ```az``` 2.44.1\n* ```kubectl``` 1.27.7\n* ```helm``` 3.10.3\n\n![VoteApp](./doc/voteapp.png)\n\nAlong the way, you'll get to see how to work with the following AKS cluster resources:\n* Namespace\n* Secret\n* Deployment\n* Service\n* StatefulSet\n* PersistentVolume\n* PersistentVolumeClaim\n* IngressController (Nginx)\n* Ingress\n* NetworkPolicy\n\n# STEP 1:\nCreate a new AKS cluster\n\n## STEP 1.1:\nAuthenticate the Azure CLI. In the terminal execute the following command:\n\n```\naz login\n```\n\n## STEP 1.2:\n\nDefine the variables used during the setup and installation:\n\n```\n{\nCLUSTER_NAME=akstest\nRESOURCE_GROUP=aks\nLOCATION=westus\nVNET_NAME=cloudacademy-aks-vnet\nK8S_VERSION=1.27.7\n}\n```\n\n## STEP 1.3:\n\nCreate new resource group.\n\n```\naz group create --location $LOCATION --resource-group $RESOURCE_GROUP\n```\n\n## STEP 1.4:\n\nCreate a new service principal. The AKS cluster will later be created with this.\n\n```\nSP=$(az ad sp create-for-rbac --name spdemocluster)\n\nAPPID=$(echo $SP | jq -r .appId)\nPASSWD=$(echo $SP | jq -r .password)\n\necho APPID: $APPID\necho PASSWD: $PASSWD\n```\n\n## STEP 1.5:\n\nCreate a new vnet and subnet for the AKS cluster\n\n```\naz network vnet create \\\n    --name $VNET_NAME \\\n    --resource-group $RESOURCE_GROUP \\\n    --location $LOCATION \\\n    --address-prefixes 10.0.0.0/8 \\\n    --subnet-name aks-subnet \\\n    --subnet-prefix 10.240.0.0/16\n```\n\n## STEP 1.6:\n\nAssign the contributor role to the service principal scoped on the vnet previously created\n\n```\nVNETID=$(az network vnet show \\\n  --name $VNET_NAME \\\n  --resource-group $RESOURCE_GROUP \\\n  --query id \\\n  -o tsv)\n\necho VNETID: $VNETID\n\naz role assignment create \\\n  --assignee $APPID \\\n  --scope $VNETID \\\n  --role Contributor\n```\n\n## STEP 1.7:\n\nCreate the AKS cluster and place it in the vnet subnet previously created\n\nStandard_B2ms\n1.15.10\n\n```\nSUBNETID=$(az network vnet subnet show \\\n  --name aks-subnet \\\n  --resource-group $RESOURCE_GROUP \\\n  --vnet-name $VNET_NAME \\\n  --query id \\\n  -o tsv)\n\necho SUBNETID: $SUBNETID\n\naz aks create \\\n  --name $CLUSTER_NAME \\\n  --resource-group $RESOURCE_GROUP \\\n  --location $LOCATION \\\n  --node-count 2 \\\n  --node-vm-size Standard_D4s_v3 \\\n  --vm-set-type VirtualMachineScaleSets \\\n  --kubernetes-version $K8S_VERSION \\\n  --network-plugin azure \\\n  --service-cidr 10.0.0.0/16 \\\n  --dns-service-ip 10.0.0.10 \\\n  --vnet-subnet-id $SUBNETID \\\n  --generate-ssh-keys \\\n  --network-policy azure \\\n  --service-principal $APPID \\\n  --client-secret $PASSWD \n```\n\nThis takes between **5-10 minutes** to complete so sit back and relax, its major chill time :+1:\n\nCongrats!! \nYou've just baked yourself a fresh AKS Kubernetes cluster!!\n\n# STEP 2:\n\nTest the kubectl client cluster authencation\n\n```\naz aks get-credentials -g $RESOURCE_GROUP --name $CLUSTER_NAME --admin\nkubectl get nodes\n```\n\n```\nkubectl config view\nkubectl config get-contexts\nkubectl config current-context\n```\n\n# STEP 3:\n\nInstall the Nginx Ingress Controller. This will allow us to direct inbound exteranl **HTTP** calls to the **Frontend** and **API** services that will be deployed into the AKS cluster.\n\n## STEP 3.1:\n\nCreate the ```nginx-ingress``` namespace - holds the Nginx Ingress Controller components.\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: nginx-ingress\n  labels:\n    name: nginx-ingress\nEOF\n```\n\n## STEP 3.2:\n\nUse Helm to install the Nginx Ingress Controller.\n\nNotes:\n1. The ```helm``` client needs to be installed locally\n2. This has beem successfully tested with ```helm``` version v3.10.3\n3. The ```helm``` client authenticates to the AKS cluster using the same ```~/.kube/config``` credentials established earlier\n4. This has beem successfully tested with the helm chart ```nginx-stable/nginx-ingress``` version 0.16.0 and app version 3.0.0\n\n\n```\nhelm version\nhelm repo add nginx-stable https://helm.nginx.com/stable\nhelm repo update\nhelm search repo nginx-ingress\nhelm install aks-nginx-ingress nginx-stable/nginx-ingress --namespace nginx-ingress\n```\n\n## STEP 3.3:\n\nQuery the Nginx Ingress Controller and determine the public ip address that has been assigned to it.\n\nWait until the Nginx Ingress Controller has been allocated a public IP address\n\n```\nkubectl get svc aks-nginx-ingress-controller -n nginx-ingress --watch\n```\n\nUse ```Ctrl-C``` key sequence to exit the watch\n\nNotes:\n1. The public IP address will be used to create both the API and Frontend service FQDNs used later on\n2. The API FQDN will be used to within the API's Ingress resource for host based path routing\n3. The Frontend FQDN will be used to within the Frontend's Ingress resource for host based path routing\n4. The https://nip.io/ dynamic DNS service is being used to provide wildcard DNS\n\n```\nkubectl get svc aks-nginx-ingress-controller -n nginx-ingress -o json\n\nINGRESS_PUBLIC_IP=$(kubectl get svc aks-nginx-ingress-controller -n nginx-ingress -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')\n\necho INGRESS_PUBLIC_IP: $INGRESS_PUBLIC_IP\n\nAPI_PUBLIC_FQDN=api.$INGRESS_PUBLIC_IP.nip.io\nFRONTEND_PUBLIC_FQDN=frontend.$INGRESS_PUBLIC_IP.nip.io\n\necho API_PUBLIC_FQDN: $API_PUBLIC_FQDN\necho FRONTEND_PUBLIC_FQDN: $FRONTEND_PUBLIC_FQDN\n```\n\n# STEP 4:\n\nCreate the ```cloudacademy``` namespace - holds the main sample cloud native application components\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: cloudacademy\n  labels:\n    name: cloudacademy\nEOF\n```\n\nConfigure the ```cloudacademy``` namespace to be the default\n\n```\nkubectl config set-context --current --namespace cloudacademy\n```\n\n# STEP 5:\n\nDeploy MongoDB 3 x ReplicaSet\n\n![AKSDeployment](./doc/aks-mongodb.png)\n\n## STEP 5.1:\n\nDisplay the available AKS storage classes. We use the default storage class in the following MongoDb deployment.\n\n```\nkubectl get storageclass\n```\n\n## STEP 5.2:\n\nCreate a new Mongo StatefulSet name ```mongo```\n\nNote: security (--auth flag) hasn't been enabled on the MongoDb database - done to make the demonstration quicker.\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: mongo\n  namespace: cloudacademy\nspec:\n  serviceName: mongo\n  replicas: 3\n  selector:\n    matchLabels:\n      role: db\n  template:\n    metadata:\n      labels:\n        role: db\n        env: demo\n        replicaset: rs0.main\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - weight: 100\n            podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: replicaset\n                  operator: In\n                  values:\n                  - rs0.main\n              topologyKey: kubernetes.io/hostname\n      terminationGracePeriodSeconds: 10\n      containers:\n        - name: mongo\n          image: mongo:4.2\n          command:\n            - \"numactl\"\n            - \"--interleave=all\"\n            - \"mongod\"\n            - \"--wiredTigerCacheSizeGB\"\n            - \"0.1\"\n            - \"--bind_ip\"\n            - \"0.0.0.0\"\n            - \"--replSet\"\n            - \"rs0\"\n          ports:\n            - containerPort: 27017\n          volumeMounts:\n            - name: mongodb-persistent-storage-claim\n              mountPath: /data/db\n  volumeClaimTemplates:\n    - metadata:\n        name: mongodb-persistent-storage-claim\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        storageClassName: default\n        resources:\n          requests:\n            storage: 0.5Gi\nEOF\n```\n\n## STEP 5.3:\n\nExamine the Mongo Pods launch ordered sequence\n\n```\nkubectl get pods --watch\nkubectl get pods\nkubectl get pods --show-labels\nkubectl get pods -l role=db\n```\n\nUse ```Ctrl-C``` key sequence to exit the watch\n\nDisplay the MongoDB Pods, Persistent Volumes and Persistent Volume Claims\n\n```\nkubectl get pod,pv,pvc\n```\n\n## STEP 5.4:\n\nCreate a new Headless Service for Mongo named ```mongo```\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Service\nmetadata:\n  name: mongo\n  namespace: cloudacademy\n  labels:\n    role: db\n    env: demo\nspec:\n  ports:\n  - port: 27017\n    targetPort: 27017\n  clusterIP: None\n  selector:\n    role: db\nEOF\n```\n\n## STEP 5.5:\n\nExamine the Mongo Headless Service\n\n```\nkubectl get svc\n```\n\nExamine the DNS records for the Mongo Headless Service\n\n```\nkubectl run -i --tty --restart=Never --rm utils --image cloudacademydevops/networkutils:v2 -- host mongo\n```\n\nExamine the individual DNS records for the Mongo Headless Service\n\n```\nkubectl run -i --tty --restart=Never --rm utils --image cloudacademydevops/networkutils:v2 -- bash -c 'for i in {0..2}; do host mongo-$i.mongo; done'\n```\n\n## STEP 5.6:\n\nConfirm that the mongo shell can resolve each of the 3 mongo headless service assigned dns names:\n\n```\nfor i in {0..2}; do kubectl exec -it mongo-0 -- mongo mongo-$i.mongo --eval \"print('mongo-$i.mongo succeeded')\" \u0026\u0026 echo; done\n```\n\nOn the ```mongo-0``` pod, initialise the mongo database replica set:\n\n```\ncat \u003c\u003c EOF | kubectl exec -it mongo-0 -- mongo\nrs.initiate();\nsleep(2000);\nrs.add(\"mongo-1.mongo:27017\");\nsleep(2000);\nrs.add(\"mongo-2.mongo:27017\");\nsleep(2000);\ncfg = rs.conf();\ncfg.members[0].host = \"mongo-0.mongo:27017\";\nrs.reconfig(cfg, {force: true});\nsleep(5000);\nEOF\n```\n\nConfirm the mongo database replication status:\n\n```\nkubectl exec -it mongo-0 -- mongo --eval \"rs.status()\" | grep \"PRIMARY\\|SECONDARY\"\n```\n\n## STEP 5.7:\n\nLoad the initial voting app data into the Mongo database\n\n```\ncat \u003c\u003c EOF | kubectl exec -it mongo-0 -- mongo\nuse langdb;\ndb.languages.insert({\"name\" : \"csharp\", \"codedetail\" : { \"usecase\" : \"system, web, server-side\", \"rank\" : 5, \"compiled\" : false, \"homepage\" : \"https://dotnet.microsoft.com/learn/csharp\", \"download\" : \"https://dotnet.microsoft.com/download/\", \"votes\" : 0}});\ndb.languages.insert({\"name\" : \"python\", \"codedetail\" : { \"usecase\" : \"system, web, server-side\", \"rank\" : 3, \"script\" : false, \"homepage\" : \"https://www.python.org/\", \"download\" : \"https://www.python.org/downloads/\", \"votes\" : 0}});\ndb.languages.insert({\"name\" : \"javascript\", \"codedetail\" : { \"usecase\" : \"web, client-side\", \"rank\" : 7, \"script\" : false, \"homepage\" : \"https://en.wikipedia.org/wiki/JavaScript\", \"download\" : \"n/a\", \"votes\" : 0}});\ndb.languages.insert({\"name\" : \"go\", \"codedetail\" : { \"usecase\" : \"system, web, server-side\", \"rank\" : 12, \"compiled\" : true, \"homepage\" : \"https://golang.org\", \"download\" : \"https://golang.org/dl/\", \"votes\" : 0}});\ndb.languages.insert({\"name\" : \"java\", \"codedetail\" : { \"usecase\" : \"system, web, server-side\", \"rank\" : 1, \"compiled\" : true, \"homepage\" : \"https://www.java.com/en/\", \"download\" : \"https://www.java.com/en/download/\", \"votes\" : 0}});\ndb.languages.insert({\"name\" : \"nodejs\", \"codedetail\" : { \"usecase\" : \"system, web, server-side\", \"rank\" : 20, \"script\" : false, \"homepage\" : \"https://nodejs.org/en/\", \"download\" : \"https://nodejs.org/en/download/\", \"votes\" : 0}});\nEOF\n```\n\n## STEP 5.8:\n\nConfirm data has been loaded correctly\n\n```\nkubectl exec -it mongo-0 -- mongo langdb --eval \"db.languages.find().pretty()\"\n```\n\n# STEP 6:\n\n![AKSDeployment - API](./doc/aks-api.png)\n\nDeploy the API consisting of a Deployment, Service, and Ingress:\n\n## STEP 6.1:\n\nCreate a secret to store the mongodb connection credentials\n\nNote: this is for demonstration purposes only - security (auth) hasn't been enabled on the MongoDb database.\n\nThe username and password values need to be ```base64``` encode first like so\n\n```\necho -n 'admin' | base64\necho -n 'password' | base64\n```\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mongodb-secret\n  namespace: cloudacademy\ndata:\n  username: YWRtaW4=\n  password: cGFzc3dvcmQ=\nEOF\n```\n\nAPI: create **deployment** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: api\n  namespace: cloudacademy\n  labels:\n    role: api\n    env: demo\nspec:\n  replicas: 4\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 1\n      maxUnavailable: 25%\n  selector:\n    matchLabels:\n      role: api\n  template:\n    metadata:\n      labels:\n        role: api\n    spec:\n      containers:\n      - name: api\n        image: cloudacademydevops/api:v2\n        imagePullPolicy: Always\n        env:\n          - name: MONGO_CONN_STR\n            value: mongodb://mongo-0.mongo,mongo-1.mongo,mongo-2.mongo:27017/langdb?replicaSet=rs0\n          - name: MONGO_USERNAME\n            valueFrom:\n              secretKeyRef:\n                name: mongodb-secret\n                key: username\n          - name: MONGO_PASSWORD\n            valueFrom:\n              secretKeyRef:\n                name: mongodb-secret\n                key: password\n        ports:\n        - containerPort: 8080\n        livenessProbe:\n          httpGet:\n            path: /ok\n            port: 8080\n          initialDelaySeconds: 2\n          periodSeconds: 5\n        readinessProbe:\n          httpGet:\n             path: /ok\n             port: 8080\n          initialDelaySeconds: 5\n          periodSeconds: 5\n          successThreshold: 1\nEOF\n```\n\n## STEP 6.2:\n\nAPI: create **service** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Service\nmetadata:\n  name: api\n  namespace: cloudacademy\n  labels:\n    role: api\n    env: demo\nspec:\n  ports:\n   - protocol: TCP\n     port: 8080\n  selector:\n    role: api\nEOF\n```\n\n## STEP 6.3:\n\nAPI: create **ingress** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: api\n  namespace: cloudacademy\n  annotations:\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: $API_PUBLIC_FQDN\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: api\n                port:\n                  number: 8080\nEOF\n```\n\n## STEP 6.4:\n\n- Examine the rollout of the API deployment\n- Examine the pods to confirm that they are up and running\n- Examine the API pod log to see that it has successfully connected to the MongoDB replicaset\n- Examine the API service details\n\n```\nkubectl rollout status deployment api\nkubectl get pods\nkubectl get pods -l role=api\nkubectl logs \u003cAPI_POD_NAME_HERE\u003e\nkubectl get svc\n```\n\n## STEP 6.5:\n\nTest the API route url - test the ```/ok```, ```/languages```, and ```/languages/{name}``` endpoints\n\n```\ncurl -s $API_PUBLIC_FQDN/ok\n```\n\nNote: The following commands leverage the [jq](https://stedolan.github.io/jq/) utility to format the json data responses\n\n```\ncurl -s $API_PUBLIC_FQDN/languages | jq .\ncurl -s $API_PUBLIC_FQDN/languages/go | jq .\ncurl -s $API_PUBLIC_FQDN/languages/java | jq .\ncurl -s $API_PUBLIC_FQDN/languages/nodejs | jq .\n```\n\n# STEP 7:\n\n![AKSDeployment](./doc/aks-frontend.png)\n\nCreate a new frontend Deployment\n\nNotes: \n1. The value stored in the ```$API_PUBLIC_FQDN``` variable is injected into the frontend container's ```REACT_APP_APIHOSTPORT``` environment var - this tells the frontend where to send browser initiated API AJAX calls\n\n## STEP 7.1:\n\nFrontend: create **deployment** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: frontend\n  namespace: cloudacademy\n  labels:\n    role: frontend\n    env: demo\nspec:\n  replicas: 4\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 1\n      maxUnavailable: 25%\n  selector:\n    matchLabels:\n      role: frontend\n  template:\n    metadata:\n      labels:\n        role: frontend\n    spec:\n      containers:\n      - name: frontend\n        image: cloudacademydevops/frontend:v10\n        imagePullPolicy: Always\n        env:\n          - name: REACT_APP_APIHOSTPORT\n            value: $API_PUBLIC_FQDN\n        ports:\n        - containerPort: 8080\n        livenessProbe:\n          httpGet:\n            path: /ok\n            port: 8080\n          initialDelaySeconds: 2\n          periodSeconds: 5\n        readinessProbe:\n          httpGet:\n             path: /ok\n             port: 8080\n          initialDelaySeconds: 5\n          periodSeconds: 5\n          successThreshold: 1\nEOF\n```\n\n## STEP 7.2:\n\nFrontend: create **service** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: v1\nkind: Service\nmetadata:\n  name: frontend\n  namespace: cloudacademy\n  labels:\n    role: frontend\n    env: demo\nspec:\n  ports:\n   - protocol: TCP\n     port: 8080\n  selector:\n    role: frontend\nEOF\n```\n\n## STEP 7.3:\n\nFrontend: create **ingress** resource\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: frontend\n  namespace: cloudacademy\n  annotations:\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: $FRONTEND_PUBLIC_FQDN\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: frontend\n                port:\n                  number: 8080\nEOF\n```\n\n## STEP 7.4:\n\nExamine the rollout of the Frontend Deployment\n\n```\nkubectl rollout status deployment frontend\nkubectl get pods\nkubectl get pods -l role=frontend\n```\n\n## STEP 7.5:\n\nUse the ```curl``` command to test the application via the frontend route url\n\n```\ncurl -s -I $FRONTEND_PUBLIC_FQDN\ncurl -s -i $FRONTEND_PUBLIC_FQDN\n```\n\nGenerate the frontend URL\n\n```\necho http://$FRONTEND_PUBLIC_FQDN\n```\n\nNow test the full end-to-end application using the Chrome browser...\n\nNote: Use the Developer Tools within the Chrome browser to record, filter, and observe the AJAX traffic (XHR) which is generated when any of the +1 vote buttons are clicked.\n\n![VoteApp](./doc/voteapp.png)\n\n# STEP 8\n\nQuery the MongoDb database directly to observe the updated vote data.\n\n```\nkubectl exec -it mongo-0 -- mongo langdb --eval \"db.languages.find().pretty()\"\n```\n\n# STEP 9\n\nSetup and install Network Policies to control pod-to-pod traffic\n\n# STEP 9.1\n\nDefault deny all network policy for pod-to-pod traffic within the ```cloudacademy``` namespace\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny-all\n  namespace: cloudacademy\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\nEOF\n```\n\nTest to confirm that the frontend traffic path is now blocked\n\n```\ncurl -vv -i --max-time 5 $FRONTEND_PUBLIC_FQDN\n```\n\n# STEP 9.2\n\nAllow mongo-to-mongo pod traffic, required for MongoDb data replication\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\nkind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  name: allow-from-mongo-to-mongo\n  namespace: cloudacademy\nspec:\n  podSelector:\n    matchLabels:\n      role: db\n  ingress:\n  - from:\n      - podSelector:\n          matchLabels:\n            role: db\nEOF\n```\n\n# STEP 9.3\n\nAllow api-to-mongo pod traffic, required to allow the API to read/write data into the MongoDb database\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\nkind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  name: allow-from-api-to-mongo\n  namespace: cloudacademy\nspec:\n  podSelector:\n    matchLabels:\n      role: db\n  ingress:\n  - from:\n      - podSelector:\n          matchLabels:\n            role: api\nEOF\n```\n\n# STEP 9.4\n\nAllow ingress-to-api pod traffic, required to allow API ajax calls from the browser\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\nkind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  name: allow-from-ingress-to-api\n  namespace: cloudacademy\nspec:\n  podSelector:\n    matchLabels:\n      role: api\n  ingress:\n  - from:\n      - podSelector:\n          matchLabels:\n            app.kubernetes.io/instance: aks-nginx-ingress\n        namespaceSelector:\n          matchLabels:\n            name: nginx-ingress\nEOF\n```\n\n# STEP 9.5\n\nAllow ingress-to-frontend pod traffic, required to allow the frontend (html, js, css) to be requested by the browser\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\nkind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  name: allow-from-ingress-to-frontend\n  namespace: cloudacademy\nspec:\n  podSelector:\n    matchLabels:\n      role: frontend\n  ingress:\n  - from:\n      - podSelector:\n          matchLabels:\n            app.kubernetes.io/instance: aks-nginx-ingress\n        namespaceSelector:\n          matchLabels:\n            name: nginx-ingress\nEOF\n```\n\n# STEP 9.6\n\nAllow ingress-to-kube-dns from pod traffic in cloudacademy namespace, required to allow pod dns traffic to resolve\n\n```\ncat \u003c\u003c EOF | kubectl apply -f -\nkind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  name: allow-from-cloudacademy-ns-to-kube-dns\n  namespace: kube-system\nspec:\n  podSelector:\n    matchLabels:\n      k8s-app: kube-dns\n  ingress:\n  - from:\n      - namespaceSelector:\n          matchLabels:\n            name: cloudacademy\nEOF\n```\n\n# Step 10\n\nTest to confirm that the frontend traffic path is now repaired and working\n\n```\ncurl -vv -i --max-time 5 $FRONTEND_PUBLIC_FQDN\n```\n\nTest the application again within the browser and generate some voting traffic\n\nQuery the MongoDb database directly to observe the updated vote data\n\n```\nkubectl exec -it mongo-0 -- mongo langdb --eval \"db.languages.find().pretty()\"\n```\n\n# STEP 11\n\nWhen you've finished with the AKS cluster and no longer need it **tear it down** to avoid ongoing **charges**!!\n\n```\naz aks delete --name $CLUSTER_NAME -g $RESOURCE_GROUP\n```\n\n# Step 12\n\nFind the ID of the `spdemocluster` service principle we created earlier, and delete it:\n\n```\nsp_id=$(az ad sp list --display-name spdemocluster --query \"[0].id\" -o tsv)\necho sp_id=$sp_id\naz ad sp delete --id $sp_id\napp_id=$(az ad app list --display-name spdemocluster --query \"[0].id\" -o tsv)\necho app_id=$app_id\naz ad app delete --id $app_id\n```\n\nGood luck with your AKS adventures!!\n\n:rocket:\n\nhttp://cloudacademy.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudacademy%2Faks-voteapp-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudacademy%2Faks-voteapp-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudacademy%2Faks-voteapp-demo/lists"}