Kubernetes1.10 チュートリアル201やってみた (Label, Deployment, Services, LivenessProbe)

# はじめに

Kubernetesの公式ページに、多くのチュートリアルが掲載されています。
Kubernetesの勉強の一環で、チュートリアルを消化していきます。
今回は、Kubernetes 201 が対象です。
https://kubernetes.io/docs/user-guide/walkthrough/k8s201/

なお、Kubernetesのバージョンは、2018年4月時点で最新の1.10を使用しています。



# Kubernetes 201

Kubernetes101チュートリアルでは、以下の内容を勉強していくものになっています。

- Label
- Deployments
- Service
- Health Checking





# Lebel

Podを多く作成すると、Podの分類分けを作成し整理する必要が出てくるはずです。
KubernetesではLabelを使用して、Podの分類分けをすることができます。
LabelはKubernetesの各Objectに割り当てられた、key-value です。

以下のマニフェストファイルを使用することで、PodにLabelを割り当てて、Deployすることが出来ます
6-7行目の [metadata.labels] でLabel[ app=nginx] と指定しています。

```
cat <<'EOF' > /root/kube_yaml/201_walkthrough/pod-nginx-with-label.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
EOF
```

Deployします

```
[root@sugi-kubernetes110-master01 201_walkthrough]# kubectl create -f /root/kube_yaml/201_walkthrough/pod-nginx-with-label.yaml
pod "nginx" created
```

describe pod で詳細を確認します
Labels に app=nginx と表示されていることが確認できます

```
[root@sugi-kubernetes110-master01 201_walkthrough]# kubectl describe pod nginx 
Name:         nginx
Namespace:    default
Node:         sugi-kubernetes110-node02.localdomain/192.168.120.224
Start Time:   Mon, 30 Apr 2018 01:21:12 +0900
Labels:       app=nginx
<略
```

全てのPodから、Label [ app=nginx ] に該当しているもののみlistします

```
[root@sugi-kubernetes110-master01 201_walkthrough]# kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
nginx     1/1       Running   0          3m
```

Deleteアクション時にもLabelを指定して削除が出来ます

```
[root@sugi-kubernetes110-master01 201_walkthrough]# kubectl delete pod -l app=nginx
pod "nginx" deleted
```



# Deployment

Podの管理方法を学びました。ここで、Podをスケールアウト/インする場合や、新しいバージョンにリリースするときは、どのように管理すればよいのでしょうか。
KubernetesではDeploymentを使用することで、これらを管理し、アップデートすることができます。

Deploymentは、Pod作成のテンプレートを定義し、また、レプリカの数を指定することが出来ます。

次のマニフェストファイルを用意します
PodをDeployするときには、Podの名前を指定していましたが、Deploymentを使用すると、DeploymentがPodの名前を動的に指定します。

```
cat <<'EOF' > /root/kube_yaml/201_walkthrough/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template: # create pods using pod definition in this template
    metadata:
      # unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
      # generated from the deployment name
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
EOF
```

Deploymentを作成します

```
[root@sugi-kubernetes110-master01 ~]# kubectl create -f /root/kube_yaml/201_walkthrough/deployment.yaml
deployment.apps "nginx-deployment" created
```

Deploymentを一覧表示します

```
[root@sugi-kubernetes110-master01 ~]# kubectl get deployment -o wide
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS   IMAGES        SELECTOR
nginx-deployment   2         2         2            2           2m        nginx        nginx:1.7.9   app=nginx
```

マニフェストファイルで指定した通り、PodにもLabelが設定されているため、getが出来ます

```
[root@sugi-kubernetes110-master01 ~]# kubectl get pod -o wide -l app=nginx
NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-deployment-75675f5897-mfm5r   1/1       Running   0          4m        10.244.2.6   sugi-kubernetes110-node02.localdomain
nginx-deployment-75675f5897-zgv94   1/1       Running   0          4m        10.244.1.7   sugi-kubernetes110-node01.localdomain
```

以下のマニフェストファイルを用意します。
先ほど作成したDeployment「nginx-deployment」を更新するものです。
Container Image を nginx:1.7.9 から 1.8 にバージョン指定を変更しています

```
cat <<'EOF' > /root/kube_yaml/201_walkthrough/deployment-update.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8 # Update the version of nginx from 1.7.9 to 1.8
        ports:
        - containerPort: 80
EOF
```

既に存在しているDeploymentを更新する場合は、createではなく apply を使用します。
createを使用した場合は、既にDeploymentが存在するため、エラーとなります。

```
[root@sugi-kubernetes110-master01 ~]# kubectl apply -f /root/kube_yaml/201_walkthrough/deployment-update.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps "nginx-deployment" configured
```

PodのContainerがローリングアップデートされています

```
[root@sugi-kubernetes110-master01 ~]# kubectl get pod -o wide
NAME                                READY     STATUS              RESTARTS   AGE       IP           NODE
nginx-deployment-6ddf4c5bf7-zcpzh   0/1       ContainerCreating   0          1m        <none>       sugi-kubernetes110-node01.localdomain
nginx-deployment-75675f5897-mfm5r   1/1       Running             0          9m        10.244.2.6   sugi-kubernetes110-node02.localdomain
nginx-deployment-75675f5897-zgv94   1/1       Running             0          9m        10.244.1.7   sugi-kubernetes110-node01.localdomain
```

ローリングアップデートが完了しています

```
[root@sugi-kubernetes110-master01 ~]# kubectl get pod -o wide
NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-deployment-6ddf4c5bf7-rn5wp   1/1       Running   0          2m        10.244.2.7   sugi-kubernetes110-node02.localdomain
nginx-deployment-6ddf4c5bf7-zcpzh   1/1       Running   0          3m        10.244.1.8   sugi-kubernetes110-node01.localdomain
```

使用しているImageが1.8を使用していることがわかります

```
[root@sugi-kubernetes110-master01 ~]# kubectl describe pod -l app=nginx | grep Image
    Image:          nginx:1.8
    Image ID:       docker-pullable://docker.io/nginx@sha256:c97ee70c4048fe79765f7c2ec0931957c2898f47400128f4f3640d0ae5d60d10
    Image:          nginx:1.8
    Image ID:       docker-pullable://docker.io/nginx@sha256:c97ee70c4048fe79765f7c2ec0931957c2898f47400128f4f3640d0ae5d60d10
```



# Services

Podを外部に公開するものがServicesです。
例えば、Podのスケールや再配置応じて、Podを外部公開する必要があります。
Kubernetesでは、Servicesでこれを実現しています。

以下のマニフェストファイルを用意します。

port : ServiceにアクセスするときのPort番号を指定します
targetport : Serviceがアクセスする先のPodのPort番号を指定します
selector : Serviceがアクセスする先のPodに付与されているLabelを指定します

```
cat <<'EOF' > /root/kube_yaml/201_walkthrough/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  ports:
  - port: 8000 # the port that this service should serve on
    # the container on each pod to connect to, can be a name
    # (e.g. 'www') or a number (e.g. 80)
    targetPort: 80
    protocol: TCP
  # just like the selector in the deployment,
  # but this time it identifies the set of pods to load balance
  # traffic to.
  selector:
    app: nginx
EOF
```

Serviceを作成します

```
[root@sugi-kubernetes110-master01 ~]# kubectl create -f /root/kube_yaml/201_walkthrough/service.yaml
service "nginx-service" created
```

Service一覧を確認します
[nginx-service]が今回作成したServiceです

```
[root@sugi-kubernetes110-master01 ~]# kubectl get service -o wide
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE       SELECTOR
kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP    1d        <none>
nginx-service   ClusterIP   10.98.70.219   <none>        8000/TCP   46s       app=nginx
```

BusyBoxを使用して、Pod内からClusterIPにアクセス可能確認します

```
export SERVICE_IP=$(kubectl get service nginx-service -o go-template='{{.spec.clusterIP}}')
export SERVICE_PORT=$(kubectl get service nginx-service -o go-template='{{(index .spec.ports 0).port}}')
echo "$SERVICE_IP:$SERVICE_PORT"
→ 10.98.70.219:8000
```

BusyBoxのコンテナに渡した環境変数を使用して、nginx-serviceにアクセスできたことがわかります

```
kubectl run busybox  --generator=run-pod/v1 --image=busybox --restart=Never --tty -i --env "SERVICE_IP=$SERVICE_IP" --env "SERVICE_PORT=$SERVICE_PORT"
/ # wget -qO- http://$SERVICE_IP:$SERVICE_PORT # Run in the busybox container
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ # 
/ # 
u@busybox$ exit # Exit the busybox container
$ kubectl delete pod busybox # Clean up the pod we created with "kubectl run"
```

Serviceを削除します

```
kubectl delete service nginx-service
```



# Health Checking

バグのないアプリケーションを書くことはできません。むしろ、バグが出てクラッシュしたときに、System全体で定期チェックし、自動修復する方法が良いと思います。
アプリケーションの外側からHealth Checkを定期実行するのが重要です。
アプリケーションの内部で実施する場合、アプリケーションの障害が発生した際に検知出来ない可能性があります。

Kubernetesでは、Kubernetes側でアプリケーションをモニターする機能が備わっています。各Node上で稼働しているKubelet agent が実行します。




## Process Health Checking

最もシンプルなヘルスチェックは、プロセスレベルのヘルスチェックです。Kubelet agentは常にDockerDaemonにコンテナープロセスが稼働しているか確認しています。
もしコンテナープロセスが稼働していない場合、コンテナープロセスは再起動されます。
これまでのチュートリアルで作成してきたPodは、自動的にプロセスヘルスチェックが有効になっています。
Kubernetesで実行されているすべてのコンテナーで有効になります。



## Application Health Checking

ほとんどのケースでは、プロセスレベルのチェックでは不十分です。
例えば、デッドロックが発生した場合、Dockerの視点では正常にプロセスが稼働しているように見えます。
しかし、アプリケーションの視点では、デッドロックで止まり、Responseが返って来ません。

この問題に対処するため、Kubernetesではアプリケーションヘルスチェックを実行する機能が備わっています。
アプリケーションヘルスチェックは、Kubelet agent によって実行され、アプリケーションの正常性を確認することができます。

偏在、以下の3種類のアプリケーションヘルスチェック機能が備わっています。

- HTTP Health Checks : KubeletがWebHoolを呼び出します。HTTPの response code が 200~399 の間は正常と判断されます。それ以外の response code の場合、異常と判断されます。
- Container Exec : Kubeletがコンテナーの内側でコマンドを実行します。Return Code が 0 の場合は正常と判断されます。それ以外の場合、異常と判断されます。
- TCP Socket : KubeletがコンテナへTCP Socket を open することを試みます。接続が成功した場合、正常と判断されます。それ以外の場合、異常と判断されます。

全てのケースで、異常と判断された場合、コンテナーは自動的に再起動されます。

Containerのヘルスチェックは、LivenessProbe セクションで定義します。

以下のマニフェストファイルを用意します。

```
cat <<'EOF' > /root/kube_yaml/201_walkthrough/pod-with-http-healthcheck.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-http-healthcheck
spec:
  containers:
  - name: nginx
    image: nginx
    # defines the health checking
    livenessProbe:
      # an http probe
      httpGet:
        path: /_status/healthz
        port: 80
      # length of time to wait for a pod to initialize
      # after pod startup, before applying health checking
      initialDelaySeconds: 30
      timeoutSeconds: 1
    ports:
    - containerPort: 80
EOF
```

Deployします

```
[root@sugi-kubernetes110-master01 ~]# kubectl create -f /root/kube_yaml/201_walkthrough/pod-with-http-healthcheck.yaml
pod "pod-with-http-healthcheck" created
```

使用したnginxイメージには、livenessProbeのPathが存在しないため、定期的にコンテナが再起動されているイベントが発生していることがわかる

```
[root@sugi-kubernetes110-master01 ~]# kubectl describe pod pod-with-http-healthcheck 
Name:         pod-with-http-healthcheck
Namespace:    default
Node:         sugi-kubernetes110-node02.localdomain/192.168.120.224
Start Time:   Mon, 30 Apr 2018 13:58:17 +0900
Labels:       <none>
Annotations:  <none>
Status:       Running
IP:           10.244.2.10
Containers:
  nginx:
    Container ID:   docker://27c7a66726be6476bc6f7ec383bc9c2823ed38766598cf28b57d48912421c34b
    Image:          nginx
    Image ID:       docker-pullable://docker.io/nginx@sha256:80e2f223b2a53cfcf3fd491521e5fb9b4004d42dfc391c76011bcdd9565643df
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 30 Apr 2018 14:01:56 +0900
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Mon, 30 Apr 2018 14:01:06 +0900
      Finished:     Mon, 30 Apr 2018 14:01:52 +0900
    Ready:          True
    Restart Count:  4
    Liveness:       http-get http://:80/_status/healthz delay=30s timeout=1s period=10s #success=1 #failure=3
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-q65hj (ro)
Conditions:
  Type           Status
  Initialized    True 
  Ready          True 
  PodScheduled   True 
Volumes:
  default-token-q65hj:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-q65hj
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason                 Age              From                                            Message
  ----     ------                 ----             ----                                            -------
  Normal   Scheduled              4m               default-scheduler                               Successfully assigned pod-with-http-healthcheck to sugi-kubernetes110-node02.localdomain
  Normal   SuccessfulMountVolume  4m               kubelet, sugi-kubernetes110-node02.localdomain  MountVolume.SetUp succeeded for volume "default-token-q65hj"
  Normal   Created                2m (x3 over 3m)  kubelet, sugi-kubernetes110-node02.localdomain  Created container
  Normal   Started                2m (x3 over 3m)  kubelet, sugi-kubernetes110-node02.localdomain  Started container
  Normal   Pulling                1m (x4 over 4m)  kubelet, sugi-kubernetes110-node02.localdomain  pulling image "nginx"
  Warning  Unhealthy              1m (x7 over 3m)  kubelet, sugi-kubernetes110-node02.localdomain  Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing                1m (x3 over 2m)  kubelet, sugi-kubernetes110-node02.localdomain  Killing container with id docker://nginx:Container failed liveness probe.. Container will be killed and recreated.
  Normal   Pulled                 1m (x4 over 3m)  kubelet, sugi-kubernetes110-node02.localdomain  Successfully pulled image "nginx"
```





# 参考URL

Kubernetes 201 公式チュートリアルページ
https://kubernetes.io/docs/user-guide/walkthrough/k8s201/

Docs Overview
https://kubernetes.io/docs/home/?path=users&persona=app-developer&level=foundational