├── README.md ├── api ├── 1.api.deployment.yaml ├── 2.api.service.yaml └── 3.api.ingress.yaml ├── database └── mongo.statefulset.yaml ├── doc └── k8sdeploy.png ├── frontend ├── 1.frontend.deployment.yaml ├── 2.frontend.service.yaml └── 3.frontend.ingress.yaml ├── ingress └── nginx-ingress-controller.yaml ├── namespace └── namespace.yaml └── netpol ├── 1.network.policy.deny-all.yaml ├── 2.network.policy.allow-mongo-mongo.yaml └── 3.network.policy.allow-mongo-api.yaml /README.md: -------------------------------------------------------------------------------- 1 | # CloudAcademy + DevOps 2 | This is part of the [CloudAcademy](https://cloudacademy.com/library/) Kubernetes/React/Go/MongoDB Learning Path! 3 | 4 | * https://github.com/cloudacademy/voteapp-frontend-react 5 | * https://github.com/cloudacademy/voteapp-api-go 6 | * https://github.com/cloudacademy/voteapp-k8s 7 | 8 | # Background 9 | 10 | ![K8s Deployment](/doc/k8sdeploy.png) 11 | 12 | Provides the declarative YAML files used to deploy an end-to-end web voting application to a Kubernetes cluster. The deployment of the web voting application is used to demonstrate the following K8s resources: 13 | * Nginx Ingress Controller 14 | * Deployments 15 | * Services 16 | * Pods 17 | * StatefulSet 18 | * Persistent Volumes 19 | * Persistent Volume Claims 20 | * Network Policies 21 | -------------------------------------------------------------------------------- /api/1.api.deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: api 5 | namespace: cloudacademy 6 | labels: 7 | role: api 8 | env: demo 9 | spec: 10 | replicas: 4 11 | strategy: 12 | type: RollingUpdate 13 | rollingUpdate: 14 | maxSurge: 1 15 | maxUnavailable: 25% 16 | selector: 17 | matchLabels: 18 | role: api 19 | template: 20 | metadata: 21 | labels: 22 | role: api 23 | spec: 24 | containers: 25 | - name: api 26 | image: cloudacademy/api:v1 27 | imagePullPolicy: IfNotPresent 28 | env: 29 | - name: MONGO_CONN_STR 30 | value: "mongodb://mongo-0.mongo,mongo-1.mongo,mongo-2.mongo:27017/langdb" 31 | ports: 32 | - containerPort: 8080 33 | livenessProbe: 34 | httpGet: 35 | path: /ok 36 | port: 8080 37 | initialDelaySeconds: 10 38 | periodSeconds: 5 39 | readinessProbe: 40 | httpGet: 41 | path: /ok 42 | port: 8080 43 | initialDelaySeconds: 5 44 | periodSeconds: 5 45 | successThreshold: 1 46 | -------------------------------------------------------------------------------- /api/2.api.service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: api 5 | namespace: cloudacademy 6 | labels: 7 | role: api 8 | env: demo 9 | spec: 10 | ports: 11 | - protocol: TCP 12 | port: 8080 13 | selector: 14 | role: api -------------------------------------------------------------------------------- /api/3.api.ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: api 5 | namespace: cloudacademy 6 | labels: 7 | role: api 8 | env: demo 9 | spec: 10 | rules: 11 | - host: api.X.X.X.X.nip.io 12 | http: 13 | paths: 14 | - path: / 15 | backend: 16 | serviceName: api 17 | servicePort: 8080 -------------------------------------------------------------------------------- /database/mongo.statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mongo 5 | namespace: cloudacademy 6 | labels: 7 | role: db 8 | env: demo 9 | spec: 10 | ports: 11 | - port: 27017 12 | targetPort: 27017 13 | clusterIP: None 14 | selector: 15 | role: db 16 | --- 17 | apiVersion: apps/v1beta1 18 | kind: StatefulSet 19 | metadata: 20 | name: mongo 21 | namespace: cloudacademy 22 | spec: 23 | serviceName: mongo 24 | replicas: 3 25 | template: 26 | metadata: 27 | labels: 28 | role: db 29 | env: demo 30 | replicaset: rs0.main 31 | spec: 32 | affinity: 33 | podAntiAffinity: 34 | preferredDuringSchedulingIgnoredDuringExecution: 35 | - weight: 100 36 | podAffinityTerm: 37 | labelSelector: 38 | matchExpressions: 39 | - key: replicaset 40 | operator: In 41 | values: 42 | - rs0.main 43 | topologyKey: kubernetes.io/hostname 44 | terminationGracePeriodSeconds: 10 45 | containers: 46 | - name: mongo 47 | image: mongo 48 | command: 49 | - "numactl" 50 | - "--interleave=all" 51 | - "mongod" 52 | - "--wiredTigerCacheSizeGB" 53 | - "0.1" 54 | - "--bind_ip" 55 | - "0.0.0.0" 56 | - "--replSet" 57 | - "rs0" 58 | ports: 59 | - containerPort: 27017 60 | volumeMounts: 61 | - name: mongodb-persistent-storage-claim 62 | mountPath: /data/db 63 | volumeClaimTemplates: 64 | - metadata: 65 | name: mongodb-persistent-storage-claim 66 | namespace: cloudacademy 67 | annotations: 68 | volume.beta.kubernetes.io/storage-class: "standard" 69 | spec: 70 | accessModes: [ "ReadWriteOnce" ] 71 | resources: 72 | requests: 73 | storage: 1Gi -------------------------------------------------------------------------------- /doc/k8sdeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudacademy/voteapp-k8s/703706528a2dfec6aa8af5c27c128b0f865545db/doc/k8sdeploy.png -------------------------------------------------------------------------------- /frontend/1.frontend.deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | namespace: cloudacademy 6 | labels: 7 | role: frontend 8 | env: demo 9 | spec: 10 | replicas: 4 11 | strategy: 12 | type: RollingUpdate 13 | rollingUpdate: 14 | maxSurge: 1 15 | maxUnavailable: 25% 16 | selector: 17 | matchLabels: 18 | role: frontend 19 | template: 20 | metadata: 21 | labels: 22 | role: frontend 23 | spec: 24 | containers: 25 | - name: frontend 26 | image: cloudacademy/frontend:v1 27 | imagePullPolicy: IfNotPresent 28 | ports: 29 | - containerPort: 80 30 | livenessProbe: 31 | httpGet: 32 | path: / 33 | port: 80 34 | initialDelaySeconds: 2 35 | periodSeconds: 5 36 | readinessProbe: 37 | httpGet: 38 | path: / 39 | port: 80 40 | initialDelaySeconds: 5 41 | periodSeconds: 5 42 | successThreshold: 1 43 | -------------------------------------------------------------------------------- /frontend/2.frontend.service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend 5 | namespace: cloudacademy 6 | labels: 7 | role: frontend 8 | env: demo 9 | spec: 10 | ports: 11 | - protocol: TCP 12 | port: 80 13 | selector: 14 | role: frontend -------------------------------------------------------------------------------- /frontend/3.frontend.ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: frontend 5 | namespace: cloudacademy 6 | spec: 7 | rules: 8 | - host: frontend.X.X.X.X.nip.io 9 | http: 10 | paths: 11 | - path: / 12 | backend: 13 | serviceName: frontend 14 | servicePort: 80 -------------------------------------------------------------------------------- /ingress/nginx-ingress-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | deployment.kubernetes.io/revision: "1" 6 | kubectl.kubernetes.io/last-applied-configuration: | 7 | {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/part-of":"ingress-nginx"},"name":"nginx-ingress-controller","namespace":"ingress-nginx"},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/part-of":"ingress-nginx"}},"template":{"metadata":{"annotations":{"prometheus.io/port":"10254","prometheus.io/scrape":"true"},"labels":{"app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/part-of":"ingress-nginx"}},"spec":{"containers":[{"args":["/nginx-ingress-controller","--configmap=$(POD_NAMESPACE)/nginx-configuration","--tcp-services-configmap=$(POD_NAMESPACE)/tcp-services","--udp-services-configmap=$(POD_NAMESPACE)/udp-services","--publish-service=$(POD_NAMESPACE)/ingress-nginx","--annotations-prefix=nginx.ingress.kubernetes.io"],"env":[{"name":"POD_NAME","valueFrom":{"fieldRef":{"fieldPath":"metadata.name"}}},{"name":"POD_NAMESPACE","valueFrom":{"fieldRef":{"fieldPath":"metadata.namespace"}}}],"image":"quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0","livenessProbe":{"failureThreshold":3,"httpGet":{"path":"/healthz","port":10254,"scheme":"HTTP"},"initialDelaySeconds":10,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":10},"name":"nginx-ingress-controller","ports":[{"containerPort":80,"name":"http"},{"containerPort":443,"name":"https"}],"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/healthz","port":10254,"scheme":"HTTP"},"periodSeconds":10,"successThreshold":1,"timeoutSeconds":10},"securityContext":{"allowPrivilegeEscalation":true,"capabilities":{"add":["NET_BIND_SERVICE"],"drop":["ALL"]},"runAsUser":33}}],"serviceAccountName":"nginx-ingress-serviceaccount"}}}} 8 | generation: 1 9 | labels: 10 | app.kubernetes.io/name: ingress-nginx 11 | app.kubernetes.io/part-of: ingress-nginx 12 | name: nginx-ingress-controller 13 | namespace: ingress-nginx 14 | resourceVersion: "132295" 15 | selfLink: /apis/extensions/v1beta1/namespaces/ingress-nginx/deployments/nginx-ingress-controller 16 | uid: ba72e966-3bf5-4ae0-839a-b62cdfc0aa13 17 | spec: 18 | progressDeadlineSeconds: 600 19 | replicas: 1 20 | revisionHistoryLimit: 10 21 | selector: 22 | matchLabels: 23 | app.kubernetes.io/name: ingress-nginx 24 | app.kubernetes.io/part-of: ingress-nginx 25 | strategy: 26 | rollingUpdate: 27 | maxSurge: 25% 28 | maxUnavailable: 25% 29 | type: RollingUpdate 30 | template: 31 | metadata: 32 | annotations: 33 | prometheus.io/port: "10254" 34 | prometheus.io/scrape: "true" 35 | creationTimestamp: null 36 | labels: 37 | app.kubernetes.io/name: ingress-nginx 38 | app.kubernetes.io/part-of: ingress-nginx 39 | spec: 40 | containers: 41 | - args: 42 | - /nginx-ingress-controller 43 | - --configmap=$(POD_NAMESPACE)/nginx-configuration 44 | - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services 45 | - --udp-services-configmap=$(POD_NAMESPACE)/udp-services 46 | - --publish-service=$(POD_NAMESPACE)/ingress-nginx 47 | - --annotations-prefix=nginx.ingress.kubernetes.io 48 | env: 49 | - name: POD_NAME 50 | valueFrom: 51 | fieldRef: 52 | apiVersion: v1 53 | fieldPath: metadata.name 54 | - name: POD_NAMESPACE 55 | valueFrom: 56 | fieldRef: 57 | apiVersion: v1 58 | fieldPath: metadata.namespace 59 | image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0 60 | imagePullPolicy: IfNotPresent 61 | livenessProbe: 62 | failureThreshold: 3 63 | httpGet: 64 | path: /healthz 65 | port: 10254 66 | scheme: HTTP 67 | initialDelaySeconds: 10 68 | periodSeconds: 10 69 | successThreshold: 1 70 | timeoutSeconds: 10 71 | name: nginx-ingress-controller 72 | ports: 73 | - containerPort: 80 74 | name: http 75 | protocol: TCP 76 | - containerPort: 443 77 | name: https 78 | protocol: TCP 79 | readinessProbe: 80 | failureThreshold: 3 81 | httpGet: 82 | path: /healthz 83 | port: 10254 84 | scheme: HTTP 85 | periodSeconds: 10 86 | successThreshold: 1 87 | timeoutSeconds: 10 88 | resources: {} 89 | securityContext: 90 | allowPrivilegeEscalation: true 91 | capabilities: 92 | add: 93 | - NET_BIND_SERVICE 94 | drop: 95 | - ALL 96 | runAsUser: 33 97 | terminationMessagePath: /dev/termination-log 98 | terminationMessagePolicy: File 99 | dnsPolicy: ClusterFirst 100 | restartPolicy: Always 101 | schedulerName: default-scheduler 102 | securityContext: {} 103 | serviceAccount: nginx-ingress-serviceaccount 104 | serviceAccountName: nginx-ingress-serviceaccount 105 | terminationGracePeriodSeconds: 30 106 | hostNetwork: true 107 | status: 108 | availableReplicas: 1 109 | conditions: 110 | - lastTransitionTime: "2019-08-22T07:13:29Z" 111 | lastUpdateTime: "2019-08-22T07:13:29Z" 112 | message: Deployment has minimum availability. 113 | reason: MinimumReplicasAvailable 114 | status: "True" 115 | type: Available 116 | - lastTransitionTime: "2019-08-22T07:13:19Z" 117 | lastUpdateTime: "2019-08-22T07:13:29Z" 118 | message: ReplicaSet "nginx-ingress-controller-7995bd9c47" has successfully progressed. 119 | reason: NewReplicaSetAvailable 120 | status: "True" 121 | type: Progressing 122 | observedGeneration: 1 123 | readyReplicas: 1 124 | replicas: 1 125 | updatedReplicas: 1 126 | -------------------------------------------------------------------------------- /namespace/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: cloudacademy 5 | labels: 6 | name: cloudacademy -------------------------------------------------------------------------------- /netpol/1.network.policy.deny-all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: NetworkPolicy 3 | metadata: 4 | name: default-deny-all 5 | namespace: cloudacademy 6 | spec: 7 | podSelector: {} 8 | policyTypes: 9 | - Ingress -------------------------------------------------------------------------------- /netpol/2.network.policy.allow-mongo-mongo.yaml: -------------------------------------------------------------------------------- 1 | kind: NetworkPolicy 2 | apiVersion: networking.k8s.io/v1 3 | metadata: 4 | name: allow-to-mongo-from-mongo 5 | namespace: cloudacademy 6 | spec: 7 | podSelector: 8 | matchLabels: 9 | role: db 10 | ingress: 11 | - from: 12 | - podSelector: 13 | matchLabels: 14 | role: db -------------------------------------------------------------------------------- /netpol/3.network.policy.allow-mongo-api.yaml: -------------------------------------------------------------------------------- 1 | kind: NetworkPolicy 2 | apiVersion: networking.k8s.io/v1 3 | metadata: 4 | name: allow-to-mongo-from-api 5 | namespace: cloudacademy 6 | spec: 7 | podSelector: 8 | matchLabels: 9 | role: db 10 | ingress: 11 | - from: 12 | - podSelector: 13 | matchLabels: 14 | role: api --------------------------------------------------------------------------------