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/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. ---