From 02e2f153790742d95ca229758b24cd48c7fea55d Mon Sep 17 00:00:00 2001 From: "saifeddine.rajhi" Date: Fri, 8 Mar 2024 08:42:03 +0100 Subject: [PATCH] feat: add kubernetes-exercises --- .github/workflows/pluto-deprecated.yaml | 43 ++ kubernetes-exercises/.gitignore | 78 +++ .../.test/extract_all_k8s_from_md.sh | 2 + kubernetes-exercises/.test/lint_kube.sh | 37 ++ .../.test/parse_k8s_from_md.sh | 2 + kubernetes-exercises/CONTRIBUTION.md | 34 + kubernetes-exercises/README.md | 91 +++ .../accessing-your-application.md | 221 +++++++ .../done/backend-pod.yaml | 11 + .../done/frontend-pod.yaml | 16 + .../start/backend-pod.yaml | 11 + .../start/frontend-pod.yaml | 10 + kubernetes-exercises/cheatsheet.md | 43 ++ .../done/backend-deployment.yaml | 30 + .../configmap-secrets/done/backend-svc.yaml | 14 + .../done/frontend-deployment.yaml | 24 + .../configmap-secrets/done/frontend-svc.yaml | 14 + .../done/postgres-configmap.yaml | 9 + .../done/postgres-deployment.yaml | 40 ++ .../done/postgres-secret.yaml | 7 + .../done/postgres-service.yaml | 15 + .../start/backend-deployment.yaml | 37 ++ .../configmap-secrets/start/backend-svc.yaml | 14 + .../start/frontend-deployment.yaml | 24 + .../configmap-secrets/start/frontend-svc.yaml | 14 + .../start/postgres-deployment.yaml | 31 + .../start/postgres-service.yaml | 15 + kubernetes-exercises/configmaps-secrets.md | 375 +++++++++++ kubernetes-exercises/deployments-ingress.md | 602 ++++++++++++++++++ .../done/backend-deployment.yaml | 20 + .../deployments-ingress/done/backend-svc.yaml | 15 + .../done/frontend-deployment.yaml | 25 + .../done/frontend-ingress.yaml | 23 + .../done/frontend-svc.yaml | 15 + .../deployments-ingress/done/poll.sh | 9 + .../start/backend-deployment.yaml | 10 + .../start/backend-pod.yaml | 12 + .../start/backend-svc.yaml | 14 + .../start/frontend-deployment.yaml | 10 + .../start/frontend-ingress.yaml | 24 + .../start/frontend-pod.yaml | 17 + .../start/frontend-svc.yaml | 14 + .../deployments-ingress/start/poll.sh | 11 + kubernetes-exercises/desired-state.md | 180 ++++++ .../desired-state/nginx-deployment.yaml | 23 + kubernetes-exercises/exercise-template.md | 40 ++ kubernetes-exercises/img/app-front-back.png | Bin 0 -> 33050 bytes kubernetes-exercises/intro.md | 209 ++++++ kubernetes-exercises/manifests.md | 133 ++++ .../manifests/done/frontend-pod.yaml | 10 + .../manifests/start/frontend-pod.yaml | 10 + kubernetes-exercises/old/07-healthchecks.md | 79 +++ .../exercise_setup/00-setup-kubectl-linux.md | 109 ++++ .../old/exercise_setup/00-setup-namespace.md | 64 ++ .../99-setup-kubectl-generic.md | 201 ++++++ .../beyond-this-course-setting-up-your-own.md | 73 +++ .../old/extras/08-ingress-gke.md | 118 ++++ .../old/extras/08-ingress-nginx.md | 74 +++ .../old/extras/08-ingress-traefik.md | 340 ++++++++++ .../old/extras/09-helm-package-manager.md | 169 +++++ .../extras/10-secrets-ssl-certs-in-nginx.md | 51 ++ .../old/health-checks/probes-svc.yaml | 12 + .../old/health-checks/probes.yaml | 38 ++ .../old/ingress-gke/ingress.yml | 10 + .../old/ingress-nginx/ingress.yml | 20 + .../nginx-backend/nginx-backend.yml | 37 ++ .../nginx-backend/nginx-service.yml | 12 + .../ingress-nginx/nginx-controller-svc.yml | 72 +++ .../old/ingress-nginx/self-signed-cert.sh | 2 + .../old/ingress-traefik/README.md | 1 + .../old/ingress-traefik/example-ingress.yaml | 56 ++ .../old/ingress-traefik/my-ingress.yml | 13 + .../nginx-on-traefik-dashboard.png | Bin 0 -> 158126 bytes .../old/ingress-traefik/traefik-dashboard.png | Bin 0 -> 77188 bytes .../ingress-traefik/traefik-deployment.yaml | 56 ++ .../traefik-ingress-controller.yml | 37 ++ .../old/ingress-traefik/traefik-rbac.yaml | 38 ++ .../old/ingress-traefik/traefik-service.yml | 36 ++ .../traefik-webui-ingress.yaml | 28 + kubernetes-exercises/old/secrets/Dockerfile | 7 + .../old/secrets/deployment.yml | 25 + .../old/secrets/final.deployment.yml | 31 + kubernetes-exercises/old/secrets/secretapp.js | 10 + .../extra/multitool-deployment.yaml | 24 + .../extra/multitool-svc.yaml | 17 + .../multitool-deployment.yaml | 24 + .../nginx-deployment.yaml | 24 + .../generate-self-signed-certs.sh | 19 + .../old/support-files/nginx-connectors.conf | 25 + .../nginx-persistent-storage.yaml | 30 + .../nginx-simple-deployment.yaml | 23 + .../old/support-files/nginx-ssl.yaml | 35 + .../old/support-files/pvc-nginx.yaml | 21 + .../old/support-files/traefik-deployment.yaml | 40 ++ .../old/support-files/traefik-helm.values | 96 +++ .../traefik-rbac-serviceaccount.yaml | 38 ++ .../old/support-files/traefik-service.yaml | 58 ++ .../old/support-files/traefik.toml | 23 + .../trainer/examples/health-check/README.md | 16 + .../trainer/examples/health-check/pod.yaml | 29 + .../old/trainer/examples/nextcloud/README.md | 44 ++ .../examples/nextcloud/docker-compose.yml | 25 + kubernetes-exercises/persistent-storage.md | 338 ++++++++++ .../done/backend-configmap.yaml | 9 + .../done/backend-deployment.yaml | 31 + .../done/backend-service.yaml | 15 + .../done/frontend-deployment.yaml | 38 ++ .../done/frontend-service.yaml | 14 + .../done/postgres-configmap.yaml | 9 + .../done/postgres-deployment.yaml | 50 ++ .../persistent-storage/done/postgres-pvc.yaml | 12 + .../done/postgres-secret.yaml | 7 + .../done/postgres-service.yaml | 15 + .../start/backend-configmap.yaml | 9 + .../start/backend-deployment.yaml | 31 + .../start/backend-service.yaml | 15 + .../start/frontend-deployment.yaml | 38 ++ .../start/frontend-service.yaml | 14 + .../start/postgres-configmap.yaml | 9 + .../start/postgres-deployment.yaml | 42 ++ .../start/postgres-secret.yaml | 7 + .../start/postgres-service.yaml | 15 + .../quotes-flask/backend-configmap.yaml | 9 + .../quotes-flask/backend-deployment.yaml | 31 + .../quotes-flask/backend-service.yaml | 17 + .../quotes-flask/frontend-deployment.yaml | 37 ++ .../quotes-flask/frontend-service.yaml | 14 + .../quotes-flask/postgres-configmap.yaml | 9 + .../quotes-flask/postgres-deployment.yaml | 50 ++ .../quotes-flask/postgres-pvc.yaml | 12 + .../quotes-flask/postgres-secret.yaml | 7 + .../quotes-flask/postgres-service.yaml | 15 + kubernetes-exercises/rolling-updates.md | 153 +++++ .../done/backend-deployment.yaml | 24 + .../rolling-updates/done/backend-svc.yaml | 14 + .../done/frontend-deployment.yaml | 24 + .../rolling-updates/done/frontend-svc.yaml | 14 + .../extra/nginx-deployment.yaml | 28 + .../rolling-updates/extra/nginx-svc.yaml | 15 + .../start/backend-deployment.yaml | 19 + .../rolling-updates/start/backend-svc.yaml | 14 + .../start/frontend-deployment.yaml | 24 + .../rolling-updates/start/frontend-svc.yaml | 14 + kubernetes-exercises/scenarios/README.md | 143 +++++ .../scenarios/connectivity/README.md | 38 ++ .../scenarios/connectivity/deployment.yaml | 22 + .../scenarios/connectivity/pod.yaml | 16 + .../scenarios/connectivity/service.yaml | 10 + .../scenarios/connectivity/setup.sh | 5 + .../scenarios/limits/README.md | 24 + .../scenarios/limits/pod.yaml | 24 + .../scenarios/limits/setup.sh | 5 + .../scenarios/persistency/README.md | 37 ++ .../scenarios/persistency/pod.yaml | 24 + .../scenarios/persistency/pvc.yaml | 11 + .../scenarios/persistency/setup.sh | 5 + .../scenarios/pod-not-starting/README.md | 26 + .../pod-not-starting/pod-definition.yaml | 8 + .../scenarios/pod-not-starting/setup.sh | 18 + kubernetes-exercises/services.md | 368 +++++++++++ .../services/done/backend-pod.yaml | 12 + .../services/done/backend-svc.yaml | 14 + .../services/done/frontend-pod.yaml | 17 + .../services/done/frontend-svc.yaml | 14 + .../services/start/backend-pod.yaml | 12 + .../services/start/backend-svc.yaml | 14 + .../services/start/frontend-pod.yaml | 17 + .../services/start/frontend-svc.yaml | 14 + kubernetes-exercises/trainer-notes.md | 5 + .../trainer/quotes-flask-rbac.yaml | 26 + 170 files changed, 7309 insertions(+) create mode 100644 .github/workflows/pluto-deprecated.yaml create mode 100644 kubernetes-exercises/.gitignore create mode 100644 kubernetes-exercises/.test/extract_all_k8s_from_md.sh create mode 100644 kubernetes-exercises/.test/lint_kube.sh create mode 100644 kubernetes-exercises/.test/parse_k8s_from_md.sh create mode 100644 kubernetes-exercises/CONTRIBUTION.md create mode 100644 kubernetes-exercises/README.md create mode 100644 kubernetes-exercises/accessing-your-application.md create mode 100644 kubernetes-exercises/accessing-your-application/done/backend-pod.yaml create mode 100644 kubernetes-exercises/accessing-your-application/done/frontend-pod.yaml create mode 100644 kubernetes-exercises/accessing-your-application/start/backend-pod.yaml create mode 100644 kubernetes-exercises/accessing-your-application/start/frontend-pod.yaml create mode 100644 kubernetes-exercises/cheatsheet.md create mode 100644 kubernetes-exercises/configmap-secrets/done/backend-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/backend-svc.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/frontend-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/frontend-svc.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/postgres-configmap.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/postgres-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/postgres-secret.yaml create mode 100644 kubernetes-exercises/configmap-secrets/done/postgres-service.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/backend-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/backend-svc.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/frontend-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/frontend-svc.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/postgres-deployment.yaml create mode 100644 kubernetes-exercises/configmap-secrets/start/postgres-service.yaml create mode 100644 kubernetes-exercises/configmaps-secrets.md create mode 100644 kubernetes-exercises/deployments-ingress.md create mode 100644 kubernetes-exercises/deployments-ingress/done/backend-deployment.yaml create mode 100644 kubernetes-exercises/deployments-ingress/done/backend-svc.yaml create mode 100644 kubernetes-exercises/deployments-ingress/done/frontend-deployment.yaml create mode 100644 kubernetes-exercises/deployments-ingress/done/frontend-ingress.yaml create mode 100644 kubernetes-exercises/deployments-ingress/done/frontend-svc.yaml create mode 100755 kubernetes-exercises/deployments-ingress/done/poll.sh create mode 100644 kubernetes-exercises/deployments-ingress/start/backend-deployment.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/backend-pod.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/backend-svc.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/frontend-deployment.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/frontend-ingress.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/frontend-pod.yaml create mode 100644 kubernetes-exercises/deployments-ingress/start/frontend-svc.yaml create mode 100755 kubernetes-exercises/deployments-ingress/start/poll.sh create mode 100644 kubernetes-exercises/desired-state.md create mode 100644 kubernetes-exercises/desired-state/nginx-deployment.yaml create mode 100644 kubernetes-exercises/exercise-template.md create mode 100644 kubernetes-exercises/img/app-front-back.png create mode 100644 kubernetes-exercises/intro.md create mode 100644 kubernetes-exercises/manifests.md create mode 100644 kubernetes-exercises/manifests/done/frontend-pod.yaml create mode 100644 kubernetes-exercises/manifests/start/frontend-pod.yaml create mode 100644 kubernetes-exercises/old/07-healthchecks.md create mode 100644 kubernetes-exercises/old/exercise_setup/00-setup-kubectl-linux.md create mode 100644 kubernetes-exercises/old/exercise_setup/00-setup-namespace.md create mode 100644 kubernetes-exercises/old/exercise_setup/99-setup-kubectl-generic.md create mode 100644 kubernetes-exercises/old/exercise_setup/beyond-this-course-setting-up-your-own.md create mode 100644 kubernetes-exercises/old/extras/08-ingress-gke.md create mode 100644 kubernetes-exercises/old/extras/08-ingress-nginx.md create mode 100644 kubernetes-exercises/old/extras/08-ingress-traefik.md create mode 100644 kubernetes-exercises/old/extras/09-helm-package-manager.md create mode 100644 kubernetes-exercises/old/extras/10-secrets-ssl-certs-in-nginx.md create mode 100644 kubernetes-exercises/old/health-checks/probes-svc.yaml create mode 100644 kubernetes-exercises/old/health-checks/probes.yaml create mode 100644 kubernetes-exercises/old/ingress-gke/ingress.yml create mode 100644 kubernetes-exercises/old/ingress-nginx/ingress.yml create mode 100644 kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-backend.yml create mode 100644 kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-service.yml create mode 100644 kubernetes-exercises/old/ingress-nginx/nginx-controller-svc.yml create mode 100755 kubernetes-exercises/old/ingress-nginx/self-signed-cert.sh create mode 100644 kubernetes-exercises/old/ingress-traefik/README.md create mode 100644 kubernetes-exercises/old/ingress-traefik/example-ingress.yaml create mode 100644 kubernetes-exercises/old/ingress-traefik/my-ingress.yml create mode 100644 kubernetes-exercises/old/ingress-traefik/nginx-on-traefik-dashboard.png create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-dashboard.png create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-deployment.yaml create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-ingress-controller.yml create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-service.yml create mode 100644 kubernetes-exercises/old/ingress-traefik/traefik-webui-ingress.yaml create mode 100644 kubernetes-exercises/old/secrets/Dockerfile create mode 100644 kubernetes-exercises/old/secrets/deployment.yml create mode 100644 kubernetes-exercises/old/secrets/final.deployment.yml create mode 100644 kubernetes-exercises/old/secrets/secretapp.js create mode 100644 kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-deployment.yaml create mode 100644 kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-svc.yaml create mode 100644 kubernetes-exercises/old/service-discovery-loadbalancing/multitool-deployment.yaml create mode 100644 kubernetes-exercises/old/service-discovery-loadbalancing/nginx-deployment.yaml create mode 100755 kubernetes-exercises/old/support-files/generate-self-signed-certs.sh create mode 100644 kubernetes-exercises/old/support-files/nginx-connectors.conf create mode 100644 kubernetes-exercises/old/support-files/nginx-persistent-storage.yaml create mode 100644 kubernetes-exercises/old/support-files/nginx-simple-deployment.yaml create mode 100644 kubernetes-exercises/old/support-files/nginx-ssl.yaml create mode 100644 kubernetes-exercises/old/support-files/pvc-nginx.yaml create mode 100644 kubernetes-exercises/old/support-files/traefik-deployment.yaml create mode 100644 kubernetes-exercises/old/support-files/traefik-helm.values create mode 100644 kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml create mode 100644 kubernetes-exercises/old/support-files/traefik-service.yaml create mode 100644 kubernetes-exercises/old/support-files/traefik.toml create mode 100644 kubernetes-exercises/old/trainer/examples/health-check/README.md create mode 100644 kubernetes-exercises/old/trainer/examples/health-check/pod.yaml create mode 100644 kubernetes-exercises/old/trainer/examples/nextcloud/README.md create mode 100644 kubernetes-exercises/old/trainer/examples/nextcloud/docker-compose.yml create mode 100644 kubernetes-exercises/persistent-storage.md create mode 100644 kubernetes-exercises/persistent-storage/done/backend-configmap.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/backend-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/backend-service.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/frontend-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/frontend-service.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/postgres-configmap.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/postgres-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/postgres-pvc.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/postgres-secret.yaml create mode 100644 kubernetes-exercises/persistent-storage/done/postgres-service.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/backend-configmap.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/backend-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/backend-service.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/frontend-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/frontend-service.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/postgres-configmap.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/postgres-deployment.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/postgres-secret.yaml create mode 100644 kubernetes-exercises/persistent-storage/start/postgres-service.yaml create mode 100644 kubernetes-exercises/quotes-flask/backend-configmap.yaml create mode 100644 kubernetes-exercises/quotes-flask/backend-deployment.yaml create mode 100644 kubernetes-exercises/quotes-flask/backend-service.yaml create mode 100644 kubernetes-exercises/quotes-flask/frontend-deployment.yaml create mode 100644 kubernetes-exercises/quotes-flask/frontend-service.yaml create mode 100644 kubernetes-exercises/quotes-flask/postgres-configmap.yaml create mode 100644 kubernetes-exercises/quotes-flask/postgres-deployment.yaml create mode 100644 kubernetes-exercises/quotes-flask/postgres-pvc.yaml create mode 100644 kubernetes-exercises/quotes-flask/postgres-secret.yaml create mode 100644 kubernetes-exercises/quotes-flask/postgres-service.yaml create mode 100644 kubernetes-exercises/rolling-updates.md create mode 100644 kubernetes-exercises/rolling-updates/done/backend-deployment.yaml create mode 100644 kubernetes-exercises/rolling-updates/done/backend-svc.yaml create mode 100644 kubernetes-exercises/rolling-updates/done/frontend-deployment.yaml create mode 100644 kubernetes-exercises/rolling-updates/done/frontend-svc.yaml create mode 100644 kubernetes-exercises/rolling-updates/extra/nginx-deployment.yaml create mode 100644 kubernetes-exercises/rolling-updates/extra/nginx-svc.yaml create mode 100644 kubernetes-exercises/rolling-updates/start/backend-deployment.yaml create mode 100644 kubernetes-exercises/rolling-updates/start/backend-svc.yaml create mode 100644 kubernetes-exercises/rolling-updates/start/frontend-deployment.yaml create mode 100644 kubernetes-exercises/rolling-updates/start/frontend-svc.yaml create mode 100644 kubernetes-exercises/scenarios/README.md create mode 100644 kubernetes-exercises/scenarios/connectivity/README.md create mode 100644 kubernetes-exercises/scenarios/connectivity/deployment.yaml create mode 100644 kubernetes-exercises/scenarios/connectivity/pod.yaml create mode 100644 kubernetes-exercises/scenarios/connectivity/service.yaml create mode 100644 kubernetes-exercises/scenarios/connectivity/setup.sh create mode 100644 kubernetes-exercises/scenarios/limits/README.md create mode 100644 kubernetes-exercises/scenarios/limits/pod.yaml create mode 100644 kubernetes-exercises/scenarios/limits/setup.sh create mode 100644 kubernetes-exercises/scenarios/persistency/README.md create mode 100644 kubernetes-exercises/scenarios/persistency/pod.yaml create mode 100644 kubernetes-exercises/scenarios/persistency/pvc.yaml create mode 100644 kubernetes-exercises/scenarios/persistency/setup.sh create mode 100644 kubernetes-exercises/scenarios/pod-not-starting/README.md create mode 100644 kubernetes-exercises/scenarios/pod-not-starting/pod-definition.yaml create mode 100644 kubernetes-exercises/scenarios/pod-not-starting/setup.sh create mode 100644 kubernetes-exercises/services.md create mode 100644 kubernetes-exercises/services/done/backend-pod.yaml create mode 100644 kubernetes-exercises/services/done/backend-svc.yaml create mode 100644 kubernetes-exercises/services/done/frontend-pod.yaml create mode 100644 kubernetes-exercises/services/done/frontend-svc.yaml create mode 100644 kubernetes-exercises/services/start/backend-pod.yaml create mode 100644 kubernetes-exercises/services/start/backend-svc.yaml create mode 100644 kubernetes-exercises/services/start/frontend-pod.yaml create mode 100644 kubernetes-exercises/services/start/frontend-svc.yaml create mode 100644 kubernetes-exercises/trainer-notes.md create mode 100644 kubernetes-exercises/trainer/quotes-flask-rbac.yaml diff --git a/.github/workflows/pluto-deprecated.yaml b/.github/workflows/pluto-deprecated.yaml new file mode 100644 index 00000000..4ccc6258 --- /dev/null +++ b/.github/workflows/pluto-deprecated.yaml @@ -0,0 +1,43 @@ +name: Lint + +on: [push, pull_request] + +jobs: + outdated-kubernetes: + name: Outdated K8s Resources + + runs-on: ubuntu-latest + + strategy: + matrix: + kubernetes-version: [ '1.24.0', '1.25.0' ] + + steps: + - uses: actions/checkout@v3 + + - name: Extract K8s YAML from Markdown files + run: sh kubernetes-exercises/.test/extract_all_k8s_from_md.sh + + - name: Detect Outdated Kubernetes Resources + run: | + docker run -v $(pwd):/files/ quay.io/fairwinds/pluto:v4.1.2 \ + detect-files \ + --directory /files/ \ + --target-versions k8s=v${{ matrix.kubernetes-version }} \ + --ignore-deprecations `# Do not error when merely deprecated resources are found` \ + --output custom \ + --columns "NAME,KIND,VERSION,REPLACEMENT,DEPRECATED,DEPRECATED,DEPRECATED IN,REMOVED,REMOVED IN,FILEPATH" + + valid-kubernetes-resources: + name: Valid K8s Resources + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Extract K8s YAML from Markdown files + run: bash kubernetes-exercises/.test/extract_all_k8s_from_md.sh + + - name: Lint Kubernetes Resources + run: bash kubernetes-exercises/.test/lint_kube.sh \ No newline at end of file diff --git a/kubernetes-exercises/.gitignore b/kubernetes-exercises/.gitignore new file mode 100644 index 00000000..db0e05a5 --- /dev/null +++ b/kubernetes-exercises/.gitignore @@ -0,0 +1,78 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/macos,linux,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows + +# Yaml extracted from Markdown files +*.md.yaml + diff --git a/kubernetes-exercises/.test/extract_all_k8s_from_md.sh b/kubernetes-exercises/.test/extract_all_k8s_from_md.sh new file mode 100644 index 00000000..438e372a --- /dev/null +++ b/kubernetes-exercises/.test/extract_all_k8s_from_md.sh @@ -0,0 +1,2 @@ +#!/bin/bash +find . -iname "*.md" -exec sh -c "cat {} | sh kubernetes-exercises/.test/parse_k8s_from_md.sh > {}.yaml" \; diff --git a/kubernetes-exercises/.test/lint_kube.sh b/kubernetes-exercises/.test/lint_kube.sh new file mode 100644 index 00000000..f8435012 --- /dev/null +++ b/kubernetes-exercises/.test/lint_kube.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Select all yaml files, except the Helmsman related ones. These are +# detected by exluding files with filenames starting with "helmfile." or "values-". +KUBERNETES_RESOURCE_FILES=$(find * -type f \( -iname '*.yml' -or -iname '*.yaml' \) -and ! \( -iname "helmfile.yaml" -or -iname "values-*.yaml" -or -iname "*docker-compose*" \)) + +excludes=( + kubernetes-exercises/deployments-ingress/start/frontend-deployment.yaml + kubernetes-exercises/deployments-ingress/start/backend-deployment.yaml + kubernetes-exercises/manifests/start/frontend-pod.yaml + kubernetes-exercises/services/start/backend-svc.yaml + kubernetes-exercises/services/start/frontend-svc.yaml + kubernetes-exercises/kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml + kubernetes-exercises/old/ingress-nginx/ingress.yml + kubernetes-exercises/old/support-files/traefik-service.yaml + kubernetes-exercises/old/extras/08-ingress-gke.md.yaml + kubernetes-exercises/old/extras/08-ingress-gke.md.yaml + kubernetes-exercises/old/extras/08-ingress-traefik.md.yaml + kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml + kubernetes-exercises/old/extras/08-ingress-traefik.md.yaml + kubernetes-exercises/old/extras/08-ingress-traefik.md.yaml + kubernetes-exercises/old/extras/08-ingress-traefik.md.yaml + kubernetes-exercises/old/ingress-gke/ingress.yml + kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml + kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml + kubernetes-exercises/old/ingress-traefik/traefik-webui-ingress.yaml + kubernetes-exercises/old/ingress-traefik/example-ingress.yaml + kubernetes-exercises/old/ingress-traefik/my-ingress.yml + kubernetes-exercises/old/ingress-traefik/traefik-ingress-controller.yml +) +for exclude in ${excludes[@]} +do + KUBERNETES_RESOURCE_FILES=("${KUBERNETES_RESOURCE_FILES[@]/$exclude}") +done + +# Run all files through kubeconform +docker run --rm -v ${PWD}:/fixtures -w /fixtures ghcr.io/yannh/kubeconform -summary $KUBERNETES_RESOURCE_FILES diff --git a/kubernetes-exercises/.test/parse_k8s_from_md.sh b/kubernetes-exercises/.test/parse_k8s_from_md.sh new file mode 100644 index 00000000..93290afb --- /dev/null +++ b/kubernetes-exercises/.test/parse_k8s_from_md.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sed -n '/^```[^\n]*k8s/,/^```/p' | sed '/^```[^\n]*k8s/d' | sed -e 's/```/---/g' diff --git a/kubernetes-exercises/CONTRIBUTION.md b/kubernetes-exercises/CONTRIBUTION.md new file mode 100644 index 00000000..96b8c84e --- /dev/null +++ b/kubernetes-exercises/CONTRIBUTION.md @@ -0,0 +1,34 @@ +# Contribution + +## Exercise structure + +The exercise should have the following sections: + +* Learning goals (bullet points on what the student should learn) +* Introduction (cleartext description of the exercise) +* Exercise (Step-by-step instructions on how to solve the exercise) +* Extras and wrap-up (optional) + +When creating a new exercise, you should use the [exercise template](exercise-template.md) as a starting point. +## Best practices + +### Hints +Use :bulb: `:bulb:` to indicate a hint to the exercise. + +### Dealing with text rich content + +When ever you think there is too much text, but that it is necessary, please use the `details` tag to make the text toggleable, by clicking the arrow: + +
+ A Hint + It helps reducing the amount of word overload that we sometimes write +
+ +The code to make this happen is the following: + +```markdown +
+ Hint + All markdown in here is hidden in an expandable field +
+``` diff --git a/kubernetes-exercises/README.md b/kubernetes-exercises/README.md new file mode 100644 index 00000000..dc117717 --- /dev/null +++ b/kubernetes-exercises/README.md @@ -0,0 +1,91 @@ +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)][gitpod] + +# kubernetes-exercises + +A selection of exercises for Kubernetes (k8s). + +The exercises are ordered in the way we think it makes sense to introduce Kubernetes concepts. + +You can find a summary of many of the commands used in the exercises in the +[cheatsheet.md](cheatsheet.md). + +> :exclamation: The exercises expect that you have access to a kubernetes cluster. +> Please have a look at the [Setup](#setup) section if that is not the case. +> There are plenty of free and easy options. + +## exercises in suggested order: + +- [intro](intro.md) +- [desired-state](desired-state.md) +- [manifests](manifests.md) +- [accessing-your-application](accessing-your-application.md) +- [services](services.md) +- [deployments-loadbalancing](deployments-loadbalancing.md) +- [rolling-updates](rolling-updates.md) +- [configmaps-secrets](configmaps-secrets.md) +- [persistent-storage](persistent-storage.md) + +## Setup + +There are several ways to get a free Kubernetes cluster for running the exercises. + +[Amazon][eks], [Google][gke], [Microsoft][aks] and [Oracle][oke] provide various degrees of free managed clusters. + +Alternatively, you can set up a local cluster with [Docker +Desktop][docker-desktop] or [Kind][kind]. + +Once you have access to a cluster, the following exercises will help you get setup for running the exercises. + +- [setup-kubectl-linux](old/exercise_setup/00-setup-kubectl-linux.md) - Skip if + you've already installed `kubectl` and have access to a cluster. +- [setup-namespace](old/exercise_setup/00-setup-namespace.md) - Skip if you've + already created a personal namespace and set it as your default. + +### kubectl autocompletion + +On Linux, using bash, run the following commands: + +```shell +echo "source <(kubectl completion bash)" >> ~/.bashrc +. ~/.bashrc +``` + +The commands above will enable kubectl autocompletion when you start a new bash session and source (reload) bashrc i.e. enable kubectl autocompletion in your current session. + +See: [Kubernetes.io - Enabling shell autocompletion][autocompletion] for more info. + +# Cheatsheet + +A collection of useful commands to use throughout the exercises: + +```bash +kubectl api-resources # List resource types + + +kubectl explain # Show information about a resource +kubectl explain deployment + + +# List resources in cluster +kubectl get # In current namespace +kubectl get -n # In specific namespace +kubectl get --all-namespaces # In all namespaces +kubectl get -o wide # Add extended information +kubectl get -o yaml # output in YAML format +kubectl get -o json # output in JSON format + +# Example +kubectl get pods [-n abc|--all-namespaces] [-o wide|yaml|json] +``` + +See: +[kubectl - Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/) +for a more extended overview of the `kubectl` command. + +[eks]: https://aws.amazon.com/ecs/pricing/ +[gke]: https://cloud.google.com/kubernetes-engine/pricing#cluster_management_fee_and_free_tier +[aks]: https://azure.microsoft.com/en-us/pricing/free-services/ +[oke]: https://www.oracle.com/cloud/free/#free-cloud-trial +[docker-desktop]: https://docs.docker.com/desktop/ +[kind]: https://kind.sigs.k8s.io/ +[autocompletion]: https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion diff --git a/kubernetes-exercises/accessing-your-application.md b/kubernetes-exercises/accessing-your-application.md new file mode 100644 index 00000000..aeb861bb --- /dev/null +++ b/kubernetes-exercises/accessing-your-application.md @@ -0,0 +1,221 @@ +# Accessing your Application + +## Learning Goals + +- Ability to reach a pod inside the cluster from your local machine. +- By using `kubectl port-forward` to forward a local port to a port in a pod. +- Use environment variables to configure containers in pods. + +## Introduction + +Deploying a pod is not enough to make it accessible from outside the cluster. + +In this exercise you will learn how to make temporary connections to a pod inside the cluster via `kubectl port-forward`. + +## Port-forward + +The `kubectl port-forward` command allows you to forward one or more local ports to a pod. This can be used to access a pod that is running in the cluster, using for example a web browser or a command line tool like `curl`. + +The command takes two arguments: the pod name and the port to forward. The port is specified as `local:remote` to forward a local port to a remote port inside the pod. + +For example, if you want to forward port 8080 on your local machine to port 5000 in the pod, you can use the following command: + +`kubectl port-forward frontend 8080:5000` + +You can then access the pod on `localhost:8080`. + +
+:bulb: How does this port-forward work? + +Port forwarding is a network address translation that redirects internet packets form one IP address with specified port number to another `IP:PORT` set. + +In Kubernetes `port-forward` creates a tunnel between your local machine and Kubernetes cluster on the specified `IP:PORT` pairs in order to establish connection to the cluster. `kubectl port-forward` allows you to forward not only pods but also services, deployments and other. + +More information can be found from [here](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) + +
+ +## Exercise + +### Overview + +- Deploy the frontend pod +- Expose the frontend with port-forward. +- Look at the frontend in a browser. +- Delete the frontend pod +- Deploy the backend pod +- Add environment variables to the frontend pod +- Expose the frontend with port-forward. +- Execute a curl command to the backend from the frontend pod + +> :bulb: If you get stuck somewhere along the way, you can check the solution in the done folder. + +### Step by step instructions + +
+ +Step by step: + + +- Go into the `accessing-your-application` directory and the `start` folder. +- Deploy the frontend pod + +
+ +Hint on doing that + + +You can use the `kubectl apply -f ` command to deploy the pod. +The pod is defined in the `frontend-pod.yaml` file. + +
+ +- Check that the pod is running with `kubectl get pods` command. + +You should see something like this: + +``` +NAME READY STATUS RESTARTS AGE +frontend 1/1 Running 0 2m +``` + +- Expose the frontend with port-forward + +Port forward can be achieved with: + +`kubectl port-forward --address 0.0.0.0 frontend 8080:5000` + +> :bulb: We add the `--address 0.0.0.0` option to the port-forward command to make it accept commands coming from remote machines, like your laptop! +> `0.0.0.0` Means any address, so you probably don't want to do this on your own machine in, unless you want to expose something to the internet. + +And can then be accessed on `inst..eficode.academy:8080` (from the internet). + +> :bulb: VSCode will ask you if you what to see the open port. Unfortunately vscode proxy does not proxy requests correctly back to the pod, so use the URL of the instance instead. + +- Look at it in the browser. + +Now we will deploy both the frontend and backend pods. + +- Stop the port-forward process by pressing `Ctrl-c` in the terminal. +- Delete the frontend pod with `kubectl delete pod frontend` command. +- Deploy the backend pod with `kubectl apply -f backend-pod.yaml` command. +- Check that the pod is running, and note down the IP with `kubectl get pods -o wide` command. + +You should see something like this: + +``` +k get pods backend -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +backend 1/1 Running 0 11s 10.0.40.196 ip-10-0-35-102.eu-west-1.compute.internal +``` + +In this case the IP is `10.0.40.196`, but it will be different in your case. + +**Add environment variables to the frontend pod** + +- Open the `frontend-pod.yaml` file and add the following environment variables to the pod: + +```yaml +env: + - name: BACKEND_HOST + value: "10.0.40.196" # Use the IP address you noted down above + - name: BACKEND_PORT + value: "5000" +``` + +The `env` key is part of the `spec.containers[0]`: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:0c8adaa4fe8a40fe703cdda414a8f191f4966fc4 + ports: + - containerPort: 5000 + # +``` + +
+ +Help me! (solution) + + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:0c8adaa4fe8a40fe703cdda414a8f191f4966fc4 + ports: + - containerPort: 5000 + env: + - name: BACKEND_HOST + value: "10.0.40.196" + - name: BACKEND_PORT + value: "5000" +``` + +
+ +- Deploy the frontend pod with `kubectl apply -f frontend-pod.yaml` command. + +- Check that the pod is running with `kubectl get pods` command. + +- Forward a local port to the pod using `kubectl port-forward`. + +- Visit the frontend in the browser. + +You should see something like this: + +![alt](img/app-front-back.png) + +(if you don't you might need to refresh the page) + +- Exec into the frontend pod with `kubectl exec -it frontend -- /bin/sh` command. + +- Execute a curl command to the backend `curl http://:5000`. + +### Extra + +
+ +Extra exercise + + +While still having the port-forward running + +- Access the frontend in the browser and check that it still works and that frontend has access to the backend. +- Try to delete the backend pod with `kubectl delete pod backend` command. +- Try to recreate the backend pod with `kubectl apply -f backend-pod.yaml` command. +- Access the frontend in the browser. +- Does it still have access to the backend? + +If not, why not? + +
+Solution + +The frontend pod is not configured to automatically re-resolve the backend IP address. +So when we deleted the pod, and recreated it, the IP address changed, but the frontend pod still has the old IP address in its environment variables. + +Thankfully Kubernetes has a networking abstraction called `services` which solves this exact (and more!) problem, which we will learn about in the next exercise. + +
+
+
+ +### Clean up + +- Stop the port-forward with `Ctrl+C` command. +- Delete the pod with `kubectl delete pod frontend` command. +- Delete the pod with `kubectl delete pod backend` command. + +Congratulations! You have now learned how to make temporary connections to a pod inside the cluster via `kubectl port-forward`, and how to use environment variables to configure the pod. +And lastly, you have learned how to use `kubectl exec` to execute commands inside a pod. diff --git a/kubernetes-exercises/accessing-your-application/done/backend-pod.yaml b/kubernetes-exercises/accessing-your-application/done/backend-pod.yaml new file mode 100644 index 00000000..cbff51e7 --- /dev/null +++ b/kubernetes-exercises/accessing-your-application/done/backend-pod.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: backend + name: backend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/accessing-your-application/done/frontend-pod.yaml b/kubernetes-exercises/accessing-your-application/done/frontend-pod.yaml new file mode 100644 index 00000000..45234ae4 --- /dev/null +++ b/kubernetes-exercises/accessing-your-application/done/frontend-pod.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:release + ports: + - containerPort: 5000 + env: + - name: BACKEND_HOST + value: 10.0.40.196 + - name: BACKEND_PORT + value: '5000' diff --git a/kubernetes-exercises/accessing-your-application/start/backend-pod.yaml b/kubernetes-exercises/accessing-your-application/start/backend-pod.yaml new file mode 100644 index 00000000..cbff51e7 --- /dev/null +++ b/kubernetes-exercises/accessing-your-application/start/backend-pod.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: backend + name: backend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/accessing-your-application/start/frontend-pod.yaml b/kubernetes-exercises/accessing-your-application/start/frontend-pod.yaml new file mode 100644 index 00000000..95993186 --- /dev/null +++ b/kubernetes-exercises/accessing-your-application/start/frontend-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:release + ports: + - containerPort: 5000 \ No newline at end of file diff --git a/kubernetes-exercises/cheatsheet.md b/kubernetes-exercises/cheatsheet.md new file mode 100644 index 00000000..dfe243e3 --- /dev/null +++ b/kubernetes-exercises/cheatsheet.md @@ -0,0 +1,43 @@ +# Cheatsheet - Commonly used kubectl Commands + +```bash +kubectl config get-contexts # See all available contexts +kubectl config view # See current cluster context +kubectl config set-context $(kubectl config current-context) --namespace=my-namespace + # Change default namespace + +kubectl help run # See help about run (or other commands) +kubectl explain pod.spec # Documenation on any resource attribute + +kubectl get nodes # See nodes in cluster +kubectl get pods -o wide # See pods in current namespace +kubectl get pod -o yaml # See info about pod in yaml format +kubectl describe pod # Show information about pod +kubectl describe service # Show information about service + +kubectl api-resources # See resources types and abbreviations + +kubectl create namespace my-namespace # Create namespace + # Set default namespace +kubectl config set-context $(kubectl config current-context) --namespace= + +kubectl run multitool --image=ghcr.io/eficode-academy/network-multitool --restart Never # Create plain pod +kubectl create deployment nginx --image=nginx:1.7.9 # Create deployment + +kubectl set image deployment/nginx nginx=nginx:1.9.1 # Update image in deployment pod template +kubectl scale deployment nginx --replicas=4 # Scale deployment +kubectl rollout status deployment/nginx # See rollout status +kubectl rollout undo deployment/nginx # Undo a rollout + +kubectl exec -it bash # Execute bash inside pod +kubectl exec -it -- my-cmd -with -args # Execute cmd with arguments inside pod + +kubectl delete pod # Delete pod with name +kubectl delete deployment # Delete deployment with name + +kubectl expose deployment envtest --type=NodePort --port=3000 # Create service, expose deployment + +kubectl create configmap language --from-literal=LANGUAGE=Elvish # Create configmap + # Create secret +kubectl create secret generic apikey --from-literal=API_KEY=oneringtorulethemall +``` diff --git a/kubernetes-exercises/configmap-secrets/done/backend-deployment.yaml b/kubernetes-exercises/configmap-secrets/done/backend-deployment.yaml new file mode 100644 index 00000000..a25d7b85 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/backend-deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: backend + name: backend +spec: + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + imagePullPolicy: Always + envFrom: + - configMapRef: + name: postgres-config + - secretRef: + name: postgres-secret diff --git a/kubernetes-exercises/configmap-secrets/done/backend-svc.yaml b/kubernetes-exercises/configmap-secrets/done/backend-svc.yaml new file mode 100644 index 00000000..cf716ac8 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/configmap-secrets/done/frontend-deployment.yaml b/kubernetes-exercises/configmap-secrets/done/frontend-deployment.yaml new file mode 100644 index 00000000..1960d2ea --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/frontend-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" diff --git a/kubernetes-exercises/configmap-secrets/done/frontend-svc.yaml b/kubernetes-exercises/configmap-secrets/done/frontend-svc.yaml new file mode 100644 index 00000000..24900d61 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/configmap-secrets/done/postgres-configmap.yaml b/kubernetes-exercises/configmap-secrets/done/postgres-configmap.yaml new file mode 100644 index 00000000..2cfaafeb --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/postgres-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + DB_HOST: postgres + DB_PORT: "5432" + DB_USER: superuser + DB_NAME: quotes diff --git a/kubernetes-exercises/configmap-secrets/done/postgres-deployment.yaml b/kubernetes-exercises/configmap-secrets/done/postgres-deployment.yaml new file mode 100644 index 00000000..7c61208a --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/postgres-deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: postgres + spec: + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ports: + - containerPort: 5432 + env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_USER + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_NAME + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: DB_PASSWORD diff --git a/kubernetes-exercises/configmap-secrets/done/postgres-secret.yaml b/kubernetes-exercises/configmap-secrets/done/postgres-secret.yaml new file mode 100644 index 00000000..b5eb1203 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/postgres-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +kind: Secret +metadata: + creationTimestamp: null + name: postgres-secret diff --git a/kubernetes-exercises/configmap-secrets/done/postgres-service.yaml b/kubernetes-exercises/configmap-secrets/done/postgres-service.yaml new file mode 100644 index 00000000..797aa462 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/done/postgres-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: postgres + type: ClusterIP diff --git a/kubernetes-exercises/configmap-secrets/start/backend-deployment.yaml b/kubernetes-exercises/configmap-secrets/start/backend-deployment.yaml new file mode 100644 index 00000000..02b00488 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/backend-deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: backend + name: backend +spec: + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + imagePullPolicy: Always + ### using hardcoded env vars + env: + - name: DB_HOST + value: postgres + - name: DB_PORT + value: "5432" + - name: DB_USER + value: superuser + - name: DB_PASSWORD + value: complicated + - name: DB_NAME + value: quotes diff --git a/kubernetes-exercises/configmap-secrets/start/backend-svc.yaml b/kubernetes-exercises/configmap-secrets/start/backend-svc.yaml new file mode 100644 index 00000000..cf716ac8 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/configmap-secrets/start/frontend-deployment.yaml b/kubernetes-exercises/configmap-secrets/start/frontend-deployment.yaml new file mode 100644 index 00000000..1960d2ea --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/frontend-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" diff --git a/kubernetes-exercises/configmap-secrets/start/frontend-svc.yaml b/kubernetes-exercises/configmap-secrets/start/frontend-svc.yaml new file mode 100644 index 00000000..24900d61 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/configmap-secrets/start/postgres-deployment.yaml b/kubernetes-exercises/configmap-secrets/start/postgres-deployment.yaml new file mode 100644 index 00000000..333fb3ac --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/postgres-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: postgres + spec: + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ports: + - containerPort: 5432 + env: + - name: POSTGRES_USER + value: superuser + - name: POSTGRES_PASSWORD + value: complicated + - name: POSTGRES_DB + value: quotes diff --git a/kubernetes-exercises/configmap-secrets/start/postgres-service.yaml b/kubernetes-exercises/configmap-secrets/start/postgres-service.yaml new file mode 100644 index 00000000..797aa462 --- /dev/null +++ b/kubernetes-exercises/configmap-secrets/start/postgres-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: postgres + type: ClusterIP diff --git a/kubernetes-exercises/configmaps-secrets.md b/kubernetes-exercises/configmaps-secrets.md new file mode 100644 index 00000000..24e096e3 --- /dev/null +++ b/kubernetes-exercises/configmaps-secrets.md @@ -0,0 +1,375 @@ +# ConfigMaps and Secrets + +## Learning Goals + +- learn how to create `configmaps` and `secrets` +- learn how to use `configmaps` and `secrets` in a `deployment` + +## Introduction + +Configmaps and secrets are a way to store information that is used by several deployments and pods in your cluster. +This makes it easy to update the configuration in one place, when you want to change it. + +Both configmaps and secrets are generic `key-value` pairs, but secrets are `base64 encoded` and configmaps are not. + +> :bulb: Secrets are not encrypted, they are encoded. This means that if someone gets access to the cluster, they can will be able to read the values. + +## ConfigMaps + +You use a ConfigMap to keep your application code separate from your configuration. + +It is an important part of creating a [Twelve-Factor Application](https://12factor.net/). + +This lets you change easily configuration depending on the environment (development, production, testing, etc.) and to dynamically change configuration at runtime. + +A ConfigMap manifest looks like this in yaml: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config +data: + key1: value1 + key2: value2 + key3: value3 +``` + +There are three ways to create ConfigMaps using the `kubectl create configmap` command. + +- Use the contents of an entire directory with `kubectl create configmap my-config --from-file=./my/dir/path/` +- Use the contents of a file or specific set of files with `kubectl create configmap my-config --from-file=./my/file.properties` + +
+ +:bulb: More info + + +Env-files contain a list of environment variables. +These syntax rules apply: + +- Each line in an env file has to be in VAR=VAL format. +- Lines beginning with # (i.e. comments) are ignored. +- Blank lines are ignored. +- There is no special handling of quotation marks (i.e. they will be part of the ConfigMap value). + +```properties +enemies=aliens +lives=3 +allowed="true" + +# This comment and the empty line above it are ignored +``` + +Will be rendered as: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config +data: + enemies: aliens + lives: "3" + allowed: "true" +``` + +[Documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-files) + +
+ +- Use literal key-value pairs defined on the command line with `kubectl create configmap my-config --from-literal=key1=value1 --from-literal=key2=value2` + +> :bulb: remember the `--dry-run=client -o yaml` trick to see what the yaml file will look like before you apply it. + +
+ +:bulb: More info + + +[Summary of Configmaps](https://matthewpalmer.net/kubernetes-app-developer/articles/ultimate-configmap-guide-kubernetes.html) + +
+ +## Secrets + +`secrets` are used for storing configuration that is considered sensitive, and well ... _secret_. + +When you create a `secret` Kubernetes will go out of it's way to not print the actual values of secret object, to things like logs or command output. + +You should use `secrets` to store things like passwords for databases, API keys, certificates, etc. + +Rather than hardcode this sensitive information and commit it to git for all the world to see, we source these values from environment variables. + +`secrets` function for the most part identically to `configmaps`, but with the difference that the actual values are `base64` encoded. +`base64` encoded means that the values are obscured, but can be trivially decoded. +When values from a `secret` are used, Kubernetes handles the decoding for you. + +> :bulb: As `secrets` don't actually make their data secret for anyone with access to the cluster, you should think of `secrets` as metadata for humans, to know that the data contained within is considered secret. + +## Using ConfigMaps and Secrets in a deployment + +To use a configmap or secret in a deployment, you can either mount it in as a volume, or use it directly as an environment variable. + +### Injecting a ConfigMap as environment variables + +This will inject all key-value pairs from a configmap as environment variables in a container. +The keys will be the name of variables, and the values will be values of the variables. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + ... + spec: + containers: + - name: my-app + image: my-app:latest + ports: + - containerPort: 8080 + envFrom: + - configMapRef: # this is the configmap that we want to use + name: my-config # the name of the configmap we want to use +``` + +## Exercise + +### Overview + +- Add the database part of the application +- Change the database user into a configmap and implement that in the backend +- Change the database password into a secret, and implement that in the backend. +- Change database deployment to use the configmap and secret. + +### Step by step instructions + +
+ +Step by step: + + +> :bulb: All files for the exercise are found in the `configmap-secrets/start` folder. + +**Add the database part of the application** + +We have already created the database part of the application, with a deployment and a service. + +- Look at the database deployment file `postgres-deployment.yaml`. + Notice the database username and password are injected as hardcoded environment variables. + +> :bulb: This is not a good practice, as we do not want to store these values in version control. +> We will fix this in the next steps. + +- Look at the service file in `postgres-svc.yaml`. + It provides a service for the database, so that the backend can connect to it. + +- Apply the whole folder with `kubectl apply -f .` + +- Check that the applications are running with `kubectl get pods` + +Expected output: + +```bash +NAME READY STATUS RESTARTS AGE +backend-7d64597fcf-98vv5 1/1 Running 0 4s +backend-7d64597fcf-npvnq 1/1 Running 0 4s +backend-7d64597fcf-nrchp 1/1 Running 0 4s +frontend-5f9b5f46c8-jkw9n 1/1 Running 0 4s +postgres-6fbd757dd7-ttpqj 1/1 Running 0 4s +``` + +**Refactor the database user into a configmap and implement that in the backend** + +We want to change the database user into a configmap, so that we can change it in one place, and use it on all deployments that needs it. + +- Create a configmap with the name `postgres-config` and filename `postgres-config.yaml` and the information about database configuration as follows: + +```yaml +data: + DB_HOST: postgres + DB_PORT: "5432" + DB_USER: superuser + DB_PASSWORD: complicated + DB_NAME: quotes +``` + + +:bulb: If you are unsure how to do this, look at the [configmap section](#configmaps) above. + +
+Help me out! + +If you are stuck, here is the solution: + +This will generate the file: + +``` +kubectl create configmap postgres-config --from-literal=DB_HOST=postgres --from-literal=DB_PORT=5432 --from-literal=DB_USER=superuser --from-literal=DB_PASSWORD=complicated --from-literal=DB_NAME=quotes --dry-run=client -o yaml > postgres-config.yaml +``` + +You can also write it by hand: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + DB_HOST: postgres + DB_PORT: "5432' + DB_USER: superuser + DB_NAME: quotes + DB_PASSWORD: complicated +``` + +
+ + +- apply the configmap with `kubectl apply -f postgres-config.yaml` + +- In the `backend-deployment.yaml`, change the environment variables to use the configmap instead of the hardcoded values. + +Change this: + +```yaml +env: + - name: DB_HOST + value: postgres + - name: DB_PORT + value: "5432" + - name: DB_USER + value: superuser + - name: DB_PASSWORD + value: complicated + - name: DB_NAME + value: quotes +``` + +To this: + +```yaml +envFrom: + - configMapRef: + name: postgres-config +``` + +- re-apply the backend deployment with `kubectl apply -f backend-deployment.yaml` +- check that the website is still running. + +**Change the database password into a secret, and implement that in the backend.** + +We want to change the database password into a secret, so that we can change it in one place, and use it on all deployments that needs it. +In order for this, we need to change the backend deployment to use the secret instead of the configmap for the password itself. + +- create a secret with the name `postgres-secret` and the following data: + +```yaml +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +``` + +
+ +Help me out! + + +If you are stuck, here is the solution: + +``` +kubectl create secret generic postgres-secret --from-literal=DB_PASSWORD=complicated --dry-run=client -o yaml > postgres-secret.yaml +``` + +You can also write the secret by hand: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +``` + +
+ +- apply the secret with `kubectl apply -f postgres-secret.yaml` + +- In the `backend-deployment.yaml`, change the environment variables to use the secret instead of the configmap for the password. + +Change this: + +```yaml +envFrom: + - configMapRef: + name: postgres-config +``` + +To this: + +```yaml +envFrom: + - configMapRef: + name: postgres-config + - secretRef: + name: postgres-secret +``` + +- Delete the password from the configmap, and re-apply the configmap with `kubectl apply -f postgres-config.yaml` + +- Re-apply the backend deployment with `kubectl apply -f backend-deployment.yaml` + +- Check that the website is still running. + +**Change database deployment to use the configmap and secret.** + +We are going to implement the configmap and secret in the database deployment as well. + +The standard Postgres docker image can be configured by setting specific environment variables, ([you can see the documentation here](https://hub.docker.com/_/postgres)). +By populating these specific values we can configure the credentials for root user and the name of the database to be created. + +This means that we need to change the way we are injecting the environment variables, in order to make sure the environment variables have the correct names. + +- Open the `postgres-deployment.yaml` file, and change the way the environment variables are injected to use the configmap and secret. + +```yaml +### using configMapKeyRef +env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_USER + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_NAME + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: DB_PASSWORD +``` + +- re-apply the database deployment with `kubectl apply -f postgres-deployment.yaml` +- check that the website is still running, and that the new database can be reached from the backend. + +Congratulations! You have now created a configmap and a secret, and used them in your application. + +
+ +### Extra + +If you have time, try to get the secret data decoded again. + +Here is a snippet to get you started: + +```bash +kubectl get secret -o jsonpath="{.data.password}" | base64 --decode +``` + +### Clean up + +Delete the resources you have deployed by running `kubectl delete -f .` in the `configmaps-secrets/start` directory. diff --git a/kubernetes-exercises/deployments-ingress.md b/kubernetes-exercises/deployments-ingress.md new file mode 100644 index 00000000..08bfa17e --- /dev/null +++ b/kubernetes-exercises/deployments-ingress.md @@ -0,0 +1,602 @@ +# Deployments and Loadbalancing + +## Learning Goals + +- Learn how to expose a deployment using Ingress +- Learn how to use `deployments` +- Learn how to scale deployments +- Learn how to use `services` to do loadbalance between the pods of a scaled deployment + +## Introduction + +In this exercise, you'll learn how to deploy a pod using a deployment, how to scale it, and how to expose it using an `Ingress` resource with URL routing. + +## Deployments + +Deployments are a higher level abstraction than pods, and controls the lifecycle and configuration of a "deployment" of an application. +They are used to manage a set of pods, and to ensure that a specified number of pods are always running with the desired configuration. +`deployments` are a Kubernetes `kind` and defined in a manifest file. + +A `deployment` manifest file looks like this: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: # Deployment name +spec: + replicas: # the number of pods to run + selector: # How to select pods belonging to this deployment, must match the pod template's labels + matchLabels: # List of labels to match pods + template: # Pod template + metadata: + labels: # List of labels + spec: + containers: # List of containers belonging to the pod + - name: # Name of the container + image: # Container image +``` + +
+:bulb: An Example: Nginx deployment + +An example of a deployment manifest file for nginx would look like this: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 + selector: + matchLabels: + run: nginx + template: + metadata: + labels: + run: nginx + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 +``` + +
+ +### High Availability + +In order to make our application stable, we want **high availability**. +High availability means that we replicate our applications, such that we have **redundant copies**, which means that _when_ an application fails, our users are not impacted, as they will simply use one of the other copies, while the failed instance recovers. + +In Kubernetes this is done in practice by `scaling` a deployment, e.g. by adding or removing `replicas`. +`replicas` are **identical** copies of the **the same pod**. + +To scale a deployment, we change the number of `replicas` in the manifest file, and then apply the changes using `kubectl apply -f `. + +## Ingress + +Ingress in Kubernetes that manages external access to the services in a cluster, typically HTTP and HTTPS. + +Ingress can provide load balancing, SSL termination, and name-based virtual routing. + +Ingress builds on top of the `service` concept, and is implemented by an `ingress controller`. + +An example Ingress manifest to be used in AWS looks like this: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + #annotations are used to configure the ingress controller behavior. + alb.ingress.kubernetes.io/scheme: internet-facing + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + name: quotes-ingress +spec: + # rules are used to configure the routing behavior. + rules: + # each rule has a host and a list of paths. The host is used to match the host header of the request, normally the domain name. + - host: quotes-1.prosa.eficode.academy + http: + paths: + - pathType: Prefix + path: "/" + # each path has a backend, which is used to route the request to a service. + backend: + service: + # This is the name of the service to route to. + name: quotes-frontend + port: + number: 80 +``` + +When you apply this manifest, an A-record will be created in Route53, pointing to our ingress-controller called ALB, and the ALB will route traffic to the service. + +## Exercise + +### Overview + +- Add frontend Ingress and let it reconcile +- Turn the backend pod manifests into a deployment manifest +- Apply the backend deployment manifest +- Scale the deployment by adding a replicas key +- Turn frontend pod manifests into a deployment manifest +- Apply the frontend deployment manifest +- Test service promise of high availability + +> :bulb: If you get stuck somewhere along the way, you can check the solution in the done directory. + +### Step by step instructions + +
+ +Step by step: + + +- Go into the `deployments-ingress/start` directory. + +In the directory we have the pod manifests for the backend and frontend that have created in the previous exercises. +We also have two services, one for the backend (type ClusterIP) and one for the frontend (type NodePort) as well as an ingress manifest for the frontend. + +**Add Ingress to frontend service** + +As it might take a while for the ingress to work, we will start by adding the ingress to the frontend service, even though we have not applied the service yet. + +- Open the `frontend-ingress.yaml` file in your editor. +- Change the hostname to `quotes-..eficode.academy`. Just as long as it is unique. +- Change the service name to match the name of the frontend service. +- Apply the ingress manifest. + +``` +kubectl apply -f frontend-ingress.yaml +``` + +Expected output: + +``` +ingress.networking.k8s.io/frontend-ingress created +``` + +- Check that the ingress has been created. + +``` +kubectl get ingress +``` + +Expected output: + +``` +NAME HOSTS ADDRESS PORTS AGE +frontend-ingress quotes-..eficode.academy 80 1m +``` + +Congratulations, you have now added an ingress to the frontend service. +It will take a while for the ingress to work, so we will continue with the backend deployment. + +**Turn the backend pod manifests into a deployment manifest** + +- Deploy the frontend pod as well as the two services `backend-svc.yaml` and `frontend-svc.yaml`. + Use the `kubectl apply -f` command. + +- Verify that the frontend is accessible from the browser. + +
+ + +How do I connect to a pod through a NodePort service? + + +> :bulb: In previous exercises you learned how connect to a pod exposed through a NodePort service, you need to find the nodePort using `kubectl get service` and the IP address of one of the nodes using `kubectl get nodes -o wide` +> Then combine the node IP address and nodePort with a colon between them, in a browser or using curl: + +``` +http://: +``` + +
+ +**Turn the backend pod manifests into a deployment manifest** + +- Open both the backend-deployment.yaml and the backend-pod.yaml files in your editor. + +- add the api-version and kind keys to the backend-deployment.yaml file. The api-version should be `apps/v1` and the kind should be `Deployment`. +- Give the deployment a name of backend under `metadata.name` key, use `backend`. +- Add a label of `run: backend` under `metadata.labels` key. +- The `spec.replicas` key denotes how many replicas we would like. Set it to 1 to begin with. + +Before we go to the selector key, we need to add the pod template. +The pod template is the same as the pod manifest we have been using. + +We want to populate the deployment manifest with the information from the pod manifest. + +- Copy the `metadata.labels` (do not copy `metadata.name`) and `spec` contents of the backend-pod.yaml file into the backend-deployment.yaml file under the `spec.template` key. + +
+ +:bulb: hint (solution) + + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + run: backend + template: + metadata: + labels: + run: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend +``` + +
+ +Now we want the deployment controller to manage the pods. +We need to add a selector to the deployment manifest. + +- Add a selector key under the `spec` key. + The selector key should have a matchLabels key. + The matchLabels key should have a `run: backend` key-value pair. + +
+ +:bulb: hint + + +The `matchLabels` key should look like this: + +```yaml +... +spec: + replicas: 1 + selector: + matchLabels: + run: backend + template: + ... +``` + +The same as the labels key in the metadata key of the pod template. + +
+ +**Apply the deployment manifest** + +- Apply the deployment manifest, the same way we have applied the pod manifests, just pointing to a different file. + +``` +kubectl apply -f backend-deployment.yaml +``` + +Expected output: + +``` +deployment.apps/backend-deployment created +``` + +- Check that the deployment has been created. + +``` +kubectl get deployments +``` + +Expected output: + +``` +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +backend 1 1 1 1 1m +``` + +- Check that the pod has been created. + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +backend-5f4b8b7b4-5x7xg 1/1 Running 0 1m +``` + +- Access the frontend again from the browser. Now the Ingress should work and you should be able to access the frontend from the browser using the hostname you specified in the ingress manifest. + +The url should look something like this: + +``` +http://quotes-..eficode.academy +``` + +- If it still does not work, you can check it through NodePort service instead. + +- You should now see the backend. + +- If this works, please delete the `backend-pod.yaml` file, as we now have upgraded to a deployment and no longer need it! + +**Scale the deployment by adding a replicas key** + +- Scale the deployment by changing the replicas key in the deployment manifest. + Set the replicas key to 3. + +- Apply the deployment manifest again. + +``` +kubectl apply -f backend-deployment.yaml +``` + +Expected output: + +``` +deployment.apps/backend-deployment configured +``` + +- Check that the deployment has been scaled. + +``` +kubectl get deployments +``` + +Expected output: + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +backend 3/3 3 3 3m29s +``` + +- Check that the pods have been scaled. + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +backend-5f4b8b7b4-5x7xg 1/1 Running 0 2m +backend-5f4b8b7b4-6j6xg 1/1 Running 0 1m +backend-5f4b8b7b4-7x7xg 1/1 Running 0 1m +``` + +- Access the frontend again from the browser. It should now periodically change the `hostname` part of the website. + + + + + + +**Turn frontend pod manifests into a deployment manifest** + +You will now do the exact same thing for the frontend, we will walk you through it again, but at a higher level, if get stuck you can go back and double check how you did it for the backend. + +- Open both the frontend-deployment.yaml and the frontend-pod.yaml files in your editor. +- add the api-version and kind keys to the frontend-deployment.yaml file. +- Give the deployment a name of `frontend` under `metadata.name` key. +- Add a label of `run: frontend` under `metadata.labels` key. +- Set `spec.replicas` to 3. +- Copy the `metadata` and `spec` contents of the frontend-pod.yaml file into the frontend-deployment.yaml file under the `spec.template` key. +- Add a selector key under the `spec` key. + The selector key should have a matchLabels key. + The matchLabels key should have a `run: frontend` key-value pair. + +**Apply the frontend deployment manifest** + +- First, delete the frontend pod. + +``` +kubectl delete pod frontend +``` + +Expected output: + +``` +pod "frontend" deleted +``` + +- Apply the frontend deployment manifest. + +``` +kubectl apply -f frontend-deployment.yaml +``` + +Expected output: + +``` +deployment.apps/frontend-deployment created +``` + +- Check that the deployment has been created. + +``` +kubectl get deployments +``` + +Expected output: + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +backend 3/3 3 3 2m41s +frontend 3/3 3 3 2m41s +``` + +- Check that the pod has been created. + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +backend-5f4b8b7b4-5x7xg 1/1 Running 0 3m +backend-5f4b8b7b4-6j6xg 1/1 Running 0 2m +backend-5f4b8b7b4-7x7xg 1/1 Running 0 2m +frontend-47b45fb8b-4x7xg 1/1 Running 0 1m +frontend-47b45fb8b-4j6xg 1/1 Running 0 1m +frontend-47b45fb8b-4x7xg 1/1 Running 0 1m +``` + +- Access the frontend again from the browser. + Note that both the frontend and backend hostname parts of the website should change periodically. + +- If this works, please delete the `frontend-pod.yaml` file, as we now have upgraded to a deployment and no longer need it! + + +
+ +### Clean up + +- Delete the deployments. + +``` +kubectl delete -f frontend-deployment.yaml +kubectl delete -f backend-deployment.yaml +``` + +- Delete the services + +``` +kubectl delete -f frontend-svc.yaml +kubectl delete -f backend-svc.yaml +``` + +- Delete the ingress + +``` +kubectl delete -f frontend-ingress.yaml +``` + +> :bulb: If you ever want to delete all resources from a particular directory, you can use: `kubectl delete -f .` which will point at **all** files in that directory! + +# Extra Exercise + +Test Kubernetes promise of resiliency and high availability + +
+ +An example of using a LoadBalancer service to route traffic to replicated pods + + +We can use the `ghcr.io/eficode-academy/network-multitool` image to illustrate both high availability and load balancing of `services`. +The `network-multitool` pod will serve a tiny webpage that dynamically contains the pod hostname and IP address of the pod. +This enables us to see which of a group of network-multitool pods that served the request. + +Create the network-multitool deployment: + +``` +kubectl create deployment customnginx --image ghcr.io/eficode-academy/network-multitool --port 80 --replicas 4 +``` + +We create the network-multitool deployment with the name "customnginx" and with four replicas, so we expect to have four pods. + +We also create a service of type `LoadBalancer`: + +``` +kubectl expose deployment customnginx --port 80 --type LoadBalancer +``` + +> :bulb: It might take a minute to provision the LoadBalancer, if you are using AWS, then `kubectl get services` will show you the DNS name of the provisioned LoadBalancer immediately, but it will be a moment before it is ready. + +When the LoadBalancer is ready we setup a loop to keep sending requests to the pods: + +``` +while true; do curl --connect-timeout 1 -m 1 -s ; sleep 0.5; done +``` + +Expected output: + +``` +Eficode Academy Network MultiTool (with NGINX) - customnginx-7fcfd947cf-zbvtd - 100.96.2.36

+Eficode Academy Network MultiTool (with NGINX) - customnginx-7fcfd947cf-zbvtd - 100.96.1.150

+Eficode Academy Network MultiTool (with NGINX) - customnginx-7fcfd947cf-zbvtd - 100.96.2.37

+Eficode Academy Network MultiTool (with NGINX) - customnginx-7fcfd947cf-zbvtd - 100.96.2.37

+Eficode Academy Network MultiTool (with NGINX) - customnginx-7fcfd947cf-zbvtd - 100.96.2.36

+``` + +We see that when we query the LoadBalancer IP, it is giving us result/content from all four pods. +None of the curl commands time out. +Now, if we kill three out of four pods, the service should still respond, without timing out. +We let the loop run in a separate terminal, and kill three pods of this deployment from another terminal. + +``` +kubectl delete pod customnginx-3557040084-1z489 customnginx-3557040084-3hhlt customnginx-3557040084-c6skw +``` + +Expected output: + +``` +pod "customnginx-3557040084-1z489" deleted +pod "customnginx-3557040084-3hhlt" deleted +pod "customnginx-3557040084-c6skw" deleted +``` + +Immediately check the other terminal for any failed curl commands or timeouts. + +``` +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-4w4gf - 10.244.0.19 +``` + +Expected output: + +``` +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-h2dbg - 10.244.0.21 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-5xbjc - 10.244.0.22 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-h2dbg - 10.244.0.21 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-4wn9c - 10.244.0.20 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-5xbjc - 10.244.0.22 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-h2dbg - 10.244.0.21 +Eficode Academy Network MultiTool (with NGINX) - customnginx-59db6cff7b-5xbjc - 10.244.0.22 +``` + +We notice that no curl commands failed, and actually we have started seeing new IPs. +Why is that? +It is because, as soon as the pods are deleted, the deployment sees that it's desired state is four pods, and there is only one running, so it immediately starts three more to reach the desired state of four pods. +And, while the pods are in process of starting, one surviving pod serves all of the traffic, preventing our application from missing any requests. + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +customnginx-3557040084-0s7l8 1/1 Running 0 15s +customnginx-3557040084-1z489 1/1 Terminating 0 16m +customnginx-3557040084-3hhlt 1/1 Terminating 0 16m +customnginx-3557040084-bvtnh 1/1 Running 0 15s +customnginx-3557040084-c6skw 1/1 Terminating 0 16m +customnginx-3557040084-fw1t3 1/1 Running 0 16m +customnginx-3557040084-xqk1n 1/1 Running 0 15s +``` + +This proves, Kubernetes enables high availability, by using multiple replicas of a pod, and loadbalancing between them. + +Remember to clean up the deployment afterwards with: + +``` +kubectl delete deployment customnginx +``` + +And delete the LoadBalancer service: + +``` +kubectl delete service customnginx +``` + +
diff --git a/kubernetes-exercises/deployments-ingress/done/backend-deployment.yaml b/kubernetes-exercises/deployments-ingress/done/backend-deployment.yaml new file mode 100644 index 00000000..d4d6d951 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/backend-deployment.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + run: backend + template: + metadata: + labels: + run: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend diff --git a/kubernetes-exercises/deployments-ingress/done/backend-svc.yaml b/kubernetes-exercises/deployments-ingress/done/backend-svc.yaml new file mode 100644 index 00000000..32fe49cc --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/backend-svc.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + run: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: backend + type: ClusterIP diff --git a/kubernetes-exercises/deployments-ingress/done/frontend-deployment.yaml b/kubernetes-exercises/deployments-ingress/done/frontend-deployment.yaml new file mode 100644 index 00000000..aa640f09 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/frontend-deployment.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + run: frontend + template: + metadata: + labels: + run: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: '5000' diff --git a/kubernetes-exercises/deployments-ingress/done/frontend-ingress.yaml b/kubernetes-exercises/deployments-ingress/done/frontend-ingress.yaml new file mode 100644 index 00000000..d74d2812 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/frontend-ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + # These annotations are required to use ALB Ingress Controller + alb.ingress.kubernetes.io/scheme: internet-facing + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + name: frontend-ingress +spec: + rules: + # you need to change the host to match your own + - host: quotes-sal.prosa.eficode.academy + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + # change this to match your service name + name: frontend + port: + number: 5000 \ No newline at end of file diff --git a/kubernetes-exercises/deployments-ingress/done/frontend-svc.yaml b/kubernetes-exercises/deployments-ingress/done/frontend-svc.yaml new file mode 100644 index 00000000..48f05fa5 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/frontend-svc.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + run: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: frontend + type: NodePort diff --git a/kubernetes-exercises/deployments-ingress/done/poll.sh b/kubernetes-exercises/deployments-ingress/done/poll.sh new file mode 100755 index 00000000..0e4a63d9 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/done/poll.sh @@ -0,0 +1,9 @@ +#! /bin/sh +#check if any parameters are passed +if [ $# -eq 0 ] +then + echo "No arguments supplied, please pass the ip address of the server" + echo "And remember the port as well" + exit 1 +fi +while true; do curl --connect-timeout 1 -m 1 -s $1; sleep 0.5; done \ No newline at end of file diff --git a/kubernetes-exercises/deployments-ingress/start/backend-deployment.yaml b/kubernetes-exercises/deployments-ingress/start/backend-deployment.yaml new file mode 100644 index 00000000..5137f82e --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/backend-deployment.yaml @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: # Deployment name + labels: # Deployment labels +spec: + replicas: # the number of pods to run + selector: # How to select pods belonging to this deployment. Must match the pod template's labels + matchLabels: # List of labels + template: # This is the pod template, paste in the pod spec from backend-pod.yaml diff --git a/kubernetes-exercises/deployments-ingress/start/backend-pod.yaml b/kubernetes-exercises/deployments-ingress/start/backend-pod.yaml new file mode 100644 index 00000000..8f928311 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/backend-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: backend + name: backend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + resources: {} + restartPolicy: Always diff --git a/kubernetes-exercises/deployments-ingress/start/backend-svc.yaml b/kubernetes-exercises/deployments-ingress/start/backend-svc.yaml new file mode 100644 index 00000000..76cd6048 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + run: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: backend + type: ClusterIP diff --git a/kubernetes-exercises/deployments-ingress/start/frontend-deployment.yaml b/kubernetes-exercises/deployments-ingress/start/frontend-deployment.yaml new file mode 100644 index 00000000..289bea09 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/frontend-deployment.yaml @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: # Deployment name + labels: # Deployment labels +spec: + replicas: # the number of pods to run + selector: # How to select pods belonging to this deployment. Must match the pod template's labels + matchLabels: # List of labels + template: # This is the pod template, paste in the pod spec from frontend-pod.yaml diff --git a/kubernetes-exercises/deployments-ingress/start/frontend-ingress.yaml b/kubernetes-exercises/deployments-ingress/start/frontend-ingress.yaml new file mode 100644 index 00000000..15816dbf --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/frontend-ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + # These annotations are required to use ALB Ingress Controller + alb.ingress.kubernetes.io/scheme: internet-facing + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + name: frontend-ingress +spec: + rules: + # you need to change the host to match your own + - host: quotes-..eficode.academy + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + # change this to match your service name + name: + port: + # this is the port your service is listening on + number: 5000 \ No newline at end of file diff --git a/kubernetes-exercises/deployments-ingress/start/frontend-pod.yaml b/kubernetes-exercises/deployments-ingress/start/frontend-pod.yaml new file mode 100644 index 00000000..243d3e9a --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/frontend-pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: frontend + name: frontend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: frontend + resources: {} + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" + restartPolicy: Always diff --git a/kubernetes-exercises/deployments-ingress/start/frontend-svc.yaml b/kubernetes-exercises/deployments-ingress/start/frontend-svc.yaml new file mode 100644 index 00000000..e9cc98e1 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + run: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: frontend + type: NodePort diff --git a/kubernetes-exercises/deployments-ingress/start/poll.sh b/kubernetes-exercises/deployments-ingress/start/poll.sh new file mode 100755 index 00000000..0b2e31a3 --- /dev/null +++ b/kubernetes-exercises/deployments-ingress/start/poll.sh @@ -0,0 +1,11 @@ +#! /bin/sh +#check if any parameters are passed +if [ $# -eq 0 ]; then + echo "No arguments supplied, please pass the ip address of the server" + echo "And remember the port as well" + exit 1 +fi +while true; do + curl --connect-timeout 1 -m 1 -s $1 + sleep 0.5 +done diff --git a/kubernetes-exercises/desired-state.md b/kubernetes-exercises/desired-state.md new file mode 100644 index 00000000..c748084f --- /dev/null +++ b/kubernetes-exercises/desired-state.md @@ -0,0 +1,180 @@ +# Desired State + +Desired state is one of the core concepts of Kubernetes. It is the state that you want your cluster to be in. It is the state that you define in your Kubernetes manifests. +It means that the cluster continously will try to fulfill your desired state, even if it will never be posible to reach it. + +In this exercise you will apply Kubernetes manifests to your cluster, and learn how Kubernetes fulfills your desired state. + +## Controllers + +Kubernetes controllers are the components that fulfills your desired state. As the example we will use here, a deployment controller will attempt to do so that the number of pods that you have defined in your manifest is always running in the cluster. + +## Learning Goals + +- Applying Kubernetes manifests using `kubectl apply -f `. +- Verifying Kubernetes promise of fulfilling your desired state. + +## Exercise + +### Overview + +- Inspect existing Kubernetes manifest for a `deployment` object. +- Apply the manifest using the `kubectl apply` command. +- Delete a pod managed by the deployment controller. +- Observe that a new pod is created in it's place by the controller. + +### Step by step instructions + +
+ +Step by step: + + +## Inspect existing Kubernetes manifest for a `deployment` object. + +We have prepared a Kubernetes manifest for you. + +You can find the manifest in the file: `desired-state/nginx-deployment.yaml`. + +Below is the contents of the manifest: + +```yaml +# anything after a `#` are comments! +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx # deployment resource name, pods running as a part of the deployment will share the name. + labels: + app: nginx # deployment resource label +spec: + replicas: 1 # number of pods to run + selector: + matchLabels: # selector labels the replicaset looks for + app: nginx + template: + metadata: + labels: + app: nginx # pod labels that must match selector + version: latest # arbitrary label we can match on elsewhere + spec: + containers: + - name: nginx # name of the container running inside a pod, different from the pod name + image: nginx:latest + ports: + - containerPort: 80 # port the container is listening on +``` + +## Apply the manifest using the `kubectl apply`. + +Use the `kubectl apply -f ` command to send the manifest with your desired state to Kubernetes: + +``` +kubectl apply -f desired-state/nginx-deployment.yaml +``` + +Expected output: + +``` +deployment.apps/nginx applied +``` + +Verify that the deployment is created: + +``` +kubectl get deployments +``` + +Expected output: + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +nginx 1/1 1 1 36s +``` + +Check if the pods are running: + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +nginx-431080787-9r0lx 1/1 Running 0 40s +``` + +Kubernetes is now doing everything it can to satisfy our desired state of running our nginx webserver. + +Let's test that Kubernetes actually keeps it's promise of fulfilling the desired state. + +## Test Kubernetes promise of desired state by deleting a pod + +Since we have asked Kubernetes to run our nginx pod using a `deployment`, the deployment controller will keep monitoring our pods and make sure that a nginx pod keeps running. + +Let's see this in action: + +We will use the `kubectl delete ` command to delete our nginx pod. + +We then expect a new pod to be created by the deployment controller in its place. + +First, find the name of your pod using `kubectl get pods`, like you did above. + +The name will be something like `nginx-431080787-9r0lx`. **Yours will have a different, but similar name**. + +``` +kubectl delete pod nginx-431080787-9r0lx +``` + +Expected output: + +``` +pod "nginx-431080787-9r0lx" deleted +``` + +The desired state we have defined specifies that exactly one nginx pod should exist, since we have now deleted the nginx pod, we have forced our `deployment` to drift away from the desired state, as there are now zero nginx pods. + +Therefore Kubernetes must make a change to the state of the cluster to once again fulfill our desired state, therefore Kubernetes will create a new nginx pod to replace the one we have deleted. + +## Observe that a new pod is created in it's place by the deployment controller + +We use `kubectl get` to verify that a **new** nginx pod is created (with a different name): + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +nginx-431080787-tx5m7 0/1 ContainerCreating 0 5s +``` + +And after few more seconds: + +``` +kubectl get pods +``` + +Expected output: + +``` +NAME READY STATUS RESTARTS AGE +nginx-431080787-tx5m7 1/1 Running 0 12s +``` + +Congratulations! You have now created a deployment using a Kubernetes manifest. + +You have also seen that Kubernetes keeps it's promise of fulfilling your desired state, by creating a new pod in the place of the deleted pod. + +
+ + +### Clean up + +Delete your desired state by using the `kubectl delete -f ` command. + +``` +kubectl delete -f desired-state/nginx-deployment.yaml +``` \ No newline at end of file diff --git a/kubernetes-exercises/desired-state/nginx-deployment.yaml b/kubernetes-exercises/desired-state/nginx-deployment.yaml new file mode 100644 index 00000000..2a3fa94b --- /dev/null +++ b/kubernetes-exercises/desired-state/nginx-deployment.yaml @@ -0,0 +1,23 @@ +# a comment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx # arbitrary label on deployment +spec: + replicas: 1 + selector: + matchLabels: # labels the replica selector should match + app: nginx + template: + metadata: + labels: + app: nginx # label for replica selector to match + version: latest # arbitrary label we can match on elsewhere + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 \ No newline at end of file diff --git a/kubernetes-exercises/exercise-template.md b/kubernetes-exercises/exercise-template.md new file mode 100644 index 00000000..efe288f9 --- /dev/null +++ b/kubernetes-exercises/exercise-template.md @@ -0,0 +1,40 @@ +# Template headline + +## Learning Goals + +- provide a list of goals to learn here + +## Introduction + +Here you will provide the bare minimum of information people need to solve the exercise. + +## Subsections + +You can have several subsections if needed. + +
+:bulb: If an explanaition becomes too long, the more detailed parts can be encapsulated in a drop down section +
+ +## Exercise + +### Overview + +- In bullets, what are you going to solve as a student + +### Step by step instructions + +
+More Details + +**take the same bullet names as above and put them in to illustrate how far the student have gone** + +- all actions that you believe the student should do, should be in a bullet + +> :bulb: Help can be illustrated with bulbs in order to make it easy to distinguish. + +
+ +### Clean up + +If anything needs cleaning up, here is the section to do just that. \ No newline at end of file diff --git a/kubernetes-exercises/img/app-front-back.png b/kubernetes-exercises/img/app-front-back.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe8f551bb7dc2d07c05343d97a0b2a0e67920c1 GIT binary patch literal 33050 zcmdqHQ;;W5`0m-$wr$&XPusR_Ti@=U=Cp0wwrz9Twry?y{t;*6?A6|!z1fPWc(XFI z;*F}zs*LQW+vb~v$yP=aQkhz_`ttp+ev6HE(owJ3#%QZ+3|4%2n|2hdfnHsuS+S?JT zSlXHbsaUuYGO`g$8QKvtGcvOfGIDVzTG8FzE%F}x=h4@Mw4sNfi&{H6(CMzKi4gay-8p6= zK;wTqfl5f^-hTo-1nunI+|1m+cp4gO3WDhO(pqL;vW4! z98V5i!1*cCF)&sgcW7I#H}|hL#VyWd3P&FtpUXqss`@=tn!zBs`3I9|DbF;nQ7BMZt$GFf$Js6LLwk%GK}>AS{8C8?yvg+f{IZ3x&G ztbAN1{nSrO`Pbmi+?D-Gfls?7cmAD90af;YikZy|C6aI^zjhpRD+|Q2e=&VCl1uh5E@n2RF&MJYEn+=?PD7)B zeLPZShO*{*DV1^e%H;7n_nv5(j3}bBJ~qC2+~#x5|9r*YXLmz>)bzS&KS_GJKj#0t z>~OWY$i^?OTyhD#!Fxch_On=8}V7k+3SkF$^h zT6e5ox2MF@{q#nw^)E6_-F$Ce6IHuDfd{?p`|AY`=Kfw2){X6D=Cs&9WzGFnU)1bNXKoTr3DAc7nWhbIG*UFm zD&A9mJ5;{2HpM|5gvQ$-e(%vL8gs68KZtcZ&-vzh$!Ig}_`=G2N06f76j^C_#CYC^ z0!CU1!w8^ka4R4>!@tROPlm$aiIb`zar1tfA7pZwF_Kv1s#^07djx=mMe0MWoOwhV z9&ze*E~Pd}bukLkeEV2mr+88yFMg|Bnb!K`q?EmQ96Yjra6Kl)pGMrDZo#0xhpwhn4h{n8hr;SpPyju;fcw5LhBDNX?4^^u|?b zuejcT9$&p6VdBS(Z=?qdO(?ag@o6>Mg_czL4r4_;9^a+0WcD}(YP(A(TF8%r!#-E$bph=HLZrTHqgAk86QvQP{k;9L&4G|eUwJ@ljP zSNnGFms>rrnPNVHAtS=c1W%0PN0Rg5uT;vGGpB9K6A-+* zuGpHh-w;XvIll4~*-Z34`^W4%Eirbm2xHu`<4q zf%jU2;CXQ!#@6ffl}Wb{`nbBO*~a_YY)Kwk5@&pV&h|+4xk#ppD}gX$wAzE7hA3N* zvx4%S-rPCf;OmJwygyp7xtRI536^e;GT&QWeZY=&+_W?%x#{tP`hHL0Y;>fOZvUR_ ztYaJfrgt^Bh>R2Z#o%1#+5ajnXmNS(mzfgFwygQNZ{s%7$^R%WY!b_`90+Lhb)uL^ zVKtfmAUIXve&*Z9m|NClHUnLNe?`hUVq6pn=G~VYWx3Mfo!;{Pw^&00Qk3`(Xe>it z!|2d#69_6~m-WvUhFSB*&Y#rIfp@LcGZn#cOHJa*PEbX;7x{4hO5;g=GIqaV%@Qx* zO8U3%jz0FP>2vz=8O;%3q9s*1q2AB6L>E);K^5LZhY88{HDbni(lHR^yQun$^~LTp zJbED6>FSa26&vaP&*k=W{&D7nh~JK0FnOkQggA@G9NxS(I57z{i}{9h5w#4`P@N^o z%k3YATY}UQ875jW%Z?V^;(%5P?=(&qbaP_?vEnWN(1Vl3T^Sz|iEcSjBzgBcAMk{p z!kHQIGexV`9#fDKp&3QGl5sO%(+@uP)eu^QUDo6;q4;-E!67cOwuSbIpV6nauB_ zTT!k9+w5@5RizWR+2UVIXJ4I6E-S}GOMjhZYet11x}=Oh2$Fz$D8qdT`TL)g?xNNDAKkS zhfWs)m@vv;Z}07;!JMemXLaZ$#21zDNZ`DW{=aa;Wylw*QTmr3*-Edt1&%0b(LwM63#lhJXtK`T8hXpyAu79 zNhyO;g^($-;?7YiTS_f96<@HK+$tobH{x_ZgNCBBI`bH=>FspD8gKW_1cPYwMesXx3YWEA;keGjer3As7qu{8YdN!@7A(fv}Tb#)_V_tI3 zcZH#N(C^CpjJ(8=LwqFSQ9UerJGnkJt$8jNoDIP>oR|xB_nYJS`Ber5-Dj)<1WYxQ zR15h86p2O70o^J=5$#Aac#6k!Sy9Or}K2)%Lm>;?`DuT>udPwQ= zZ}H^+806&zKawtOdcLprH^X_8PUnW14x<$Yo$l8_wk$n$gV~lw7>qX);XPK*y3h?k z!}?lQI$pfJ*sG-zkYG5UE_A`&1%l!Mt{F;04@;Voi8Ty;ZI*(+Kn7FW721L`L9v9){sEV&r1$;Etuc>p`oZG+N`BR7q@pOWOIJhWbW+T z!szr|PejyOAft(;Y99|wFLKD{hDJoaQ&tR;>hVkrwI*%_cdN6cn=-De$CZFrSsfZ4 z4<8(_r9))$5d=8Y`ix}9hVRjvGOFvzXFn8>?RWxVZ`~fKr|xdJA)jYF9d2w*RqD2+ z@nK}6Bv|GB$_v|hs!n_0_lZZk|1yX*SECy%B^!Sx7LU$6nxBGr0T)9oP%|{LlojM5 zlb1}hAvH1%{VbiybX7)s9_Hx@&zOmqvzW^2Ir;a5#P|MOXbi#ujX9RaqjZ&W>96z- zU^tH!FM5cEX3aI_rkSIJVart=!us&0JXY(J>pc)- zo}7t+{Kb9^@G&#hYA3xr*j-4(&+dm5PjUA~$9{IeSfG$GK70j2W}dSMkD0Hvg1UW_ zewePhf5g@F0gHGQR{P@pk`?rl>{vG3(;b4PXctahS2o4p-$)> z9fWfC;qPv5FaOPqe|3NZdg?dp|8Vd!);}+i5b?9-UMNhErY3;gcs9$IH3hy zefF$tz$|`4s-Y*i?`o=Wf%{$w5@Dmr*5*hh(|ek+V&-_1_ClMr_8FJP_;G?#b<&h@ z4L1JeN`G5xqPbG?^w7>4*mRcP<#sTI22ZjamM@!qF* z_ZzsS_oq+>nDCU8%tfaqiw$U9aLm zlV;EB4k|LR^pD=NRi84w6-rqjD`I>nz-0}r)m-e6e)W-*NHw=1*=f?nrJ~hUt?4?P zQu62`veS+@H^Jy+3fS5D`YND`L%*%(d#5-witLdDDL9YlnrAH`$QaM;phoRf)3VV*WRljYR<=tJ)M{q@_EnW zjf5`Ov}RdQerStH$<;2nOoJ3kd0+g8St<6I`EZOFp`H5$dkrdO{?T2KK^9D zfLxqK8(22U>8x~RCt0`DJ{yhKHqDASI8Pt|92r7QX+rkM)V~v)(rj$qk?C!X*S%$W zJw)$=Y%^w1p0}VNg$Vd$*xCfcEd2vIw?&pJWu!6qpwXsNl$eO4c{uP*tIhMfrvyrr ze9xo|cg8|J1?F1xG{aZZjZ2jbE#h+Gi(tCjoW`v^B^p{I#XjY(@r&jCRgpSWXnt8j zbvkn#u0G-*w|dhoInt=0(koG_gG&J=zLsvc>WtR}1FZr10qcprxzjC((Btt44_2hf zA1%Im;cvTFM>`u@Pr|B{M$%E4=p(PU$lT2@%XQCTZMvG)1bBxeKc zLtbyKxPtW6ulFCa1=3df5<0zVLSjSPx>K>V^#O@trtuH-^>v$J`xni8W6}m)3~&rj zc87!S-L}&{L@#aTlRNT-i1YSfReVPu?L46r-2XEDT~$?XBrF^7G;GX zq|j&NOz5tW1cD+evtp`}5cbI&3{6I5#U2~5P8lWa;lT)=)mzd}6p;qG(HoJzDp5N_&tM=Ahs#%o-yVcjTA@zvTI_%gH*)p;Y9hC%lb9BE_SHw)Dur^$ zHL4gsYh|3ex$!-bWf?4-a&}$*(D&QaL72exaHEs)nMR4#8h*`ghNZaDRM6C)e$)mP zgmIA<;VbY?aqz@oDm6}3?pur%Z_<}QiZDuA>-qNT$BG2GL4BjEK{A~PPg~(fWjDY# zOou+04_HX`l!Y2KMX|R@)=9$oqUw5ipf zv@*f**P_Sct-#UeQV=lDaMV8LrKshB0f@G=+dNVqniv+lAtkV<8{v5QdZKL+GC{Q zZe_TfVO2_PS_bk#WOUrYhQG^gH~mqP&ffcl)KMJ`Owc`rn#07qybWSD@qywy2k z)CvCh-!L_3V?DTM%cm!j<*$4%SoqGt7t5M@L#LmUEINvG-;g}efna#a;kRN{9UI(P zL8bHf&GpEmc4>JlFT&;n<2&y6V*&G0+-e^m5Fn{1Y*$?9H8PognyVZ% z(L5xN0LMSvn#$**=HYHRX5J(^vPiL7$rK+QP~It2h|*O)*~|Nq6NmCO1p0i@on|V= zqB!z?y2IRmZ^kR;pPVA*_D+lkcG34`&)}@!$vcm(youOz(|)eHRkaE%FL<1yGf! z7j8i>yqpiZ(T8g~`Qmj%=T03FReGnha>-Z-wKW_6T6Qz>-9B4aZ^ixq?a7*WaX;3+ zvpo5|u6D(BV|^E2sND?P6`ng_!xLvER*=m%1;}Ln%ue)FCBR-Q#w%WHU%vjtzoSXi z;B47%5k)C`CW^o?A*UN~f=yri<-OmSQzo^de5Okfd6ts9a4nrSH-u-MbOneAg4pm% zTujYS!5+J)u~r=28HiR@&1VsiuZ+p+j&^B`Y4g&3uJz!iQZ77XRhzjG;N97WNGsY&W1Z||Li|IovQP`4DwrMR$W;atY-zp=PmnEOhkyzLhyKeKNo2lL;Bt*GKMJ+C}ydSWK zbs1Ybz;a;mYy?Lti#y^-DRH9!9fRZDl-9ej(rfMd9Q*f{*WZIgv3& zS!c-^??~T$!3T)0c>aJ4pELMbPLZd#7N0ETF$m=EOwZolXB$yh7BhQ`r&KlSY*5~3 zbdJ9o5FezH#EwJl#UjHOF8pZ9=1wUy*sqRm(keE5?*d{i;J}a64yLT7yM@RcDik8f zMfI+FF^}mRR+V2N&;MN>+^phk-1`VxsNjYPg>~ma9eGyPP3gxH4}~k2C-T{`Nf$Yz z6v&6M_tb5AB1k9O?(Xz(c)Sp2VzObTts9wNixs82S}Mve3msz3l}LQnXlUoLL2%d` zF7GDjv_41n))sCT&?-!lQC0yXL?*uDSR8XVtBZ+P4!_SRzV zvF6Tm3@#EQuzSK7s5ep$NhjvZw5^?XN9Wg8j;j!XaQFiNav2#koJ z!9ScxsLu?I519HM0VIIna{DGrx8UEh5+;^%vS}6lD)zLgqd5&#Pik+pT2FO)J*?4{ zZc7jfjcK5lR+Hj=f=hbq`oI$+d*!A($QiqCNz_p*Ci~GOe0-fQtu6mY8NvB~Q$zsq z4SN6Gb7~yNC-oC0=LNz4zm2;jpyQxMMoX(b8Os*P$g_ZlE~y+|SzxF!3OOO0O5VSu zlH*susY1CwY3R-oOfQvfpRUf(*pB@yL&O)bblzTWHQ$1U2)+o=CR`7%N3dDlQ)a95 zIKEoZa~4+PNvZyt+W(;YF@5D_B%S78Y%qNZJw3UR(g?|XetZ_>PDV?bv{^Cob3f7{^9P*9UNl=^Z#Lw*IqB4 zZCkI&g#AQFiYe#J3Tf$kx>Ne%M$1@eizB6iFx7vV2e0-1RNNbHD(MQ;YrYg47fb)y z|1AX_>0gSd)P#NdRbof+t}k@UFn0VSAo>t+i6Q*abXL(RMNgpMb!K<;ddOe5JsARa zjQtpMjh)L?DaElU+;u8g3+{u8GNAyrxU{!a5av<2o>9=8?P|GF=SZ~exEW?13hy=$ z`c2jHT<#nfx-?2vkC0!S__1#JAxH2H=_y5pfh=Sb9!xIwcbK*0p)7teCAynZoNdV^ zQWbHN{BdU;KMdEJe7?>XEi-eIO1p{b(vMqA101d*v~~E*-dnLI9wYi{$#Ugd{fS>v zX1wGYd9AH=4zTs9;)w!Ub^W)&+52cy{>^%_D@*2bIX?UfpHtmN=y)un4KJhKXJvH= zu-d`bMD49SJO&b@^{D|khF-u)87e#G239(xyWOTzaV{TLADQ<=FQJaq-k+AyZOD4u zw=&2G^HO=o+iel7D!Vw14%9X+Z^7)ljfq z*S3bXPYsuWSg0)Fh(DJvp5DRv<-p0Q>eXd_Ta)TH+DHsJWpi?R?x=OKeVXGQS*Dx+ ztJ~P*kJ6j6+BrK5GtZiiTy=zW7&`v*ovwjD}_?x$($sV=OW% zHMG~sFQ&|#xs>b(V8_$hoUv-?YCDb5NkI~(mDWh#BGtj{kEpMNXQPFBg6)__VTKQ; z=LcIR^+IQjF!xvM-}c3u^fLGS`&b5Fp|&u|%r-!kTb$T6LP=#>LN~{&_d&3f!BxP7 z(GlNzqPm56s^Jy0Z?tBaLF5cK_zbrfex6Hr5?Nl6CBq#=kJzO#WU=RW`E8BKg zVsfOv%6FZzG;>sYBH#3&NFh}e_Ir453BDU~81wjz;a5@EdPVlHGW%o5M~>PTfj)vH z!(UA&PI#8R4;gSDM;zTXtA4D!bNffA->Gt;AUg7L`lL1(qMfo7^0mEtqPH3*PIaH>b!+639hO8d3Tm3I5=FUOpyqA~FOaS~-5t zJoRt6rmmphJKc3Ms(ldQ- z3}t@_s<=NOgEWi(2;iaH0&1v{c3geB$U_1rGeL7+Fv<1<{=39iT{~GK#z2syZ zRDOUl8`G17jZ?DF;G1uDf8OWz-rTwBo!6CqrOjQc$N%yIAZ6)ph^8$06AzF?2@nr^ zbcAaO=%U)A`GUXHP-)Zuw$FdrN{TxD^vwzRMzwiGjo9pGeREXBe_S;r( zF7WX!v#fSrRwLT@Ouj>AbMb82jGq`g)TLRDr45~a{`#j@nQ ztJSQ3mkAZoRF~U~*;fd?vnDF!i6Y?(=JzAx&}{Z`e$Dawe!t4QWqODDi4ceUL(c_w z&)S|x#=@4%AI?eQn`pRel7G}02%8o9avIR!oropy(;pgTkEnIDAKrm7KHal>)oQ39 zqNN44UDOLvUwMD?SL7sy{&mv9k?WhMYP0#x!1P$|eB5i8eOM)0;1rSzM?tC+>tPPf zwozOtD`Y67QHZW!3Xm8+ye-R#fqQ>)Faw~KZ}ASD-T*IF=xHstFw1NHaa+KMy*X!? z%=niQ)q_@ImNF-UtMoTNT;@O*N6Y~elly3BFN%5z5gQvm*|W}*u66aGNvsT>gj5OU z=+t~_a1c{e2srFdC?thYMd2UU>=prZL|}j5V4=M8FX^9!L4zGkiY(caR>6t{G6L10 z5Ge7Wu1QSa8*bYid)~!Rgi=07%wJZo(zQuvO(8(2iIR@CPKA=GrCvmzcULqQUCLy3 z(b|s5?<9pbx@tNXYM@kM5f}LRet%SzYMm055-F5b7o5y2`RJtV!h-3Mg1alZyT6c7 zR0SvmwTdIs)Ajx4C5P>U*_D0!F@kue8tY}H&`wKLLnAe>v9dqN)5lR>`3@S^Mw$$a z#)kudjbXZ443Y`oa&+M<0L^YC<|+K!IJzhpIM7J=#al)-a)C6jzrB_JdH!5Lf+6F5v8oQfSp{0MlZ5j_GQPrLMiFEk#JrqI)8?MS)-zRazPv{ zu~s$7JYF+q1*v&I6?V8T>QUuNO0~5T-Ur!J{&WwuL0y4Ad=&`DvE_U*U3J;67_2L5 zrJ+9QYBZONbA#UAD?Y-~bxzr+3ITQMXyaJmweFXOz%1bib1iL864GgIBv>2uV4x@h+(}mgdG@|rA?p#r5 zNva*bzRowz_;kj-4Uk<^-fzinB@(i6=!0bwaE$nLGB}E4UJUEw|bhKj+<$oz4f`Ds`m9 zKlKVQA+CDTGei59Xg3ISzV-^E*=^Y#_sere7NGdG+JBieD~ zHGO|9kkf-)6NuQZ*4#JEK_GD@Okw{NZq4I^7uD;5P&8P|74T7iFgUuY25qtwDnj<0 zjeyyev_Rs9vzEF&*r}qv6vmke{-`y=DX%B7ynJ9T$5CvW`CWeb?SHEeyQ4E<#d*L+ z$TW!{P$3p+ef{x7q+*Jsctw1-tQ;8JZMSC|TsJMegRnyEBCVMYDkpLq=9c?9Mq6?}vC(fbIYHK{fIu zog@{GY>=gXKheOjPwy!t5`pPj-5&POJ>0P`Wwk7i6UslA0tN;BwwzMw|_l-8=;(k)E;?AqttSr<26}?JoYBrD@b@3vDDl|_@r>p<*n!_87(1RIYf)jGAe-GZds zhS1e%5}R1~U4LOqp-Oi8BrAUbLhm%D-yNKs(D-$%!SBN>JUp|X=&5ZsjTiZMZoTJO zzv#AWG?>yoB-%RZ;X*mS@Vn9m&r3F6v_LU;{xzVG^pIaG3OrbfOdlq$@Vhkzk@9~? z!vYyZ(vNGCR1yN~e=7ce9e+kP3T`({pYkkoI+WCB&m=doa3&7Y!1a5L^<~?)Ut~VV zNbXxC@J=?>gBCfip)J8F6>@9jWKH!Z%`f`cQzxBqS7I~+!?vG4 zB#};fVB0=-EbPLkIevU|u8gc_+pEh2jOhG*_hchv)xExavFU~2JL9^ zx?L=kz2rW=!xAKhvCJ4EWRgqFr0nehn>o?nwv(~7JH)=d(Rj8Uf&g%AuVu_ULQimz z;)PCGPr5w+uE6!L$d1Vaa00}!5SGYf4)4S}A4L6FVU#9|R#f|Fcs1sHL7)#Gc(FQU zPG!@JM3qMsFOc z4h!LjIis&RyP#gCq8V#=8d!>JsZm48%RBkSamJd=@*aa?s1zs0M%(ZhB9^B_G!rs8 zw)bgk?twDZrl9{8*v*uZY%^*J{4?yXL|(^XBY+$G8D=wv23v z#&iEf1^qZ&_VjB(4c_)OEAJ9#@9-!@Ihfq#`;}0B!NmZ@)5`;1SZld&Y6&HVI}5Ah z!s!Xl6JR8=Xv;2#_q(8ci?dz5NG`?x<=y5i?>AlFH@%U^dV6a}`|g2mMhM^60YIpn z75Lxm>QGud{B)wr<-BRU(P9n6j`kU-kaK#b#L~=GU`X7zy zZ?<>_E%rhqE_vKcUWbaU^e-0p9=Ax#G6tD~WM???Oo_7$vub!CSw!pZl z``vFSmf|e^?jeoSAB>i8vQdjoUwv3Q+eolo-6JQ4>GLMcfgxith& z77#1EJ?A;f&YIND=;h>?KT~P+6$1jrvN@Pz3k@i4$XXNu>+0PWc>F9Rq$MV@1u66G zo$uRBbK+uf?ZK2e2F&SfHHQ;;>r*(~N_A742SBb)4HES@|LZ0pDfUQ^2X&m80nb>J z)K)WoX|Z)w_(hGQLyH!AzRU^ufJdi$V=1oW65-#OO?Q>SN0i^f6Id9P`bEjaa3D93 z4M$~GMf|cyj$WNTN*HK#fqxxLlq1|_?df~Q`Yhth&2iVynMJ2EbiOd^$lmki9j<+5 zAt(XASXTsRMnSn1=N^N9Pcg%Q?yf@xfv} zQhLB2%+@aZS+7~Le;viFG#gM2fcKt772GoaLw0gR(3TTZa_?n)LuV%Dmk87T)z>5- z&g@nd;`P~Wra?uLpR+~QovR9d>_&_6eInoT+MQ^zmNvIJ3CZ0Qc8KEb$YKOW%`XsUI;qDFtW{C{AcgwC*|I*r*aSxy%%Y5?1q zpL8d+uiXJPuD3mEpB7QjTXX7Cer2sazqC%2U)lpoU28*$gGGbFhP>Z#PVIqHi#swI z^V{*F)`ug{X)noB6xtzfMd+^#{m>NA(kNa!@+Eh)=}k2lfhfc|=%m4uz`D~5 z2F1)QCHWQgELu78&Zn{098uYlv%T|uDuThxRN8WGuLyr0l7*S>Vg1UJD=I0DG^*oOYk@Xs`w93M2kf zZn`A5R-+49d<@-H+vUs5G!ZLgbe1fS^Rl?+dNzx>xH_Y;k!PVYSc~Fx>cT#_p|O?d zA%;1Sw*W3mVz)5Z(9F`Bx*D?^3iuU+nR7-?ZFFD-_atUYWp2Iz02UQ zR*~P@08&gGe-jGJu7*CCmx#4??91!NMV+f2z)bcq3krr<31-`?DYpss-tJ08wHFo> zL!p<2QR5_v_-yAuL-biF;91`P@?j6}_sPXE)wA8vR)}pRaj+KF5R{=-q&fhoxe`J% z+iJAaNDPvu;KTtdM?R!9M;W@Z)Mxpg+W*F*Cd5&%B}L1~WgS#E%$~E~ zC6B!~z})a)2g}t^$}>Pp+uY7@k%kW~HI^cS4(^u!Z#FgN@uF=Q;@!r4qOU#Kn#19I z<(WDCgQ`BDriUz9> zi|fn|5LX&`uaXtcM#LM-FrwOicp9 zkdaa1Ns`zT?0jlmwvrAQQW1)tsA96M@(j42tJ9Bp z0qLR4`oHeZF;7{Un8e~@ZwwoZ7Z&}KM`s<7*}f_-G_#XxY|D?a<$}<&JmZ;*sn=4I z^_*V}XZY<9Tyh6jH*q2Biwq~E#+IW)YHJ}I6}_?a46o0(iycG~qo3r1eR4EuRlde& zB6^Aj!cRHbPsW03ycNl|&(V@~W}soXis!bni+jwtI`d0KS6vwqh7sI$D`lOzB|@I;l5vAiw}EbZY^;Fbas+d zZOwd!Y-Cj8T#<2#vmHVq#vz)Rs}u>;-VFOIUw$XWTu@ltQRkaC~}IYJ;{XQtc-Q zrJgohrFSf!Y&vfOIvFBrpl^#`*QkFQ zdE0y=AQR3W2$s{{LV&OzVp8(~(z$?)4YliX=F1apYc&1pjPaT#j9b^G#IzdXH*T07 z-9gnP7LpFtX#j(6o=BboAu@_2YNtCQo8$Aa>NO=tHsmE zjs6`?OSHJrb^+H_jhMrO1J$+vSjhTD41=V*{k=;$(+|`T11nweMBWwj8>?i&u8_6*j*;ni3sYcWF5eNBN)pH;oN+NiC&&y%@`k8K8PV)9w?<>Bs#(WFeF*a*)vorrgKY@eMvfcEHw$i(w z@~QR_9=&=nS1@#1-e!|ORz()(!Whi?^H{nrO^CaW`1R}O$qE1TC~C&KCZBS>eBcW3&(j}3!SdIgtW*|-I%zaePX|(7 zC}P%9Lpj{sBLQQiOu%oaG#g?XYzon^Wwq{|syv=QadtoIrJQzWAT;~@gtai494~sw(xE1`bPRIVZItf;1hc^k!wAb&k}^G zAIRBSJKR$x-(BM6^5?M2odwMMV)buMlz;N zn(_Nm9o;5#D34MYx_xnPBDmo6&^EnMRtI*5-ri(C1c-R)O=FEq+5AFgcv-&vovo5H z^i=pWtyHdacTkERA z-L#wgqt=Wq37x^{@M`nJl@K<{(N`+PdZ1FnW^F5{FMH{GwrT$z zObYc#1*;{MEZB_MdehA85}r&^06{eKT#5O|SY58fOhVmgFL}y#@1fU4#~-Z6Qx~O! z+3CRKY5vt$uN%RMj2basPxzGNHLs;!Pn^EOC;)G>I<=j5ODSVRb-5XQy6>eRCd!Sc zXw3VU&Y*0`UPiwz-jZjZiEd9Y!oGyDxC`X%^kT0^ged3*0FnGZ2n6IXdRh2C211n* z@2~WqQ^O_p|D!mXoU=-Ox|4}FCiRm?sU3f#EzZ_*0>9|L^rx|OJ>pncSZPa=|I->W zIW^TlXS-UE_+KmD53Kk<66;MBU8tVh@n2H8%v&7$pSc~+f-6bR7SrJzRsiHx5&# zU_8EP&YT{msh4ABXj$6)J9_!}#g*i$Hk$XN2M$IvEoQl7a>$_tLq++mPRKXG)^j0f z>1FB-&7&kYV($iiil47R?d9i7%mOMui#T_^KliU$F~(&NV>9mVs9to&&y_i&<%wbl zq^J1{R1OQgVy+fPQ_77iW=k~EVvw2|d3oymo8BuFtWyyPTwz)N9vtzk&=x4<#e|69 zA)q=ba~%|S9`J&_Ns)!EMP3sNq};usM(-<+?vKO|BzV#~lr4up!GHTxK1wPWD3($! zV6&HGM?oDD1*BDQx*XEW2jtIUW8_Y+2&AnIq8G6zPT(A|cuy%-^TrG{ zhC)2{V2;M3N2k&N?X@;u@R(h94GokC1`R>O+J-Cll@RDZRmkP3h|>Xc%(u)B7k*EVC{__%m!zu;8aRk@nurix5q$Xevi^ zjO~KOmH9MnMyYp2c#9?e{T1k`J>wvNS}Eme$Y3;)Y8zzyWR{WE1l!4x?)YaZ*Y%k2IPFMr`)mULc&q zVrDkF7&K>dI=w8g4W;c5n67jl!=8uB69IitQh|1l9O}G0;m$n%KiceG%0SV|3*#K- zc`L;*|9ve5DX`K?$9jd|dp^lgxc!qI!4?~~s9lpewbo-+-PlkO$q0R$kRr3PUtB&2 z_vCz7M>gP!F`Vs=Faj}=BW9N-XNQV^LJtNRgfW}Az(>gS88=olPar5(yGg(+o$@Y&IR3 z6p#PYyugZ}1>~Qm|Cbj4l~}(N7w^phn)Y7ZTqKL(L~{}x1es-JGQ2rToT<$LaI`;CP#N^>GzqlZ7=*$RcnOmy}l-69y2<0uC6gIg^S_G z-Vm#}Z?YR-kM)v7Df^$m=U-Z~=Lw_x!p6i*y?h-v{97OU* zPS-npP5ufF=hvoke8QG@O%Nz#-t$%wBqb#AkIITVp{iKd^<@1EtWF0f95oJRUIeV| zj|*g8Fe@R{Y1gx}+qM8Oq(pRRP-f68Z%hor>gMWd8sB?HTGr-a1&S7U_Y+YKf~yEH z_`4dj-4^*j+)Pmu{mtO0s!&Yd>!OdSh*+%q6SZdia+6s^nuE%m7b9i%t!Ka_hJ5o# zzAqi|Ax~OIkD}B=&YT64ct7<8RD8A%u+NrO2*bLh!gtyv92C*uFmT%*>P-mTKbpG$(k*^y=xe;gtA!mRz8(< zjiW@h%mq(;bGp>i1t2L6VpbqvWE!MWFdY*)y z4E`%4!dvUUnPpfpdjucT_RT}~{(+H68gM*)Vp?D^Gs!3chA?>#-dDK!|JB+#23OW~ z`#$M(td7yKZFFqgwr#s(8y(xW)8P(w*fBfUvF+SE=hUfl?)&A|dFy^yHRfIzHEU|E z{agPr7V?M+f~5Jo?4dvoKs)>|aViSd4YS9Gex6><3z_?5UWjs2)=_kNz22H5w>eUe zr`eF{h|j8uhJKhji!xFhj!s^X?1Q>fYkr6#<%CUbQRC4FU(n*=binZ5LSO#zBb*Pk z7&GyFqMHOfLoD0!*L#cN8sXR6hjt$Yeo_E~nU$o$NT)66O?S4cn?P=@Uc-lFXGP4n zdv8$YgWkW`!tc^X@U9_vXufmqBW^07nv!eI!BpXNJl^rIE5x*Jxvu5f2!12$D2KQG zA(!8eFcv*%DA%UK*3z)dOx*cxr4XA5sVIe_f=Rb+JFAika#~VC2t_xRupd1D<<#0L&tPSd@WKdMR#O!hpvjdAT1Rp$saWF-Ry$b zPo;d~l2P#N(t0h^z}^o?Op}{I=RaBtH6+i;89Sg(mF+Zih%iMF3Q%B8x$dFkQYIt= zM_?D*Tcokv0Nj8MXK$Nm$hqm^YkUGW@dTm^Fi|DlLteh3nJV>1HZc$3@M6!KFetoa zeK!c>GYR7xq(#|OHN!JDNc~Rw!Z-F2s!41Fh%lPI@mIy>C~-QKoggVH%!sxl>Nuv; zkv}zDsL*6VCq0tP1-9=Bl|C&iCitg_n0d3&*FD=O2tQZE-kwZN%OJ+MvU}xZ z%k#IbDy`y*s>;%{ePaEK4D4-7OQ5Wm6pr^8ro_v>8)UicN92=E{Gvj+VkiVrI^j+Y z;zVScP$R*>@i!d=t*Um5C-14l=Sxtw1onI@@)?r9L0&}C81fqC)i2(d7UXpW=y*ZA zu^I!4va3Odyv9al@Ue8t)lefnmlO>S-|@6nLAZ0DprigAm@ThGN$rY|P}iYU5_LQO z$g*TJsEsSa-pF>)@ItvNH&3=IYFN~(4cq0stMytQlN`x?!|H9%KMX?BnsOTMC- z24ZN8|5`Qj;Z;TM6s$k zDaWp;6Q*ISiyv>!&SEG}aFVz#bhQimiYvQDHNq?xLodh-yKm8JYAZXIZ1jk6X=&vt z=tBYKch8rhz>>$bK^v=u`K8U;iy6pZi?x$RnO?^f;Qm4z8B zkgghfl6Cr}4eldyNi*IGMu1%vh8h(UtKgDYcmkU8Q8|(;M)9+G@wtZj`avb-a=IxB z3Ec{Z2wfey5)vb;(yBU}8YCH58k;%}zU zQ(>PohW7^*He@mPVeKy|B<>EI5|L^WonpEd#8R4TnnGR#9jS23dfl#;FefvZ(TGxH zOV6y-hoaPI->}fB3|HLa9_D!Q1ZEj3qz5OIB52wgs;&Z zyL`>WI!*!QWs6`@rlCFZKcyj4SgvW!(CttO?cQ_sun5GI5X7=M1QkfFKpH8eGUv@M zO~N#lrR2($Nk3E#^V?*Q3C@~xGJ?laAj<8W7FCmU)T4x)$p?nNRMe+^(_oxG9HVCx ztL^&};AJf+Li!OvmrNT?t%!bn?GL&-dtroR>7dEE#-)YBhy=3`cvX{->YN|1uG2~F zP!(8(l(~aH5SMz+R!LEjWg{!d#Pqsl#hFiSIjlqu5#2CDHsHttMjE)og=LQZguGEYB zGn2VP{oCUYObKrDMD?11mCew?L3nI0YplpK0E_pKPP<1r@hAXIZJ6KxhmQZ?L58k- z$Xx~65$=x!4QMZ?=5|aO+E9FfzKNhlOODbF5seSn>YUB63}x7|>oeIW5W*Ftx9Vrq zhG!eClrz$n!}G8tceqv-OQOAyiI$haH;Kw@SbKdBs>~3zL)pc+vmk~yo#)#1Y2*sD zLsX{7h2*Ya8|>p_%SDQ`nUd)fx{Jf#9dGd1u8vp&=^p03>1A3MdUAnvamT=oSE^k0 zUi-=V_@bhXY~-ig$X?A{&B1VZ0VBO6QV-r26v9m`;8q8p-KG^q9p4P1-suCKcL3}} zfC5pSE|ea26IKB5=QcyJoz7HYKp$YBjsq=6eIzf>lqGkm6`5{-;y_|I??s?j^O3>9 zuRewkxce zK8Y<0#kqpsGFi+GCfgBu_$|r3TA|4FGLh@+_Doaius(3^Ow0Y%qqxQk=YS>o#~Ph} zf@2T5^NzrF?VZg^Mq+&bT(G+Q{V2JUXgbZPmd&pYCwLzk+lXs-k-VO)^hN)v{ebJ3 z$|S(}hk1ErwZ^zeD#=9N}ZX?^0hcgzU$aI!8xU-7Vj0S&|%d- zmmzCr>;7z#fRP6=(>-6YE~ZVGf=Ux~RDM@<8zU@)dmRBTmX)hX2iATy-Uu)|{87lp zfb`oKS`x7*tx21*Jf_Qt3F$Ckcf$lzqU0Pxn!f6z2FCs#uf>wW;K+N9-%d2c%Ok`pD3q1XP0h zP8Jk}?mN&cbq8SU(=&-6SXx?G%+2MtD7Zcy^7Q%4n);`-Mai3t#H;ADjuF0}X@B-( z%E{FQ%bmA_l$jrE^J9U=+f~Ur>ppRB#KP8d&iAS7r@eDmolBn5OBc;%kD9S}rtc~( zy=kA-<8b4sJ#w!}{%$mW_vrD`jdy;XYtCWbG=xr3%}wN`sPk#B7%S*az##3;mnmXi{K+W?jDZqDAcjQ_YVlXb(3M<9VJ_#tMbv zYar&P?_c>&@m9+26PWY2zB@8iy>-C?!v|x6Y*$LZPB)5bHyv#c0^l-b-(SJ^)c*AZ zkBARm7e+rGiCLOPfz85wRt@R7)g3bvr<*VwRcp3bt^nz|ZDWi0lE|E4&b*MG z7eW1=7rnwfO{rrQ0~g0uQx|>vrT-^pCrFIQhxk>12QR*N#VWTfVTHtG7It{R*?wE(@B$)A`t(7t9i? zX&JH7(!!WI<;c!)VdbKLy&h2Hva3&GzdjZNcoIhKc2CBf9~{FzXvjf-gwug9Y^NvG z1A3LHK(X<`?mVX{b6^HV*58+gK^o=Ygx9I_nZ{VsWTPqmM#|k|Q}cW2$I%2*Gn z!sZj$L7#7Y3n(BPC&>$)F+OJsJDL}%1PkqyGbvsigT-{%eCihv*7P%zks>atCU*n0 z4C9-S@Y#Aas_ zUMVG*ey%eX`ue_4+TA{#6W-^Wb=7N~pEhVfZzf?zssHGY^2c}nwT)-}$75p7}Yls!&nKfV#H$w|hPb!*5{0 z_Zw)1J$ph<*Op(l>-XH#n7V1@p*-bn$bis*T8()ux zd(8mwIb~$nTC`3hpTf!>@F=lJC(sjI3xIfRsW?LK?bs|&XO|khK1{jBf-E-{!XHPx zi`-w4D`MI+;yYd*o=>eeC>w0v$x<>mWDUAQ&Q`ZyShz_ zY`nu6b#=h7fR~1GYwX_VrpdBjN3)E5f7OuHe}k`#tE=}=L`M_G*Yao%jFxlF`u!f$ ztQN6#L*Q{SX0f+iLWg)Xp_yLOo#*4SPtxov$KzadzkA-+YYw_LtJb`Sm3enS5;={( z9!Xj8b?@AtKAB+A3`ISsRMJ)sLl}L6UO}t-#^KCn_dS!kYTofV+Kcx&IEZIUZ=mR` z<{0AFHr@;Je!ewlL=c0wwY7D5MI!9s3_;oIS9v{`Q%pe-$rl(~5Zzp(si4AXfr+ZN zhl!{|_(T|g<^iJtxmr?&_fN8w;Qk)M-B3gL--2p(%ofVXjm$^9Pyi|deE!^X>)&)g zu_$?`g29o{EeI?O^iftYKkpj2G3d5K?Yun8H7aNduX6FfW>bY=?^C@+))|7`EuD! zh8J&m*humG)O$!JI!n`ZVj0!mUU5Q=C5+S6PP~6lrXVLFUHQJMJCrv~f0IRY^!XWIy!{T{Hl~=schZYxUZ5vG;@Dz+OPe+Mf#w z9=iGt>ax5qz5*RXnXJmu!=24}vU*?l6o-b)0LUm{+z~Q>8rnI;dHCl%RsdAfE(=x5 zmqMncX+*rBtOIYD-`tF6i|=F!&Z#v%Rc$(wR%H2Qzusa%5bA4qA)ib3p`QaQ4s6Xx zm{L(%BiVAeB5kC}d2suL5Xsr0u=uO@&ujRja=qgZhwT7HnHqd5`Nxo7UGge4Pb^J( z*o*8EUS@-3wiSrQe|xbq2-YJnb1Y6cm06yQ)=2o1!yE2{UXK2vL=;IIIo9)JW)9M6 zUJ;yF3ws*f+W@Wli~MME@a1V>Ma1}50<7CAj^-|FS zjxjJkSxTBfzPS$rk%jWf-5$n?dbbzK+j;(?V2qI>ZpCI%XY#mlZ#-ksWVR!L5YaYD zI~q+<^g-bI0OdOLQQjNp&ewurOCw!PExM6W6KNsugP`V^r`;?f!nvNC&17|ZLSVN(8T{&6I{bodnid7V7*uN=UIDKQR?7 zsI0|RG%_;}6Ky#{*veu`?y9cJhJbZX_@S#QO1~i5_W?6q*JgfO#CW62<57J}UF`)j z;5H<^L3We)n$(qkx+dZIPQdKMsTph>pz#!8@p<{1fSuRvpj-0L& z7TTB9$Asc_YHEE`8Ep7>>CC}>`FNImZB40iVm;fRg{2nt@RF~oEgDdLQLDJQKnq<(DNQ`sG zYy%;powsZM`O8N2X2Qr{DSpITkt{S=o(G~=^3Z{6WAr@c?oDQdsoI_Q!2_7{Yclv( zaCB+fiEiomG_Fp2#pdAY$ANGEF#mh zmN>3p4;){ITYJXbGblC@>Lo$>iz`~sof(p)VM?E6Dw37IsJS=tO)o(@i4FyX5>b^B zGENWXAvN9KEoyA-Y{tMe))N36a+u9x>rgx#GSv7OLQEywm6P1j=pxuteaq;1jL0 ztGij>gXVI!U=+@oET^3!8Z^S5mWSz^3ZyjR`75;V_LiezkT>2_c44j=!kQUHhO|%3 z)^;~E-c-gSnmJx$nHm-B~N){xTOvx@Fh&-3dIJVQS(0$#G7M} zWn_<(E;sScGycT7@XP7dY6;P7yBys&$GHMYYP3U}df>&vv6!ys z8Bo{a&2A&w94{0_65=T%CFdyL*2(mL|_0Z({|evkqY$DtRFrZ*SC+jdA(*9bu6k;hEcwi*`cBnt}X; z@G6`sG}nt>Wz-8;wRl>gJ8vD1x|{_BiskcVev_#yoFmm5l~)yMWoZhh^Vc%Ho3{<_l3HSh6Y2k+&sWc9c)$<0U%=*gr~l(xj{x%7xJe@IGSrl2hcRy$q8 zU-LeVoUI+>}!s2&%KEwDjad1xvK=O4mRQg zOz*-4R47(<@cHB9Qv-TcEx%A+?eBh=%%5Cb?Jw%n^_q~#Xe?jO7Mj`Ww??Fb;1&c~nazqo9D(6VKnqx9B1R)@$ zQPV_hC-WKH$cy8<%|T(}-5UyJ-2+cIVMXMqEt;mBdNEVRA%F%ikf{*jtOK-g0V2fE z%5=5Z&sCEzkyyxg#v;=Myh|oL!Tvt+TE|DcXgE?2f`pahf0pA`3}gdX5*#gwo7h=B zYyEhh!#^LzfZL|eUe`LO*h#|juaxU%qx&oNeuroKLbO%5*KoTL3~C8Yi!3fhI1UeI z5*iLZa7`XJbFFnNx)tk>qLk$EsaYSf3xPhrP}ZZ~<-OtHc9v_uq~P^#+#;z5pPP9b z{(!)%;tCKC$M>qj-@Uviki9!dDz<*y#WxErk%>>Nm3LjI*_l?rdW!55h1h=Lg6L z_=npcVncc9B)hy*?mN6iHWlXA=}dfhM>s|vtaKAA(*Cv#v= z_jZ8&2U}c@-3@?TGe6e@!&2|l(o9B`h5s8c6*~x;s#p|^WgItTS@+7S$xzqrJK{|e z$G1hnq_ibcdT+Yvw4%DVM}RQX~XgRE=Z4bY24i0?&N)|IO(t7MD1*`0d$qMq+aLQGjQ>nJ`#=rBhRGiRfZV_U?sdW@el`W8&ud zt(ZXIyj)JAcf_=gHW1ghtn~3cH-`;%@M~-CSR7ga zpU0n{C9$!Sa#9D{Lo+rTMwc?c$l=*Y7&m!b-E7nYoPyJb+~u6uVT&t>+Fc3hY=<`2jY!(royF}!y2*xPyFDG`&p04^tJhy|fut8Nlz4J7? zk5(Y@Tl+u1zylZ+H^p)L4oC`G8UfnW1?6+MSp=ms$*iHg>r+y=gUk{Jlc%E|rzI9)_+?NpRmC(htI0y`6 z2>BhZyZqQCc!}7WldIw^aK4y$ZPPcKjX}~hh;TjrjqXHJ3^nOh7dJIR>#OnGZN8?hQFaDy|IpO-g8!sX0T%IKx1w%;T#o~LM46NsxR+vy{{(yaSej*h;&dkq%>{pevw$@6K1lj+1p^Z+% zlTgVJdXme_VuV?m{t7`}p51-f7=p&tp-N*|=4|r!RDywC1FBPrX&>_bf-APyAsrsN zV4y)w0SeK%r!mX+PiTdLd&li`K@diapC?)$VU_WzVikOV8XD6T#%4gfnxA8j&sV$V zhpYKxhh^rYt(f}snO2v0z2r#nE+=>)Rz>J3^X z&BkO`v_C12Cqms6%-(2Ov{w2)VCbN9cF-|!_yT{#?o|`&E4Q&ljfk3_;7(AUR?8Ve z$>iIYD(6x_NLajF<7MgeAqs-+Zk(GQF)Va|*<`C|RBf*qofhhh&5vF0@NF}46>ooJ z1HUUCD|19Uhw552@iglEMq=CF_%X+L5nW=4pUI@I-@+_etiO)}(r53y=D)HYm3){a z>5j%Jq}3OcLL>9CfB`Zv=roff^czbT0uy7-P=024v1>WKXg%!ePpJlqje?oS*@lPr zu&ZlBkmHoCJa5tCjdIi|Wu2G|@}cpY6d2Gc(EBB;As7y?LmxT4vvLP~2y}Px59*vj zTsAb0M6sYw+3!3Vw0_g`^TI{F2X9(-UHZm*y~yeW!rdzmYgG|!EYo<<>Yi)h>o-p& z%ib86=(FDo?4`})eI@fqsWTbUsFJ$%Ng(xTGkY8N>dwZPT0`y`?Fp+{?_r5kBE2xL zFG8E*X04Ro)#|!Y(N`EP;adE?AZvSgM*@lK-K~<=&yu0ek7`z87GTp_NjH%Z9)eQN zp{TCx1Zqu3K9jt^#ao5Us!Ya7APXsjOLq=S(u$(~b&B%@tzMixhF`>DadEGz4d z7_g^5eKg3Na>UJ)pY{0~o_&6PJ|8KbL{~>%*;kv@n@((CbAr%;60u|qmJHqU(wZ}5 z%#W|`M%;E}xXPqtspOg~gxi6I#?0n+G8RO6Q3SjReBvce9AL4O zc${2g>EPp7%*Efhlq_NE7JL*43YMn2H8lZS*n?V+tC>-EPnjn&ic%$2Rq+aATOSj* zPZ>X4`nd04Da7S%2OkFW*^%s#qSmH6$2%J4Iu6u z?m;Krl<9Xz2hNNYG@idyL5pKqM94f;KPJZZSm=C?| zm!yp62{qj*uBTX?gxym3!#TFFpc@<;5ko!Tr%s13i|Po7<3(jfo{v z=)ZbcfD0|R@-qmlA{DefoRFOHL`h`IW{!7JO+l+wDdWlR!&6Xj7IDtj6q%CsMwk|9 zDf9CMZ;#=CY@-A3yI`7 zNp-s`Q1$X7m7}`o3>671adWt}5^fCEB>V|5Ihxzp(-dbp`Q_AngdTZOYarF(`Ip9m z+Z|D`Ou^_3uvPye?4isY;^9v}={Jd~CC23SU+w@| zUf|UAY$my{f$d-AyhH6ZDYqEACESzMUEZ6Gtm<+{P>>JSRpSP#T00Cgwg9_SHbHPQfBe<10}5->gBdwm-7Qw%^ih93{WWl*xTG5C0pq5nFD3C)8cEd_NT^KQxU3c644 z=&b#&OR-|;P$oPV%KWr)km2i;BTZYawloEBOSG+TQz}R~NKz{Z)Rk73M|@m0bnsED z6RXH?))jJ|GcFCX$qk{Xea}uirUc+vNljpg?a>lh0UO#i5t0ydIf&A+9BaGxGqVgD za#jhDZ7`h8xM5S_s36JqGzvYr{u zT>q29NpsUyFR)l~K~m}7b61YDX7e+m@xBZs)>{|*kZG+TyOQHthpB1 zv_lO>dZOBFU_bp1jIIFc-X>;=z5qVT6NI#sJj_+}+Y#bkHQh60QhfDI| zN&bQD%Aje^ZwzGim7v#nU3Xh)E$R}M_Rh}j!Z^yc0SsiGiua3jjTmJquh5@+f5XaJ zM8`uz16q1ET0HFNbnaegG!%hz%dO9xJB;gH>bGX5xXoX6NeQW-ZlM&(@Y66+m$EyG zQj~NlNV%3AvUJVE%es(Kgrg<`t2Tu_ZVZ54v!X$9?BWZ1roB9x zqY3tUo+k6y@^#vuVNn}Gowht38P(1>sYQ%6HDqxqJbzY);iSfhyL5#NPzOJv09S&0 z_?|YXUKU$irSbSfr;F;ND*_Eox4oQ+3Liu;9usN&A2F78UC9x;Y$Utp*Uoo7{U!o=n;YY) zF(qDpu82i`RG+2ZX=u#BNXNy3${#{;;I+QA`NBfC6umkwxH2UPAzS`?0PMrtx?i_V ziHeXRD3u%IX~P%0-WKS&X}HJoWhyR5vQ^tNOBanAf6|yZX;YF|U%TniT0WRw*--D_ zE-Y3bkgtwjxzJ+>WGw`E=Gn!Hv3y*XvWXuvCDWBFWOp2W$+zv+Sl*}=SY%q-JJ$;5 zES&Z{Ij-F{=W55+0mxY!p8K_mBQYnU1L+d6)pkT&&)6$^&okTS(Df(!;W~k=%1_it zBWeYsTG`T6MuLpcK$U6K01Ak!_I80K^COcoN?CbPRrrQB- z?3b`dPURdt-Nm99Th2KIZyBONO2fv+vQBjdpFZu_`KPmG=y2`eEN2qOPwnSA^}w{dqG?K` zcR1Y0ZlJ2aKZv-e9nNYl?;PSv1JoJrdPGxsJosPeBJ2Fer$4lY%v~%7!XTAt+;-kM zu@pNkxpV})gD^9Q<1CaoS^F((~nIz5I||!XB5f z5)L%=y-4#O__xLbHdh5x3x`+xakg4>uG$g>EAl(ZqH}awuk@VglR{a8KL6X@MUI7( zgS-7BREzmTnb+XG=6sK?TGZ%9T7J_LW1R`t&QX$GWTRoI#+vfJt?${1OEno`0;tnj z$t8DrbLm5mDhx2UfesjfelRf=#RJ4t*C?zFA5=BV;$ey@21Z52?7n&oD&SQqfbijLt;Zf-J{S7hwUmJ06?}JsoLm=p7MoTe zr7RiwlzpJLe_5xFg3wk~P)-&atkp5E>w#)!yVmiuM(C!!=mec2=b6(Bv6jw-;TgjG z#Qa7%T(Xl}BPpdmQ^S8lyz)!_f#oal95+(g>pJt?t(L6;in&6eeV zDAlI>?(J19`uDn@cK*n927E%FI_jw)bs3BGYro9OPyO0PitFczAz8vzc3#Ok@31OV z$SR|QXVn$s24yIWpx}KKB$7}3BK#!_AffDl@!yC0$`nMCPnYJ|(w)BrU30MHdmzsS zcL-K3e3s%%r^3pUDdtlSey>jzWyv{IHhjv{K6zJXMU#`>5a0NkthqYUCz6tw>zH4= zMpMwPNlOqEfVw@OB{OL4OIz#tS8|p$c~EK(^*^{-de%~r1JEahL=o;&dN!Gx@t+zr zInl4upHxtYN%((h3zhhrQVncRWr{xC{d9C;@8Z1qN7N=PLma06Z|UUzx4OE2J^xc@ z=g1nUUuW_3aPq^AJBYi7;A%x?n?x#U#+t1e)?wHW4Nx%4wrWKM^Yb#EeLOtt&YRDH z@i`;=)t5}{3oHs%s0^9Z7f5ue{>hbJ7I~hUUuA^8$*LD2^e}w*>-i;!3JakKDYySD zq@tpdl-%1pXnKA51exSQ-$J0+rCHzSkG_(X@Jqr8i=dbAYvyw2%*<<{d=;`(EV`vS zx%E!KB{eG;PGrbt{A?zQN+s_U<8rlj{T*GDul@6p0Fi$bfd6Z8;lDP*=WhpCSgGzg zg>PRAv`~Xk6I_GVpO!ysI))OWvk8nSrSKfJ)%U(47Wd{fR+8ap&b!3E=w{2>_Fo-Y z`xH73*hzks7HqsCS>8aE(Fk}wWq^`~WwoL7?Z%(oa6C<^cXl6KkzQ+utZ^nH`V9_G zi~N(1e(2@!v&-#Cl0UR-R~Peg-+X4sfzTd2@NClbpkc3K%DnBvpkO@tx>o?y8}3bl(qUAnj*1e!kouehma3)SsmS5i zH|!LtQ?pk+ZX1bU^UK(Fc}u{`FA1)h6p)=s&dN&Y)_=wOGQ*xA@jk9&|4d%I+2I6~ zcV~o*2PY~3jUsln)O+smU#ec)ZQfm%b&6y~S8O1ua&gJB=J_74@`7pW6AVH(ZmtWpElujEC5?c{;X@P2!Z*M73X){NK$8 zVaZvRik*#T2IHAVx)KZow8u+tCze#uc=w$M&WxrSE;bKNlA%Z_1tx&*X0NTTu;1t2 zH8ogV3jF%~Z{y(xCgh_Fx&vLxkm}8J5UkY3C#n0KXFo?^0f=Qouwq_WsX{_1JU$P8 zwb=H!wxdvXu>)Ni1B>^ydq(Oho#7CdP{5ZRRh!YFTMZ!D{SA!V#^E)aY_HmMN}UEf z>#b1L%;{Pc69a251>U*5Rspgrf*IJw38&GLJ9mnJDuZx%ntkcSR`h(mO#Ewi|4dX~ z9_ip(b-B|Eb>4~z`Dvxz^-4X-y4FoojEoF}=!F{v)iORs|54&X3nW_K$DZ^~=QP$5 z_?+pL4)nv}_kkPq1(uJ??cY}5((yr9ZMYx^@h{T7+j3exXDRxBPBZ$9BPeO>NCD57 zZVtUG9JW{qyAME`9zcq&{8Q0aMa=ihWg#c>`f6ChFZ(NBAlx(qw5udIj@6j%vaP|e4N(6bu8z!=R>r`;k)CFkW9Tga1L>Gow5wFjkLNeft|5s>KNu1;Dpajufbawh<6sXTVD`Rd5W1 zP*qo~a3=oo`&byD6swRx$W0?D3)~;6y+K7%(zCQFQFOedF|IR^-Hd=r{r@;;*s!p4No*{83Y^RtbAvY&Gv2y3}C>>r1 z^|#_sVh&(T0|;Xe3ddm+dV>AbPZLtC7kyk+1xJEBi zx#P@WMC9)!uq@xVRrZDu85lW&hoed&=e4b7PR~Er2CrjC&G=*Hda186c_>Vb-p zSv3izE3akC7dUa@J5d_qOy}3-@{vN&UIm68g_b;;p!OIBWlSIBCCC(XjG815_?BJ> z;H3%?q?f!G3hAgU=TDcMixvF=CKVkjA6u-d;uQRFn9|983?@~=F{eH2a&C%89u-&L z8l(x9q(*fX+&p?z#-%jl7$84)G$WPYcvt3=my6K;+E)v{TqQVXed3eS*7^E*$H+uM zyHI-9(!0{~*J}VjC_69d^57}deQ03U5oeJ6YkCjn`z8XrkW*Z#2DWcj7CBi?#PiM@ zwiwiWznHL%`7^#Ut$n#kL%zKu)7fd8-Tax#O?7FMDha9TyFVz$SuehIo;;7Nw4C)# zgw(Pmde>I-E3r1;fw3_ou-lI4?%28#gNe)mw4m^y@`%OLbp)b>`8gxZ7Eb_LIF1^n zRTFG{Bvl-H!V^f1wk42!xl~+jZ7e3CPDJk!))FS6Zlp)C6Sl(I4ldot1E5yMK+}jR z)aZk|+}LyvObMEcGc*>HQVnH^`*l?k=ZHwSDA5TYety2;r5p@sJ>_&M8aewq0jXuG zN?Xnw4AasZXXxKF@z0$vl<9N5ALwyIN3l=Skdt2cyWGj6$E!va=G(}CC@{X48$&L_ zLFs#_xVon5^sEZk5~z$ieRabkpE!t(S6uQeOIP+`J*vt;(I+Wnh9%j)L|C9`XC8+8 zt9zH3Oikv88~)fb$GwL8q}p+oWo49tKg zY_|DiA!J_Z!zbU$7I#l>)B5ABlOI~>lbNyoyxjaTn-7yke)beNp$k;pI_o*kS`Sm& zaSNN-g^F+r^_&F)K`8d^<6@`W8IlVIBNg0Jubn|TLCHs3=43a)K8d-B3RX2CQzNhn z;cV)?9cjDi8y4La|JmUg_VLPj{U3|AJL#krV--!m7Xa}IU<%nSpJ`YL_W=(4CS$E^ zwGm>{%o0V@S085^eKM&^V1Vt5f)hnH{W)TiSk$4uk>N3cf|iB?=GC9SiE*`^Bg0x@ ztRrwq%4MsCo+E{Xr9#v>6-3C%yd5uh#BwcqE}h#i;0dREF1QXPYMweTdFwa?s-JiT z@=Ek4N7A2oy*oDGK594`eLp^9oArRgqpts2vsrf>_-c?YlOOEGm$YkzuyvEZeymo_Uso$(slrVjT5tu%EW zF86CyimB|;(ZsuMJ>R^A8jk>lK7qjCf_d!+Uwg;<^{1X*GU#g(Iu1D) z0$9PIyfYK$nm-yc3O$v6d+7#4DDMq%b~fg1 zQEh_El%}pIF?Tlh?F;`>U-Y@(+?!@_!`|9jI?C`mPgz=8+S)p-YXc~&aQ2V@=02Zp z(wbDfbOL%_p9C^?1E=+;FH&u3N=(cDjO`71nwOT%T^(a!8~VYMgW-Km3kQ>RO1%IB n0~3)F6;c%pR{ZjBZ-Wk^6gnD6n>8|@?!ctP ` in a terminal. + +## Exercise + +### Overview + +- Inspect existing Kubernetes manifest for a `deployment` object. +- Apply the Quotes flask application using the `kubectl apply` command. +- Access the application from the Internet + +### Step by step instructions + +
+Step by step + +**take the same bullet names as above and put them in to illustrate how far the student have gone** + +## Inspect existing Kubernetes manifest for a `deployment` object. + + +We have prepared all the Kubernetes manifests that you need for the application to run. + +You can find the manifest in the folder called `quotes-flask`. + +- Open up the frontend manifest located at `quotes-flask/frontend-deployment.yaml`. + +Try to see if you can find information about: + +- The name of the deployment +- The number of replicas +- The image used for the container +- The port the container listens on + +Do not worry if you don't understand everything yet, we will go through it in detail later in the course. + +## Apply the manifest using the `kubectl apply`. + +Use the `kubectl apply -f ` command to send the manifest with your desired state to Kubernetes: + +``` bash +kubectl apply -f quotes-flask/ +``` + +Expected output: + +``` +configmap/backend-config created +deployment.apps/backend created +service/backend created +deployment.apps/frontend created +service/frontend created +configmap/postgres-config created +deployment.apps/postgres created +persistentvolumeclaim/postgres-pvc created +secret/postgres-secret created +service/postgres created +``` + +- You can verify that the deployment is created by running the `kubectl get deployments` command. + +``` bash +kubectl get deployments +``` + +Expected output: + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +backend 1/1 1 1 27s +frontend 1/1 1 1 27s +postgres 1/1 1 1 27s +``` + +> :bulb: You might need to issue the command a couple of times, as it might take a few seconds for the deployment to be created and available. + +## Access the application from the Internet + +We are getting a little ahead of our exercises here, but to illustrate that we actually have +a functioning application running in our cluster, let's try accessing it from a browser! + +First of, get the `service` called `frontend` and note down the NodePort, by finding the `PORT(S)` column and noting the number on the right side of the colon `:` + +> :bulb: A `service` is a networking abstraction that enables a lot of the neat networking features of Kubernetes. +> We will cover `services` in detail in a later exercise, so just go with it for now :-) + +``` bash +kubectl get service frontend +``` + +Expected output: + +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +frontend NodePort 10.96.223.218 80:32458/TCP 12s +``` + +In this example, Kubernetes has chosen port `32458`, you will most likely get a different number. + +Finally, look up the IP address of a node in the cluster with: + +``` bash +kubectl get nodes -o wide +``` + +> :bulb: The `-o wide` flag makes the output more verbose, i.e. to include the IPs + +Expected output: + +``` +NAME STATUS . . . INTERNAL-IP EXTERNAL-IP . . . +node1 Ready . . . 10.123.0.8 35.240.20.246 . . . +node2 Ready . . . 10.123.0.7 35.205.245.42 . . . +``` + +In the example your external IPs are either `35.240.20.246` or `35.205.245.42`. + +Since your `service` is of type `NodePort` it will be exposed on _all_ of the nodes. The service will be exposed on the port with the number you noted down above. + +Choose one of the `EXTERNAL-IP`'s, and point your web browser to the address: `:`. + +In this example, the address could be `35.240.20.246:32458`, or `35.205.245.42:32458`. + +You should see the application in the browser now! + +
+ +Congratulations! You have deployed your first application in Kubernetes! +Easy, right :-) + + +### Clean up + +To clean up, run the following command: + +``` +kubectl delete -f quotes-flask/ +``` + +### Extra + +If you have more time, take a look at the YAML manifests that we used to deploy the application. +They are in the `quotes-flask` folder. +First take a look at the deployment manifest, and see if you can find the following information: + +- The name of the deployment +- The number of replicas +- The image used for the container + +Then take a look at the service manifest, and see if you can find the following information: + +- The name of the service +- The port the service listens on + diff --git a/kubernetes-exercises/manifests.md b/kubernetes-exercises/manifests.md new file mode 100644 index 00000000..a336b1d1 --- /dev/null +++ b/kubernetes-exercises/manifests.md @@ -0,0 +1,133 @@ +# Manifests + +## Learning Goals + +- Write your own declarative manifest to run a simple web application in a pod. + +## Introduction + +### Manifest files + +A [manifest][manifest_def] describes the `desired state` of an object that you want Kubernetes to manage. + +Manifests are described in `yaml` files and have the following general structure: + +```yaml +apiVersion: +kind: +metadata: + labels: +spec: +``` + +[manifest_def]: https://kubernetes.io/docs/reference/glossary/?all=true#term-manifest + +
+:bulb: Extra: The general structure of a declarative manifest + +The general structure of a manifest is like the following. This is not only for pods, but for all Kubernetes resources. + +```yaml +apiVersion: # Version of the API used for the kind/resource +kind: # The kind/resource or "type" of the object +metadata: # Metadata about the object + name: # The name of the object (must be unique within this kind) + labels: # Labels for the object (used for grouping, key-value pairs) +spec:# The desired state of the object + # The spec varies depending on the kind/resource +``` + +
+ +## Exercise + +### Overview + +- Write your own `pod` manifest. +- Apply the `pod` manifest. +- Verify the `pod` is created correctly. + +
+ +Step by step: + + +### Write your own `pod` manifest. + +- Go into the `manifests/start` directory. +- Open the `frontend-pod.yaml` file in a text editor. + +It looks like this: + +```yaml +apiVersion: +kind: +metadata: + name: +spec: + containers: + - name: + image: + ports: +``` + +- Find the API version for the `pod` resource in the [Kubernetes API documentation][pod-api] and fill out the `apiVersion` + +[pod-api]: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/ + +
+:bulb: Help me out! + +The API version for the `pod` resource is `v1` + +
+ +- the `kind` should be `Pod` +- the `name` should be `frontend` for both the metadata and the spec +- the `image` should be `ghcr.io/eficode-academy/quotes-flask-frontend:release` +- the `containerPort` section should have `5000` + +
+:bulb: Help me out! + +The entire manifest should look like this: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:release + ports: + - containerPort: 5000 +``` + +
+ +### Apply the `pod` manifest. + +Try to apply the manifest with `kubectl apply -f frontend-pod.yaml` command. + +### Verify the `pod` is created correctly. + +Check the status of the pod with `kubectl get pods` command. + +Expected output: + +```bash +NAME READY STATUS RESTARTS AGE +frontend 1/1 Running 0 1m +``` + +Congratulations! You have now learned how to make a manifest detailing our frontend pod, and applied it to the cluster. + +
+ +### Clean up + +Delete the pod with `kubectl delete pod frontend` command. + +Congratulations! You have now learned how to make a manifest detailing our frontend pod. diff --git a/kubernetes-exercises/manifests/done/frontend-pod.yaml b/kubernetes-exercises/manifests/done/frontend-pod.yaml new file mode 100644 index 00000000..95993186 --- /dev/null +++ b/kubernetes-exercises/manifests/done/frontend-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: frontend +spec: + containers: + - name: frontend + image: ghcr.io/eficode-academy/quotes-flask-frontend:release + ports: + - containerPort: 5000 \ No newline at end of file diff --git a/kubernetes-exercises/manifests/start/frontend-pod.yaml b/kubernetes-exercises/manifests/start/frontend-pod.yaml new file mode 100644 index 00000000..5d2c367a --- /dev/null +++ b/kubernetes-exercises/manifests/start/frontend-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: +kind: +metadata: + name: +spec: + containers: + - name: + image: + ports: + - containerPort: diff --git a/kubernetes-exercises/old/07-healthchecks.md b/kubernetes-exercises/old/07-healthchecks.md new file mode 100644 index 00000000..c567a6fa --- /dev/null +++ b/kubernetes-exercises/old/07-healthchecks.md @@ -0,0 +1,79 @@ +# Kubernetes health checks + +Health checks in Kubernetes are a mechanism to +check if a pod is able to handle load. This status +can be used by Kubernetes to avoid routing traffic +to pods which are unhealthy, or automatically +recreate unhealthy pods. + +Kubernetes has two built-in ways of making a +health check on a pod: + +- _readiness probes_ that finds out if your pod is + ready to receive traffic +- _liveness probes_ that finds out if your pod is + alive and well + +When we use `kubectl` to print the status of a +pod, we receive information on the status of the +pod. + +``` +NAME READY STATUS RESTARTS AGE +probe-59cf4f5578-vwllc 1/1 Running 1 10m +``` + +In this example, "1/1" in the READY-column means +shows the amount of containers in this pod which +Kubernetes identified to be in the READY-state. + +The difference between a container being healthy +or unhealthy, is vital. A container can be +creating, failing or otherwise deployed but +unavailable - and in this state Kubernetes will +choose not to route traffic to the container if it +deems it unhealthy. + +## Tasks + +Apply the deployment and service found in the +`health-checks` folder: + +- `kubectl apply -f health-checks/probes.yaml ` +- `kubectl apply -f health-checks/probes-svc.yaml` +- Try to access the service through the public IP + of one of the nodes, just like we worked with in + the + [service discovery assignment](./02-service-discovery-and-loadbalancing.md). +- Scale the deployment by changing the `replicas` + amount to 2 in the `probes.yaml` +- Again, access the application through your + browser. Refresh the page multiple times such + that you hit both of the instances +- Execute a bash session in one of the instances + `kubectl exec -ti probe-59cf4f5578-vwllc -- bash` +- First, remove the file `/tmp/ready`, and monitor + that the browser will eventually not route + traffic to that pod. +- Remove the file `/tmp/alive`, and observe that + within a short while you will get kicked out of + the container, as the pod is restarting. +- Observe that the pod has now been restarted when + you list the pods with `kubectl get pods` +- Look at the logs: + `kubectl describe pod probe-59cf4f5578-vwllc` + and see the events that you have triggered + through this exercise. + +Congratulations! + +You have now tried out both to pause traffic to a +given pod when its readinessprobe is failing, and +trigger a pod restart when the livelinessprobe is +failing. + +## Clean up + +``` +kubectl delete -f health-checks +``` diff --git a/kubernetes-exercises/old/exercise_setup/00-setup-kubectl-linux.md b/kubernetes-exercises/old/exercise_setup/00-setup-kubectl-linux.md new file mode 100644 index 00000000..6ce30464 --- /dev/null +++ b/kubernetes-exercises/old/exercise_setup/00-setup-kubectl-linux.md @@ -0,0 +1,109 @@ +# Setup kubectl + +> NB: if you are in an instructor facilited training, please don't run the commands below, +> as your machine will have already been configured with the proper access. + +It is assumed that you are provided with a kubernetes cluster by the instructor. Before you are able to do anything on the cluster, you need to be able to *talk* to this cluster from/using your computer. [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - short for Kubernetes Controller - is *the* command line tool to talk to a Kubernetes cluster. To get that on your computer follow the instructions for `kubectl` on this page: + +Kubectl is a *go* binary which allows you to execute commands on your cluster. Your cluster could be a single node VM, such as [minikube](https://github.com/kubernetes/minikube), or a set of VMs on your local computer or somewhere on a host in your data center, a bare-metal cluster, or a cluster provided by any of the cloud providers - as a service - such as GCP. + +**Note:** Due to restrictions with virtualization inside a virtual machine (nested virtualization), you cannot run minikube on cloud VMs. Minikube is a part of the Kubernetes open source project, with the single goal of getting a simple cluster up and running with just one virtual machine acting as node. + +For the remainder of this workshop, we assume you have a Kubernetes cluster on google cloud. For instructions on connecting to various types of Kubernetes cluster, check [this article](https://kubernetes.io/docs/tasks/tools/install-kubectl/#configure-kubectl) + + +## Authenticate to your Google k8s cluster: +To authenticate against your cluster, you will need a gmail account. Then, run: + +```shell + # cluster connection via service account + + # Install the tools +export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" +echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list +curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - +sudo apt-get update && sudo apt-get install google-cloud-sdk + + # Create key file on your vm - the instructor will mail you the contents +vi keyfile.json + + # authenticate with cloud +gcloud auth activate-service-account --key-file keyfile.json + + # Get the cluster credentials for kubectl +gcloud container clusters get-credentials training-cluster --zone europe-west1-b --project praqma-education +``` + +Google will do some magic under the hood, which does a few things: +* Fetches certificates and tokens (secrets) +* Puts them into the Kubernetes configuration file, located at /home/.kube/config + +## Verify configuration: +You can verify this by looking at the config file: + +```shell +kubectl config view +``` + +You should see something like this: + +```yaml +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: REDACTED + server: https://1.2.3.4 + name: gke_praqma-education_europe-west1-b_dcn-cluster-35 +contexts: +- context: + cluster: gke_praqma-education_europe-west1-b_dcn-cluster-35 + user: gke_praqma-education_europe-west1-b_dcn-cluster-35 + name: gke_praqma-education_europe-west1-b_dcn-cluster-35 +current-context: gke_praqma-education_europe-west1-b_dcn-cluster-35 +kind: Config +preferences: {} +users: +- name: gke_praqma-education_europe-west1-b_dcn-cluster-35 + user: + password: secret-password-ea4a2fb76dc9 + username: admin +``` + + +Furthermore you should now have access to the google cloud cluster! Verify by looking at the nodes for the cluster: + +```shell +kubectl get nodes +``` + +You should be able to see something similar to what is shown below: +```shell +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +ip-172-20-40-108.eu-central-1.compute.internal Ready master 1d v1.8.0 +ip-172-20-49-54.eu-central-1.compute.internal Ready node 1d v1.8.0 +ip-172-20-60-255.eu-central-1.compute.internal Ready node 1d v1.8.0 +``` + +If you add the `-o wide` parameters to the above command, you will also see the public IP addresses of the nodes: + +```shell +$ kubectl get nodes -o wide +NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +gke-dcn-cluster-35-default-pool-dacbcf6d-3918 Ready 17h v1.8.8-gke.0 35.205.22.139 Container-Optimized OS from Google 4.4.111+ docker://17.3.2 +gke-dcn-cluster-35-default-pool-dacbcf6d-c87z Ready 17h v1.8.8-gke.0 35.187.90.36 Container-Optimized OS from Google 4.4.111+ docker://17.3.2 +``` + +**Note:** On Kubernetes clusters provided by a Kubernetes service provider, you will only see worker nodes as a result of executing the above command. On other clusters, you will see both master and worker nodes. + +```shell +$ kubectl get nodes -o wide +NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +ip-172-20-40-108.eu-central-1.compute.internal Ready master 1d v1.8.0 1.2.3.4 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +ip-172-20-49-54.eu-central-1.compute.internal Ready node 1d v1.8.0 2.3.4.5 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +ip-172-20-60-255.eu-central-1.compute.internal Ready node 1d v1.8.0 5.6.7.8 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +``` + + + +**Note:** Depending on the setup for this workshop, you may not be the only tenant on the cluster; you may be sharing it with the rest of the people around you in the course! So be careful! diff --git a/kubernetes-exercises/old/exercise_setup/00-setup-namespace.md b/kubernetes-exercises/old/exercise_setup/00-setup-namespace.md new file mode 100644 index 00000000..597ddae8 --- /dev/null +++ b/kubernetes-exercises/old/exercise_setup/00-setup-namespace.md @@ -0,0 +1,64 @@ +# Setup Namespace + +> NB: If you are in an instructor facilitated training, the namespaces have already been configured, +> but please read on, to get to know more about namespaces. + +Namespaces are the default way for kubernetes to separate resources. + Namespaces do not share anything between them, which is important to know, + and thus come in handy when you have multiple users on the same cluster, + that you don't want stepping on each other's toes :) + +## 1.1 Create a namespace + +Namespaces are resources themselves, so they can be created like any other resource. Choose a name for your namespace, something unique so you don't clash with one of the other participants at the workshop. + +```shell +$ kubectl create namespace my-namespace +namespace "my-namespace" created +``` + +## 1.2 Scoping the kubectl command + +You want to target your own namespace instead of default one every time you use `kubectl`. + You can run a command in a specific namespace by using the `-n, --namespace=''`-flag. + +The below commands do the same thing, because kubernetes commands will default to the default namespace: + +```shell +kubectl get pods -n default +kubectl get pods +``` + +## 1.3 Set your default namespace + +It gets tedious however to write this every time you want to select your own namespace, + so it makes sense to set this as the default. + +To overwrite the default namespace for your current `context`, run: + +```shell +$ kubectl config set-context $(kubectl config current-context) --namespace=my-namespace +Context "" modified. +``` + +You can verify that you've updated your current `context` by running: + +```shell +kubectl config get-contexts +``` + +Notice that the namespace column has the value of ``. + +Most errors you will get throughout the rest of the workshop will 99% be due to deploying into a namespace, + where someone's already done the exercise before you; always ensure you're using your newly created namespace! + +## 1.4 More on Namespaces + +Namespaces are quite powerful. On a user level, it is also possible to limit namespaces and resources by users but this is a bit too involved for your first experience with Kubernetes. + Therefore, please be aware that other people's namespaces are off limits for this workshop; even if you do have access ;) + +Kubernetes clusters come with a namespace called `default`, which in this case might contain some pods deployed previously by the trainers, + and usually one called `kube-system` which will contain some of the kubernetes services running in the cluster. + +You might see later that the namespace is specified directly in the yaml files describing the resources. + This makes it possible to have the resource created in the specific namespace without specifying the `-n` flag on creation. diff --git a/kubernetes-exercises/old/exercise_setup/99-setup-kubectl-generic.md b/kubernetes-exercises/old/exercise_setup/99-setup-kubectl-generic.md new file mode 100644 index 00000000..13acb6d1 --- /dev/null +++ b/kubernetes-exercises/old/exercise_setup/99-setup-kubectl-generic.md @@ -0,0 +1,201 @@ +# Setup kubectl + +It is assumed that you are provided with a kubernetes cluster by the instructor. Before you are able to do anything on the cluster, you need to be able to *talk* to this cluster from/using your computer. [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - short for Kubernetes Controller - is *the* command line tool to talk to a Kubernetes cluster. To get that on your computer, you will need to follow instruction from [here](https://kubernetes.io/docs/tasks/tools/install-kubectl/). We have provided you simplified steps: + +## MAC OS: +```shell +brew install kubectl +``` + +OR + +```shell +$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x ./kubectl && sudo mv ./kubectl /usr/local/bin/kubectl +``` + +## Linux (RedHat/CentOS/Fedora): + +```shell +curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x ./kubectl && sudo mv ./kubectl /usr/local/bin/kubectl +``` + +OR + +```shell +cat < /etc/yum.repos.d/kubernetes.repo +[kubernetes] +name=Kubernetes +baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg +EOF +yum install -y kubectl +``` + +## Debian/Ubuntu: +```shell +curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x ./kubectl && sudo mv ./kubectl /usr/local/bin/kubectl +``` + +OR + +```shell +sudo apt-get update && sudo apt-get install -y apt-transport-https +curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - +sudo touch /etc/apt/sources.list.d/kubernetes.list +echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list +sudo apt-get update +sudo apt-get install -y kubectl +``` + + + +Kubectl is a *go* binary which allows you to execute commands on your cluster. Where, "your cluster" could be: a single node VM, such as [minikube](https://github.com/kubernetes/minikube), or a set of VMs on your local computer or somewhere on a host in your data center, a bare-metal cluster, or a cluster provided by any of the cloud providers - as a service - such as GCP. + +**Note:** Due to restrictions with virtualization inside a virtual machine (nested virtualization), you cannot run minikube on cloud VMs. Minikube is a part of the Kubernetes open source project, with the single goal of getting a simple cluster up and running with just one virtual machine acting as both master-node and worker-node. + +For the remainder of this workshop, we assume you have a Kubernetes cluster on google cloud. + + +# Authenticate to your Google k8s cluster: +In some workshops we provide a preconfigured **config** file, which you simply save as ~/.kube/config on your local computer, and you are golden. +However, in other setups, you may need to actually authenticate to the cluster, and get a config file directly from GCP. Actually, the instructors may also need to use/do the following steps when they are setting up individual VMs for each student. Either way, the access to cluster can only be obtained after authenticating to google. To authenticate against your cluster, you will need google cloud SDK installed on your computer, and a gmail account. Assuming you already have a gmail account, we proceed to install google SDK on the local computer. To do that we have provided a summary of steps you need to perform. For more details, visit [this URL](https://cloud.google.com/sdk/install) . + + +## MAC OS: + +```shell +curl https://sdk.cloud.google.com | bash +exec -l $SHELL +gcloud init +``` + +## Linux (Debian/RedHat): +```shell +curl https://sdk.cloud.google.com | bash +exec -l $SHELL +gcloud init +``` + +OR + +## Linux (Debian/Unbuntu): +```shell +export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" +echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list +curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - +sudo apt-get update && sudo apt-get install google-cloud-sdk +gcloud init +``` + + +## Linux (RedHat/CentOS/Fedora): +```shell +sudo tee -a /etc/yum.repos.d/google-cloud-sdk.repo << EOM +[google-cloud-sdk] +name=Google Cloud SDK +baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el7-x86_64 +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg + https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg +EOM + +yum install google-cloud-sdk +gcloud init +``` + + +## Windows: +Follow these instructions: [https://cloud.google.com/sdk/docs/downloads-interactive#windows](https://cloud.google.com/sdk/docs/downloads-interactive#windows) + + +Then, run: +```shell + # cluster connection via service account + # Create key file on your vm - the instructor will mail you the contents +vi keyfile.json + + # authenticate with cloud +gcloud auth activate-service-account --key-file keyfile.json + + # Get the cluster credentials for kubectl +gcloud container clusters get-credentials cluster-london --zone europe-west1-b --project praqma-education +``` + +Google will do some magic under the hood, which does a few things: +* Fetches certificates and tokens (secrets) +* Puts them into the Kubernetes configuration file, located at ~/.kube/config + +## Verify configuration: +You can verify this by looking at the config file using the following command: + +```shell +kubectl config view +``` + +You should see something like this: + +```yaml +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: REDACTED + server: https://1.2.3.4 + name: gke_praqma-education_europe-west1-b_dcn-cluster-35 +contexts: +- context: + cluster: gke_praqma-education_europe-west1-b_dcn-cluster-35 + user: gke_praqma-education_europe-west1-b_dcn-cluster-35 + name: gke_praqma-education_europe-west1-b_dcn-cluster-35 +current-context: gke_praqma-education_europe-west1-b_dcn-cluster-35 +kind: Config +preferences: {} +users: +- name: gke_praqma-education_europe-west1-b_dcn-cluster-35 + user: + password: secret-password-ea4a2fb76dc9 + username: admin +``` + + +You should now have access to the google cloud cluster! Verify by looking at the nodes for the cluster: + +```shell +kubectl get nodes +``` + +You should be able to see something similar to what is shown below: +```shell +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +ip-172-20-40-108.eu-central-1.compute.internal Ready master 1d v1.8.0 +ip-172-20-49-54.eu-central-1.compute.internal Ready node 1d v1.8.0 +ip-172-20-60-255.eu-central-1.compute.internal Ready node 1d v1.8.0 +``` + +If you add the `-o wide` parameters to the above command, you will also see the public IP addresses of the nodes: + +```shell +$ kubectl get nodes -o wide +NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +gke-dcn-cluster-35-default-pool-dacbcf6d-3918 Ready 17h v1.8.8-gke.0 35.205.22.139 Container-Optimized OS from Google 4.4.111+ docker://17.3.2 +gke-dcn-cluster-35-default-pool-dacbcf6d-c87z Ready 17h v1.8.8-gke.0 35.187.90.36 Container-Optimized OS from Google 4.4.111+ docker://17.3.2 +``` + +**Note:** On Kubernetes clusters provided by a Kubernetes service provider, you will only see worker nodes as a result of executing the above command. On other clusters, you will see both master and worker nodes. + +```shell +$ kubectl get nodes -o wide +NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +ip-172-20-40-108.eu-central-1.compute.internal Ready master 1d v1.8.0 1.2.3.4 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +ip-172-20-49-54.eu-central-1.compute.internal Ready node 1d v1.8.0 2.3.4.5 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +ip-172-20-60-255.eu-central-1.compute.internal Ready node 1d v1.8.0 5.6.7.8 Debian GNU/Linux 8 (jessie) 4.4.78-k8s docker://1.12.6 +``` + + + +**Note:** Depending on the setup for this workshop, you may not be the only tenant on the cluster; you may be sharing it with the rest of the people around you in the course! So be careful! diff --git a/kubernetes-exercises/old/exercise_setup/beyond-this-course-setting-up-your-own.md b/kubernetes-exercises/old/exercise_setup/beyond-this-course-setting-up-your-own.md new file mode 100644 index 00000000..e179a54b --- /dev/null +++ b/kubernetes-exercises/old/exercise_setup/beyond-this-course-setting-up-your-own.md @@ -0,0 +1,73 @@ +# Setting up multiple nodes in a cluster +We will be using a tool called [KubeAdm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/) to set up a multi node cluster. + +KubeAdm is built for setting up and provisioning a cluster on bare metal, especially when having to interact with existing environments set up by puppet, ansible and etc. + +For Microsoft Azure the [Azure Container Service (AKS)](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes) does this for you. + +Similarly on Google Cloud Platform there is the [Google Container Engine (GCE)](https://cloud.google.com/container-engine/). + +For Amazon Web Services [Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Also [Kubernetes Operations (KOPS)](https://github.com/kubernetes/kops) is used quite often to have more control over the cluster. + +Going back to kubeadm, you will have to select a master and a given amount of nodes. + +Run ssh to get onto the master, and verify that kubeadm is there: +``` +kubeadm --help +``` + +On the machine you have chosen as master, run: +``` +sudo kubeadm init +``` + +This process takes about a minute or so, while the master sets up. + +Copy the kubeadm join command output in the terminal (similar to the one below) before continuing, as you will need it for the nodes to join. + +``` +kubeadm join --token 2731ee.bb0be06012dbac00 172.31.18.205:6443 --discovery-token-ca-cert-hash sha256:fe634423c08ba596351dffd610503b4311f7160efcd49e343de83949ff4df610 +``` + +KubeAdm will tell you to copy some files for configurations, but in case you missed it run: + +``` +mkdir -p $HOME/.kube +sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config +sudo chown $(id -u):$(id -g) $HOME/.kube/config +``` + +This will set up the configurations needed to allow the other machines to join the cluster. + +To allow the pods on each node to talk, we will need to apply a pod network. For this exercise the choice is on Weave Net, but other options are Calico, Canal, Flannel, Kube-Router and Romana. + +``` +export kubever=$(kubectl version | base64 | tr -d '\n') +kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$kubever" +``` + +We can verify that the pod network is correctly installed by running : +``` +kubectl get pods --all-namespaces +``` +The relevant pod is kube-dns, which needs to have 3/3 running before continuing to join nodes to the cluster. + +Ssh to all nodes and run the kubeadm join command as root/sudo that you copied previously. + +``` +sudo kubeadm join --token 2731ee.bb0be06012dbac00 172.31.18.205:6443 --discovery-token-ca-cert-hash sha256:fe634423c08ba596351dffd610503b4311f7160efcd49e343de83949ff4df610 +``` + +This should allow the node to join the cluster ! Be mindful that for this to work port 6443 has to be open on master. + +Now you can run commands on master by using kubectl! + +## Optionally, execute commands on cluster from another machine than master +Optionally, having to ssh to master is not the best of things. This can be changed by handing over the config to another machine: + +``` +scp root@:/etc/kubernetes/admin.conf . +kubectl --kubeconfig ./admin.conf get nodes +``` + +## Kubernetes yml diff --git a/kubernetes-exercises/old/extras/08-ingress-gke.md b/kubernetes-exercises/old/extras/08-ingress-gke.md new file mode 100644 index 00000000..d6552fcb --- /dev/null +++ b/kubernetes-exercises/old/extras/08-ingress-gke.md @@ -0,0 +1,118 @@ +# Ingress on Google Cloud + +GoogleKubernetesEngine (GKE) does Ingress a bit differently. For reference, those brave of you who want to play with Minikube, I have two more you can do - [nginx](06-ingress-nginx.md) and [traefik](06-ingress-traefik.md). + +However because we are running things on GKE, we are going to focus on that today. + +An ingress is loosely translatable to DNS namespace, and makes it so that: + +- Traffic to port `80` (http) and port `443` (https) goes to ingress rules instead AND +- A name call to (example) myapplication.example.local gets translated to an IP and then routed to a container. + +Create an Nginx deployment and service, exposing the service as a nodeport. If you need a hint as to how this is done, look at the [Pods and Deployments](01-pods-deployments.md) and [Service Discovery and Loadbalancing](02-service-discovery-and-loadbalancing) exercises. + +GKE automatically exposes NodePorts through a Loadbalancer, but the 'correct' way is the ingress rule, which will work on all infrastructure. + +## Creating an 'address' on Google Cloud + +> NB: To manipulate `address` on Google Cloud you need the `Compute Admin Network`-right or similar on your service account. +> If you have trouble creating the `address` verify (or ask your trainer to verify) that your service account is configured properly. + +Google cloud need an address created. All the steps running gcloud commands, are unusual and can be disregarded for a normal Kubernetes setup outside of GKE. + +```shell +$ gcloud compute addresses create --global --project praqma-education +Created [https://www.googleapis.com/compute/v1/projects/praqma-education/global/addresses/]. +``` + +You can see the allocated IP of the address you created by running the following command: + +```shell +$ gcloud compute addresses describe --global --format='value(address)' --project praqma-education + +``` + +## Using the created address in an ingress object + +Ingress' can be told by annotation where the source request came from. This is useful when going through multiple networks, as the ingress rule then understand things like sticky sessions and other networking things like SSL termination and so on. + +```yaml +annotations: + kubernetes.io/ingress.global-static-ip-name: "my-address-name" +``` + +The above only works for GKE, but an equivalent for nginx would look like this: + +```yaml +annotations: + kubernetes.io/ingress.class: "nginx" + nginx.org/ssl-services: "my-service" +``` + +So lets put the following ingress spec into `ingress-file.yaml`: + +```yaml,k8s +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: nginx + annotations: + kubernetes.io/ingress.global-static-ip-name: "my-address-name" # should match the name of the address you created before +spec: + backend: + serviceName: nginx + servicePort: 80 +``` + +And create it! + +```shell +$ kubectl apply -f ingress-file.yml +ingress.extensions/nginx created +``` + +Find it in the Kubernetes cluster (hint: `.. get ing nginx`) + +The `ADDRESS` bit, should match the IP of the address you created before. + +## See your application on the internet + +You should be able to visit this address, and see the nginx homesite! +> NB: if you get an error from Google, +> try checking your ingress object with `kubectl describe ingress nginx`, +> the `annotation` called `backends` needs to be `HEALTHY`, like below: +> ```shell +> Annotations: +> ... +> kubernetes.io/ingress.global-static-ip-name: my-address-name +> ingress.kubernetes.io/backends: {"":"HEALTHY"} +> ... +> ``` +> if the `backend` is `UNKNOWN` and your `ingress.global-static-ip-name` +> points correctly to the address you created before, don't fear, +> Google Cloud Load Balancers needs a certain amount of successes on an endpoint, +> before it starts serving traffic; the backend should become `HEALTHY` within a few minutes. + +## DNS Rules + +Normally you *COULD* add a dns rule, and say "I want nginx.local to route to this container" and it would look something like this: + +```yaml,k8s +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: my-ingress +spec: + rules: + - host: nginx.local + http: + paths: + - path: / + backend: + serviceName: ingress-test + servicePort: replaceport +``` + +Because GKE "makes ingress easy", these things need a DNS setup on GKE which we are not going to do. But this should give an impression of ingress. + +I thoroughly recommend looking at minikube and ingress, if you plan to use this more extensively. diff --git a/kubernetes-exercises/old/extras/08-ingress-nginx.md b/kubernetes-exercises/old/extras/08-ingress-nginx.md new file mode 100644 index 00000000..d46a86cb --- /dev/null +++ b/kubernetes-exercises/old/extras/08-ingress-nginx.md @@ -0,0 +1,74 @@ +# Ingress +If we look at Service as an endpoint, then the Kubernetes ingress object is a DNS. +It takes care of things like load balancing traffic, terminating SSL and naming among other things. + +To enable an ingress object, we need an ingress controller. In this example we will be using [NGINX](https://www.nginx.com/). If you prefer [Træfik (optimized for Kubernetes) there is another exercise doing it with Træfik](./03-ingress-traefik.md). + +To get started with NGINX ingress, we (re)deploy an app of our choice: +```shell +kubectl create deployment ingress-test --image= +kubectl scale deployment ingress-test --replicas=3 +kubectl expose deployment ingress-test --port= +``` + +The NGINX ingress controller requires a default backend which serves as a fallback for nginx in case a request fails. +It will: +- Serve a 404 page at / +- Serve 200 on /healthz + +The [example deployment file for nginx-ingress-controller](ingress-nginx/nginx-backend/nginx-backend.yml) is taken from [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx), as is the [service exposing the backend](ingress-nginx/nginx-backend/nginx-service.yml). + +Deploy by running: +```shell +kubectl create -f ingress-nginx/nginx-backend/. +``` + +We will need a certificate for NGINX, which has been prepared in [a script](ingress-nginx/self-signed-cert.sh). We will need to turn that into a secret. + +When prompted to put a "common name" in the certificate, write myapp.local (*important!*). This is going to be our host rule for our ingress later. + +```shell +./ingress-nginx/self-signed-cert.sh +kubectl create secret tls tls-certificate --key tls-key.key --cert tls-cert.crt +kubectl create secret generic tls-dhparam --from-file=dhparam.pem +``` + +We then have to enable ingress, which is very simple: +``` +minikube addons enable ingress +``` +The reason it is not natively enabled, is because early versions of Kubernetes shipped without it. + +Enabling gave us: +- A configmap + * kubectl get configmap nginx-load-balancer-conf -n kube-system + * This describes the nginx configuration +- The nginx-ingress-controller + * kubectl get rc nginx-ingress-controller -n kube-system + * This enables us to do ingress +- A service exposing default NGINX backend pod handling + * kubectl get svc default-http-backend -n kube-system + * This serves (together with the deployed yaml from earlier) the 404 and 200. + + +Nginx can be accessed this way : +```shell +curl $(minikube service nginx-ingress --url) +``` + +Which will return 404 default backend. + +Go and modify [the yaml for your ingress](./ingress-nginx/ingress.yml) to reflect the correct service and deployment. + +Ingress works by using the DNS name, so we need to modify our hostfile to reflect the correct name (modify hosts file to include myapp.local pointing to the cluster): + +```shell +echo "$(minikube ip) myapp.local" | sudo tee -a /etc/hosts +``` + +To make it work for your cluster, replace the minikube ip with a node ip. + +You can now access it on http://myapp.local, though you probably get a https error on the certificate because it was self signed. + +This concludes the exercise for ingress. + diff --git a/kubernetes-exercises/old/extras/08-ingress-traefik.md b/kubernetes-exercises/old/extras/08-ingress-traefik.md new file mode 100644 index 00000000..9ed63376 --- /dev/null +++ b/kubernetes-exercises/old/extras/08-ingress-traefik.md @@ -0,0 +1,340 @@ +# Ingress - Traefik +If we look at Service as an endpoint, then the Kubernetes ingress object is a DNS look-alike. +It takes care of things like load balancing traffic, terminating SSL and naming among other things. + +To enable an ingress object, we need an ingress controller. In this example we will be using [Træfik](https://traefik.io/). If you prefer [NGINX there is another exercise doing it with NGINX](./05-ingress-nginx.md). + + + +## Related RBAC configuration: +Create a file named traefik-rbac.yaml with the following contents. This will setup correct global cluster role binding. + +```yaml,k8s +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list + - watch +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: traefik-ingress-controller +subjects: +- kind: ServiceAccount + name: traefik-ingress-controller + namespace: kube-system +``` + +Create the traefik RBAC configuration using: +```shell +kubectl create -f ingress-traefik/traefik-rbac.yaml +``` + +## Deploy Traefik: +Create a file named traefik-deployment.yaml with the following contents. + +```yaml,k8s +apiVersion: v1 +kind: ServiceAccount +metadata: + name: traefik-ingress-controller + namespace: kube-system +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: traefik-ingress-controller + namespace: kube-system + labels: + k8s-app: traefik-ingress-lb +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: traefik-ingress-lb + template: + metadata: + labels: + k8s-app: traefik-ingress-lb + name: traefik-ingress-lb + spec: + serviceAccountName: traefik-ingress-controller + terminationGracePeriodSeconds: 60 + containers: + - image: traefik + name: traefik-ingress-lb + ports: + - name: http + containerPort: 80 + - name: admin + containerPort: 8080 + args: + - --api + - --kubernetes + - --logLevel=INFO +--- +kind: Service +apiVersion: v1 +metadata: + name: traefik-ingress-service + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - protocol: TCP + port: 80 + name: web + - protocol: TCP + port: 8080 + name: admin + type: LoadBalancer +``` +**Note: You can also use type: NodePort** in the Service section above. + + +Create the objects defined in the traefik-deployment.yaml: +```shell +kubectl create -f ingress-traefik/traefik-deployment.yaml +``` + + +## Create Ingress for Traefik Web-UI: +Create a file traefik-webui-ingress.yaml with the following contents to create a Service and an Ingress that will expose the Traefik Web UI. +```yaml,k8s +apiVersion: v1 +kind: Service +metadata: + name: traefik-web-ui + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - name: web + port: 80 + targetPort: 8080 +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: traefik-web-ui + namespace: kube-system +spec: + rules: + - host: traefik-ui.example.com + http: + paths: + - path: / + backend: + serviceName: traefik-web-ui + servicePort: web +``` + +Create the objects: +```shell +kubectl create -f traefik-webui-ingress.yaml +``` + + +You can check the public IP of the Traefik service, and on your local computer, edit the `/etc/hosts` file to set up name resolution for this IP as: + +``` +127.0.0.1 localhost localhost.localdomain +35.240.21.22 traefik-ui.example.com www.example.com +``` + +OR. If you have a domain under your control, setup DNS accordingly. You can use any other DNS name for your setup other than example.com. + +Now visit the address `traefik-ui.example.com` , you should see a dashboard. + +![](ingress-traefik/traefik-dashboard.png) + +## Setup additional ingress for your application(s): +It's time to setup an additional service for any of our application. For now, I will use a simple nginx web server. Create a file examplenginx-deployment.yaml with the following contents: + +```yaml,k8s +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: nginx + labels: + name: nginx + app: nginx +spec: + ports: + - port: 80 + selector: + app: nginx + +--- + +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + namespace: default + name: example-nginx-ingress + labels: + app: nginx +spec: + rules: + - host: www.example.com + http: + paths: + - path: / + backend: + serviceName: nginx + servicePort: 80 +``` + +Create the objects from the above file: + +```shell +kubectl create -f ingress-traefik/example-nginx-deployment.yaml +``` + + +If you visit the Traefik dashboard now, you should be able to see a new ingress pop up. Visit the web page `www.example.com` to verify that you can access the nginx web server. + +![](ingress-traefik/nginx-on-traefik-dashboard.png) + + +Visiting www.example.com should show nginx webpage: + +```shell +$ curl www.example.com + + + +Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + +``` + + +------------ +(Ignore anything below this line - for now) + + + + + + + + +So, therefore we start with the ingress controller: +```shell +kubectl create -f ingress-traefik/traefik-ingress-controller.yml +``` + +Ingress has a nice little GUI which shows current ingress rules and settings in a cluster, which we can take advantage of to create an overview. + +Create the service: +```shell +kubectl create -f ingress-traefik/traefik-service.yml +``` +... and the ingress rule: +```shell +kubectl create -f ingress-traefik/traefik-ingress.yml +``` + +Instead of going through the trouble of setting up a proper DNS, we can modify the host file - below is an example of doing this for minikube: +```shell +echo "$(minikube ip) traefik-ui.local" | sudo tee -a /etc/hosts +``` + +To make it work for your cluster, replace the minikube ip with a node ip. + +Which means you can access it like by clicking http://traefik-ui.local + +So the magic here is that: +- You send a request which is translated to the node ip +- The node looks up the incoming name record and finds it in an ingress. +- The ingress rule says that traffic from traefik-ui.local needs to go to backend service 'traefik-web-ui' on port 80 + * kubectl get svc traefik-web-ui -n kube-system +- The service then redirects to the container, in this case the ingress controller itself, on port 8080. + +Let's try with a different container. Deploy any given image and expose a service for it. + +```shell +kubectl create deployment ingress-test --image= +kubectl scale deployment ingress-test --replicas=3 +kubectl expose deployment ingress-test --port= +``` + +Then [go to the ingress] and modify the port (servicePort: replaceport), followed by: +```shell +kubectl create -f ingress-traefik/my-ingress.yml +echo "$(minikube ip) myapp.local" | sudo tee -a /etc/hosts +``` + +Access it on http://myapp.local. + +This concludes the exercise for ingress. + diff --git a/kubernetes-exercises/old/extras/09-helm-package-manager.md b/kubernetes-exercises/old/extras/09-helm-package-manager.md new file mode 100644 index 00000000..653325e8 --- /dev/null +++ b/kubernetes-exercises/old/extras/09-helm-package-manager.md @@ -0,0 +1,169 @@ +# The Kubernetes package manager + +## Learning goal + +- Try the Helm cli to spin up a chart + +## Introduction + +[Enter Helm](https://github.com/helm/helm) - the +answer to how to package multi-container +applications, and how to easily install packages +on Kubernetes. + +Helm helps you to: + +- Achieve a simple (one command) and repeatable + deployment +- Manage application dependency, using specific + versions of other application and services +- Manage multiple deployment configurations: test, + staging, production and others +- Execute post/pre deployment jobs during + application deployment +- Update/rollback and test application deployments + +## Using helm charts + +Helm uses a packaging format called charts. A +Chart is a collection of files that describe k8s +resources. + +
+ More details +Charts can be simple, describing something like a +standalone web server but they can also be more +complex, for example, a chart that represents a +full web application stack included web servers, +databases, proxies, etc. + +Instead of installing k8s resources manually via +kubectl, we can use Helm to install pre-defined +Charts faster, with less chance of typos or other +operator errors. + +When you install Helm, it does not have a +connection to any default repositories. This is +because Helm wants to decouple the application to +the repository in use. + +One of the largest Chart Repositories is the +[BitNami Chart Repository](https://charts.bitnami.com/bitnami) +is however going to be used in these exercises. + +The chart repository are very dynamic due to +updates and new additions. To keep Helm's local +list updated with all these changes, we need to +occasionally run the +[repository update](https://docs.helm.sh/helm/#helm-repo-update) +command. + +
+ +## Exercise + +### Overview + +- Add a chart repository to your helm cli +- Install Nginx chart +- Access the Nginx load balanced service +- Look at the status of the deployment with + `helm ls` +- Clean up the chart deployment + +### Step by step + +
+ More details + +**Add a chart repository to your helm cli** + +To install the Bitnami Helm Repo and update Helm's +local list of Charts, run: + +- `helm repo add bitnami https://charts.bitnami.com/bitnami` +- `helm repo update` + +**Install Nginx Chart** + +To get something installed fasted and easy we have +chosen the Nginx chart. + +- `helm install my-release bitnami/nginx` + +This command creates a release called `my-release` +with the bitnami/nginx chart. + +The command will output information about your +newly deployed mysql setup similar to this: + +``` +NAME: my-release +LAST DEPLOYED: Tue Apr 20 12:46:10 2021 +NAMESPACE: user1 +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +** Please be patient while the chart is being deployed ** + +NGINX can be accessed through the following DNS name from within your cluster: + + my-release-nginx.user1.svc.cluster.local (port 80) + +To access NGINX from outside the cluster, follow the steps below: + +1. Get the NGINX URL by running these commands: + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace user1 -w my-release-nginx' + + export SERVICE_PORT=$(kubectl get --namespace user1 -o jsonpath="{.spec.ports[0].port}" services my-release-nginx) + export SERVICE_IP=$(kubectl get svc --namespace user1 my-release-nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo "http://${SERVICE_IP}:${SERVICE_PORT}" +``` + +**Access the Nginx load balanced service** + +Get the external IP and port with the following +three commands. + +- `export SERVICE_PORT=$(kubectl get --namespace user1 -o jsonpath="{.spec.ports[0].port}" services my-release-nginx)` +- `export SERVICE_IP=$(kubectl get svc --namespace user1 my-release-nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')` +- `echo "http://${SERVICE_IP}:${SERVICE_PORT}"` +- Navigate your browser to the url printed out by + the last command + +**Look at the status of the deployment with `helm` +and `kubectl`** + +Running `helm ls` will show all current +deployments. + +- Run `helm ls` and observe that you have a + release named `my-release` +- Run `kubectl get pods,deployments,svc` and look + at a few of the kubernetes objects the release + created. + +> :bulb: As said before Helm deals with the +> concept of +> [charts](https://github.com/kubernetes/charts) +> for its deployment logic. bitnami/nginx was a +> chart, +> [found here](https://github.com/bitnami/charts/tree/master/bitnami/nginx) +> that describes how helm should deploy it. It +> interpolates values into the deployment, which +> for nginx looks +> [like this](https://github.com/bitnami/charts/blob/master/bitnami/nginx/templates/deployment.yaml). +> The charts describe which values can be given +> for overwriting default behavior, and there is +> an active community around it. + +**Clean up the chart deployment** + +To remove the `my-release` release run: + +- `helm uninstall my-release` + +
diff --git a/kubernetes-exercises/old/extras/10-secrets-ssl-certs-in-nginx.md b/kubernetes-exercises/old/extras/10-secrets-ssl-certs-in-nginx.md new file mode 100644 index 00000000..24a22d66 --- /dev/null +++ b/kubernetes-exercises/old/extras/10-secrets-ssl-certs-in-nginx.md @@ -0,0 +1,51 @@ +# Kubernetes secrets + +The objective of this exercise use SSL certs in nginx, using secrets. + +Generate self signed certs: (check support-files/ directory) +``` +./generate-self-signed-certs.sh +``` +This will create `tls.*` files. + + +Create (tls type) secret for nginx: + +``` +kubectl create secret tls nginx-certs --cert=tls.crt --key=tls.key +``` + +Examine the secret you just created: +``` +kubectl describe secret nginx-certs +``` + +``` +kubectl get secret nginx-certs -o yaml +``` + + +Create configmap for nginx: (check support-files/ directory) +``` +kubectl create configmap nginx-config --from-file=nginx-connectors.conf +``` + +Examine the configmap you just created: + +``` +kubectl describe configmap nginx-config +``` + +``` +kubectl get configmap nginx-config -o yaml +``` + + +Create a nginx deployment with SSL support using the secret and config map you created in the previous steps (above): (check support-files/ directory) +``` +kubectl create -f nginx-ssl.yaml +``` + +You should be able to see nginx running. Expose it as a service and curl it from your computer. You can also curl it through the multitool pod from within the cluster. + + diff --git a/kubernetes-exercises/old/health-checks/probes-svc.yaml b/kubernetes-exercises/old/health-checks/probes-svc.yaml new file mode 100644 index 00000000..8b87214e --- /dev/null +++ b/kubernetes-exercises/old/health-checks/probes-svc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: probe +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: probe + type: NodePort diff --git a/kubernetes-exercises/old/health-checks/probes.yaml b/kubernetes-exercises/old/health-checks/probes.yaml new file mode 100644 index 00000000..4c830903 --- /dev/null +++ b/kubernetes-exercises/old/health-checks/probes.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: probe +spec: + replicas: 1 + selector: + matchLabels: + app: probe + template: + metadata: + labels: + app: probe + spec: + containers: + - name: probe + image: ghcr.io/eficode-academy/network-multitool + command: ['sh', '-c', 'touch /tmp/alive && touch /tmp/ready && /docker/entrypoint.sh /usr/sbin/nginx -g "daemon off;"'] + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 + livenessProbe: + exec: + command: + - cat + - /tmp/alive + initialDelaySeconds: 5 + periodSeconds: 5 + readinessProbe: + exec: + command: + - cat + - /tmp/ready + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/kubernetes-exercises/old/ingress-gke/ingress.yml b/kubernetes-exercises/old/ingress-gke/ingress.yml new file mode 100644 index 00000000..d8b724d7 --- /dev/null +++ b/kubernetes-exercises/old/ingress-gke/ingress.yml @@ -0,0 +1,10 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: nginx + annotations: + kubernetes.io/ingress.global-static-ip-name: "kubernetes-ingress" +spec: + backend: + serviceName: nginx + servicePort: 80 diff --git a/kubernetes-exercises/old/ingress-nginx/ingress.yml b/kubernetes-exercises/old/ingress-nginx/ingress.yml new file mode 100644 index 00000000..67bc999a --- /dev/null +++ b/kubernetes-exercises/old/ingress-nginx/ingress.yml @@ -0,0 +1,20 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: my-first-ingress + annotations: + kubernetes.io/ingress.class: "nginx" + nginx.org/ssl-services: "my-service" +spec: + tls: + - hosts: + - myapp.local + secretName: tls-certificate + rules: + - host: myapp.local + http: + paths: + - path: / + backend: + serviceName: + servicePort: \ No newline at end of file diff --git a/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-backend.yml b/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-backend.yml new file mode 100644 index 00000000..5c18c7ff --- /dev/null +++ b/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-backend.yml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: default-http-backend +spec: + replicas: 1 + selector: + matchLabels: + app: default-http-backend + template: + metadata: + labels: + app: default-http-backend + spec: + terminationGracePeriodSeconds: 60 + containers: + - name: default-http-backend + # Any image is permissable as long as: + # 1. It serves a 404 page at / + # 2. It serves 200 on a /healthz endpoint + image: gcr.io/google_containers/defaultbackend:1.0 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + ports: + - containerPort: 8080 + resources: + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi diff --git a/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-service.yml b/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-service.yml new file mode 100644 index 00000000..72a557b9 --- /dev/null +++ b/kubernetes-exercises/old/ingress-nginx/nginx-backend/nginx-service.yml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: default-http-backend +spec: + selector: + app: default-http-backend + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/old/ingress-nginx/nginx-controller-svc.yml b/kubernetes-exercises/old/ingress-nginx/nginx-controller-svc.yml new file mode 100644 index 00000000..eb17beef --- /dev/null +++ b/kubernetes-exercises/old/ingress-nginx/nginx-controller-svc.yml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-ingress-controller + labels: + app: nginx-ingress-controller +spec: + replicas: 1 + revisionHistoryLimit: 3 + selector: + matchLabels: + app: nginx-ingress-controller + template: + metadata: + labels: + k8s-app: nginx-ingress-lb + spec: + containers: + - args: + - /nginx-ingress-controller + - "--default-backend-service=$(POD_NAMESPACE)/default-http-backend" + - "--default-ssl-certificate=$(POD_NAMESPACE)/tls-certificate" + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: "gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.5" + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 5 + name: nginx-ingress-controller + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 443 + name: https + protocol: TCP + volumeMounts: + - mountPath: /etc/nginx-ssl/dhparam + name: tls-dhparam-vol + terminationGracePeriodSeconds: 60 + volumes: + - name: tls-dhparam-vol + secret: + secretName: tls-dhparam +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-ingress +spec: + type: LoadBalancer + ports: + - name: http + port: 80 + targetPort: http + - name: https + port: 443 + targetPort: https + selector: + k8s-app: nginx-ingress-lb \ No newline at end of file diff --git a/kubernetes-exercises/old/ingress-nginx/self-signed-cert.sh b/kubernetes-exercises/old/ingress-nginx/self-signed-cert.sh new file mode 100755 index 00000000..4425be1a --- /dev/null +++ b/kubernetes-exercises/old/ingress-nginx/self-signed-cert.sh @@ -0,0 +1,2 @@ +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls-key.key -out tls-cert.crt +curl https://ssl-config.mozilla.org/ffdhe2048.txt > dhparam.pem diff --git a/kubernetes-exercises/old/ingress-traefik/README.md b/kubernetes-exercises/old/ingress-traefik/README.md new file mode 100644 index 00000000..7a8fcb5c --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/README.md @@ -0,0 +1 @@ +Follow: [https://github.com/traefik/traefik/blob/v1.7/docs/user-guide/kubernetes.md](https://github.com/traefik/traefik/blob/v1.7/docs/user-guide/kubernetes.md) diff --git a/kubernetes-exercises/old/ingress-traefik/example-ingress.yaml b/kubernetes-exercises/old/ingress-traefik/example-ingress.yaml new file mode 100644 index 00000000..8be7fb57 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/example-ingress.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: nginx + labels: + name: nginx + app: nginx +spec: + ports: + - port: 80 + selector: + app: nginx + +--- + +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + namespace: default + name: example-nginx-ingress + labels: + app: nginx +spec: + rules: + - host: www.example.com + http: + paths: + - path: / + backend: + serviceName: nginx + servicePort: 80 + diff --git a/kubernetes-exercises/old/ingress-traefik/my-ingress.yml b/kubernetes-exercises/old/ingress-traefik/my-ingress.yml new file mode 100644 index 00000000..158277a3 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/my-ingress.yml @@ -0,0 +1,13 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: my-ingress +spec: + rules: + - host: myapp.local + http: + paths: + - path: / + backend: + serviceName: ingress-test + servicePort: replaceport \ No newline at end of file diff --git a/kubernetes-exercises/old/ingress-traefik/nginx-on-traefik-dashboard.png b/kubernetes-exercises/old/ingress-traefik/nginx-on-traefik-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..67e41cd6835df8b42aa803eca91ba16947348ddf GIT binary patch literal 158126 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 HUJU;a@KU2p literal 0 HcmV?d00001 diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-dashboard.png b/kubernetes-exercises/old/ingress-traefik/traefik-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..b44455ec8b3e58b87ade1e2a23031cba82ffda54 GIT binary patch literal 77188 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 literal 0 HcmV?d00001 diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-deployment.yaml b/kubernetes-exercises/old/ingress-traefik/traefik-deployment.yaml new file mode 100644 index 00000000..2aa0a386 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/traefik-deployment.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: traefik-ingress-controller + namespace: kube-system +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: traefik-ingress-controller + namespace: kube-system + labels: + k8s-app: traefik-ingress-lb +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: traefik-ingress-lb + template: + metadata: + labels: + k8s-app: traefik-ingress-lb + name: traefik-ingress-lb + spec: + serviceAccountName: traefik-ingress-controller + terminationGracePeriodSeconds: 60 + containers: + - image: traefik + name: traefik-ingress-lb + ports: + - name: http + containerPort: 80 + - name: admin + containerPort: 8080 + args: + - --api + - --kubernetes + - --logLevel=INFO +--- +kind: Service +apiVersion: v1 +metadata: + name: traefik-ingress-service + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - protocol: TCP + port: 80 + name: web + - protocol: TCP + port: 8080 + name: admin + type: LoadBalancer diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-ingress-controller.yml b/kubernetes-exercises/old/ingress-traefik/traefik-ingress-controller.yml new file mode 100644 index 00000000..6fc58b68 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/traefik-ingress-controller.yml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: traefik-ingress-controller + namespace: kube-system + labels: + k8s-app: traefik-ingress-lb +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: traefik-ingress-lb + template: + metadata: + labels: + k8s-app: traefik-ingress-lb + name: traefik-ingress-lb + spec: + terminationGracePeriodSeconds: 60 + containers: + - image: traefik + name: traefik-ingress-lb + resources: + limits: + cpu: 100m + memory: 100Mi + requests: + cpu: 20m + memory: 20Mi + ports: + - containerPort: 80 + hostPort: 80 + - containerPort: 8080 + args: + - --web + - --kubernetes diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml b/kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml new file mode 100644 index 00000000..7367dfda --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/traefik-rbac.yaml @@ -0,0 +1,38 @@ +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +rules: + - apiGroups: + - "" + resources: + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list + - watch +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: traefik-ingress-controller + namespace: kube-system + diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-service.yml b/kubernetes-exercises/old/ingress-traefik/traefik-service.yml new file mode 100644 index 00000000..221a89a3 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/traefik-service.yml @@ -0,0 +1,36 @@ +kind: Service +apiVersion: v1 +metadata: + name: traefik-ingress-service + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 80 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + - name: webui + protocol: TCP + port: 8080 + targetPort: 8080 + type: LoadBalancer +--- + +apiVersion: v1 +kind: Service +metadata: + name: traefik-web-ui + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - port: 80 + targetPort: 8080 + diff --git a/kubernetes-exercises/old/ingress-traefik/traefik-webui-ingress.yaml b/kubernetes-exercises/old/ingress-traefik/traefik-webui-ingress.yaml new file mode 100644 index 00000000..0fd011e6 --- /dev/null +++ b/kubernetes-exercises/old/ingress-traefik/traefik-webui-ingress.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: traefik-web-ui + namespace: kube-system +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - name: web + port: 80 + targetPort: 8080 +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: traefik-web-ui + namespace: kube-system +spec: + rules: + - host: traefik-ui.example.com + http: + paths: + - path: / + backend: + serviceName: traefik-web-ui + servicePort: web + diff --git a/kubernetes-exercises/old/secrets/Dockerfile b/kubernetes-exercises/old/secrets/Dockerfile new file mode 100644 index 00000000..908a1e94 --- /dev/null +++ b/kubernetes-exercises/old/secrets/Dockerfile @@ -0,0 +1,7 @@ +FROM node:9.1.0-alpine +EXPOSE 3000 +ENV LANGUAGE English +ENV API_KEY 123-456-789 +COPY secretapp.js . +ENTRYPOINT node secretapp.js + diff --git a/kubernetes-exercises/old/secrets/deployment.yml b/kubernetes-exercises/old/secrets/deployment.yml new file mode 100644 index 00000000..fc2eedd5 --- /dev/null +++ b/kubernetes-exercises/old/secrets/deployment.yml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: envtest +spec: + selector: + matchLabels: + name: envtest + replicas: 1 + template: + metadata: + labels: + name: envtest + spec: + containers: + - name: envtest + image: eficodeacademy/secrets-demo + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: LANGUAGE + value: Polish + - name: API_KEY + value: 333-444-555 diff --git a/kubernetes-exercises/old/secrets/final.deployment.yml b/kubernetes-exercises/old/secrets/final.deployment.yml new file mode 100644 index 00000000..7dbc5e09 --- /dev/null +++ b/kubernetes-exercises/old/secrets/final.deployment.yml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: envtest +spec: + selector: + matchLabels: + name: envtest + replicas: 1 + template: + metadata: + labels: + name: envtest + spec: + containers: + - name: envtest + image: eficodeacademy/secrets-demo + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: LANGUAGE + valueFrom: + configMapKeyRef: + name: language + key: LANGUAGE + - name: API_KEY + valueFrom: + secretKeyRef: + name: apikey + key: API_KEY diff --git a/kubernetes-exercises/old/secrets/secretapp.js b/kubernetes-exercises/old/secrets/secretapp.js new file mode 100644 index 00000000..7c420c62 --- /dev/null +++ b/kubernetes-exercises/old/secrets/secretapp.js @@ -0,0 +1,10 @@ +// Licensed under the Apache License, Version 2.0 (the "License") +var http = require('http'); +var server = http.createServer(function (request, response) { + const language = process.env.LANGUAGE; + const API_KEY = process.env.API_KEY; + response.write(`Language: ${language}\n`); + response.write(`API Key: ${API_KEY}\n`); + response.end(`\n`); +}); +server.listen(3000); diff --git a/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-deployment.yaml b/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-deployment.yaml new file mode 100644 index 00000000..f14358ec --- /dev/null +++ b/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: customnginx + name: customnginx +spec: + replicas: 4 + selector: + matchLabels: + app: customnginx + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: customnginx + spec: + containers: + - image: ghcr.io/eficode-academy/network-multitool + name: network-multitool + resources: {} +status: {} diff --git a/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-svc.yaml b/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-svc.yaml new file mode 100644 index 00000000..7380e7e5 --- /dev/null +++ b/kubernetes-exercises/old/service-discovery-loadbalancing/extra/multitool-svc.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: customnginx + name: customnginx +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: customnginx + type: LoadBalancer +status: + loadBalancer: {} diff --git a/kubernetes-exercises/old/service-discovery-loadbalancing/multitool-deployment.yaml b/kubernetes-exercises/old/service-discovery-loadbalancing/multitool-deployment.yaml new file mode 100644 index 00000000..f410c8fb --- /dev/null +++ b/kubernetes-exercises/old/service-discovery-loadbalancing/multitool-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: multitool + name: multitool +spec: + replicas: 1 + selector: + matchLabels: + app: multitool + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: multitool + spec: + containers: + - image: ghcr.io/eficode-academy/network-multitool + name: network-multitool + resources: {} +status: {} diff --git a/kubernetes-exercises/old/service-discovery-loadbalancing/nginx-deployment.yaml b/kubernetes-exercises/old/service-discovery-loadbalancing/nginx-deployment.yaml new file mode 100644 index 00000000..845e52d0 --- /dev/null +++ b/kubernetes-exercises/old/service-discovery-loadbalancing/nginx-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: nginx + name: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: nginx + spec: + containers: + - image: nginx:1.7.9 + name: nginx + resources: {} +status: {} diff --git a/kubernetes-exercises/old/support-files/generate-self-signed-certs.sh b/kubernetes-exercises/old/support-files/generate-self-signed-certs.sh new file mode 100755 index 00000000..8087996f --- /dev/null +++ b/kubernetes-exercises/old/support-files/generate-self-signed-certs.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# This script generates SSL certs (.crt and .key) to be used with nginx. +# The files are generated in .PEM format. + +echo "Generating self signed certificate ..." +openssl req \ + -x509 -newkey rsa:2048 -nodes -days 365 \ + -keyout tls.key -out tls.crt -subj '/CN=*.example.com' + +echo "...Done." + +#echo "Creating a combined PEM file out of the two certificate files ... (in case you need it later)" +#cat tls.crt tls.key > tls-cert-plus-key.pem +#echo +echo "Here are the generated certificate files:" +ls -1 tls.* + + + diff --git a/kubernetes-exercises/old/support-files/nginx-connectors.conf b/kubernetes-exercises/old/support-files/nginx-connectors.conf new file mode 100644 index 00000000..019b92fb --- /dev/null +++ b/kubernetes-exercises/old/support-files/nginx-connectors.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } +} + +server { + listen 443; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + ssl on; + ssl_certificate /certs/tls.crt; + ssl_certificate_key /certs/tls.key; +} + + diff --git a/kubernetes-exercises/old/support-files/nginx-persistent-storage.yaml b/kubernetes-exercises/old/support-files/nginx-persistent-storage.yaml new file mode 100644 index 00000000..5528bfeb --- /dev/null +++ b/kubernetes-exercises/old/support-files/nginx-persistent-storage.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + volumes: + - name: nginx-htmldir-volume + persistentVolumeClaim: + claimName: pvc-nginx + containers: + - name: nginx + image: nginx:1.9.1 + ports: + - containerPort: 443 + - containerPort: 80 + volumeMounts: + - mountPath: "/usr/share/nginx/html" + name: nginx-htmldir-volume + diff --git a/kubernetes-exercises/old/support-files/nginx-simple-deployment.yaml b/kubernetes-exercises/old/support-files/nginx-simple-deployment.yaml new file mode 100644 index 00000000..de05ea7a --- /dev/null +++ b/kubernetes-exercises/old/support-files/nginx-simple-deployment.yaml @@ -0,0 +1,23 @@ +# a comment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx # arbitrary label on deployment +spec: + replicas: 1 + selector: + matchLabels: # labels the replica selector should match + app: nginx + template: + metadata: + labels: + app: nginx # label for replica selector to match + version: 1.7.9 # arbitrary label we can match on elsewhere + spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80 diff --git a/kubernetes-exercises/old/support-files/nginx-ssl.yaml b/kubernetes-exercises/old/support-files/nginx-ssl.yaml new file mode 100644 index 00000000..97c86a68 --- /dev/null +++ b/kubernetes-exercises/old/support-files/nginx-ssl.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + volumes: + - name: certs-volume + secret: + secretName: nginx-certs + - name: config-volume + configMap: + name: nginx-config + containers: + - name: nginx + image: nginx:1.9.1 + ports: + - containerPort: 443 + - containerPort: 80 + volumeMounts: + - mountPath: /certs + name: certs-volume + - mountPath: /etc/nginx/conf.d + name: config-volume + diff --git a/kubernetes-exercises/old/support-files/pvc-nginx.yaml b/kubernetes-exercises/old/support-files/pvc-nginx.yaml new file mode 100644 index 00000000..2f4cbfef --- /dev/null +++ b/kubernetes-exercises/old/support-files/pvc-nginx.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-nginx + labels: + name: pvc-nginx + app: efk +spec: + # If you are using dynamic provisioning, it is important to specify a storageClassName. + # storageClassName: "standard" + #selector: + # matchLabels: + # name: pv-nginx + accessModes: + # Though accessmode is already defined in pv definition. It is still needed here. + # - ReadWriteMany + - ReadWriteOnce + resources: + requests: + storage: 5Gi + diff --git a/kubernetes-exercises/old/support-files/traefik-deployment.yaml b/kubernetes-exercises/old/support-files/traefik-deployment.yaml new file mode 100644 index 00000000..1495c091 --- /dev/null +++ b/kubernetes-exercises/old/support-files/traefik-deployment.yaml @@ -0,0 +1,40 @@ +kind: Deployment +# https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ +apiVersion: apps/v1 +metadata: + name: traefik-ingress-controller + namespace: default + labels: + k8s-app: traefik-ingress-lb +spec: + replicas: 1 + selector: + matchLabels: + k8s-app: traefik-ingress-lb + template: + metadata: + labels: + k8s-app: traefik-ingress-lb + name: traefik-ingress-lb + spec: + serviceAccountName: traefik-ingress-controller + # terminationGracePeriodSeconds: 60 + containers: + - image: traefik + name: traefik-ingress-lb + args: + - --web + - --kubernetes + volumeMounts: + - name: traefik-toml + mountPath: "/etc/traefik/" + - name: traefik-certs + mountPath: "/certs" + volumes: + - name: traefik-toml + configMap: + name: traefik-toml + - name: traefik-certs + secret: + secretName: "traefik-certs" + diff --git a/kubernetes-exercises/old/support-files/traefik-helm.values b/kubernetes-exercises/old/support-files/traefik-helm.values new file mode 100644 index 00000000..0dd3484b --- /dev/null +++ b/kubernetes-exercises/old/support-files/traefik-helm.values @@ -0,0 +1,96 @@ +## Default values for Traefik helm chart +image: traefik +imageTag: 1.5.2 +## can switch the service type to NodePort if required +serviceType: LoadBalancer +loadBalancerIP: +# loadBalancerSourceRanges: [] +replicas: 1 +# Resource limits with reasonable limits for CPU and memory. +# I noticed that the new Traefik instance does not take more than 30 MB even when in high load. +# Still gave it 48 MB for just in case. +cpuRequest: 100m +memoryRequest: 32Mi +cpuLimit: 200m +memoryLimit: 48Mi + +debug: + enabled: false +nodeSelector: {} + # key: value +tolerations: [] +# - key: "key" +# operator: "Equal|Exists" +# value: "value" +# effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" +ssl: + enabled: true + enforced: true + defaultCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVtekNDQTRPZ0F3SUJBZ0lKQUpBR1FsTW1DMGt5TUEwR0NTcUdTSWIzRFFFQkJRVUFNSUdQTVFzd0NRWUQKVlFRR0V3SlZVekVSTUE4R0ExVUVDQk1JUTI5c2IzSmhaRzh4RURBT0JnTlZCQWNUQjBKdmRXeGtaWEl4RkRBUwpCZ05WQkFvVEMwVjRZVzF3YkdWRGIzSndNUXN3Q1FZRFZRUUxFd0pKVkRFV01CUUdBMVVFQXhRTktpNWxlR0Z0CmNHeGxMbU52YlRFZ01CNEdDU3FHU0liM0RRRUpBUllSWVdSdGFXNUFaWGhoYlhCc1pTNWpiMjB3SGhjTk1UWXgKTURJME1qRXdPVFV5V2hjTk1UY3hNREkwTWpFd09UVXlXakNCanpFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVgpCQWdUQ0VOdmJHOXlZV1J2TVJBd0RnWURWUVFIRXdkQ2IzVnNaR1Z5TVJRd0VnWURWUVFLRXd0RmVHRnRjR3hsClEyOXljREVMTUFrR0ExVUVDeE1DU1ZReEZqQVVCZ05WQkFNVURTb3VaWGhoYlhCc1pTNWpiMjB4SURBZUJna3EKaGtpRzl3MEJDUUVXRVdGa2JXbHVRR1Y0WVcxd2JHVXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBdHVKOW13dzlCYXA2SDROdUhYTFB6d1NVZFppNGJyYTFkN1ZiRUJaWWZDSStZNjRDCjJ1dThwdTNhVTVzYXVNYkQ5N2pRYW95VzZHOThPUHJlV284b3lmbmRJY3RFcmxueGpxelUyVVRWN3FEVHk0bkEKNU9aZW9SZUxmZXFSeGxsSjE0VmlhNVFkZ3l3R0xoRTlqZy9jN2U0WUp6bmg5S1dZMnFjVnhEdUdEM2llaHNEbgphTnpWNFdGOWNJZm1zOHp3UHZPTk5MZnNBbXc3dUhUKzNiSzEzSUloeDI3ZmV2cXVWcENzNDFQNnBzdStWTG4yCjVIRHk0MXRoQkN3T0wrTithbGJ0ZktTcXM3TEFzM25RTjFsdHpITHZ5MGE1RGhkakpUd2tQclQrVXhwb0tCOUgKNFpZazErRUR0N09QbGh5bzM3NDFRaE4vSkNZK2RKbkFMQnNValFJREFRQUJvNEgzTUlIME1CMEdBMVVkRGdRVwpCQlJwZVc1dFhMdHh3TXJvQXM5d2RNbTUzVVVJTERDQnhBWURWUjBqQklHOE1JRzVnQlJwZVc1dFhMdHh3TXJvCkFzOXdkTW01M1VVSUxLR0JsYVNCa2pDQmp6RUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQWdUQ0VOdmJHOXkKWVdSdk1SQXdEZ1lEVlFRSEV3ZENiM1ZzWkdWeU1SUXdFZ1lEVlFRS0V3dEZlR0Z0Y0d4bFEyOXljREVMTUFrRwpBMVVFQ3hNQ1NWUXhGakFVQmdOVkJBTVVEU291WlhoaGJYQnNaUzVqYjIweElEQWVCZ2txaGtpRzl3MEJDUUVXCkVXRmtiV2x1UUdWNFlXMXdiR1V1WTI5dGdna0FrQVpDVXlZTFNUSXdEQVlEVlIwVEJBVXdBd0VCL3pBTkJna3EKaGtpRzl3MEJBUVVGQUFPQ0FRRUFjR1hNZms4TlpzQit0OUtCemwxRmw2eUlqRWtqSE8wUFZVbEVjU0QyQjRiNwpQeG5NT2pkbWdQcmF1SGI5dW5YRWFMN3p5QXFhRDZ0YlhXVTZSeENBbWdMYWpWSk5aSE93NDVOMGhyRGtXZ0I4CkV2WnRRNTZhbW13QzFxSWhBaUE2MzkwRDNDc2V4N2dMNm5KbzdrYnIxWVdVRzN6SXZveGR6OFlEclpOZVdLTEQKcFJ2V2VuMGxNYnBqSVJQNFhac25DNDVDOWdWWGRoM0xSZTErd3lRcTZoOVFQaWxveG1ENk5wRTlpbVRPbjJBNQovYkozVktJekFNdWRlVTZrcHlZbEpCemRHMXVhSFRqUU9Xb3NHaXdlQ0tWVVhGNlV0aXNWZGRyeFF0aDZFTnlXCnZJRnFhWng4NCtEbFNDYzkzeWZrL0dsQnQrU0tHNDZ6RUhNQjlocVBiQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + defaultKey: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdHVKOW13dzlCYXA2SDROdUhYTFB6d1NVZFppNGJyYTFkN1ZiRUJaWWZDSStZNjRDCjJ1dThwdTNhVTVzYXVNYkQ5N2pRYW95VzZHOThPUHJlV284b3lmbmRJY3RFcmxueGpxelUyVVRWN3FEVHk0bkEKNU9aZW9SZUxmZXFSeGxsSjE0VmlhNVFkZ3l3R0xoRTlqZy9jN2U0WUp6bmg5S1dZMnFjVnhEdUdEM2llaHNEbgphTnpWNFdGOWNJZm1zOHp3UHZPTk5MZnNBbXc3dUhUKzNiSzEzSUloeDI3ZmV2cXVWcENzNDFQNnBzdStWTG4yCjVIRHk0MXRoQkN3T0wrTithbGJ0ZktTcXM3TEFzM25RTjFsdHpITHZ5MGE1RGhkakpUd2tQclQrVXhwb0tCOUgKNFpZazErRUR0N09QbGh5bzM3NDFRaE4vSkNZK2RKbkFMQnNValFJREFRQUJBb0lCQUhrTHhka0dxNmtCWWQxVAp6MkU4YWFENnhneGpyY2JSdGFCcTc3L2hHbVhuQUdaWGVWcE81MG1SYW8wbHZ2VUgwaE0zUnZNTzVKOHBrdzNmCnRhWTQxT1dDTk1PMlYxb1MvQmZUK3Zsblh6V1hTemVQa0pXd2lIZVZMdVdEaVVMQVBHaWl4emF2RFMyUnlQRmEKeGVRdVNhdE5pTDBGeWJGMG5Zd3pST3ZoL2VSa2NKVnJRZlZudU1melFkOGgyMzZlb1UxU3B6UnhSNklubCs5UApNc1R2Wm5OQmY5d0FWcFo5c1NMMnB1V1g3SGNSMlVnem5oMDNZWUZJdGtDZndtbitEbEdva09YWHBVM282aWY5ClRIenBleHdubVJWSmFnRG85bTlQd2t4QXowOW80cXExdHJoU1g1U2p1K0xyNFJvOHg5bytXdUF1VnVwb0lHd0wKMWVseERFRUNnWUVBNzVaWGp1enNJR09PMkY5TStyYVFQcXMrRHZ2REpzQ3gyZnRudk1WWVJKcVliaGt6YnpsVQowSHBCVnk3NmE3WmF6Umxhd3RGZ3ljMlpyQThpM0F3K3J6d1pQclNJeWNieC9nUVduRzZlbFF1Y0FFVWdXODRNCkdSbXhKUGlmOGRQNUxsZXdRalFjUFJwZVoxMzlYODJreGRSSEdma1pscHlXQnFLajBTWExRSEVDZ1lFQXcybkEKbUVXdWQzZFJvam5zbnFOYjBlYXdFUFQrbzBjZ2RyaENQOTZQK1pEekNhcURUblZKV21PeWVxRlk1eVdSSEZOLwpzbEhXU2lTRUFjRXRYZys5aGlMc0RXdHVPdzhUZzYyN2VrOEh1UUtMb2tWWEFUWG1NZG9xOWRyQW9INU5hV2lECmRSY3dEU2EvamhIN3RZV1hKZDA4VkpUNlJJdU8vMVZpbDBtbEk5MENnWUVBb2lsNkhnMFNUV0hWWDNJeG9raEwKSFgrK1ExbjRYcFJ5VEg0eldydWY0TjlhYUxxNTY0QThmZGNodnFiWGJHeEN6U3RxR1E2cW1peUU1TVpoNjlxRgoyd21zZEpxeE14RnEzV2xhL0lxSzM0cTZEaHk3cUNld1hKVGRKNDc0Z3kvY0twZkRmeXZTS1RGZDBFejNvQTZLCmhqUUY0L2lNYnpxUStQREFQR0YrVHFFQ2dZQmQ1YnZncjJMMURzV1FJU3M4MHh3MDBSZDdIbTRaQVAxdGJuNk8KK0IvUWVNRC92UXBaTWV4c1hZbU9lV2Noc3FCMnJ2eW1MOEs3WDY1NnRWdGFYay9nVzNsM3ZVNTdYSFF4Q3RNUwpJMVYvcGVSNHRiN24yd0ZncFFlTm1XNkQ4QXk4Z0xiaUZhRkdRSDg5QWhFa0dTd1d5cWJKc2NoTUZZOUJ5OEtUCkZaVWZsUUtCZ0V3VzJkVUpOZEJMeXNycDhOTE1VbGt1ZnJxbllpUTNTQUhoNFZzWkg1TXU0MW55Yi95NUUyMW4KMk55d3ltWGRlb3VJcFZjcUlVTXl0L3FKRmhIcFJNeVEyWktPR0QyWG5YaENNVlRlL0FQNDJod294Nm02QkZpQgpvemZFa2wwak5uZmREcjZrL1p2MlQ1TnFzaWxaRXJBQlZGOTBKazdtUFBIa0Q2R1ZMUUJ4Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== +acme: + enabled: false + +dashboard: + enabled: true + domain: traefik.example.com + ingress: + annotations: + kubernetes.io/ingress.class: traefik + # labels: + # key: value + auth: + # basic: + # username: password + statistics: + ## Number of recent errors to show in the ‘Health’ tab + # recentErrors: +service: + # annotations: + # key: value + # labels: + # key: value + ## Further config for service of type NodePort + ## Default config with empty string "" will assign a dynamic + ## nodePort to http and https ports + nodePorts: + http: "" + https: "" + ## If static nodePort configuration is required it can be enabled as below + ## Configure ports in allowable range (eg. 30000 - 32767 on minikube) + # nodePorts: + # http: 30080 + # https: 30443 +gzip: + enabled: false +accessLogs: + enabled: true + ## Path to the access logs file. If not provided, Traefik defaults it to stdout. + # filePath: "" + format: common # choices are: common, json + ## Kubernetes ingress filters +kubernetes: +# List of namespaces disabled by Kamran (22 jan 2018) to allow access to all namespaces. +# namespaces: +# - default +# - production +# - kube-system + +# labelSelector: +rbac: + enabled: false +## Enable the /metrics endpoint, for now only supports prometheus +## set to true to enable metric collection by prometheus +metrics: + prometheus: + enabled: false + # buckets: 0.1,0.3,1.2,5 + datadog: + enabled: false + # address: "localhost:8125" + # pushInterval: 30s + statsd: + enabled: false + # address: "localhost:8125" + # pushInterval: 30s + diff --git a/kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml b/kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml new file mode 100644 index 00000000..c844a99b --- /dev/null +++ b/kubernetes-exercises/old/support-files/traefik-rbac-serviceaccount.yaml @@ -0,0 +1,38 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +rules: + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list + - watch +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: traefik-ingress-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: traefik-ingress-controller +subjects: +- kind: ServiceAccount + name: traefik-ingress-controller + namespace: kube-system + diff --git a/kubernetes-exercises/old/support-files/traefik-service.yaml b/kubernetes-exercises/old/support-files/traefik-service.yaml new file mode 100644 index 00000000..c3f71a6d --- /dev/null +++ b/kubernetes-exercises/old/support-files/traefik-service.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: traefik-ingress-controller + namespace: default +--- +kind: Service +apiVersion: v1 +metadata: + name: traefik-ingress-service + namespace: default +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 80 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + - name: webui + protocol: TCP + port: 8080 + targetPort: 8080 + type: LoadBalancer +--- +apiVersion: v1 +kind: Service +metadata: + name: traefik-web-ui + namespace: default +spec: + selector: + k8s-app: traefik-ingress-lb + ports: + - port: 80 + targetPort: 8080 +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: traefik-web-ui + namespace: default + annotations: + kubernetes.io/ingress.class: traefik +spec: + rules: + - host: traefik-ui.aws.wbitt.com + http: + paths: + - backend: + serviceName: traefik-web-ui + servicePort: 80 + + diff --git a/kubernetes-exercises/old/support-files/traefik.toml b/kubernetes-exercises/old/support-files/traefik.toml new file mode 100644 index 00000000..47987fdf --- /dev/null +++ b/kubernetes-exercises/old/support-files/traefik.toml @@ -0,0 +1,23 @@ +MaxIdleConnsPerHost = 200 +defaultEntryPoints = ["http", "https"] +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + entryPoint = "https" + [entryPoints.https] + address = ":443" + [entryPoints.https.tls] + [[entryPoints.https.tls.certificates]] + CertFile = "/certs/tls.crt" + KeyFile = "/certs/tls.key" + +[respondingTimeouts] + readTimeout = "0s" + writeTimeout = "0s" + idleTimeout = "0s" + logLevel = "ERROR" + +[kubernetes] + namespaces = ["default", "production", "staging"] + diff --git a/kubernetes-exercises/old/trainer/examples/health-check/README.md b/kubernetes-exercises/old/trainer/examples/health-check/README.md new file mode 100644 index 00000000..dab63fd8 --- /dev/null +++ b/kubernetes-exercises/old/trainer/examples/health-check/README.md @@ -0,0 +1,16 @@ +# Health and live probes + +Start `pod.yaml` + +Run three shells: + +1. `kubectl describe pod probing| grep -A20 Events` +2. `watch kubectl get pods -o wide` +3. `kubectl exec -ti probing sh` + +- in [3] try to delete /tmp/ready , and see that + the status in [2] changes. +- make the file again. +- in [3] try to delete the /tmp/health and see + that after the failureThreshold, the pod will be + restarted diff --git a/kubernetes-exercises/old/trainer/examples/health-check/pod.yaml b/kubernetes-exercises/old/trainer/examples/health-check/pod.yaml new file mode 100644 index 00000000..bd56dfba --- /dev/null +++ b/kubernetes-exercises/old/trainer/examples/health-check/pod.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + test: liveness + name: probing +spec: + containers: + - name: liveness + image: gcr.io/google_containers/busybox + args: + - /bin/sh + - -c + - touch /tmp/healthy; touch /tmp/ready; echo "ready"; sleep 6000 + livenessProbe: + exec: + command: + - cat + - /tmp/healthy + initialDelaySeconds: 10 + periodSeconds: 15 + failureThreshold: 1 + readinessProbe: + exec: + command: + - cat + - /tmp/ready + initialDelaySeconds: 5 + periodSeconds: 2 \ No newline at end of file diff --git a/kubernetes-exercises/old/trainer/examples/nextcloud/README.md b/kubernetes-exercises/old/trainer/examples/nextcloud/README.md new file mode 100644 index 00000000..18421613 --- /dev/null +++ b/kubernetes-exercises/old/trainer/examples/nextcloud/README.md @@ -0,0 +1,44 @@ +# Nextcloud docker-compose transformation showcase + +# download kompose + +```sh +curl -L https://github.com/kubernetes/kompose/releases/download/v1.22.0/kompose-linux-amd64 -o kompose +chmod +x kompose +sudo mv ./kompose /usr/local/bin/kompose +``` + +Convert the file + +```sh +kompose convert +``` + +examine the result. + +realize that the connection between + +## Additions + +- add `type:NodePort` to service +- add new service for app to talk to DB: + +```Yaml +apiVersion: v1 +kind: Service +metadata: + labels: + type: database + name: db +spec: + ports: + - name: "mysqlport" + port: 3306 + targetPort: 3306 + selector: + io.kompose.service: db + type: ClusterIP + +``` + +Go into the website at see that it is running diff --git a/kubernetes-exercises/old/trainer/examples/nextcloud/docker-compose.yml b/kubernetes-exercises/old/trainer/examples/nextcloud/docker-compose.yml new file mode 100644 index 00000000..ca5602ca --- /dev/null +++ b/kubernetes-exercises/old/trainer/examples/nextcloud/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3' +volumes: + nextcloud: + db: +services: + app: + image: nextcloud:17 + # image: nextcloud:18 + depends_on: + - db + environment: + MYSQL_PASSWORD: nextcloud + MYSQL_DATABASE: nextcloud + MYSQL_USER: root + MYSQL_HOST: db + ports: + - 80:80 + volumes: + - nextcloud:/var/www/html:rw + db: + image: mariadb:10.4.12-bionic + environment: + MYSQL_ROOT_PASSWORD: nextcloud + volumes: + - db:/var/lib/mysql:rw \ No newline at end of file diff --git a/kubernetes-exercises/persistent-storage.md b/kubernetes-exercises/persistent-storage.md new file mode 100644 index 00000000..ffdee4f1 --- /dev/null +++ b/kubernetes-exercises/persistent-storage.md @@ -0,0 +1,338 @@ +# Persistent Storage + +In this exercise you will learn how to persist the filesystem state of your containers using dynamic volume provisioning. + +## Learning Goals + +- How to attach a volume to container in a pod +- How to use dynamic volume provisioning to create persistent block storage to save the state of your applications +- How to create PersistentVolumes and use them with PersistentVolumeClaims + +## Introduction + +Kubernetes, a persistent volume claim (PVC) is a request for storage by a user. +It is a way for a pod to request a specific amount of storage from the cluster. When a PVC is created, it is automatically bound to a persistant volume (PV) that satisfies the PVC's requirements. + +A storage class (SC) is a way to define the properties of a PV. It is a blueprint for creating PVs, and it specifies things like the type of storage, the amount of storage, and the access modes for the PV. +The cluster then uses the storage class to find or create a PV that matches the PVC's requirements. + +In summary: + +- PVs are the actual storage resources in the cluster +- PVCs are requests for storage made by users +- SCs are the specifications for creating PVs. + +## Exercise + +### Overview + +- Observe that the state of the postgres database is not persisted between pod lifecycles +- Inspect the Available StorageClasses (sc) of the cluster +- Create a PersistentVolume (pv) using dynamic volume provisioning +- Consume the PersistentVolume using a PersistentVolumeClaim (pvc) and mounting the volume to a pod +- Delete pod with volume attached and observe that state is persisted when a new pod is created + +### Step by step instructions: + +
+ +Step by step: + + +### Observe that the state of the postgres database is not persisted between pod lifecycles + +Deploy the manifests located in `persistent-storage/start` + +> :bulb: If you have resources already deployed from a previous exercise, you might want to clean them up first. + +- Open the frontend webpage in your browser. +- Observe that the frontend reports that it is connected to the database. +- Add some quotes. We will need them later to test persistency +- Retrieve all of the quotes, observe that your quotes are part of the retrieved quotes. +- Now delete the postgres pod using `kubectl delete pod ` +- In the frontend webpage, retrieve quotes, and observe that you now only get the default 5 quotes from the database. + +What we have observed here is that the state of our database is not persisted between pod lifecycles! +In order to fix this, we need to persist the filesystem state of our database container to a `volume`. + +### Inspect the Available StorageClasses (sc) of the cluster + +Use `kubectl` to get the available `StorageClasses` in the cluster, the shortname for `StorageClass` is `sc`: + +``` +kubectl get StorageClasses +``` + +Expected output: + +``` +AME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 54m +``` + +We see that we indeed have a `StorageClass` available and ready for use! + +
+ +:bulb: What do the columns mean? + +The output of the `kubectl get sc` command provides some useful information about the StorageClass: + +- `PROVISIONNER` what is the underlying storage provider, in this case `AWS EBS` (Elastic Block Storage) +- `RECLAIMPOLICY` what will happen with the volume when the `PersistentVolume` resource is deleted, in this case `Delete` will delete the block storage. +- `VOLUMEBINDINGMODE` specifies how to provision the actual volume, `WaitForFirstConsumer` will provision the actual volume object once there is a matching claim. +- `ALLOWVOLUMEEXPANSION` defines whether a volume can be expanded in size at a later point in time. + +
+ +### Create a PersistentVolume (pv) using dynamic volume provisioning + +Let's create a `PersistentVolume` (pv)! + +While we could create a manifest for a `PersistentVolume` manually we will not do that in this exercise. + +In practice we will almost always create a `PersistentVolume` by creating `PersistentVolumeClaim`, which uses a `StorageClass` to create the actual volume. + +Create a new file `persistent-storage/start/postgres-pvc.yaml` + +Copy and paste the below boilerplate yaml to the new file: + +```yaml +apiVersion: +kind: +metadata: + name: +spec: + storageClassName: + accessModes: + - + resources: + requests: + storage: +``` + +Next we fill in the values: + +- The `apiVersion` should be `v1` +- The `kind` is `PersistentVolumeClaim` +- The `metadata.name` should be `postgres-pvc` +- From the previous section we know that we have one available `StorageClass`, so the value of `spec.storageClassName` is the name of that, in this case `"gp2"` (with quotes) +- The `spec.accessModes` list should contain one item with the value `ReadWriteOnce` +- the `spec.resources.requests.storage` is the size of the volume in Gibibytes (Gi), set it to `5Gi` + +
+The finished manifest should look like this + +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + storageClassName: "gp2" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +``` + +
+ +Apply your new `PersistenVolumeClaim` with `kubectl apply`: + +``` +kubectl apply -f persistent-storage/start/postgres-pvc.yaml +``` + +Expected output: + +``` +persistentvolumeclaim/postgres-pvc created +``` + +Check that the `PersistenVolumeClaim` was created using `kubectl get`: + +``` +kubectl get persistentvolumeclaim +``` + +Expected output: + +``` +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +postgres-pvc Pending gp2 3m19s +``` + +Check if a `PersistentVolume` was created using `kubectl get`: + +``` +kubectl get persistentvolume +``` + +Expected output + +``` +No resources found +``` + +> :bulb: `PersistentVolumes` objects are `cluster-wide`, ie. "not-namespaced", so you might see `PersistentVolumes` belonging to other users. + +We expect that a PersistentVolume has not been created _yet._ + +As we can see in the `kubectl get persistentvolumeclaim` output above, our `PersistenVolumeClaim` is in the `Pending` status. + +This is because the `VOLUMEBINDINGMODE` of the StorageClass is set to `WaitForFirstConsumer`, as we saw in the previous section. + +`WaitForFirstConsumer` will not create the actual volume object until it is used by a pod. + +> :bulb: The reason you might not want to not always create volumes as soon as `pvc` objects are created is to reduce costs, by not creating resources that are not used before they are attached to a pod. + +Let's attach the PersistenVolumeClaim to our postgres pod! + +### Consume the PersistentVolume using a PersistentVolumeClaim (pvc) and mounting the volume to a pod + +Open the postgres deployment manifest in your text editor `persistent-storage/start/postgres-deployment.yaml`. + +In the `spec.template.spec` add the following section: + +```yaml + ... + spec: + volumes: + - name: + persistentVolumeClaim: + claimName: + ... +``` + +Add the values to the snippet: + +- `spec.template.spec.volumes[0].name` is the name we will reference when we mount the volume to a container in a moment. + set it to `postgres-pvc` +- `spec.template.spec.volumes[0].persistentVolumeClaim.claimName` is the `name` of the `PersistenVolumeClaim` we have created above, set it to the name you used, e.g. `postgres-pvc` + +> :bulb: In this case the volume name and reference to the `pvc` name are the same, this is coincidental, and they can be different. + +
+How the finished manifest should look + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + ... +spec: + ... + template: + metadata: + ... + spec: + volumes: + - name: postgres-pvc # name we can reference below in container + persistentVolumeClaim: + claimName: postgres-pvc # name of the actual pvc + containers: + ... +``` + +
+ +Next we mount the volume we have defined to the postgres container: + +In the deployment manifest file, add the following section to the postgres container spec, e.g. `spec.template.spec.containers[0].volumeMounts` + +```yaml +volumeMounts: + - name: + mountPath: + subPath: +``` + +Fill in the values: + +- `name` should be the name we specified above when we declared the available volumes. + In this case this should be `postgres-pvc` +- `mountPath` is the path in container to mount the volume to. For postgres, the database state is stored to the path `/var/lib/postgresql/data` +- `subPath` should be `postgres`, and specifies a directory to be created within the volume, we need this because of a quirk with combining `AWS EBS` with Postgres. + (If you are curios why: https://stackoverflow.com/a/51174380) + +
+The finished manifest should look like this + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + ... +spec: + ... + template: + metadata: + ... + spec: + volumes: + - name: postgres-pvc # name we can reference below in container + persistentVolumeClaim: + claimName: postgres-pvc # name of the actual pvc + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ... + env: + ... + volumeMounts: + - name: postgres-pvc + mountPath: /var/lib/postgresql/data + subPath: postgres +``` + +
+ +Apply the changes to the postgres deployment using `kubectl apply`: + +``` +kubectl apply -f persistent-storage/start/postgres-deployment +``` + +Expected output: + +``` +deployment.apps/postgres configured +``` + +Observe that the `PersistentVolume` is now created: + +``` +kubectl get persistentvolumeclaims,persistentvolumes +``` + +Expected output: + +``` +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +persistentvolumeclaim/postgres-pvc Bound pvc-d60a8787-330e-4b34-96d9-c2ad4dc18dbc 5Gi RWO gp2 38m + +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +persistentvolume/pvc-d60a8787-330e-4b34-96d9-c2ad4dc18dbc 5Gi RWO Delete Bound default/postgres-pvc gp2 4m10s +``` + +### Delete pod with volume attached and observe that state is persisted when a new pod is created + +Now that the state of our postgres database is persisted to the volume, let's verify: + +- Open the frontend webpage and add some quotes +- Retrieve quotes from the database, and observe that your quotes are among them +- Delete the database pod with `kubectl delete pod ` +- Wait for the postgres pod to be recreated (you can watch for pod changes with `kubectl get pods --watch`) +- In the frontend webpage, retrieve quotes and obeserve that your quotes are among them + +
+ +### Clean up + +Delete all resources created using `kubectl delete -f ` + +``` +kubectl delete -f persistent-storage/start +``` diff --git a/kubernetes-exercises/persistent-storage/done/backend-configmap.yaml b/kubernetes-exercises/persistent-storage/done/backend-configmap.yaml new file mode 100644 index 00000000..2913abf1 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/backend-configmap.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-config +data: + # all data points are handled as strings + backend_host: "backend" + backend_port: "5000" diff --git a/kubernetes-exercises/persistent-storage/done/backend-deployment.yaml b/kubernetes-exercises/persistent-storage/done/backend-deployment.yaml new file mode 100644 index 00000000..ab5233d8 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/backend-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + ports: + - containerPort: 5000 + resources: {} + envFrom: + - configMapRef: + name: postgres-config + - secretRef: + name: postgres-secret +status: {} diff --git a/kubernetes-exercises/persistent-storage/done/backend-service.yaml b/kubernetes-exercises/persistent-storage/done/backend-service.yaml new file mode 100644 index 00000000..a15c2352 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/backend-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP diff --git a/kubernetes-exercises/persistent-storage/done/frontend-deployment.yaml b/kubernetes-exercises/persistent-storage/done/frontend-deployment.yaml new file mode 100644 index 00000000..24dd293a --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/frontend-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + ports: + - containerPort: 5000 + resources: {} + env: + - name: BACKEND_HOST + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_host + - name: BACKEND_PORT + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_port + +status: {} diff --git a/kubernetes-exercises/persistent-storage/done/frontend-service.yaml b/kubernetes-exercises/persistent-storage/done/frontend-service.yaml new file mode 100644 index 00000000..86c78a5f --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/frontend-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort diff --git a/kubernetes-exercises/persistent-storage/done/postgres-configmap.yaml b/kubernetes-exercises/persistent-storage/done/postgres-configmap.yaml new file mode 100644 index 00000000..d3635057 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/postgres-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + DB_HOST: postgres + DB_PORT: '5432' + DB_USER: superuser + DB_NAME: quotes \ No newline at end of file diff --git a/kubernetes-exercises/persistent-storage/done/postgres-deployment.yaml b/kubernetes-exercises/persistent-storage/done/postgres-deployment.yaml new file mode 100644 index 00000000..577b6f6f --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/postgres-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: postgres + spec: + volumes: + - name: postgres-pvc # name we can reference below in container + persistentVolumeClaim: + claimName: postgres-pvc # name of the actual pvc + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ports: + - containerPort: 5432 + resources: {} + env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: DB_PASSWORD + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_NAME + volumeMounts: + - name: postgres-pvc + mountPath: /var/lib/postgresql/data + subPath: postgres +status: {} diff --git a/kubernetes-exercises/persistent-storage/done/postgres-pvc.yaml b/kubernetes-exercises/persistent-storage/done/postgres-pvc.yaml new file mode 100644 index 00000000..98d94e4c --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/postgres-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + # default. Use `kubectl get sc` too what storage classes are configured + storageClassName: "gp2" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi diff --git a/kubernetes-exercises/persistent-storage/done/postgres-secret.yaml b/kubernetes-exercises/persistent-storage/done/postgres-secret.yaml new file mode 100644 index 00000000..063f08ab --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/postgres-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +kind: Secret +metadata: + creationTimestamp: + name: postgres-secret diff --git a/kubernetes-exercises/persistent-storage/done/postgres-service.yaml b/kubernetes-exercises/persistent-storage/done/postgres-service.yaml new file mode 100644 index 00000000..797aa462 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/done/postgres-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: postgres + type: ClusterIP diff --git a/kubernetes-exercises/persistent-storage/start/backend-configmap.yaml b/kubernetes-exercises/persistent-storage/start/backend-configmap.yaml new file mode 100644 index 00000000..2913abf1 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/backend-configmap.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-config +data: + # all data points are handled as strings + backend_host: "backend" + backend_port: "5000" diff --git a/kubernetes-exercises/persistent-storage/start/backend-deployment.yaml b/kubernetes-exercises/persistent-storage/start/backend-deployment.yaml new file mode 100644 index 00000000..ab5233d8 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/backend-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + ports: + - containerPort: 5000 + resources: {} + envFrom: + - configMapRef: + name: postgres-config + - secretRef: + name: postgres-secret +status: {} diff --git a/kubernetes-exercises/persistent-storage/start/backend-service.yaml b/kubernetes-exercises/persistent-storage/start/backend-service.yaml new file mode 100644 index 00000000..8f99215f --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/backend-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/persistent-storage/start/frontend-deployment.yaml b/kubernetes-exercises/persistent-storage/start/frontend-deployment.yaml new file mode 100644 index 00000000..24dd293a --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/frontend-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + ports: + - containerPort: 5000 + resources: {} + env: + - name: BACKEND_HOST + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_host + - name: BACKEND_PORT + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_port + +status: {} diff --git a/kubernetes-exercises/persistent-storage/start/frontend-service.yaml b/kubernetes-exercises/persistent-storage/start/frontend-service.yaml new file mode 100644 index 00000000..86c78a5f --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/frontend-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort diff --git a/kubernetes-exercises/persistent-storage/start/postgres-configmap.yaml b/kubernetes-exercises/persistent-storage/start/postgres-configmap.yaml new file mode 100644 index 00000000..d3635057 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/postgres-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + DB_HOST: postgres + DB_PORT: '5432' + DB_USER: superuser + DB_NAME: quotes \ No newline at end of file diff --git a/kubernetes-exercises/persistent-storage/start/postgres-deployment.yaml b/kubernetes-exercises/persistent-storage/start/postgres-deployment.yaml new file mode 100644 index 00000000..fa5c66e5 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/postgres-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: postgres + spec: + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ports: + - containerPort: 5432 + resources: {} + env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: DB_PASSWORD + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_NAME +status: {} diff --git a/kubernetes-exercises/persistent-storage/start/postgres-secret.yaml b/kubernetes-exercises/persistent-storage/start/postgres-secret.yaml new file mode 100644 index 00000000..063f08ab --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/postgres-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +kind: Secret +metadata: + creationTimestamp: + name: postgres-secret diff --git a/kubernetes-exercises/persistent-storage/start/postgres-service.yaml b/kubernetes-exercises/persistent-storage/start/postgres-service.yaml new file mode 100644 index 00000000..797aa462 --- /dev/null +++ b/kubernetes-exercises/persistent-storage/start/postgres-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: postgres + type: ClusterIP diff --git a/kubernetes-exercises/quotes-flask/backend-configmap.yaml b/kubernetes-exercises/quotes-flask/backend-configmap.yaml new file mode 100644 index 00000000..2913abf1 --- /dev/null +++ b/kubernetes-exercises/quotes-flask/backend-configmap.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-config +data: + # all data points are handled as strings + backend_host: "backend" + backend_port: "5000" diff --git a/kubernetes-exercises/quotes-flask/backend-deployment.yaml b/kubernetes-exercises/quotes-flask/backend-deployment.yaml new file mode 100644 index 00000000..ab5233d8 --- /dev/null +++ b/kubernetes-exercises/quotes-flask/backend-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + ports: + - containerPort: 5000 + resources: {} + envFrom: + - configMapRef: + name: postgres-config + - secretRef: + name: postgres-secret +status: {} diff --git a/kubernetes-exercises/quotes-flask/backend-service.yaml b/kubernetes-exercises/quotes-flask/backend-service.yaml new file mode 100644 index 00000000..05a3d44f --- /dev/null +++ b/kubernetes-exercises/quotes-flask/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP +status: + loadBalancer: {} diff --git a/kubernetes-exercises/quotes-flask/frontend-deployment.yaml b/kubernetes-exercises/quotes-flask/frontend-deployment.yaml new file mode 100644 index 00000000..05463ce0 --- /dev/null +++ b/kubernetes-exercises/quotes-flask/frontend-deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + ports: + - containerPort: 5000 + resources: {} + env: + - name: BACKEND_HOST + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_host + - name: BACKEND_PORT + valueFrom: + configMapKeyRef: + name: backend-config + key: backend_port + diff --git a/kubernetes-exercises/quotes-flask/frontend-service.yaml b/kubernetes-exercises/quotes-flask/frontend-service.yaml new file mode 100644 index 00000000..86c78a5f --- /dev/null +++ b/kubernetes-exercises/quotes-flask/frontend-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort diff --git a/kubernetes-exercises/quotes-flask/postgres-configmap.yaml b/kubernetes-exercises/quotes-flask/postgres-configmap.yaml new file mode 100644 index 00000000..d3635057 --- /dev/null +++ b/kubernetes-exercises/quotes-flask/postgres-configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-config +data: + DB_HOST: postgres + DB_PORT: '5432' + DB_USER: superuser + DB_NAME: quotes \ No newline at end of file diff --git a/kubernetes-exercises/quotes-flask/postgres-deployment.yaml b/kubernetes-exercises/quotes-flask/postgres-deployment.yaml new file mode 100644 index 00000000..577b6f6f --- /dev/null +++ b/kubernetes-exercises/quotes-flask/postgres-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: postgres + spec: + volumes: + - name: postgres-pvc # name we can reference below in container + persistentVolumeClaim: + claimName: postgres-pvc # name of the actual pvc + containers: + - image: docker.io/library/postgres:14.3 + name: postgres + ports: + - containerPort: 5432 + resources: {} + env: + - name: POSTGRES_USER + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: DB_PASSWORD + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: DB_NAME + volumeMounts: + - name: postgres-pvc + mountPath: /var/lib/postgresql/data + subPath: postgres +status: {} diff --git a/kubernetes-exercises/quotes-flask/postgres-pvc.yaml b/kubernetes-exercises/quotes-flask/postgres-pvc.yaml new file mode 100644 index 00000000..98d94e4c --- /dev/null +++ b/kubernetes-exercises/quotes-flask/postgres-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc +spec: + # default. Use `kubectl get sc` too what storage classes are configured + storageClassName: "gp2" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi diff --git a/kubernetes-exercises/quotes-flask/postgres-secret.yaml b/kubernetes-exercises/quotes-flask/postgres-secret.yaml new file mode 100644 index 00000000..063f08ab --- /dev/null +++ b/kubernetes-exercises/quotes-flask/postgres-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + DB_PASSWORD: Y29tcGxpY2F0ZWQ= +kind: Secret +metadata: + creationTimestamp: + name: postgres-secret diff --git a/kubernetes-exercises/quotes-flask/postgres-service.yaml b/kubernetes-exercises/quotes-flask/postgres-service.yaml new file mode 100644 index 00000000..797aa462 --- /dev/null +++ b/kubernetes-exercises/quotes-flask/postgres-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: postgres + name: postgres +spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: postgres + type: ClusterIP diff --git a/kubernetes-exercises/rolling-updates.md b/kubernetes-exercises/rolling-updates.md new file mode 100644 index 00000000..fe280206 --- /dev/null +++ b/kubernetes-exercises/rolling-updates.md @@ -0,0 +1,153 @@ +# Rolling Updates + +## Learning Goals + +- Learn about how to update deployments +- Learn about how to test resiliency of your deployment +- Learn about how to control the rollout process with `maxSurge` and `maxUnavailable` + +## Introduction + +In this exercise you'll learn about how to update a deployment. + +### maxSurge and maxUnavailable parameters + +The `maxSurge` and `maxUnavailable` parameters control how many pods can be created above the desired number of pods and how many pods can be unavailable during the update. + +- `maxSurge` is the maximum number of pods that can be created above the desired number of pods. It can be a number or a percentage. The default value is 25%. + +- `maxUnavailable` is the maximum number of pods that can be unavailable during the update. It can be a number or a percentage. The default value is 25%. + +### Rolling update + +A rolling update is a deployment strategy that allows you to update your application without downtime. It works by creating a new version of the application and then slowly replacing the old version with the new one. + +## Exercise + +### Overview + +- Roll out new version of either the frontend or backend and see the changes +- Roll out a new version that does not exist +- Change the `maxSurge` and `maxUnavailable` parameters and see how it affects the rollout + +### Step by step instructions + +
+ +Step by step: + + +- Create backend deployment + +> :bulb: All files for the exercise are found in the `rolling-updates/start` folder. + +- Look at the backend deployment files `backend-deployment.yaml` and `frontend-deployment.yaml` +- Look at the service files in `backend-svc.yaml` and `frontend-svc.yaml` + +Now go ahead and `apply` the deployments and the services: + +- `kubectl apply -f .`. This will apply all the files in the current directory + +* Access the frontend by the NodePort service + +
+:bulb: How is it that you do that? + +- Find the service with `kubectl get services` command. + +- Note down the port number for the frontend service. In this case it is `31941` + +- Get the nodes EXTERNAL-IP address. Run `kubectl get nodes -o wide`. + +Copy the external IP address of any one of the nodes, for example, `34.244.123.152` and paste it in your browser. + +Copy the port from your frontend service that looks something like `31941` and paste it next to your IP in the browser, for example, `34.244.123.152:31941` and hit it. + +
+ +## Update Deployment + +Now we will try to roll out an update to the backend image. + +- Change the image tag from `release` to `2.0.0`: + +```yaml + ... + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:2.0.0 +``` + +- Apply the new version of your deployment. + +- Check the rollout status: `kubectl rollout status deployment backend` + +Expected output: + +``` +Waiting for deployment "backend" rollout to finish: 1 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 1 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 1 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 2 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 2 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 2 out of 3 new replicas have been updated... +Waiting for deployment "backend" rollout to finish: 1 old replicas are pending termination... +Waiting for deployment "backend" rollout to finish: 1 old replicas are pending termination... +deployment "backend" successfully rolled out +``` + +It might be that you only see the last line, as the rollout is very fast. + +- Check the version of the backend image in the browser + +- Try rolling out other image version while looking at the frontend. You can do it by repeating the commands from above. Suggested image versions are `1.0.0` and `3.0.0`. + +- Try also rolling out a version that does not exist: + +```yaml +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:not-a-version + name: backend +``` + +What happened - do the frontend still work? And are you able to see the backend version in the browser? + +- Investigate the running pods with: `kubectl get pods` + +What happens to the pods that are running the old version? + +- Reset back to a version that exists. + +## maxSurge and maxUnavailable + +We will now try to control the rollout process a bit more by setting `maxSurge` and `maxUnavailable` parameters. + +- open up two terminals and run `kubectl get pods --watch` in one of them + +- Add the `maxSurge` and `maxUnavailable` parameters in the deployment file `backend-deployment.yaml`: + +```yaml +spec: + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +``` + +- Change the image tag to `3.0.0` and apply the changes + +- Check the rollout process in the first terminal + +- Change the `maxSurge` and `maxUnavailable` parameters and see how it affects the rollout. Try to set `maxSurge` and `maxUnavailable` both to 100%. What happens? + +
+ +## Clean up + +Delete deployments and services as follow: + +- `kubectl delete -f .` + +Congratulations! You have now learned how to update a deployment and how to control the rollout process with `maxSurge` and `maxUnavailable` parameters. diff --git a/kubernetes-exercises/rolling-updates/done/backend-deployment.yaml b/kubernetes-exercises/rolling-updates/done/backend-deployment.yaml new file mode 100644 index 00000000..73d83ddd --- /dev/null +++ b/kubernetes-exercises/rolling-updates/done/backend-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: backend + name: backend +spec: + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend diff --git a/kubernetes-exercises/rolling-updates/done/backend-svc.yaml b/kubernetes-exercises/rolling-updates/done/backend-svc.yaml new file mode 100644 index 00000000..cf716ac8 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/done/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/rolling-updates/done/frontend-deployment.yaml b/kubernetes-exercises/rolling-updates/done/frontend-deployment.yaml new file mode 100644 index 00000000..0517a4c2 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/done/frontend-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + env: + - name: BACKEND_URL + value: http://backend + - name: BACKEND_PORT + value: "5000" \ No newline at end of file diff --git a/kubernetes-exercises/rolling-updates/done/frontend-svc.yaml b/kubernetes-exercises/rolling-updates/done/frontend-svc.yaml new file mode 100644 index 00000000..24900d61 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/done/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/rolling-updates/extra/nginx-deployment.yaml b/kubernetes-exercises/rolling-updates/extra/nginx-deployment.yaml new file mode 100644 index 00000000..93736188 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/extra/nginx-deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: nginx + name: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: nginx + spec: + containers: + - image: nginx:1.7 + name: nginx + resources: {} +status: {} diff --git a/kubernetes-exercises/rolling-updates/extra/nginx-svc.yaml b/kubernetes-exercises/rolling-updates/extra/nginx-svc.yaml new file mode 100644 index 00000000..bf075981 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/extra/nginx-svc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: nginx + name: nginx +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: nginx + type: NodePort diff --git a/kubernetes-exercises/rolling-updates/start/backend-deployment.yaml b/kubernetes-exercises/rolling-updates/start/backend-deployment.yaml new file mode 100644 index 00000000..1f8eb304 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/start/backend-deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: backend + name: backend +spec: + replicas: 3 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend diff --git a/kubernetes-exercises/rolling-updates/start/backend-svc.yaml b/kubernetes-exercises/rolling-updates/start/backend-svc.yaml new file mode 100644 index 00000000..cf716ac8 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/start/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/rolling-updates/start/frontend-deployment.yaml b/kubernetes-exercises/rolling-updates/start/frontend-deployment.yaml new file mode 100644 index 00000000..26e3ce00 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/start/frontend-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: frontend + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: quotes-flask-frontend + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" \ No newline at end of file diff --git a/kubernetes-exercises/rolling-updates/start/frontend-svc.yaml b/kubernetes-exercises/rolling-updates/start/frontend-svc.yaml new file mode 100644 index 00000000..24900d61 --- /dev/null +++ b/kubernetes-exercises/rolling-updates/start/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/README.md b/kubernetes-exercises/scenarios/README.md new file mode 100644 index 00000000..ca8c8b4e --- /dev/null +++ b/kubernetes-exercises/scenarios/README.md @@ -0,0 +1,143 @@ + + + +### 3. Persistent Volume Issues + +#### Overview +Applications might face issues when trying to mount persistent volumes, which can be due to misconfigurations or underlying storage issues. + +#### Tasks +- **Creating the Problem**: + 1. Create a persistent volume with incorrect access modes. + 2. Create a persistent volume claim that references the volume. + 3. Create a pod that mounts the persistent volume claim. + 4. Apply all configurations and observe the pod failing to start. + +- **Fixing the Problem**: + 1. Update the persistent volume to have the correct access modes. + 2. Apply the updated configuration: `kubectl apply -f persistent-volume.yaml`. + 3. Delete and recreate the pod to attempt mounting the volume again. + +--- + +### 4. Resource Limitations + +#### Overview +Setting appropriate resource requests and limits is crucial to ensure that your applications have the resources they need to run effectively. + +#### Tasks +- **Creating the Problem**: + 1. Create a pod with very low resource limits. + 2. Apply the configuration: `kubectl apply -f pod-definition.yaml`. + 3. Observe that the pod might be evicted or fail to run effectively. + +- **Fixing the Problem**: + 1. Update the pod definition to have more reasonable resource limits. + 2. Apply the updated configuration: `kubectl apply -f pod-definition.yaml`. + 3. Observe that the pod is now running effectively. + +--- + +### 5. Security and RBAC Issues + +#### Overview +Role-Based Access Control (RBAC) in Kubernetes helps in defining what actions users or applications can perform. Misconfigurations can lead to access issues. + +#### Tasks +- **Creating the Problem**: + 1. Create a Role and RoleBinding (or ClusterRole and ClusterRoleBinding) with very limited permissions. + 2. Try to perform an action that requires more permissions and observe the failure. + +- **Fixing the Problem**: + 1. Update the Role (or ClusterRole) to include the necessary permissions. + 2. Apply the updated configuration: `kubectl apply -f role-definition.yaml`. + 3. Retest the action and observe that it now succeeds. + +--- + +### 6. ConfigMap and Secret Issues + +#### Overview +Applications might fail if they cannot access the necessary configuration data or secrets. + +#### Tasks +- **Creating the Problem**: + 1. Create a ConfigMap or Secret with incorrect data. + 2. Mount the ConfigMap or Secret in a pod. + 3. Apply all configurations and observe the application failing. + +- **Fixing the Problem**: + 1. Update the ConfigMap or Secret with the correct data. + 2. Apply the updated configuration: `kubectl apply -f configmap-or-secret.yaml`. + 3. Delete and recreate the pod to mount the updated ConfigMap or Secret. + +--- + +### 7. Upgrade Issues + +#### Overview +Upgrading Kubernetes or applications can sometimes lead to issues if not done carefully. + +#### Tasks +- **Creating the Problem**: + 1. Upgrade your Kubernetes cluster or application without checking compatibility. + 2. Observe any issues that arise post-upgrade. + +- **Fixing the Problem**: + 1. Roll back to the previous version if necessary. + 2. Check compatibility and perform any required pre-upgrade steps. + 3. Retry the upgrade. + +--- + +### 8. High Availability and Failover + +#### Overview +Ensuring high availability and seamless failover is crucial for production applications. + +#### Tasks +- **Creating the Problem**: + 1. Set up an application with a single replica. + 2. Simulate a node failure or delete the pod and observe downtime. + +- **Fixing the Problem**: + 1. Update the deployment to use multiple replicas spread across different nodes. + 2. Apply the updated configuration: `kubectl apply -f deployment.yaml`. + 3. Retest node failure or pod deletion and observe reduced downtime. + +--- + +### 9. Monitoring and Logging + +#### Overview +Proper monitoring and logging are essential for troubleshooting and maintaining the health of your applications. + +#### Tasks +- **Creating the Problem**: + 1. Set up an application without any monitoring or logging solutions in place. + 2. Try to troubleshoot an issue without sufficient logs or metrics. + +- **Fixing the Problem**: + 1. Set up and configure a monitoring and logging solution such as Prometheus, Grafana, and ELK stack. + 2. Ensure logs and metrics are being collected. + 3. Retest troubleshooting with the available logs and metrics. + +--- + +### 10. Resource Leaks and Orphaned Resources + +#### Overview +Over time, Kubernetes clusters might accumulate unused or orphaned resources, leading to resource wastage. + +#### Tasks +- **Creating the Problem**: + 1. Create and delete numerous resources without cleaning up associated resources. + 2. Observe the accumulation of orphaned resources. + +- **Fixing the Problem**: + 1. Identify and manually delete orphaned resources. + 2. Use tools like `kubectl-prune` to automate the cleanup of unused resources. + +--- + +Each section provides a basic guide on how to create a specific problem in a Kubernetes environment and steps to resolve it. Adjustments may be needed based on the specific Kubernetes setup and application configurations. \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/connectivity/README.md b/kubernetes-exercises/scenarios/connectivity/README.md new file mode 100644 index 00000000..1ff7b77a --- /dev/null +++ b/kubernetes-exercises/scenarios/connectivity/README.md @@ -0,0 +1,38 @@ +### Service Connectivity Issues + + +#### Overview +Services in Kubernetes provide a way for pods to communicate with each other. Misconfigurations or network policies can lead to connectivity issues. + +In this scenario, we have an `nginx` deployment that has a service assosiated with it. The goal is to get connectivity between the `probe` pod and the `nginx` pod through the service. + +#### Tasks +- **Creating the Problem**: + 1. run `bash setup.sh` + 1. Execute into the `probe` pod and see that you can connect to the nginx deployment through the service with a curl call: `curl nginx-service:80` + +- **Fixing the Problem**: + +
+ Hint +It is clear that the connection is not working. +Try to describe the service and see if you can find the problem. +
+ +
+ Hint + +A service describes it's endpoints under the `Endpoints` section. Is there any there? + +
+ + Hint + +The service is not assosiated with any pods, because the selector does not match any labels. +Make sure that the labels on both service and deployment/pod are the same. + + + +### Clean up + +Remove the pod by executing `kubectl delete -f .` in the folder \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/connectivity/deployment.yaml b/kubernetes-exercises/scenarios/connectivity/deployment.yaml new file mode 100644 index 00000000..ddab10bf --- /dev/null +++ b/kubernetes-exercises/scenarios/connectivity/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: nginx + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/kubernetes-exercises/scenarios/connectivity/pod.yaml b/kubernetes-exercises/scenarios/connectivity/pod.yaml new file mode 100644 index 00000000..b14dbe21 --- /dev/null +++ b/kubernetes-exercises/scenarios/connectivity/pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: probe + labels: + name: probe +spec: + containers: + - name: probe + image: ghcr.io/eficode-academy/network-multitool + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/kubernetes-exercises/scenarios/connectivity/service.yaml b/kubernetes-exercises/scenarios/connectivity/service.yaml new file mode 100644 index 00000000..21870d57 --- /dev/null +++ b/kubernetes-exercises/scenarios/connectivity/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx-service +spec: + selector: + app: app + ports: + - port: 80 + targetPort: 80 diff --git a/kubernetes-exercises/scenarios/connectivity/setup.sh b/kubernetes-exercises/scenarios/connectivity/setup.sh new file mode 100644 index 00000000..0b9eece0 --- /dev/null +++ b/kubernetes-exercises/scenarios/connectivity/setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# apply the configuration to your Kubernetes cluster +kubectl apply -f . + +echo "setup completed" diff --git a/kubernetes-exercises/scenarios/limits/README.md b/kubernetes-exercises/scenarios/limits/README.md new file mode 100644 index 00000000..b9b018eb --- /dev/null +++ b/kubernetes-exercises/scenarios/limits/README.md @@ -0,0 +1,24 @@ +### 3. Limits + +#### Overview +Apps should have boundaries on what type of resources they can take up + +#### Tasks +- **Creating the Problem**: + 1. run `bash setup.sh` + 1. Observe that the pod is failing to start. + +- **Fixing the Problem**: + +
+ Hint + +`kubectl get pods` will show you +
+ +
+ + +### Clean up + +Remove the pod by executing `kubectl delete -f .` in the folder \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/limits/pod.yaml b/kubernetes-exercises/scenarios/limits/pod.yaml new file mode 100644 index 00000000..7f7dcba4 --- /dev/null +++ b/kubernetes-exercises/scenarios/limits/pod.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: mysql-pod +spec: + containers: + - name: mysql + image: mysql:5.7 + env: + - name: MYSQL_ROOT_PASSWORD + value: "my-secret-pw" + ports: + - containerPort: 3306 + volumeMounts: + - mountPath: /var/lib/mysql + name: my-volume + resources: + limits: + memory: "128Mi" + cpu: "500m" + volumes: + - name: my-volume + persistentVolumeClaim: + claimName: my-pvc diff --git a/kubernetes-exercises/scenarios/limits/setup.sh b/kubernetes-exercises/scenarios/limits/setup.sh new file mode 100644 index 00000000..0b9eece0 --- /dev/null +++ b/kubernetes-exercises/scenarios/limits/setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# apply the configuration to your Kubernetes cluster +kubectl apply -f . + +echo "setup completed" diff --git a/kubernetes-exercises/scenarios/persistency/README.md b/kubernetes-exercises/scenarios/persistency/README.md new file mode 100644 index 00000000..b4504e5f --- /dev/null +++ b/kubernetes-exercises/scenarios/persistency/README.md @@ -0,0 +1,37 @@ +### 3. Persistency Issues + +#### Overview +Applications might face issues when trying to mount persistent volumes, which can be due to misconfigurations or underlying storage issues. + +Note that this scenario only works on AWS clusters, as persistency is very cloud provider specific. + +#### Tasks +- **Creating the Problem**: + 1. run `bash setup.sh` + 1. Observe that the pod is failing to start. + +- **Fixing the Problem**: + +
+ Hint + +`kubectl get pods` will show you +
+ +
+ Hint + +A service describes it's endpoints under the `Endpoints` section. Is there any there? + +
+ + Hint + +The service is not assosiated with any pods, because the selector does not match any labels. +Make sure that the labels on both service and deployment/pod are the same. + +
+ +### Clean up + +Remove the pod by executing `kubectl delete -f .` in the folder \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/persistency/pod.yaml b/kubernetes-exercises/scenarios/persistency/pod.yaml new file mode 100644 index 00000000..7e7262c9 --- /dev/null +++ b/kubernetes-exercises/scenarios/persistency/pod.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: mysql-pod +spec: + containers: + - name: mysql + image: mysql:5.7 + env: + - name: MYSQL_ROOT_PASSWORD + value: "my-secret-pw" + ports: + - containerPort: 3306 + volumeMounts: + - mountPath: /var/lib/mysql + name: my-volume + resources: + limits: + memory: "512Mi" + cpu: "500m" + volumes: + - name: my-volume + persistentVolumeClaim: + claimName: my-pvc diff --git a/kubernetes-exercises/scenarios/persistency/pvc.yaml b/kubernetes-exercises/scenarios/persistency/pvc.yaml new file mode 100644 index 00000000..7ac5cd8f --- /dev/null +++ b/kubernetes-exercises/scenarios/persistency/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: my-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: standard diff --git a/kubernetes-exercises/scenarios/persistency/setup.sh b/kubernetes-exercises/scenarios/persistency/setup.sh new file mode 100644 index 00000000..0b9eece0 --- /dev/null +++ b/kubernetes-exercises/scenarios/persistency/setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# apply the configuration to your Kubernetes cluster +kubectl apply -f . + +echo "setup completed" diff --git a/kubernetes-exercises/scenarios/pod-not-starting/README.md b/kubernetes-exercises/scenarios/pod-not-starting/README.md new file mode 100644 index 00000000..fb4d579c --- /dev/null +++ b/kubernetes-exercises/scenarios/pod-not-starting/README.md @@ -0,0 +1,26 @@ +### 1. Pods Not Starting + +#### Overview +Sometimes pods in Kubernetes fail to start due to various reasons such as image pull errors, resource constraints, or configuration issues. Identifying and resolving the root cause is crucial to ensure the smooth operation of your applications. + +#### Tasks +- **Creating the Problem**: + 1. run `bash setup.sh` + +- **Fixing the Problem**: + 1. Observe the pod status: `kubectl get pods`. + 1. Describe the pod to see the detailed error message: `kubectl describe pod `. + 1. Fix the problem + +
+ Hint +Try to see the avaliable tags for [Nginx](https://hub.docker.com/_/nginx). +Correct the image in your pod definition. +
+ + 1. Apply the updated configuration: `kubectl apply -f pod-definition.yaml`. + 1. Check that the pod is now running: `kubectl get pods`. + +### Clean up + +Remove the pod by executing `kubectl delete -f .` in the folder \ No newline at end of file diff --git a/kubernetes-exercises/scenarios/pod-not-starting/pod-definition.yaml b/kubernetes-exercises/scenarios/pod-not-starting/pod-definition.yaml new file mode 100644 index 00000000..8e4f1752 --- /dev/null +++ b/kubernetes-exercises/scenarios/pod-not-starting/pod-definition.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Pod +metadata: + name: my-pod +spec: + containers: + - name: my-container + image: nginx:2.0 diff --git a/kubernetes-exercises/scenarios/pod-not-starting/setup.sh b/kubernetes-exercises/scenarios/pod-not-starting/setup.sh new file mode 100644 index 00000000..b12ff7ea --- /dev/null +++ b/kubernetes-exercises/scenarios/pod-not-starting/setup.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# create pod definition with non-existent image or incorrect image name +cat < pod-definition.yaml +apiVersion: v1 +kind: Pod +metadata: + name: my-pod +spec: + containers: + - name: my-container + image: nginx:2.0 +EOF + +# apply the configuration to your Kubernetes cluster +kubectl apply -f pod-definition.yaml + +echo "setup completed" diff --git a/kubernetes-exercises/services.md b/kubernetes-exercises/services.md new file mode 100644 index 00000000..a1dcc039 --- /dev/null +++ b/kubernetes-exercises/services.md @@ -0,0 +1,368 @@ +# Services + +## Learning Goals + +- Communicate between pods internally in the cluster. +- Reach pods from outside the cluster. +- Understand service discovery. + +## Introduction + +In this exercise you'll learn about how pods can be exposed using services and test connectivity between them. + +## Service Discovery & Services + +One of the features of Kubernetes is that we do not have to care on which machine our pods are running. +This does create an interesting problem for us - if we don't know where (the IP address) the pod is, how can we route traffic to it? +This is solved by what is called **service discovery**, as the words imply, Kubernetes will look for your pods, and dynamically route traffic to them. + +This is achieved using the Kubernetes kind `service`, which is a network abstraction for service discovery (and more!). + +The `service` receives a static IP address that will not change throughout the life cycle of the service, so while the pod IP addresses might change, the `service` will not. + +Service discovery works in Kubernetes by putting `labels` on pods, and then using a `selector` in the `service` to match the same labels. +This is handled by the Kubernetes API, which means that we only need to know the labels of the pods we want to send traffic to, and Kubernetes will take of the rest! + +The `service` then routes traffic the pods that it selects, you can think of `service` as a kind of proxy - you route traffic to the service, and the service routes the traffic to your pods. + +While the `service` gets a static IP address we actually prefer not use it, because Kubernetes actually runs its own DNS server in the cluster network, and every time we create a `service` a DNS record is created that points to the `service` IP address. +The DNS record is always the `name` of the service, and can be referenced either from the same namespace by using the name or from a different namespace by using the long form: `..svc.cluster.local`. + +
+ +Example + + +An Example `pod` with labels + +```yaml +apiVersion: v1 +kind: Pod +metadata: + labels: + app: frontend # <-- These labels are selected by the service + environment: dev + name: frontend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: frontend + resources: {} +``` + +An example `service` that selects the labels of the pod + +```yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: frontend # <-- The service selects pods that have this list of labels + environment: dev + type: ClusterIP +``` + +
+ +## `service` Types + +Since Kubernetes can recreate pods at any time, it is not a good idea to rely on the IP address of a pod. +Instead, we can create a service that will expose the pod(s) to the outside world. +The service IP address will not change, even if the pod(s) it is exposing are destroyed and recreated. +It will only change if the service is deleted and recreated. + +The service also load balances traffic between the pods it is exposing if there are more than one pod. + +The service finds the pods it is exposing by using labels. The service will expose all pods that have the labels specified in the `selector` part of the service. + +### `service` Manifest + +A generic service manifest file looks like this: + +```yaml +apiVersion: v1 +kind: Service +metadata: + labels: # list of labels for this service + name: # Service name +spec: + ports: # Ports to expose + - port: + protocol: # TCP or UDP + targetPort: # Pod port to route to + selector: # List of labels to match pods + type: # ClusterIP, NodePort or LoadBalancer +``` + +Here is an example `service` manifest: + +```yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + app: backend + type: ClusterIP +``` + +Services come in three types: `ClusterIP`, `NodePort` and `LoadBalancer`. + +### Service type: ClusterIP + +The `ClusterIP` service type is for routing internal network traffic between pods in the cluster. + +We can use either the static IP address or the DNS name to route traffic between pods. + +We prefer to use the DNS name, because we know what it will be _before_ the service is created and receives a random static IP address. + +The `ClusterIP` service type does not allow for any communication from clients outside the cluster. + +
+ :bulb: More about ClusterIP + +The service type ClusterIP does not have any external IP. This means it is not accessible over the internet, but we can still access it from within the cluster using its `CLUSTER-IP`. + +- The IPs assigned to services as Cluster-IP are from a different Kubernetes network called _Service Network_, which is a completely different network altogether. i.e. it is not connected (nor related) to pod-network or the infrastructure network. Technically it is actually not a real network per-se; it is a labeling system, which is used by Kube-proxy on each node to setup correct iptables rules. (This is an advanced topic, and not our focus right now). +- No matter what type of service you choose while _exposing_ your pod, Cluster-IP is always assigned to that particular service. +- Every service has end-points, which point to the actual pod serving as a backend of a particular service. +- As soon as a service is created, and is assigned a Cluster-IP, an entry is made in Kubernetes' internal DNS against that service, with this service name and the Cluster-IP. e.g. `backend.default.svc.cluster.local` would point to Cluster-IP `172.20.114.230` . + +
+ +### Service type: NodePort + +Services of type `NodePort` have all of the functionality of `ClusterIP` services, but add more functionality: it will also open up a port on each node in the cluster, which will route traffic to the service. + +For example a `NodePort` service might open port `32593` on all nodes, and route traffic from this port to the service. + +So now, if we know the IP of our nodes (and they are externally accessible), we can access this service from the internet. + +### Other types + +There are other types of services, like `LoadBalancer`, but we won't cover them in this exercise. + +If you want to know more about Services, you can read more about them [here](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types). + +> :bulb: Hint: You can use the `kubectl explain` command to get more information about the fields in the yaml file. For example, `kubectl explain service.spec` will give you more information about the spec field in the service yaml file. + +## Exercise + +In this exercise you will start both the frontend and backend quotes-flask pods. + +### Overview + +- Apply both frontend and backend pods +- Create backend service with type ClusterIP +- Exec into frontend pod, reach backend through service DNS name +- Create frontend service with type NodePort +- Access it from the nodes IP address + +:bulb: If you get stuck somewhere along the way, you can check the solution in the done folder. + +### Step by step instructions + +
+ +Step by step: + + +- Go into the `services/start` directory. +- Apply the `backend-pod.yaml` & `frontend-pod.yaml` files. + +
+:bulb: Hint + +You can use the `kubectl apply -f ` command to deploy the pod. +The pod is defined in the `backend-pod.yaml` file. +Hint: the apply command can take more than one `-f` parameter to apply more than one yaml file + +
+ +- Check that the pods are running with `kubectl get pods` command. + +You should see something like this: + +``` +NAME READY STATUS RESTARTS AGE +pod/backend 1/1 Running 0 28s +pod/frontend 1/1 Running 0 20s +``` + +Now that we have the pods running, we can create a service that will expose the backend pod to the cluster network, so we will create a service of type `ClusterIP`. + +- Open the `backend-svc.yaml` file and fill in the missing parts. +- apiVersion and kind are already filled in for you. +- Metadata section should have the name `backend` and a label with key `run` and value `backend`. +- Spec section should have a port with port `5000`, protocol `TCP` and targetPort `5000`. +- Selector section should have a label with key `run` and value `backend`. +- Type should be `ClusterIP`. + +> :bulb: If you get stuck somewhere along the way, you can check the solution in the done folder. + +- Apply backend-svc.yaml that you just created. `kubectl apply -f backend-svc.yaml` + +- Check that the service is created with `kubectl get services` command. + +You should see something like this: + +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/backend ClusterIP 172.20.114.230 5000/TCP 23s +``` + +- Exec into frontend pod + `kubectl exec -it frontend -- bash` + +You should see something like this: + +``` +root@frontend:/app# +``` + +Make sure that you are inside a pod and not in your terminal window. + +- Try to reach backend pod through backend service `Cluster-IP` from within your frontend pod + +```sh +curl 172.20.114.230:5000 +``` + +You should see something like this: + +``` +Hello from the backend! +``` + +- Try accessing the service using dns name now + +```sh +curl backend:5000 +``` + +You should see the same output as above. + +You can type `exit` or press `Ctrl-d` to exit from your container. + +- Next we create the service file for the frontend with type `NodePort`. + +- While we can write manifests by hand, we can also use some tricks to generate boilerplate manifests: For example we can use the `kubectl expose` command to create a service from a pod or deployment. + +> For example, `kubectl expose pod frontend --type=NodePort --port=5000` will create a service for the frontend pod with type NodePort and port 5000. +> We can then use Unix shell pipes (`>`) to direct the output of the command to a file, e.g. ` > ` +> We run `kubectl expose` with the arguments `--dry-run=client -o yaml` to only perform the operation locally without sending the result to the server, and formatting the output as `yaml`. + +- Create the frontend service manifest with the command above: `kubectl expose pod frontend --type=NodePort --port=5000 -o yaml --dry-run=client > frontend-svc.yaml` + +- Apply frontend-svc.yaml that you just created. + +- Check that the service is created with `kubectl get services` command. + +You should see something like this: + +``` +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +frontend NodePort 10.106.136.250 5000:31941/TCP 23s +service/backend ClusterIP 172.20.114.230 5000/TCP 23s +``` + +- Note down the port number for the frontend service. In this case it is `31941` (yours will be different). + +- Get the nodes IP address. Run `kubectl get nodes -o wide`. + +You should see something like this: + +``` +NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +ip-10-0-33-234.eu-west-1.compute.internal Ready 152m v1.23.9-eks-ba74326 10.0.33.234 54.194.220.73 Amazon Linux 2 +5.4.219-126.411.amzn2.x86_64 docker://20.10.17 +ip-10-0-38-95.eu-west-1.compute.internal Ready 152m v1.23.9-eks-ba74326 10.0.38.95 34.244.123.152 Amazon Linux 2 +5.4.219-126.411.amzn2.x86_64 docker://20.10.17 +ip-10-0-57-206.eu-west-1.compute.internal Ready 152m v1.23.9-eks-ba74326 10.0.57.206 34.242.240.121 Amazon Linux 2 +5.4.219-126.411.amzn2.x86_64 docker://20.10.17 +ip-10-0-62-15.eu-west-1.compute.internal Ready 152m v1.23.9-eks-ba74326 10.0.62.15 54.246.17.102 Amazon Linux 2 +5.4.219-126.411.amzn2.x86_64 docker://20.10.17 +``` + +Copy the external IP address of any one of the nodes, for example, `34.244.123.152` and paste it in your browser. + +Copy the port from your frontend service that looks something like `31941` and paste it after to your IP in the browser, separated by a colon (`:`), for example `34.244.123.152:31941` and load the page. + +Alternatively, you could also test it using curl from your terminal window. + +```sh +curl 34.244.123.152:31941 | grep h1 +``` + +You should see something like this: + +``` + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 3051 100 3051 0 0 576k 0 --:--:-- --:--:-- --:--:-- 595k +

Programming Quotes

+``` + +
+ +:bulb: Food for thought + + +Think about why you didn't need to exec into a pod to test frontend service but needed it to test the backend service. + +
+ +
+ +### Clean up + +Delete the pods and services with `kubectl delete pod frontend backend` and `kubectl delete service frontend backend` commands. + +You have successfully tested connectivity between frontend and backend pods using services. + +### Extra: Filter on the basis of labels + +
+ +Optional extra exercise + + +To filter the output of `kubectl get pods` based on a `label`, you can use the `--selector` flag followed by the label key and value. +For example, to filter the pods based on a label with the key foo and the value bar, you would run the following command: + +`kubectl get pods --selector=foo=bar` + +This will return a list of all the pods that have a label with the key foo and the value bar. + +You can use the != operator to specify that you want to exclude resources with a particular label value. +For example, to filter the pods based on a label with the key foo but exclude those with the value bar, you would run the following command: + +`kubectl get pods --selector=foo!=bar` + +Try to apply the manifests again and write four commands that does the following: + +- List only the pods with the label app=frontend +- List only the pods with the label app=backend +- List only the pods where label app is not frontend +- List only the pods where label app is not backend + +The documentation on this can be found here: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + +Remember to clean up after you are done. + +
diff --git a/kubernetes-exercises/services/done/backend-pod.yaml b/kubernetes-exercises/services/done/backend-pod.yaml new file mode 100644 index 00000000..cc6ab36b --- /dev/null +++ b/kubernetes-exercises/services/done/backend-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: backend + name: backend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + resources: {} + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/services/done/backend-svc.yaml b/kubernetes-exercises/services/done/backend-svc.yaml new file mode 100644 index 00000000..a5b57c43 --- /dev/null +++ b/kubernetes-exercises/services/done/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + run: backend + name: backend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: backend + type: ClusterIP \ No newline at end of file diff --git a/kubernetes-exercises/services/done/frontend-pod.yaml b/kubernetes-exercises/services/done/frontend-pod.yaml new file mode 100644 index 00000000..f5499ec3 --- /dev/null +++ b/kubernetes-exercises/services/done/frontend-pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: frontend + name: frontend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: frontend + resources: {} + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/services/done/frontend-svc.yaml b/kubernetes-exercises/services/done/frontend-svc.yaml new file mode 100644 index 00000000..9f36b0fc --- /dev/null +++ b/kubernetes-exercises/services/done/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + run: frontend + name: frontend +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + selector: + run: frontend + type: NodePort \ No newline at end of file diff --git a/kubernetes-exercises/services/start/backend-pod.yaml b/kubernetes-exercises/services/start/backend-pod.yaml new file mode 100644 index 00000000..cc6ab36b --- /dev/null +++ b/kubernetes-exercises/services/start/backend-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: backend + name: backend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-backend:release + name: quotes-flask-backend + resources: {} + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/services/start/backend-svc.yaml b/kubernetes-exercises/services/start/backend-svc.yaml new file mode 100644 index 00000000..6b6f656c --- /dev/null +++ b/kubernetes-exercises/services/start/backend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + run: + name: +spec: + ports: + - port: + protocol: + targetPort: + selector: + run: + type: \ No newline at end of file diff --git a/kubernetes-exercises/services/start/frontend-pod.yaml b/kubernetes-exercises/services/start/frontend-pod.yaml new file mode 100644 index 00000000..f5499ec3 --- /dev/null +++ b/kubernetes-exercises/services/start/frontend-pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: frontend + name: frontend +spec: + containers: + - image: ghcr.io/eficode-academy/quotes-flask-frontend:release + name: frontend + resources: {} + env: + - name: BACKEND_HOST + value: backend + - name: BACKEND_PORT + value: "5000" + restartPolicy: Always \ No newline at end of file diff --git a/kubernetes-exercises/services/start/frontend-svc.yaml b/kubernetes-exercises/services/start/frontend-svc.yaml new file mode 100644 index 00000000..e0d96781 --- /dev/null +++ b/kubernetes-exercises/services/start/frontend-svc.yaml @@ -0,0 +1,14 @@ +apiVersion: +kind: +metadata: + labels: + run: + name: +spec: + ports: + - port: + protocol: + targetPort: + selector: + run: + type: \ No newline at end of file diff --git a/kubernetes-exercises/trainer-notes.md b/kubernetes-exercises/trainer-notes.md new file mode 100644 index 00000000..0533666b --- /dev/null +++ b/kubernetes-exercises/trainer-notes.md @@ -0,0 +1,5 @@ +# Kubernetes cluster + +# Writing and CI + +Automated tests can extract Kubernetes YAML from markdown files and automatically lint them. For this to work, annotate your multi-line code blocks with both `yaml` and `k8s`. Your multi-line code block will have as its first line ````yaml,k8s`. diff --git a/kubernetes-exercises/trainer/quotes-flask-rbac.yaml b/kubernetes-exercises/trainer/quotes-flask-rbac.yaml new file mode 100644 index 00000000..712982b1 --- /dev/null +++ b/kubernetes-exercises/trainer/quotes-flask-rbac.yaml @@ -0,0 +1,26 @@ +# adds a Role and Rolebinding for a namespace, which allows the quotes-flask frontend to list pods in it's namespace +# you must change the 'default' namespace to the namespace you are using, e.g. 'studentX' +--- +# Documentation: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pods-list +rules: + - apiGroups: [""] + resources: [pods] + verbs: [list] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pods-list +subjects: + - kind: ServiceAccount + name: default + namespace: default +roleRef: + kind: Role + name: pods-list + apiGroup: rbac.authorization.k8s.io