diff --git a/k8s/haproxy.yaml b/k8s/haproxy.yaml index 174ba9ad..bbad7ff6 100644 --- a/k8s/haproxy.yaml +++ b/k8s/haproxy.yaml @@ -9,7 +9,7 @@ spec: name: haproxy containers: - name: haproxy - image: haproxy + image: haproxy:1 volumeMounts: - name: config mountPath: /usr/local/etc/haproxy/ diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index a40f3dff..19a7a427 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -1,13 +1,13 @@ apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: - name: kibana + name: whatever spec: rules: - - host: kibana.185.145.251.54.nip.io + - host: whatever.A.B.C.D.nip.io http: paths: - path: / backend: - serviceName: kibana - servicePort: 5601 + serviceName: whatever + servicePort: 1234 diff --git a/k8s/nginx-1-without-volume.yaml b/k8s/nginx-1-without-volume.yaml new file mode 100644 index 00000000..d9f4a0d1 --- /dev/null +++ b/k8s/nginx-1-without-volume.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-without-volume +spec: + containers: + - name: nginx + image: nginx diff --git a/k8s/nginx-2-with-volume.yaml b/k8s/nginx-2-with-volume.yaml new file mode 100644 index 00000000..c7e0cfc6 --- /dev/null +++ b/k8s/nginx-2-with-volume.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-with-volume +spec: + volumes: + - name: www + containers: + - name: nginx + image: nginx + volumeMounts: + - name: www + mountPath: /usr/share/nginx/html/ diff --git a/k8s/nginx-with-volume.yaml b/k8s/nginx-3-with-git.yaml similarity index 94% rename from k8s/nginx-with-volume.yaml rename to k8s/nginx-3-with-git.yaml index 7ad56441..c0ed9c96 100644 --- a/k8s/nginx-with-volume.yaml +++ b/k8s/nginx-3-with-git.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Pod metadata: - name: nginx-with-volume + name: nginx-with-git spec: volumes: - name: www diff --git a/k8s/nginx-4-with-init.yaml b/k8s/nginx-4-with-init.yaml new file mode 100644 index 00000000..d41ecd7a --- /dev/null +++ b/k8s/nginx-4-with-init.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx-with-init +spec: + volumes: + - name: www + containers: + - name: nginx + image: nginx + volumeMounts: + - name: www + mountPath: /usr/share/nginx/html/ + initContainers: + - name: git + image: alpine + command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ] + volumeMounts: + - name: www + mountPath: /www/ diff --git a/slides/k8s/rollout.md b/slides/k8s/rollout.md index 41aba22d..eb777c83 100644 --- a/slides/k8s/rollout.md +++ b/slides/k8s/rollout.md @@ -14,7 +14,27 @@ ## Rolling updates -- With rolling updates, when a resource is updated, it happens progressively +- With rolling updates, when a Deployment is updated, it happens progressively + +- The Deployment controls multiple Replica Sets + +- Each Replica Set is a group of identical Pods + + (with the same image, arguments, parameters ...) + +- During the rolling update, we have at least two Replica Sets: + + - the "new" set (corresponding to the "target" version) + + - at least one "old" set + +- We can have multiple "old" sets + + (if we start another update before the first one is done) + +--- + +## Update strategy - Two parameters determine the pace of the rollout: `maxUnavailable` and `maxSurge` @@ -225,6 +245,137 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide. --- +## Rolling back to an older version + +- We reverted to `v0.2` + +- But this version still has a performance problem + +- How can we get back to the previous version? + +--- + +## Multiple "undos" + +- What happens if we try `kubectl rollout undo` again? + +.exercise[ + +- Try it: + ```bash + kubectl rollout undo deployment worker + ``` + +- Check the web UI, the list of pods ... + +] + +🤔 That didn't work. + +--- + +## Multiple "undos" don't work + +- If we see successive versions as a stack: + + - `kubectl rollout undo` doesn't "pop" the last element from the stack + + - it copies the N-1th element to the top + +- Multiple "undos" just swap back and forth between the last two versions! + +.exercise[ + +- Go back to v0.2 again: + ```bash + kubectl rollout undo deployment worker + ``` + +] + +--- + +## In this specific scenario + +- Our version numbers are easy to guess + +- What if we had used git hashes? + +- What if we had changed other parameters in the Pod spec? + +--- + +## Listing versions + +- We can list successive versions of a Deployment with `kubectl rollout history` + +.exercise[ + +- Look at our successive versions: + ```bash + kubectl rollout history deployment worker + ``` + +] + +We don't see *all* revisions. + +We might see something like 1, 4, 5. + +(Depending on how many "undos" we did before.) + +--- + +## Explaining deployment revisions + +- These revisions correspond to our Replica Sets + +- This information is stored in the Replica Set annotations + +.exercise[ + +- Check the annotations for our replica sets: + ```bash + kubectl describe replicasets -l app=worker | grep -A3 + ``` + +] + +--- + +class: extra-details + +## What about the missing revisions? + +- The missing revisions are stored in another annotation: + + `deployment.kubernetes.io/revision-history` + +- These are not shown in `kubectl rollout history` + +- We could easily reconstruct the full list with a script + + (if we wanted to!) + +--- + +## Rolling back to an older version + +- `kubectl rollout undo` can work with a revision number + +.exercise[ + +- Roll back to the "known good" deployment version: + ```bash + kubectl rollout undo deployment worker --to-revision=1 + ``` + +- Check the web UI or the list of pods + +] + +--- + class: extra-details ## Changing rollout parameters diff --git a/slides/k8s/volumes.md b/slides/k8s/volumes.md index 58b6849a..ef41b4cb 100644 --- a/slides/k8s/volumes.md +++ b/slides/k8s/volumes.md @@ -66,7 +66,87 @@ class: extra-details --- -## A simple volume example +## Adding a volume to a Pod + +- We will start with the simplest Pod manifest we can find + +- We will add a volume to that Pod manifest + +- We will mount that volume in a container in the Pod + +- By default, this volume will be an `emptyDir` + + (an empty directory) + +- It will "shadow" the directory where it's mounted + +--- + +## Our basic Pod + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-without-volume +spec: + containers: + - name: nginx + image: nginx +``` + +This is a MVP! (Minimum Viable Pod😉) + +It runs a single NGINX container. + +--- + +## Trying the basic pod + +.exercise[ + +- Create the Pod: + ```bash + kubectl create -f ~/container.training/k8s/nginx-1-without-volume.yaml + ``` + +- Get its IP address: + ```bash + IPADDR=$(kubectl get pod nginx-without-volume -o jsonpath={.status.podIP}) + ``` + +- Send a request with curl: + ```bash + curl $IPADDR + ``` + +] + +(We should see the "Welcome to NGINX" page.) + +--- + +## Adding a volume + +- We need to add the volume in two places: + + - at the Pod level (to declare the volume) + + - at the container level (to mount the volume) + +- We will declare a volume named `www` + +- No type is specified, so it will default to `emptyDir` + + (as the name implies, it will be initialized as an empty directory at pod creation) + +- In that pod, there is also a container named `nginx` + +- That container mounts the volume `www` to path `/usr/share/nginx/html/` + +--- + +## The Pod with a volume ```yaml apiVersion: v1 @@ -86,30 +166,57 @@ spec: --- -## A simple volume example, explained +## Trying the Pod with a volume -- We define a standalone `Pod` named `nginx-with-volume` +.exercise[ -- In that pod, there is a volume named `www` +- Create the Pod: + ```bash + kubectl create -f ~/container.training/k8s/nginx-2-with-volume.yaml + ``` -- No type is specified, so it will default to `emptyDir` +- Get its IP address: + ```bash + IPADDR=$(kubectl get pod nginx-with-volume -o jsonpath={.status.podIP}) + ``` - (as the name implies, it will be initialized as an empty directory at pod creation) +- Send a request with curl: + ```bash + curl $IPADDR + ``` -- In that pod, there is also a container named `nginx` +] -- That container mounts the volume `www` to path `/usr/share/nginx/html/` +(We should now see a "403 Forbidden" error page.) --- -## A volume shared between two containers +## Populating the volume with another container + +- Let's add another container to the Pod + +- Let's mount the volume in *both* containers + +- That container will populate the volume with static files + +- NGINX will then serve these static files + +- To populate the volume, we will clone the Spoon-Knife repository + + - this repository is https://github.com/octocat/Spoon-Knife + + - it's very popular (more than 100K stars!) + +--- + +## Sharing a volume between two containers .small[ ```yaml apiVersion: v1 kind: Pod metadata: - name: nginx-with-volume + name: nginx-with-git spec: volumes: - name: www @@ -147,30 +254,72 @@ spec: --- -## Sharing a volume, in action +## Trying the shared volume -- Let's try it! +- This one will be time-sensitive! + +- We need to catch the Pod IP address *as soon as it's created* + +- Then send a request to it *as fast as possible* .exercise[ -- Create the pod by applying the YAML file: +- Watch the pods (so that we can catch the Pod IP address) ```bash - kubectl apply -f ~/container.training/k8s/nginx-with-volume.yaml + kubectl get pods -o wide --watch ``` -- Check the IP address that was allocated to our pod: +] + +--- + +## Shared volume in action + +.exercise[ + +- Create the pod: ```bash - kubectl get pod nginx-with-volume -o wide - IP=$(kubectl get pod nginx-with-volume -o json | jq -r .status.podIP) + kubectl create -f ~/container.training/k8s/nginx-3-with-git.yaml ``` -- Access the web server: +- As soon as we see its IP address, access it: + ```bash + curl $IP + ``` + +- A few seconds later, the state of the pod will change; access it again: ```bash curl $IP ``` ] +The first time, we should see "403 Forbidden". + +The second time, we should see the HTML file from the Spoon-Knife repository. + +--- + +## Explanations + +- Both containers are started at the same time + +- NGINX starts very quickly + + (it can serve requests immediately) + +- But at this point, the volume is empty + + (NGINX serves "403 Forbidden") + +- The other containers installs git and clones the repository + + (this takes a bit longer) + +- When the other container is done, the volume holds the repository + + (NGINX serves the HTML file) + --- ## The devil is in the details @@ -183,13 +332,100 @@ spec: - That's why we specified `restartPolicy: OnFailure` +--- + +## Inconsistencies + - There is a short period of time during which the website is not available (because the `git` container hasn't done its job yet) -- This could be avoided by using [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) +- With a bigger website, we could get inconsistent results - (we will see a live example in a few sections) + (where only a part of the content is ready) + +- In real applications, this could cause incorrect results + +- How can we avoid that? + +--- + +## Init Containers + +- We can define containers that should execute *before* the main ones + +- They will be executed in order + + (instead of in parallel) + +- They must all succeed before the main containers are started + +- This is *exactly* what we need here! + +- Let's see one in action + +.footnote[See [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) documentation for all the details.] + +--- + +## Defining Init Containers + +.small[ +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx-with-init +spec: + volumes: + - name: www + containers: + - name: nginx + image: nginx + volumeMounts: + - name: www + mountPath: /usr/share/nginx/html/ + initContainers: + - name: git + image: alpine + command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ] + volumeMounts: + - name: www + mountPath: /www/ +``` +] + +--- + +## Trying the init container + +- Repeat the same operation as earlier + + (try to send HTTP requests as soon as the pod comes up) + +- This time, instead of "403 Forbidden" we get a "connection refused" + +- NGINX doesn't start until the git container has done its job + +- We never get inconsistent results + + (a "half-ready" container) + +--- + +## Other uses of init containers + +- Load content + +- Generate configuration (or certificates) + +- Database migrations + +- Waiting for other services to be up + + (to avoid flurry of connection errors in main container) + +- etc. ---