iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔖

My Notes on Trying Out Cybozu's 'Introduction to Kubernetes' New Employee Training

に公開

Introduction

Training materials for new employees at Cybozu have been made public, and among them is a training module titled "Introduction to Kubernetes". This post serves as a personal memo of my experience trying out that module as a Kubernetes beginner.

The original site provides very detailed explanations, so please refer to it if you are looking for in-depth information. Also, I won't be covering much about kubectl commands, but this article was very easy to understand.

Impressions

My impression after trying it out is that it is very easy to follow, and I would recommend it for anyone looking to get started with Kubernetes.

Before and after going through this training, my level of understanding when reading other Kubernetes-related articles and slides is completely different, so it was highly beneficial.

About the Training Materials

The materials are composed of the following three parts:

  • Introduction to Kubernetes
  • More Introduction to Kubernetes
  • Exercise

"Introduction to Kubernetes" is the main entry-level section, while "More Introduction to Kubernetes" covers slightly more advanced content, such as "things you need to consider for production operations." "Exercise" consists of practice problems, which I did not attempt (my apologies).

This article will provide a comprehensive overview of "Introduction to Kubernetes."

I will only briefly touch upon the contents of "More Introduction to Kubernetes."

Introduction to Kubernetes

This is the basics section.

Setting up the Kubernetes Environment

We will use Minikube.

Minikube allows you to start a single-node Kubernetes cluster.

It seems that multi-node Kubernetes clusters cannot be created with Minikube.

Additionally, to use Minikube, you need to set CPUs: 2 or more in your Docker settings.
(I was warned and Minikube would not start with CPUs: 1.)

Displaying the Kubernetes Dashboard

# Install Minikube
$ brew install minikube

# Check version
$ minikube version
minikube version: v1.12.3
commit: 2243b4b97c131e3244c5f014faedca0d846599f5

# Check status
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

# Start dashboard
$ minikube dashboard

The dashboard will appear as shown below.

a.png

Deploying a Container on Kubernetes

The smallest unit that can be deployed on Kubernetes is a Pod.
A Pod is a collection of one or more containers. Containers belonging to the same Pod are always deployed on the same node.

Conceptually, the hierarchy is Container < Pod < Node.

By the way, Minikube acts as the Node.

Prepare a YAML file configured for the Pod to be deployed on Kubernetes.

For now, we will write it with the following content.

nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
  labels:
    component: nginx
spec:
  containers:
    - name: nginx
      image: nginx:latest

Deploy to Kubernetes using the kubectl apply -f [YAML file name to apply] command.

# Apply to Kubernetes
$ kubectl apply -f nginx-pod.yaml
pod/my-first-pod created

# List Pods in the cluster
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
my-first-pod   1/1     Running   0          103s

# Display details of the Pod
$ kubectl describe pod my-first-pod
Name:         my-first-pod
Namespace:    default
Priority:     0
Node:         minikube/172.17.0.3
Start Time:   Sat, 15 Aug 2020 04:15:37 +0900
Labels:       component=nginx
(Omitted)
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  5m5s   default-scheduler  Successfully assigned default/my-first-pod to minikube
  Normal  Pulling    5m4s   kubelet, minikube  Pulling image "nginx:latest"
  Normal  Pulled     4m50s  kubelet, minikube  Successfully pulled image "nginx:latest"
  Normal  Created    4m50s  kubelet, minikube  Created container nginx
  Normal  Started    4m50s  kubelet, minikube  Started container nginx

# Login to the Pod
kubectl exec -it my-first-pod -- /bin/bash

# Install curl command
root@my-first-pod:/# apt update && apt install -y curl
(Omitted)

# Access localhost:80 (Check connectivity)
root@my-first-pod:/# curl -i localhost:80
HTTP/1.1 200 OK
(Omitted)

my-first-pod is displayed in the dashboard as follows.
b.png

Accessing Other Pods

Check connectivity between Pods.

Prepare a Pod different from my-first-pod.

bastion.yaml
apiVersion: v1
kind: Pod
metadata:
  name: bastion
spec:
  containers:
    - name: bastion
      image: debian:stretch
      command: ["sleep", "infinity"]

By the way, "bastion" means a "jump host."

Check connectivity from bastion to my-first-pod (nginx).

# Apply to Kubernetes
$ kubectl apply -f bastion.yaml
pod/bastion created

# List Pods
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
bastion        1/1     Running   0          14s
my-first-pod   1/1     Running   1          10h

# Display IP addresses
$ kubectl get pod -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
bastion        1/1     Running   0          84s   172.18.0.6   minikube   <none>           <none>
my-first-pod   1/1     Running   1          10h   172.18.0.2   minikube   <none>           <none>

# Login to bastion and access 172.18.0.2 (the IP address of my-first-pod)
$ kubectl exec -it bastion -- bash
(bastion)# apt update && apt install -y curl
(bastion)# curl -i http://172.18.0.2/
HTTP/1.1 200 OK
(Omitted)

The dashboard confirms that bastion has been added.
c.png

Accessing Other Pods Using a Service

We just tested Pod-to-Pod connectivity, but since Pods disappear and are recreated, their IP addresses change, so it's not ideal to communicate directly with Pod IP addresses.

Therefore, we want a stable (= non-changing) endpoint to communicate with "any Pod that provides the desired function" rather than a specific Pod.

Usually, when communicating with other Pods, we use an object called a Service. Using a Service allows you to access "one of the Pods with a specific label" instead of "a specific Pod."

In the nginx example above, the label part is component: nginx. The words "component" and "nginx" do not have special meanings here; you can specify any string you like.

This time, we will use the following Service.

nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-first-service
spec:
  selector:
    component: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Apply the Service to Kubernetes.

The method for applying is the same as for Pods.

$ kubectl apply -f nginx-service.yaml
service/my-first-service created

$ kubectl get service
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes         ClusterIP   10.96.0.1      <none>        443/TCP   12h
my-first-service   ClusterIP   10.101.46.86   <none>        80/TCP    3m7s

The dashboard confirms that my-first-service has been added.
d.png

Now, let's access my-first-pod (labeled component: nginx) from the bastion Pod through my-first-service, just like before.

$ kubectl exec -it bastion -- bash
root@bastion:/# curl -i http://my-first-service/
HTTP/1.1 200 OK
(Omitted)

It can be illustrated as follows:

f.png

Launching Multiple Instances of the Same Pod

In the previous section, we launched one nginx instance. In actual production environments, it is common to configure a single service with multiple Pods for redundancy and scalability. Here, we will use the Kubernetes ReplicaSet object to launch the specified number of nginx instances.

Below is the ReplicaSet definition we will use this time.

nginx-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-replicaset
  labels:
    component: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      component: nginx
  template:
    metadata:
      labels:
        component: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest

While I will omit the detailed explanation, ReplicaSet is an object that automatically deploys or deletes Pods so that the "number of Pods matching the selector" equals the "number specified in replicas."

For now, let's apply it and check the number of pods.

$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
bastion        1/1     Running   0          109m
my-first-pod   1/1     Running   1          12h

$ kubectl apply -f nginx-replicaset.yaml
replicaset.apps/nginx-replicaset created

$ kubectl get replicaset
NAME               DESIRED   CURRENT   READY   AGE
nginx-replicaset   3         3         3       13s

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
bastion                  1/1     Running   0          110m
my-first-pod             1/1     Running   1          12h
nginx-replicaset-wqcdk   1/1     Running   0          28s
nginx-replicaset-xbs28   1/1     Running   0          28s

Looking at the dashboard, you can confirm that there are 3 Pods running.
g.png

Now, let's try deleting one of the Pods.
Even after deleting a Pod, it will immediately launch again so that there are 3 Pods.

$ kubectl delete pod my-first-pod
pod "my-first-pod" deleted

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
bastion                  1/1     Running   0          123m
nginx-replicaset-dn57j   1/1     Running   0          9s
nginx-replicaset-wqcdk   1/1     Running   0          13m
nginx-replicaset-xbs28   1/1     Running   0          13m

In the dashboard, you can confirm that there are still 3 Pods running.

h.png

Next, let's change replicas: 3 to replicas: 4 and apply the changes.

$ kubectl apply -f nginx-replicaset.yaml
replicaset.apps/nginx-replicaset configured

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
bastion                  1/1     Running   0          126m
nginx-replicaset-5swkc   1/1     Running   0          7s
nginx-replicaset-dn57j   1/1     Running   0          3m33s
nginx-replicaset-wqcdk   1/1     Running   0          16m
nginx-replicaset-xbs28   1/1     Running   0          16m

There are now 4 nginx Pods.

Of course, you can also confirm this on the dashboard.
i.png

Finally, let's delete the ReplicaSet from this state.

$ kubectl delete replicaset nginx-replicaset
replicaset.apps "nginx-replicaset" deleted

$ kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
bastion                  1/1     Running       0          163m
nginx-replicaset-5swkc   0/1     Terminating   0          37m
nginx-replicaset-dn57j   0/1     Terminating   0          40m
nginx-replicaset-wqcdk   0/1     Terminating   0          54m
nginx-replicaset-xbs28   0/1     Terminating   0          54m

The nginx Pods are now gone.

Checking the dashboard, you can confirm that both the nginx Pods and the ReplicaSet have been removed.
j.png

Rolling Updates

To perform rolling updates in Kubernetes, you use an object called Deployment. A Deployment is an object similar to a ReplicaSet, but unlike a ReplicaSet, it supports updates.

Incidentally, if you perform rolling updates with ReplicaSet without using Deployment, you need to follow a procedure like the one shown in the figure below.

j2.png

With Deployment, you can have Kubernetes handle this complex procedure for you.

The YAML for the Deployment used this time is as follows. It is almost the same as the ReplicaSet.

nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    component: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      component: nginx
  template:
    metadata:
      labels:
        component: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.15

Let's try applying the Deployment.

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created

$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           40s

# Verification (I am displaying the IMAGE to check the version)
$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME                                IMAGE            PHASE
bastion                             debian:stretch   Running
nginx-deployment-77bc96745b-9tdr2   nginx:1.15       Running
nginx-deployment-77bc96745b-kg96q   nginx:1.15       Running
nginx-deployment-77bc96745b-xn4v6   nginx:1.15       Running

It is displayed like this on the dashboard.
k.png

Now, let's change image: nginx:1.15 to image: nginx:1.16 and apply the Deployment.

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured

If you continue to run the kubectl get pod command from this point on, you can observe Kubernetes performing the rolling update.

$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME                                IMAGE            PHASE
bastion                             debian:stretch   Running
nginx-deployment-5b4c7f657-pnwkr    nginx:1.16       Pending
nginx-deployment-77bc96745b-9tdr2   nginx:1.15       Running
nginx-deployment-77bc96745b-kg96q   nginx:1.15       Running
nginx-deployment-77bc96745b-xn4v6   nginx:1.15       Running

$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME                                IMAGE            PHASE
bastion                             debian:stretch   Running
nginx-deployment-5b4c7f657-dcqwd    nginx:1.16       Running
nginx-deployment-5b4c7f657-pnwkr    nginx:1.16       Running
nginx-deployment-5b4c7f657-t9ddv    nginx:1.16       Running
nginx-deployment-77bc96745b-9tdr2   nginx:1.15       Running
nginx-deployment-77bc96745b-kg96q   nginx:1.15       Running
nginx-deployment-77bc96745b-xn4v6   nginx:1.15       Running

$ kubectl get pod -o 'custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[*].image,PHASE:.status.phase'
NAME                               IMAGE            PHASE
bastion                            debian:stretch   Running
nginx-deployment-5b4c7f657-dcqwd   nginx:1.16       Running
nginx-deployment-5b4c7f657-pnwkr   nginx:1.16       Running
nginx-deployment-5b4c7f657-t9ddv   nginx:1.16       Running

As you can see, all Pods have transitioned from image: nginx:1.15 to image: nginx:1.16.

You can also verify this on the dashboard.
l.png

It is said that when actually using Kubernetes, you rarely create Pods or ReplicaSets directly.

The best practice in most cases is to use Deployment when creating Pods.

Even if the number of replicas is 1, you should use Deployment to create Pods. The reason is simply that if you deploy a single Pod, if the node where the Pod is running dies, the Pod will also be lost.

Exposing Services Outside the Cluster

I will explain how to expose a service deployed on Kubernetes to the outside of the Kubernetes cluster. There are several ways to expose a service, but here I will explain the easiest method using NodePort.

Change the Service as follows:

nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-first-service
spec:
  selector:
    component: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30000
  type: NodePort

The lines nodePort: 30000 and type: NodePort have been added.

Apply the changes.

$ kubectl get service my-first-service
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
my-first-service   ClusterIP   10.101.46.86   <none>        80/TCP    3m7s

$ kubectl apply -f nginx-service.yaml
service/my-first-service configured

# The TYPE has changed
$ kubectl get service my-first-service
NAME               TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
my-first-service   NodePort   10.101.46.86   <none>        80:30000/TCP   124m

Through this operation, the type of my-first-service has been changed from ClusterIP (default) to NodePort.

You can also confirm that port 30000 is open on the dashboard.
m.png

From this point on, it differs from the training material: accessing via <IP address returned by minikube ip : 30000> did not work, so I executed it using the command below, which seems to expose the IP address and port number.

$ minikube service my-first-service --url
🏃  Starting tunnel for service my-first-service.
|-----------|------------------|-------------|------------------------|
| NAMESPACE |       NAME       | TARGET PORT |          URL           |
|-----------|------------------|-------------|------------------------|
| default   | my-first-service |             | http://127.0.0.1:53157 |
|-----------|------------------|-------------|------------------------|
http://127.0.0.1:53157

When accessing http://127.0.0.1:53157 from a browser, I was able to connect to nginx as shown below.

n.png

Debugging Pods

In many cases, you need to debug deployed Pods.
kubectl exec for logging in and kubectl describe are useful for debugging Pods.

In this section, I will explain two other useful debugging commands in addition to those two: kubectl logs and kubectl port-forward.

Viewing Container Logs

You can check logs using the kubectl logs command.

# Current list of Pods
$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-5b4c7f657-cbd8r   1/1     Running   0          103m
nginx-deployment-5b4c7f657-fvd88   1/1     Running   0          103m
nginx-deployment-5b4c7f657-lqqkm   1/1     Running   0          103m

# Display logs for a specific Pod
$ kubectl logs nginx-deployment-5b4c7f657-cbd8r
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52378/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:32:24 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52378", referrer: "http://127.0.0.1:52378/"

# Display logs for Pods matching a label
$ kubectl logs -l component=nginx
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:10:32:24 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52378/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:32:24 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52378", referrer: "http://127.0.0.1:52378/"
172.17.0.3 - - [15/Aug/2020:10:45:24 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 10:45:25 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:52724", referrer: "http://127.0.0.1:52724/"
172.17.0.3 - - [15/Aug/2020:10:45:25 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:52724/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
172.17.0.3 - - [15/Aug/2020:12:01:44 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"
2020/08/15 12:01:44 [error] 6#6: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.3, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "127.0.0.1:53157", referrer: "http://127.0.0.1:53157/"
172.17.0.3 - - [15/Aug/2020:12:01:44 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://127.0.0.1:53157/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "-"

Port Forwarding

Using the kubectl port-forward command, you can forward a specific port of a Pod to localhost.
Using this, you can access the Pod directly from a local curl command or browser.

For example, if you run the following command, it will forward port 8080 locally to port 80 of the nginx container in the cluster.

$ kubectl port-forward deployment/nginx-deployment 8080:80

When you access localhost:8080, "Welcome to nginx!" will be displayed.

o.png

More Introduction to Kubernetes

I will explain important features assuming operation in a production environment.

Here, I will just touch on them briefly.

Health Checks

Kubernetes has the following two health checks, which can be used according to the purpose:

  • Liveness probe: Checks whether a Pod is alive or dead. If the Liveness probe fails a certain number of times, the Pod will be restarted.
  • Readiness probe: Checks whether a Pod can respond to requests. Until the Readiness probe succeeds, the Pod is considered not to have finished starting up and is not added to the service's reverse proxy target. Using a Readiness probe can prevent requests from being sent before the Pod has finished starting up.

Since I could not run the sample from the training material, I defined the YAML file by referring to this article.

sample-healthcheck.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-healthcheck
  labels:
    app: sample-app
spec:
  containers:
    - name: nginx-container
      image: nginx:1.12
      ports:
        - containerPort: 80
      livenessProbe:
        httpGet:
          path: /index.html
          port: 80
          scheme: HTTP
        timeoutSeconds: 1
        successThreshold: 1
        failureThreshold: 2
        initialDelaySeconds: 5
        periodSeconds: 3
      readinessProbe:
        exec:
          command: ["ls", "/usr/share/nginx/html/50x.html"]
        timeoutSeconds: 1
        successThreshold: 2
        failureThreshold: 1
        initialDelaySeconds: 5
        periodSeconds: 3

Let's apply it.

$ kubectl apply -f sample-healthcheck.yaml
pod/sample-healthcheck created

$ kubectl get pod
NAME                 READY   STATUS    RESTARTS   AGE
sample-healthcheck   1/1     Running   0          17s

# Verify Liveness settings with describe
$ kubectl describe pod sample-healthcheck | grep "Liveness"
    Liveness:       http-get http://:80/index.html delay=5s timeout=1s period=3s #success=1 #failure=2

# Verify Readiness settings with describe
$ kubectl describe pod sample-healthcheck | grep "Readiness"
    Readiness:      exec [ls /usr/share/nginx/html/50x.html] delay=5s timeout=1s period=3s #success=2 #failure=1

I will experiment by intentionally triggering the health check.

# First, monitor the Pod status in a separate console
$ kubectl get pods sample-healthcheck --watch

# Delete index.html in DocumentRoot (deleting the file monitored by Liveness)
$ kubectl exec -it sample-healthcheck rm /usr/share/nginx/html/index.html

# The Pod restarts (since index.html is created after restarting, it will not repeat the restart)
$ kubectl get pods sample-healthcheck --watch
NAME                 READY   STATUS    RESTARTS   AGE
sample-healthcheck   0/1     Running   2          9m12s
sample-healthcheck   1/1     Running   2          9m20s

Liveness probes and Readiness probes are very important for operations, so it is recommended to always set both for resident Pods.

Resource Requests/Limits

You can pre-configure resources for Pods.

There are two types of settings:

  • Resource Requests: A feature to declare the amount of memory or CPU required by the container. The Kubernetes scheduler decides the node to deploy the Pod based on the Resource Requests value.
  • Resource Limits: A feature to set the upper limit for the amount of memory or CPU that the container can actually use. If the memory used by the container exceeds the set limit, the container is killed. Also, if CPU usage reaches the limit, the container is throttled.

Here is an example of a YAML file.

sample-resource.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    component: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.16
      resources:
        requests:
          memory: "128Mi"
          cpu: "250m"
        limits:
          memory: "128Mi"
          cpu: "500m"

Apply and check the settings.

$ kubectl apply -f sample-resource.yaml

$ kubectl describe pod nginx
(omitted)
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        250m
      memory:     128Mi
(omitted)

Volume

When a Pod has multiple containers, you may want to share a local directory between them.
For example, there is a use case where log files output by an AP server are forwarded by a log forwarding agent.

By the way, the sample uses fluentd, which is quite famous as an OSS data log collection tool.

sample-volume.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    component: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.16
      volumeMounts:
        - name: nginx-log
          mountPath: /var/log/nginx

    - name: fluentd
      image: fluent/fluentd:v1.11
      volumeMounts:
        - name: nginx-log
          mountPath: /var/log/nginx
          readOnly: true

  volumes:
    - name: nginx-log
      emptyDir: {}

Apply and check.

$ kubectl apply -f sample-volume.yaml
Pod/nginx created

$ kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   2/2     Running   0          2m5s

$ kubectl describe pod nginx
(omitted)
Containers:
  nginx:
    Container ID:   docker://3ebb817cf4ee20439c722bbeba711943bb5f05a2d75b420d308faaf1edbd0af2
    Image:          nginx:1.16
    Image ID:       docker-pullable://nginx@sha256:d20aa6d1cae56fd17cd458f4807e0de462caf2336f0b70b5eeb69fcaaf30dd9c
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 15 Aug 2020 22:26:10 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/log/nginx from nginx-log (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-q8zvm (ro)
  fluentd:
    Container ID:   docker://f001646ac161052c67e6aba83ec999b97cb55650c4db095c310e60af75a871cc
    Image:          fluent/fluentd:v1.11
    Image ID:       docker-pullable://fluent/fluentd@sha256:617fa61a8a811fd49b730c27835983ecb2a8150584138ec825649ff8352a6d44
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 15 Aug 2020 22:28:13 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/log/nginx from nginx-log (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-q8zvm (ro)
(omitted)

Conceptually, the diagram looks like this.

p.png

Anti Affinity

Suppose you are using Deployment to create two identical Pods. If you don't specify anything, those two Pods might be scheduled on the same node. In such a case, if that node dies due to failure or maintenance, both Pods will disappear simultaneously.

Using the Affinity feature, you can request the scheduler to place Pods on different nodes. Kubernetes Affinity is very flexible and can do various things, but here I will focus on the use case mentioned above.

sample-affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    component: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      component: nginx
  template:
    metadata:
      labels:
        component: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.16
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: component
                    operator: In
                    values:
                      - "nginx"
              topologyKey: "kubernetes.io/hostname"

In the definition, I set replicas: 2 so that two Pods will be created.

With the description starting from affinity:, when Kubernetes deploys this Pod, it tries to deploy it to a node that does not have a Pod with the label component: nginx.

This ensures that even if one node dies, at least one nginx Pod survives.

Let's apply it in the single-node environment of Minikube.

$ kubectl apply -f sample-affinity.yaml
deployment.apps/nginx-deployment created

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-6b7b554df-9p4b9   0/1     Pending   0          71s
nginx-deployment-6b7b554df-xf5bp   1/1     Running   0          71s

Because it is a single-node, you can see that once one Pod is started, the other Pod is not deployed and is in a Pending state.

Like this, when there is no node that satisfies the condition, the Pod is not deployed and stays in a Pending state until a node that satisfies the condition appears.

It is recommended to include Pods that have been Pending for a long time in your monitoring items.

Pod Disruption Budget

There may be cases where node reboots are necessary. For example, when a vulnerability is discovered in the Linux kernel.

If you set up multiple Pods and make them redundant, you can withstand the shutdown of a single node. However, problems can occur in situations where nodes are rebooted one after another.

For example, consider the case where two Pods are deployed on a cluster consisting of three nodes as shown in the figure below. These two Pods are configured to have 2 replicas by Deployment. We will perform a rolling reboot of the nodes one by one on this cluster.

ll.png

In the process of shutting down the first node, the Pod running on it is Evicted. The Deployment, which detects that the number of Pods has decreased, immediately deploys a new Pod. However, since it takes time for this Pod to start, it stays in the starting state. In this situation, if you shut down node 2, no available Pods will exist, and the service will stop.

To prevent this, use Pod Disruption Budget to specify the minimum number of Pods required for the service to run.

When PDB is specified, Kubernetes tools wait appropriately so that this constraint is satisfied before shutting down a node.

frontend-pdb.yaml
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      component: nginx

Apply and check.

$ kubectl apply -f frontend-pdb.yaml
poddisruptionbudget.policy/nginx-pdb created

$ kubectl get poddisruptionbudget
NAME        MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
nginx-pdb   1               N/A               0                     66s

Naturally, the PDB constraints are observed only when the cluster administrator intentionally shuts down a node. If a node dies due to failure, PDB is ignored. Also, PDB is not applied when deleting a Deployment.

Whether a Pod is available is determined by the Readiness probe. To make PDB function correctly, you must set appropriate Readiness probes for your Pods.

Exercise

I have not done the Exercise in the training material.

Sorry...

That's all.

GitHubで編集を提案

Discussion