Kubernetes コトハジメ

少し前に、とあるサービスを Kubernetes でデプロイしました。Kubernetes にはとても多くの機能が実装されていて、その多くの機能のほんの一部でしかないのですが、デプロイにあたって最低限必要そうな機能を公式のドキュメントを読みながら少し試してみたので投稿します。

## Kubernetes のアーキテクチャ

Kubernetes は、Master と Node の 2 種類のノード群から構成されるクラスタシステムになっています。
管理者は、Master 上で `kubectl` コマンドを実行して Node に「リソース」を配備していきます。
本記事では、KVM を利用してクラスタ環境を構築しました。Master のホスト名は kubemaster、Node のホスト名はkuberworker です。Kubernetes のバージョンは、v1.11 です。

## Kubernetes リソース

[公式](https://kubernetes.io/docs/concepts/) には、以下のリソースについて記述されています。

| 分類 | 説明 |
|:--|:--|
| Workloads | コンテナの実行・管理に関するリソース |
| Configuration | 計算機資源の割り当てや管理、機密情報の秘匿に関するリソース |
| Services, Load Balancing, and Networking | コンテナの外部ネットワークへの接続に関するリソース |
| Storage | ディスク領域の利用方法に関するリソース |
| Policies | クォータやセキュリティなどに関するリソース |

本記事の内容は、Workloads リソースと Services リソースの一部についてです。

## Workloads リソース

### Pod

Node にデプロイされるのは Pod と呼ばれる単位で、Kubernetes は直接コンテナを管理するのではなく、この Pod を管理すると記述されています。1Pod に 1 コンテナが一般的のようですが、Proxy の役割をするコンテナや SSL 終端用のコンテナなど 1Pod に複数コンテナを起動するデプロイ方法も肯定されています。メインコンテナの補助の役割を果たすサブコンテナはサイドカーと呼ばれます。また、このとき、同一 Pod に属しているコンテナ同士は、IP アドレスやポートを共有し、単に同じサーバで起動しているプロセス同士のような扱いになります。そのため、コンテナ内で起動しているサービス同士は、localhost:\<port> の形式で相互にやりとりができます。ホスト名は Pod 名を共有します。

同一 Pod 内に NGINX コンテナとクライアントのコンテナを起動し、コンテナ間アクセスを検証してみます。
YAML ファイルは以下のように作成しました。CentOS 7.5 のイメージから起動しているコンテナがクライアントです。

```yaml:nginx-pod.yml
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
        - containerPort: 80
    - name: centos
      image: centos:7.5.1804
      command: ["/sbin/init"]
```

`kubectl apply` コマンドによって Pod をデプロイします。

```
[homulilly@kubemaster ~]$ kubectl apply -f nginx-pod.yml 
pod/nginx-pod created

[homulilly@kubemaster ~]$ kubectl get pods
NAME        READY     STATUS    RESTARTS   AGE
nginx-pod   2/2       Running   0          7s
```

nginx-pod 内の centos コンテナにログインし、`curl` コマンドによって localhost:80 にアクセスしてみます。

```
[homulilly@kubemaster ~]$ kubectl exec -ti nginx-pod -c centos bash

[root@nginx-pod /]# curl http://localhost
<!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>
```

成功しました。ネットワークだけでなくボリュームも共有するような、密に連携するコンテナ同士の例が公式には挙げられています。

### ReplicaSet

ReplicaSet は Pod のレプリカ数管理を行います。ReplicaSet 単独で起動することも可能ですが、ReplicaSet を管理する後述の Deployment を起動する方が一般的なようです。また、label selector によって Kubernetes オブジェクトに対して付与した label を利用してオブジェクト横断的な操作が可能です。ReplicaSet では、=/==/!= によって label を選択する equality-based 方式と in/notin/exists によって label を選択する set-based 方式がサポートされています。

NGINX コンテナを内包する Pod を管理する ReplicaSet を生成します。YAML ファイルは以下のように作成しました。

```yaml:nginx-replicaset.yml
---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-rs
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
```

`kubectl apply` コマンドによって Replicaset をデプロイします。

```
[homulilly@kubemaster ~]$ kubectl apply -f nginx-replicaset.yml 

[homulilly@kubemaster ~]$ kubectl get replicaset -o wide
NAME        DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-rs    2         2         2         17s       nginx        nginx:latest      app=nginx

[homulilly@kubemaster ~]$ kubectl get pods -o wide
NAME              READY     STATUS    RESTARTS   AGE       IP          NODE
nginx-rs-qpxmh    1/1       Running   0          14m       10.44.0.3   kubeworker
nginx-rs-t7frx    1/1       Running   0          14m       10.44.0.4   kubeworker
```

Pod が 2 レプリカ生成されています。
Replicaset のレプリカ数管理によって、Pod が削除されてもすぐに再生成されることを確認します。

```
[homulilly@kubemaster ~]$ kubectl delete pod/nginx-rs-t7frx
pod "nginx-rs-t7frx" deleted

[homulilly@kubemaster ~]$ kubectl get pods -o wide
NAME              READY     STATUS    RESTARTS   AGE       IP          NODE
nginx-rs-qpxmh    1/1       Running   0          54m       10.44.0.3   kubeworker
nginx-rs-rs6z5    1/1       Running   0          8s        10.44.0.4   kubeworker
```

nginx-rs-t7frx の削除後、新たに nginx-rs-rs6z5 が起動しています。
次はスケールアウトを行います。nginx-rs のレプリカ数を 5 にします。

```
[homulilly@kubemaster ~]$ kubectl scale rs nginx-rs --replicas 5
replicaset.extensions/nginx-rs scaled

[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME        DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-rs    5         5         5         1h        nginx        nginx:latest      app=nginx

[homulilly@kubemaster ~]$ kubectl get pods -o wide
NAME              READY     STATUS    RESTARTS   AGE       IP          NODE
nginx-rs-2tl74    1/1       Running   0          37s       10.44.0.6   kubeworker
nginx-rs-qdktp    1/1       Running   0          37s       10.44.0.8   kubeworker
nginx-rs-qpxmh    1/1       Running   0          1h        10.44.0.3   kubeworker
nginx-rs-rs6z5    1/1       Running   0          10m       10.44.0.4   kubeworker
nginx-rs-v6dbd    1/1       Running   0          37s       10.44.0.7   kubeworker
```

増えました。`kubectl` コマンドで直接スケールアウトしましたが、YAML ファイルのレプリカ数を編集後、再度 `kubectl apply` でも同様です。

### Deployment

Deployment は、ReplicaSet を管理することで Pod のローリングアップデートやロールバックを行います。仕組みとしては単純で、

1. 新しい ReplicaSet を生成する
2. 新しい ReplicaSet 上のレプリカ数（Pod 数）を徐々に増やす
3. 古い ReplicaSet 上のレプリカ数（Pod 数）を徐々に減らす
4.（2、3をくり返す）
5. 古い ReplicaSet はレプリカ数0で保持する

というフローを取ります。この時、古い ReplicaSet から新しい ReplicaSet へ一度に移行するか 1Pod ずつ移行するかといった移行の速度も調整することができるようです。

NGINX コンテナを内包する Pod を管理する Replicaset を管理する Deployment を生成します。YAML ファイルは以下のように作成しました。

```yaml:nginx-deployment.yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80
```

`kubectl apply` コマンドによって Deployment をデプロイします。

```
[homulilly@kubemaster ~]$ kubectl apply -f nginx-deployment.yml 
deployment.apps/nginx-deployment created

[homulilly@kubemaster ~]$ kubectl get deployment -o wide
NAME                DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment    2         2         2            2           39s       nginx        nginx:1.7.9       app=nginx

[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME                           DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment-5ff86cd6fb    2         2         2         2m        nginx        nginx:1.7.9       app=nginx,pod-template-hash=1994278296

[homulilly@kubemaster ~]$ kubectl get pod -o wide
NAME                                 READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-deployment-5ff86cd6fb-fb8fr    1/1       Running   0          2m        10.44.0.9    kubeworker
nginx-deployment-5ff86cd6fb-l6shs    1/1       Running   0          2m        10.44.0.11   kubeworker
```

NGINX のバージョンを 1.7.9 から 1.9.1 にアップデートします。

```
[homulilly@kubemaster ~]$ kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
deployment.extensions/nginx-deployment image updated

[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME                           DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment-5ff86cd6fb    0         0         0         1h        nginx        nginx:1.7.9       app=nginx,pod-template-hash=1994278296
nginx-deployment-6947d8b574    2         2         2         26s       nginx        nginx:1.9.1       app=nginx,pod-template-hash=2503846130
```

NGINX 1.7.9 が動作するコンテナを内包する Pod を管理する ReplicaSet には Pod が存在しません。
NGINX 1.9.1 が動作するコンテナを内包する Pod を管理する ReplicaSet の方には 2 つのレプリカが起動しています。

NGINX のバージョンを 1.9.1 から 1.7.9 にダウングレードします。

```
[homulilly@kubemaster ~]$ kubectl edit deployment/nginx-deployment
deployment.extensions/nginx-deployment edited
```

メタデータを vi エディタで編集できます。NGINX のバージョン情報を書き換えました。

```
[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME                           DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment-5ff86cd6fb    2         2         1         1h        nginx        nginx:1.7.9       app=nginx,pod-template-hash=1994278296
nginx-deployment-6947d8b574    1         1         1         5m        nginx        nginx:1.9.1       app=nginx,pod-template-hash=2503846130

[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME                           DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment-5ff86cd6fb    2         2         2         1h        nginx        nginx:1.7.9       app=nginx,pod-template-hash=1994278296
nginx-deployment-6947d8b574    0         0         0         5m        nginx        nginx:1.9.1       app=nginx,pod-template-hash=2503846130
```

徐々に NGINX のバージョンが切り替わっていったことを確認できました。
次は、ロールバックという形で NGINX のバージョンを 1.9.1 に戻します。

```
[homulilly@kubemaster ~]$ kubectl rollout undo deployment nginx-deployment
deployment.extensions/nginx-deployment

[homulilly@kubemaster ~]$ kubectl get rs -o wide
NAME                           DESIRED   CURRENT   READY     AGE       CONTAINERS   IMAGES            SELECTOR
nginx-deployment-5ff86cd6fb    0         0         0         5h        nginx        nginx:1.7.9       app=nginx,pod-template-hash=1994278296
nginx-deployment-6947d8b574    2         2         2         3h        nginx        nginx:1.9.1       app=nginx,pod-template-hash=2503846130
```

## Service リソース

Service リソースは、コンテナの外部ネットワークへの接続や、label に一致するコンテナのディスカバリに利用されます。L4 Load Balancing を行う Service に対し、L7 Load Balancing を行う Ingress リソースもあるようです。

NGINX コンテナを外部ネットワークに接続します。YAML ファイルは以下のように作成しました。type には NodePort を指定しました。Node (kubeworker) の固定ポート上でコンテナを外部に公開する方式のようです。他には、クラウドサービスプロバイダのロードバランサを利用する type なんかもあるようです。

```yaml:nginx-service.yml
[homulilly@kubemaster ~]$ cat nginx-service.yml 
---
apiVersion: v1
kind: Service
metadata: 
  name: nginx-nodeport
spec:
  type: NodePort
  ports:
    - port: 80
  selector:
    app: nginx
```

`kubectl apply` コマンドによって Service をデプロイします。

```
[homulilly@kubemaster ~]$ kubectl apply -f nginx-service.yml 
service/nginx-nodeport created

[homulilly@kubemaster ~]$ kubectl get svc -o wide
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE       SELECTOR
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP        23d       <none>
nginx-nodeport   NodePort    10.96.197.250   <none>        80:30773/TCP   22h       app=nginx
```

NGINX コンテナの 80 番ポートが kubeworker の 30773 番とバインドされているようです。そこで、kubeworker の VM が動作しているホスト OS 上で Firewalld に kubeworker:30773 へのポートフォワーディングルールを追加したところ、ブラウザでNGINX コンテナまでたどり着けるようになりました。

```
[homulilly@kubemaster ~]$ kubectl describe service nginx
Name:                     nginx-nodeport
Namespace:                default
Labels:                   <none>
Annotations:              kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"nginx-nodeport","namespace":"default"},"spec":{"ports":[{"port":80}],"selector...
Selector:                 app=nginx
Type:                     NodePort
IP:                       10.96.197.250
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30773/TCP
Endpoints:                10.44.0.3:80,10.44.0.4:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
```

nginx-nodeport の Endpoints には、2 つのレプリカの IP アドレスが登録されていました。

## まとめ

以上で、デプロイするサービスを内包した Docker イメージさえ作ってしまえば、コンテナを起動して外部に公開できるところまできました。公開するだけなら Deployment と Service 用の YAML ファイルさえ作ればいいということですね。