Kubernetesで実践するクラウドネイティブDevOps -4章-

# 経緯

[Kubernetesで実践するクラウドネイティブDevOps](https://www.oreilly.co.jp/books/9784873119014/)という本を買ったので、実際に手を動かして学んでいきたいと思います。

コンテナとかKubernetesを全然知らないわけではないのですが、雰囲気でやってる部分が多いので再確認する意味でもしっかりやっていきたいと思います。

今回はKuberneteオブジェクトの概念を理解する4章です。

# Deployment

本番でコンテナを使うにはLinux（Unix）でいうsystemd、runit、supervisordのようにコンテナの状態をスーパーバイズ（監督）し、停止していた場合は起動するするような機能が必要です。

KubernetesではDeploymentがその役割を務めます。

Deploymentオブジェクトには、コンテナイメージの名前や実行したいレプリカの数をはじめ、コンテナを起動するために知っておく必要のある情報がすべて格納されます。

Deploymentリソースと連携して機能するのがコントローラです。コントローラは担当するリソースを監視して、それらのリソースが存在し機能していることを確認します。コントローラは望ましい状態を保証するために、Deploymentが定義された数のレプリカを実行していない場合はレプリカの数を増減させる役割を担っています。（実際にはReplicasetと呼ばれる関連オブジェクトが自動作成され、これがレプリカの管理をします。）

Deploymentは基本的に以下のような挙動をします

 * コンテナが仕事を完了して終了した場合、Deploymentはそのコンテナを再起動する
 * コンテナがクラッシュ、ユーザがシグナルを送って終了する、kubectlで終了してもDeploymentはそのコンテナを再起動する

上記はデフォルトでの挙動で設定変更することが可能だが、高信頼性の確保のために上記のような挙動がデフォルトになっています。

Deploymentの仕事は関連づいているコンテナを監視して、指定された数のレプリカが常に実行されている状態を確保することです。少なければ追加で起動し多ければ一部を終了させます。

以下のコマンドで現在のNamespaceで有効になっているすべてのDeploymentを確認できます

```bash
$ kubectl get deployments
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
demo   1/1     1            1           19d
```

Deploymentの詳細を確認したい場合は次のコマンド

```bash
$ kubectl describe deployments/demo
Name:                   demo
Namespace:              default
CreationTimestamp:      Mon, 24 Feb 2020 13:19:13 +0900
Labels:                 app=demo
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=demo
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=demo
  Containers:
   demo:
    Image:        <hoge>/myhello
    Port:         8888/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   demo-68b4fdb89b (1/1 replicas created)
Events:          <none>

```
`Pod Template` フィールドにPodの定義があるのですが、まずはPodについて確認しましょう。

# Pod

Podとは1つ以上のコンテナのグループを表現するKubernetesオブジェクトです。

Deploymentは個別のコンテナではなく、Pod単位で管理します。

Podの仕様にはContainersというリストがあり、先の例ではdemoに関する記述があります。

```yaml
   demo:
    Image:        <hoge>/myhello
    Port:         8888/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
```

ImageスペックとPort番号が、DeploymentがPodを起動して実行し続けるために必要な情報です。

kubectl runコマンドはPodを直接作成したわけではなく、Deploymentを作成しました。

これを受けてDeploymentがPodを作成したのです。

つまりDeploymentとは「myhelloというコンテナが内部にあるPodを1つ実行し続ける必要がある」という望ましい状態の宣言です。


# Replicaset

Deploymentが直接Podを管理するのではなく、実際にはReplicasetオブジェクトが行っています。

Replicasetが担当するのは同一のPod、レプリカのグループを管理することです。実行されているPodの数がスペックと比較して差分がある場合は、状況を是正するためにReplicasetコントローラが必要な数のPodを起動（または終了）します。

次にDeploymentがReplicasetを管理して、ユーザがレプリカを更新する際の挙動をコントロールします。

例えば、アプリケーションの新バージョンをデプロイするときに、ユーザがDeploymentを更新する際には、新しいReplicasetが作成されて新しいPodを管理し、更新が完了した時点で古いReplicasetとそのPodは終了されます。


# 望ましい状態の維持

Kubernetesのコントローラは、個々のリソースによって指定された望ましい状態に照らし合わせて継続的にチェックしています。これは永続的にループすることから、調整ループ（reconcliation loop）と呼ばれます。

Podを手動で停止して確かめてみましょう。

まずはPodの起動を確認します。

```
$ kubectl get pods --selector app=demo
NAME                    READY   STATUS    RESTARTS   AGE
demo-68b4fdb89b-r8fj4   1/1     Running   0          17s
```

Podを終了させます。

```
$ kubectl delete pods --selector app=demo
pod "demo-68b4fdb89b-r8fj4" deleted
```

この時にPodをListを表示させると、`Terminating` のコンテナと `ContainerCreating ` のコンテナがあることが確認できます。

```
$ kubectl get pods --selector app=demo
NAME                    READY   STATUS              RESTARTS   AGE
demo-68b4fdb89b-r8fj4   0/1     Terminating         0          81s
demo-68b4fdb89b-sj8gm   0/1     ContainerCreating   0          3s
```

最初と同じようにPodが1つの状態に戻っていますが、名前が変わっています。

```
$ kubectl get pods --selector app=demo
NAME                    READY   STATUS    RESTARTS   AGE
demo-68b4fdb89b-sj8gm   1/1     Running   0          19s
```

このように、ユーザが消した場合でもPodの状態を望ましい状態に保とうとします。

全てを消す場合は以下のコマンドでDeploymentを停止します。

```
$ kubectl delete all  --selector app=demo
pod "demo-68b4fdb89b-sj8gm" deleted
deployment.apps "demo" deleted
replicaset.apps "demo-68b4fdb89b" deleted
```

# Kubernetesスケジューラ

Kubernetesスケジューラの仕事は、スケジューリングされていないPodのキューを監視し、そこから次のPodを取り出して、実行場所となるノードを見つけることです。

Podがノードにスケジューリングされると、そのノードで実行されているkubeletが対応してPodに含まれるコンテナを実際に起動します。


# YAML形式のリソースマニフェスト

Kubernetesは本質的に宣言的なシステムなので、望ましい状態に照らして現在の状態を調整します。

具体的にはリソースのマニフェストファイルをユーザが変更し、Kubernetesに読み取るように指示をすれば、あとはKubernetes任せに出来ます。

以下のようなDeploymentマニフェストを実行してみましょう。


```yaml:hello-k8s/k8s/deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  labels:
    app: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
        - name: demo
          image: cloudnatived/demo:hello
          ports:
          - containerPort: 8888
```

`kubectl apply` コマンドでYAMLマニフェストをクラスタに送信します。

```
$ kubectl apply -f k8s/deployment.yaml 
deployment.apps/demo created
```

Podの作成を確認します。

```
$ kubectl get pods --selector app=demo
NAME                    READY   STATUS    RESTARTS   AGE
demo-7cbf698c44-mcv76   1/1     Running   0          20s
```

このdemo PodのWebブラウザでアクセスするには、Serviceというリソースが必要です。

## Serviceリソース

ServiceはロードバランサやRプロキシのように振舞い、バックエンドにあるPodグループにリクエストを転送します。

HTTPなどのWebだけではなく任意のPortから任意のPortに転送できます。

今回のデモで使うServiceのマニフェストを確認してみましょう

```yaml:k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demo
  labels:
    app: demo
spec:
  ports:
  - port: 9999
    protocol: TCP
    targetPort: 8888
  selector:
    app: demo
  type: ClusterIP
```
これは `9999/tcp` に来たトラフィックを `8888/tcp` に転送しています。

`selector` で指定した `app: demo` ラベルを持つPodに転送します。複数ある場合は分散して転送します。

ここで重要なのは、ユーザのアプリケーションのためのPodのセットを管理するのがDeploymentであり、こうしたPodに対するリクエストをルーティングするために単一のエントリポイントを提供するのがServiceであるという事です。

# Helm

Kubernetes用のパッケージマネージャとして人気が高いのがHelmです。

Helmコマンドラインツールを使って、アプリケーションをインストールおよび設定できます。また、Helmチャートと呼ばれるパッケージを作成できます。

YumやAPTなどに近いと思われるかもしれませんが異なる点があります。YumやAPTはバイナリ形式のファイルをダウンロードor作成しますが、Helmにはコンテナイメージが含まれません。KubernetesのDeploymentと同じように、イメージを発見できる場所に関するメタデータが含まれるだけです。

実のところ、HelmチャートはKubernetesのYAMLマニフェストを包み込むラッパのようなものです。

## Helmのインストール

公式手順（https://v2.helm.sh/docs/using_helm/#installing-helm）に従ってインストールします。
環境はUbuntu18.04です。

helmは2系と3系で動きが異なり、この本は2系を使っているので2系をインストールします。

```bash
$ wget https://get.helm.sh/helm-v2.16.6-linux-amd64.tar.gz
$ tar xvzf helm-v2.16.6-linux-amd64.tar.gz
$ sudo mv linux-amd64/helm /usr/local/bin/helm
$ helm version
Client: &version.Version{SemVer:"v2.16.6", GitCommit:"dd2e5695da88625b190e6b22e9542550ab503a47", GitTreeState:"clean"}
Error: could not find tiller
```

無事入りました。クラスタにアクセスする権限をHelmに付与するためのKubernetesリソースを作成する必要があります。

demoにそのためのYAMLがあるので適用しましょう。

```
$ cd demo/
$ cd hello-helm/
$ kubectl apply -f helm-auth.yaml 
```

次にHelmの初期化です。

```
$ helm init --service-account tiller
Creating /home/ryo/.helm 
Creating /home/ryo/.helm/repository 
Creating /home/ryo/.helm/repository/cache
Creating /home/ryo/.helm/repository/local
Creating /home/ryo/.helm/plugins
Creating /home/ryo/.helm/starters
Creating /home/ryo/.helm/cache/archive
Creating /home/ryo/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts 
$HELM_HOME has been configured at /home/ryo/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://v2.helm.sh/docs/securing_installation/
```

`helm version` コマンドで以下のように表示されれば準備完了です。

```
$ helm version
Client: &version.Version{SemVer:"v2.16.6", GitCommit:"dd2e5695da88625b190e6b22e9542550ab503a47", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.16.6", GitCommit:"dd2e5695da88625b190e6b22e9542550ab503a47", GitTreeState:"clean"}
```

## Helmチャートのインストール

`demo/hello-helm/k8s/demo` ディレクトリには以下のようなHelmチャートがあります。

```
$ ls demo/hello-helm/k8s/demo
Chart.yaml  production-values.yaml  staging-values.yaml  templates  values.yaml
```

とりあえず動かしてみましょう。

```
$ helm install --name demo demo/hello-helm/k8s/demo
NAME:   demo
LAST DEPLOYED: Sat Apr 18 16:13:23 2020
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Deployment
NAME  READY  UP-TO-DATE  AVAILABLE  AGE
demo  0/1    1           0          0s

==> v1/Pod(related)
NAME                   READY  STATUS             RESTARTS  AGE
demo-57457fb5fb-2z8ct  0/1    ContainerCreating  0         0s

==> v1/Service
NAME          TYPE          CLUSTER-IP    EXTERNAL-IP  PORT(S)       AGE
demo-service  LoadBalancer  10.105.38.26  <pending>    80:32261/TCP  0s
```

実行されているリリースは以下のコマンドで確認できます。

```
$ helm list
NAME    REVISION        UPDATED                         STATUS          CHART           APP VERSION     NAMESPACE
demo    1               Sat Apr 18 16:13:23 2020        DEPLOYED        demo-1.0.1                      default
```

## Helmで重要な用語

以下の用語を理解しておく必要があります

- **チャート**とは、アプリケーションをKubernetesで実行するために必要なリソースの定義がすべて格納されるHelmパッケージです
- **リポジトリ**とは、チャートを収集及び共有できる場所です
- **リリース**とは、Kubernetesクラスタで実行されるチャートの特定のインスタンスです


# まとめ

- PodはKubernetesリーソースの一種で、Kubernetesにおける仕事の最小単位であり、単一のコンテナまたは組み合わせで同じノードにスケジューリングされ相互に通信するコンテナのグループです
- Deploymentは高水準のKubernetesリソースであり、Podを宣言的に管理し、必要に応じてPodをデプロイ、スケジューリング、更新、起動されます
- ServiceはKubernetesリソースの一種で、ロードバランサやプロキシに相当する機能を提供し、単一かつ周知の永続的なIPアドレスまたはDNS名を通じて、条件に一致するPodのグループにトラフィックをルーティングします
- Kubernetesスケジューラは、どのノードでもまだ実行されていないPodを監視し、それに適したノードを見つけてPodの実行をノードのKubeletに指示します
- HelmはKubernetes用のパッケージマネージャです。Kubernetesアプリケーションの設定とデプロイを簡素化し、生のYAMLファイルをユーザが保守しなくても、値の単一のセット（アプリケーションや待ち受けるポートなど）やテンプレートのセットを用いてKubernetes用のYAMLファイルを生成できるようにします。