サイボウズ新人研修の「Kubernetes を使った開発入門」を触ってみた備忘録


# はじめに

[サイボウズの新人研修の資料](https://blog.cybozu.io/entry/2019/09/05/080000)が公開されており、そこに [「Kubernetes を使った開発入門」](https://cybozu.github.io/introduction-to-kubernetes/) なる研修資料があったので、そちらを Kubernetes 初心者が触ってみた備忘録になります。

解説は本家のサイトがとても詳しく解説してあるので、詳しい解説を求める方はそちらを参照ください。
また、kubectl コマンドについてもほとんど解説しませんが [こちら](https://qiita.com/Veritas666777/items/6609844efe88e7b333b6) の記事がわかりやすかったです。

# 感想
触ってみた感想ですが、かなり分かりやすかったので Kubernetes を入門するにはオススメだと思います。

その辺の Kubernetes 関連の記事やスライドを読むときの理解度が、こちらの研修を動かした前と後では全く違うので、とてもためになりました。

# 研修資料について

以下の 3 部構成です。

- Introduction to Kubernetes
- More Introduction to Kubernetes
- Exercise

「Introduction to Kubernetes」が入門編のメインになっており、「More Introduction to Kubernetes」は "本番運用を想定したらこういうことも考える必要ありますよ" というようなやや応用的な内容になります。「Exercise」は演習問題ですがこちらは手をつけてないです（甘え）。

この記事は「Introduction to Kubernetes」については手厚めに記載します。

「More Introduction to Kubernetes」の内容については触りだけ記載しています。

# Introduction to Kubernetes

基本編です。

## Kubernetes 環境を整える

Minikube を使用します。

Minikube はシングルノードの Kubernetes クラスタを立ち上げることができます。

複数ノードの Kubernetes クラスタは Minikube ではできないようです。

また、Minikube を使用するには Docker の設定で `CPUs:2` 以上が必要です。
（`CPUs:1` では怒られて Minikube を起動しませんでした。）

### Kubernetes のダッシュボードを表示する

```
# Minikube のインストール
$ brew install minikube

# バージョン確認
$ minikube version
minikube version: v1.12.3
commit: 2243b4b97c131e3244c5f014faedca0d846599f5

# ステータス確認
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

# ダッシュボードの起動
$ minikube dashboard
```

以下のようなダッシュボードが表示されます。

![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/e9d9543c-c1b0-a64c-f99e-85d6bfc7cc18.png)


## Kubernetes 上にコンテナをデプロイしてみる

Kubernetes 上にデプロイできる最小単位は Pod という単位になります。
Pod はひとつ以上のコンテナをまとめたものです。 同じ Pod に属するコンテナは常に同じノードに配置されます。

概念的には Container < Pod < Node という順でまとまった単位になります。

ちなみに Minikube は Node になります。

Kubernetes 上にデプロイする Pod を設定した YAML ファイルの準備をします。

一旦、以下の内容で記述します。

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

`kubectl apply -f [applyするYAMLファイル名]` コマンドで Kubernetes 上にデプロイします。

```
# Kubernetes への apply
$ kubectl apply -f nginx-pod.yaml
pod/my-first-pod created

# クラスタ内の Pod の一覧を表示
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
my-first-pod   1/1     Running   0          103s

# 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
（省略）
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

# Pod へのログイン
kubectl exec -it my-first-pod -- /bin/bash

# curl コマンドのインストール
root@my-first-pod:/# apt update && apt install -y curl
（省略）

# localhost:80 へのアクセス（疎通確認）
root@my-first-pod:/# curl -i localhost:80
HTTP/1.1 200 OK
（省略）
```

ダッシュボードには以下のように my-first-pod が表示されます。
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/78cdd860-0d1e-0087-4e56-d47399fde5b1.png)


## 他の Pod にアクセスする

Pod - Pod 間の疎通を確認します。

my-first-pod とは別の Pod を用意します。

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

ちなみに bastion は「踏み台」という意味になります。

bastion -> my-first-pod (nginx) への疎通を確認します。

```
# Kubernetes への apply
$ kubectl apply -f bastion.yaml
pod/bastion created

# Pod の一覧表示
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
bastion        1/1     Running   0          14s
my-first-pod   1/1     Running   1          10h

# IP アドレスの表示
$ 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>

# bastion にログインして `my-first-pod` のv IP アドレスの `172.18.0.2` にアクセス
$ 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
（省略）
```
ダッシュボードでは以下のように bastion が追加されたことが確認できます。
![c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/f8ce8d86-a32b-405f-81e6-dd5d04370a6b.png)


## Service を使って他の Pod にアクセスする

さきほど、Pod - Pod 間の疎通を試しましたが、Pod は消えたり増えたりするものなので、IP アドレスが変わってしまい Pod の IP アドレスに対して通信を行うのはよくないです。

そこで特定の Pod ではなく「所望の機能を提供する Pod のどれか」と通信するための安定した(=変化しない)エンドポイントが欲しくなります。

そこで、普通他の Pod との通信するときには Service というオブジェクトを使います。 Service を使うと「ある特定の Pod」ではなく「特定の ラベル を持つ Pod のどれか」にアクセスすることができます。

ラベルというのは、上の nginx の例だと `component: nginx` の部分です。 ここでの component や nginx が特別な意味を持っているわけではなく、利用者が好きな文字列を指定できます。

今回は以下のような Service を使用します。

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

Service を Kubernetes へ apply します。

apply の方法は Pod と同じになります。

```
$ 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
```

ダッシュボードでは以下のように my-first-service が追加されたことが確認できます。
![d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/ddf8bb76-bdf8-c489-f3f3-67e7c782f0ec.png)

bastion の Pod から my-first-service を通して `component: nginx` でラベリングしている my-first-pod の Pod に先ほど同様にアクセスしてみます。

```
$ kubectl exec -it bastion -- bash
root@bastion:/# curl -i http://my-first-service/
HTTP/1.1 200 OK
（省略）
```

図で表すと以下のようになります。

![f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/a4c3ccde-1f9b-26f3-877f-a2648c05468a.png)



## 同じ Pod をいくつも立てる

前の節では nginx を一台立ち上げました。 実際の運用環境では冗長性やスケーラビリティのために一つのサービスを複数台の Pod で構成することが一般的です。 ここでは Kubernetes の ReplicaSet というオブジェクトを利用して nginx を指定した台数だけ立ち上げてみましょう。

以下が今回使用する ReplicaSet の定義になります。

```yaml: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
```

詳しい説明は省略しますが、ReplicaSet は「selector にマッチする Pod の数」が「replicas に指定した数」になるように Pod を自動的にデプロイしたり削除したりするオブジェクトです。


とりあえず apply して pod の数を確認してみます。

```
$ 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
```

ダッシュボードをみると Pod が 3つあがっているのが確認できます。
![g.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/2b85826b-4a72-5372-4976-cc31965d535f.png)


ここで試しに Pod を一つ消してみます。
Pod を消してみてもまたすぐに Pod が 3 つになるように立ち上がります。

```
$ 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
```

ダッシュボードでも変わらず Pod が 3つあがっているのが確認できます。

![h.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/3d582027-ad72-7081-28db-b250297b7bf0.png)


つづいて `replicas: 3` -> `replicas: 4` に変更して apply してみます。

```
$ 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
```

nginx の Pod が 4 つになりましたね。

もちろんダッシュボードでも確認できます。
![i.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/841b8f22-7298-937d-2bbd-47e81e3fb3ff.png)

さらにこの状態から ReplicaSet を削除してみます。

```
$ 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
```

nginx の Pod がなくなりましたね。

ダッシュボードを確認すると nginx の Pod も ReplicaSet も消えていることが確認できます。
![j.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/10b22af7-c266-6e75-e7db-29074045f138.png)



## ローリングアップデートする

Kubernetes でローリングアップデートを行うには Deployment というオブジェクトを利用します。 Deployment は ReplicaSet と似たオブジェクトですが、ReplicaSet と違ってアップデートがサポートされています。

ちなみに Deployment を使わずに  ReplicaSet でローリングアップデートを行う場合は以下の図のような手順を踏む必要があります。

![j2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/ff9ea3ab-4057-9b7a-84c3-153abb9d7d41.png)


この複雑な手順が Deployment を使うと Kubernetes にやらせることができます。

今回使用する Deployment の YAML は以下のようになります。ほとんど ReplicaSet と同じです。

```yaml: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
```

Deployment を apply してみます。

```
$ 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

# 確認（バージョンを確認するために IMAGE を表示するようにしています）
$ 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
```

ダッシュボード上ではこのように表示されます。
![k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/caff7132-92c3-0e00-0606-896961d38d13.png)

ここで `image: nginx:1.15` -> `image: nginx:1.16` に変更して、Deployment を apply してみます。

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

以降、`kubectl get pod` コマンドを実行し続けると Kubernetes がローリングアップデートしている様子を伺うことができます。

```
$ 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
SuguruTakahashiMBP sugurutakahashi ~/git/k8s-sample $

$ 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
```

このようにすべての Pod が `image: nginx:1.15` -> `image: nginx:1.16` となりましたね。

ダッシュボードでも確認できます。
![l.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/2e623fca-777c-3f93-25ad-5e40dc8f7e34.png)

Kubernetes を実際に利用する場合、Pod や ReplicaSet を直接作成することはほとんどないそうです。

**Pod を作成するときは Deployment を使って作成することが大抵のケースでベストプラクティスとのことです。**

それが例え replica 数が１の場合でも Pod を作成する場合は Deployment を使って作るべきであり、理由は単に Pod をひとつデプロイした場合、Pod が立っているノードが死ぬと Pod も道連れになってしまうからです。


## サービスをクラスタの外部に公開する

Kubernetes 上にデプロイされたサービスを Kubernetes クラスタの外側に公開する方法を説明します。 クラスタ外にサービスを公開する方法はいくつかありますが、ここでは最も簡単な NodePort を使う方法を説明します。

Service を以下のように変更します。

```yaml: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
```

`nodePort: 30000` と `type: NodePort` の行が追加された行です。

apply します。

```
$ 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

# TYPE が変わっていますね
$ 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
```
この作業によって、my-first-service の type が ClusterIP (デフォルト) から NodePort に変更されました。

ダッシュボードでもポート番号 30000 で空いていることが確認できます。
![m.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/bda2a16b-2b42-adb3-fa44-cd2cab75c5d5.png)


ここからが研修資料と異なるのですが、<`minikube ip` で返却されたIPアドレス : 30000> でアクセスしてもうまくいかなかったので、以下のコマンドで IP アドレス＋ポート番号が公開されるみたいなのでそちらで実行してみました。

```
$ 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
```

`http://127.0.0.1:53157` にブラウザからアクセスすると以下のように nginx に接続できました。

![n.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/bfac425e-5512-a27a-c6c4-a88870dc1885.png)


## Pod をデバッグする

多くの場合、デプロイした Pod をデバッグする必要があります。
`kubectl exec` でのログインや `kubectl describe` は Pod のデバッグに役立ちます。

この節では、この２つに加えてデバッグで便利な２つのコマンド `kubectl logs` と `kubectl port-forward` について説明します。

### コンテナのログを見る

`kubectl logs` コマンドでログが確認できます。

```
# 現在の Pod 一覧
$ 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

# 特定の 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/"

# ラベルに該当する Pod に対してログを表示する
$ 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" "-"
```

### ポートフォワードする

`kubectl port-forward` コマンドを使うと、Pod の特定のポートをローカルホストにポートフォワードすることができます。
これを使えばローカルの curl コマンドやブラウザから直接 Pod にアクセスできます。

例えば、以下のコマンドを実行するとローカルの 8080 をクラスタ内の nginx の 80 にフォワードしてくれます。

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

```

`localhost:8080` にアクセスすると "Welcome to nginx!" が表示されます。

![o.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/007a8461-14f4-afab-57fc-b74f56fb48ea.png)

# More Introduction to Kubernetes

さらに本番環境での運用を想定して重要な機能を説明します。

ここではさらっと触れる程度での紹介になります。

## ヘルスチェック

Kubernetes のヘルスチェックは以下の２つがあり、用途に応じて使い分けることができます。

- Liveness probe ― Pod が生きているか死んでいるかをチェックします。一定回数 Liveness probe が失敗した場合、Pod は再起動されます。
- Readiness probe ― Pod がリクエストに応答できるかどうかをチェックします。Readiness probe が成功するまでの間、その Pod は起動が完了していないとみなされ、Service のリバプロ先に追加されません。Readiness probe を使うことで Pod が起動が完了する前にリクエストが飛んでくる現象を防ぐことができます。

研修資料のサンプルは動かすことができなかったので、[こちら](https://qiita.com/yuta_vamdemic/items/1438455946b7533b921f)の記事を参考に YAML ファイルを定義しました。

```yaml: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
```

apply してみます。

```
$ 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

# describe で Liveness の設定を確認
$ kubectl describe pod sample-healthcheck | grep "Liveness"
    Liveness:       http-get http://:80/index.html delay=5s timeout=1s period=3s #success=1 #failure=2

# describe で Readiness の設定を確認
$ 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
```

実際にわざとヘルスチェックが発動するように実験します。

```
# 先に別のコンソールで Pod の状態を監視
$ kubectl get pods sample-healthcheck --watch

# DocumentRoot の index.html の削除（Liveness での監視対象のファイルの削除）
$ kubectl exec -it sample-healthcheck rm /usr/share/nginx/html/index.html

# Pod が再起動する（再起動後は index.html が作成されるため再起動は繰り返さない）
$ 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 probe, Readiness probe は運用上非常に重要なため、常駐する Pod には必ず両方設定しておくとよいとのことです。

## Resource Requests/Limits

Pod に対してリソースをあらかじめ設定することができます。

設定は2種類あります。

- Resource Requests
コンテナが要求するメモリやCPUの量を宣言する機能で、Kubernetes のスケジューラは Resource Requests の値を見て Pod をデプロイするノードを決めます。
- Resource Limits
そのコンテナが実際に使用できるメモリやCPUの量の上限を設定する機能です。 コンテナが使用するメモリが設定された上限を超えた場合、そのコンテナは kill されます。 また CPU 使用量が上限に達した場合、このコンテナはスロットリングされます。

YMML ファイルの例です。

```yaml: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 して設定を確認します。

```
$ kubectl apply -f sample-resource.yaml

$ kubectl describe pod nginx
（省略）
    Limits:
      cpu:     500m
      memory:  128Mi
    Requests:
      cpu:        250m
      memory:     128Mi
（省略）
```

## Volume

Pod に複数のコンテナがあるとき、それらの間でローカルディレクトリを共有したいことがあります。
例えば、APサーバーが出力するログファイルをログ転送エージェントで転送するようなユースケースが挙げられます。

ちなみにサンプルでは fluentd を利用しますが、OSS のデータログ収集ツールとしてはかなり有名なものになります。

```yaml: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して確認します。

```
$ 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
（省略）
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)
（省略）
```

概念的には以下のような図になります。

![p.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/89f529c8-66b6-8891-e9f2-18f3b37ed810.png)


## Anti Affinity

Deployment を使って同じ Pod を２つ作っているとします。 何も指定していないとその２つの Pod が同じノードにスケジュールされることがあります。 このような場合、そのノードが障害やメンテナンスなどで死亡すると２つの Pod が同時に消失してしまいます。

Affinity の機能を使えば Pod が異なるノードに配置されるようにスケジューラーに要求することができます。 Kubernetes の Affinity は非常に柔軟で様々なことができますが、ここでは上で挙げたユースケースに絞って説明します。

```yaml: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"

```

定義では `replicas: 2` と 2 つの Pod が立つようにしています。

そこに `affinity:` からの記述で Kubernetes がこの Pod をデプロイするときに `component: nginx` というラベルを持つ Pod がいないノードにデプロイしようとします。

これにより、ノードが一台死亡したとしても nginx の Pod がひとつ以上生き残ることが保証されます。

Minikube のシングルノード環境で applyしてみます。

```
$ 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
```

シングルノードのため、1 つ Pod が立つと、もう 1 つの Pod がデプロイされずに Pending になっていることがわかります。

このように条件を満たすノードが存在しないときは、Pod はデプロイされず、条件を満たすノードが出現するまで Pending 状態になります。

長時間 Pending になっている Pod が存在しないかどうかは監視項目に含めておくとよいとのことです。

## Pod Disruption Budget

ノードの再起動が必要になる場合があります。 例えば Linux カーネルに脆弱性が発見された場合などです。

Pod を複数立てて冗長化しておけば、単一のノードのシャットダウンには耐えることができます。
しかし、ノードを次々と再起動していく状況では問題が起こることがあります。

例えば、下の図のように３台のノードからなるクラスタの上に２つの Pod がデプロイされている場合について考えます。
この2つの Pod は Deployment によりレプリカ数が２になるように設定されています。
このクラスタに対して、ノードを１台ずつローリングリブートしていきます。

![ll.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259125/b86013b4-6878-bfe1-9a09-5ebcbb9ab4ff.png)

１台目のノードをシャットダウンする過程で、その上で走っていた Pod が Evict されます。 Pod の数が減ったことを検知した Deployment が直ちに新しい Pod をデプロイします。 しかし、この Pod の起動には時間がかかるため、starting の状態で止まっています。 この状況で node 2 をシャットダウンすると、available な Pod が存在しなくなり、サービスが停止します。

これを防ぐには Pod Disruption Budget によってサービスの稼働に必要な Pod の数の最小値を指定しましょう。

PDB を指定すると、Kubernetes のツールがノードをシャットダウンする前にこの制約が満たされるように適切に待ってくれます。

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

apply して確認します。

```
$ 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
```

当然ですが、PDB の制約が守られるのはクラスタ管理者が意図してノードをシャットダウンする場合だけです。 障害でノードが死ぬ場合には PDB は無視されます。 また、Deployment を削除する場合も PDB は適用されません。

Pod が available かどうかの判定は Readiness probe によって行われます。PDB を正しく機能させるためには、Pod に適切な Readiness probe を設定しておく必要があります。

# Exercise

研修資料にある Exercise はやっていません。

ごめんなさい。。。

以上になります。