Kubernetes デプロイメントについての自習メモ（その２）



Kubernetes のデプロイメントについて自習したメモで、[Kubernetes デプロイメントについての自習メモ（その１）](https://qiita.com/MahoTakara/items/0245a9f18c7a97bcdd31)の続きです。 

## スケーリング

### 手動でスケール

初期状態で、レプリカ・セット数 3 で動作していたとします。

~~~
$ kubectl get rs
NAME                              DESIRED   CURRENT   READY     AGE
nginx-web-deployment-765656d945   3         3         3         9m
~~~
下記のコマンドで `--replicas=10` として ポッド数を増加（スケール）します。

~~~
$ kubectl scale deployment nginx-web-deployment --replicas=10
deployment "nginx-web-deployment" scaled
~~~
コマンド投入直後の状態です。 ポッドが10個に増え、そのうち、準備完了したものが 6ポッドあることが読み取れます。

~~~
$ kubectl get rs
NAME                              DESIRED   CURRENT   READY     AGE
nginx-web-deployment-765656d945   10        10        6         9m
~~~
スケールが完了した状態です。

~~~
$ kubectl get rs
NAME                              DESIRED   CURRENT   READY     AGE
nginx-web-deployment-765656d945   10        10        10        10m
~~~


### CPU稼働率によるスケール

ポッドのCPU使用率の閾値を超えると、レプリカ・セットの数を増加させる事ができます。 ポッドのCPU使用率とは、ノードのCPU使用率ではなくポッド毎に集計した値ということになります。 

~~~~
$ kubectl autoscale deployment nginx-web-deployment --min=10 --max=15 --cpu-percent=80
deployment "nginx-web-deployment" autoscaled
~~~~

参考資料
* [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
* [Horizontal Pod Autoscaler Walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)
* [Autoscaling Algorithm](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/horizontal-pod-autoscaler.md#autoscaling-algorithm)


## ロールアウトの一時停止と再開

これはアプリケーションのコンテナを一時的に停止させるという意味ではなく、ロールアウトを一時的に停止しておいて、複数のリクエストを適用後に、デプロイメントを再開することで、無駄なロールオーバーなどを発生させずに、リリースを実施する機能です。

### 最初のバージョンのアプリをリリース

~~~
$ kubectl create -f nginx-web1.yml
deployment "nginx-web-deployment" created
~~~

~~~
$ kubectl get deploy
NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-web-deployment   3         3         3            3           3s
~~~

~~~
$ kubectl get pod
NAME                                    READY     STATUS    RESTARTS   AGE
nginx-web-deployment-765656d945-frjhc   1/1       Running   0          9s
nginx-web-deployment-765656d945-jr9t2   1/1       Running   0          9s
nginx-web-deployment-765656d945-wx5sc   1/1       Running   0          9s
~~~

### ロールアウトの一時停止（ポーズ）

~~~
$ kubectl rollout pause deployment/nginx-web-deployment
deployment "nginx-web-deployment" paused
~~~
アプリのコンテナ・イメージを Web:v2 へ更新

~~~
$ kubectl apply -f nginx-web2.yml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment "nginx-web-deployment" configured
~~~
上記コマンド実行直後の状態ですが、レプリカセットのハッシュ 765656d945 は、最初にデプロイした時から変わっていません。

~~~
$ kubectl get pod
NAME                                    READY     STATUS    RESTARTS   AGE
nginx-web-deployment-765656d945-frjhc   1/1       Running   0          58s
nginx-web-deployment-765656d945-jr9t2   1/1       Running   0          58s
nginx-web-deployment-765656d945-wx5sc   1/1       Running   0          58s
~~~
さらに、アプリのコンテナ・イメージを Web:v3 へ更新します。

~~~
$ kubectl apply -f nginx-web3.yml
deployment "nginx-web-deployment" configured
~~~
結果を確認したすると、ハッシュ値から Web:v1 のままです。

~~~
$ kubectl get pod
NAME                                    READY     STATUS    RESTARTS   AGE
nginx-web-deployment-765656d945-frjhc   1/1       Running   0          1m
nginx-web-deployment-765656d945-jr9t2   1/1       Running   0          1m
nginx-web-deployment-765656d945-wx5sc   1/1       Running   0          1m
~~~

### ロールアウトの再開（レジューム）
次のコマンドを投入して、一時停止状態を解除します。

~~~
$ kubectl rollout resume deployment/nginx-web-deployment
deployment "nginx-web-deployment" resumed
~~~
再開直後から、ロールアウトが始まります。

~~~
$ kubectl get pod
NAME                                    READY     STATUS              RESTARTS   AGE
nginx-web-deployment-758c4bbc7f-4htz6   1/1       Running             0          4s
nginx-web-deployment-758c4bbc7f-6hgl7   1/1       Running             0          4s
nginx-web-deployment-758c4bbc7f-rf9b7   0/1       ContainerCreating   0          1s
nginx-web-deployment-765656d945-frjhc   1/1       Terminating         0          1m
nginx-web-deployment-765656d945-jr9t2   0/1       Terminating         0          1m
nginx-web-deployment-765656d945-wx5sc   1/1       Terminating         0          1m
~~~
次はロールアウトが完了した状態です。

~~~
$ kubectl get pod
NAME                                    READY     STATUS    RESTARTS   AGE
nginx-web-deployment-758c4bbc7f-4htz6   1/1       Running   0          17s
nginx-web-deployment-758c4bbc7f-6hgl7   1/1       Running   0          17s
nginx-web-deployment-758c4bbc7f-rf9b7   1/1       Running   0          14s
~~~
このハッシュは、Web:v2なのか それとも、Web:v3なのか を確認してします。 次の`kubectl describe deploy`の結果のImageのフィールドから Web:v3であることが判ります。つまり、Web:v2の無駄なロールアウトが回避され、Web:v3へ切替わったことが理解できます。

~~~
$ kubectl describe deploy
Name:                   nginx-web-deployment
Namespace:              default
CreationTimestamp:      Sat, 16 Dec 2017 05:08:19 +0000
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=2
                        kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"extensions/v1beta1","kind":"Deployment","metadata":{"annotations":{},"name":"nginx-web-deployment","namespace":"default"},"spec":{"repli...
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        registry.ng.bluemix.net/takara/web:v3
    Port:         80/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-web-deployment-758c4bbc7f (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  6m    deployment-controller  Scaled up replica set nginx-web-deployment-765656d945 to 3
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled up replica set nginx-web-deployment-758c4bbc7f to 1
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled down replica set nginx-web-deployment-765656d945 to 2
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled up replica set nginx-web-deployment-758c4bbc7f to 2
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled down replica set nginx-web-deployment-765656d945 to 1
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled up replica set nginx-web-deployment-758c4bbc7f to 3
  Normal  ScalingReplicaSet  5m    deployment-controller  Scaled down replica set nginx-web-deployment-765656d945 to 0
~~~



## デプロイメントの状態

デプロイメントはライフサイクル中に、その段階に応じた状態が変化します。 この状態について、理解を進めていきます。

### 「デプロイメント進行中」状態 
次のいずれかによって、「デプロイメント進行中」として、扱われます。

* デプロイメントが新しくレプリカ・セットを作成
* デプロイメントが、最新のレプリカ・セットでスケール・アップ中
* デプロイメントが、古いレプリカ・セットをスケール・ダウン中
* 新しくポッドが、準備完了になるケース

`kubectl rollout status` を使用してデプロイメントの進捗状況を監視できます。

### 「デプロイメント完了」状態

下記の特性を持つ状態は「デプロイメント完了」とされます。デプロイメントが完了したかどうかは、'kubectl rollout status'を使用して確認できます。 ロールアウトが正常に完了すると、'kubectl rollout status'はゼロの終了コードを返します。

* 要求された更新がすべてのレプリカで完了
* すべてのレプリカが準備完了
* 更新前の古いレプリカがすべて終了

### 「デプロイメント失敗」状態

デプロイメントが完了せずに最新のレプリカセットをデプロイしようとすると、ロールアウトが停止することがあります。 これは、次の要因のいくつかによって発生します。

* 割り当てが不十分
* Readiness プローブの失敗
* イメージ取得エラー
* 不十分な権限　　
* リミット範囲
* アプリケーションランタイムの設定ミス

この問題を検出する方法の1つは、デプロイメントにデッドライン・パラメータ `spec：（spec.progressDeadlineSeconds）`を指定することです。 `spec.progressDeadlineSeconds`は、デプロイメント・コントローラが待機する秒数を示します。

次のkubectlコマンドは、10分後にDeploymentの進行状況がコントローラに報告されないようにします。

~~~
$ kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
deployment "nginx-deployment" patched
~~~

デッドラインを超えると、デプロイメント・コントローラーは 下記の DeploymentCondition をデプロイメントの status.condition に追加します。

* Type = Progressing
* Status = False
* Reason = ProgressDeadlineExceeded

>注：Kubernetesは、Reason=ProgressDeadlineExceeded のステータスを報告する以外に、停止したデプロイメントに対しては何もしません。 上位レベルのオーケストレータは、デプロイメントを以前のバージョンにロールバックするなど、それを利用してそれに応じて行動することができます。

>注：デプロイメントを一時停止すると、Kubernetesは指定されたデットラインに対する進捗状況をチェックしません。 デッドラインをトリガーすることなく、ロールアウトの途中でデプロイメントを安全に一時停止して再開することができます。


設定したタイムアウト時間が短すぎるか、何らかの原因で一時的エラーのために、デプロイメントにエラーが発生することがあります。 たとえば、クォータが不足しているケースでは、`kubectl describe deployment` のConditionsのセクションで原因に気づくでしょう：

~~~
$ kubectl describe deployment nginx-deployment
<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>
~~~
`kubectl get deployment nginx-deployment -o yaml`を実行すると、展開ステータスは次のようになります。

~~~
status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2
~~~
最終的に、展開のデッドラインを過ぎると、進行状況のステータスと理由を更新します。

~~~
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate
~~~
割り当てが不十分であるという問題に対処するには、デプロイメントをスケールダウンする、実行中の他のコントローラをスケールダウンする、またはネームスペースのクォータを増やすなどがあります。 クォータ条件を満たし、デプロイメント・コントローラが、デプロイメントのロールアウトを完了すると、でデプロイメントのステータスが正常な状態（`Status = True および Reason = NewReplicaSetAvailable`）に更新されます。

`Type=Available` with `Status=True`は、デプロイメントに最低限の可用性があることを意味します。 最低限の可用性は、デプロイメント・ストラテジーで指定されたパラメータによって決まります。 `Type=Progressing` with `Status=True`は、デプロイメントが進行中であること、または進行状況が正常に完了し、必要な最小限の新しい複製が利用可能であることを意味します。

`kubectl rollout status` を使用して、デプロイメントが失敗したかどうかを確認できます。
もし、デプロイメントがデットラインを超えたら、`kubectl rollout status` は、ゼロ以外の終了コードを戻します。

~~~
$ kubectl rollout status deploy/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
$ echo $?
1
~~~


## クリーンナップ

デプロイメントの `.spec.revisionHistoryLimit` フィールドを設定して、このデプロイメント用の古いレプリカセットをいくつ保持するかを指定できます。 残りはバックグラウンドでガベージコレクションされます。 デフォルトでは、すべてのリビジョン履歴が保持されます。 将来のバージョンでは、デフォルトで2に切り替わります。

> 注：このフィールドを明示的に0に設定すると、デプロイメントのすべての履歴がクリーンアップされ、デプロイメントはロールバックできなくなります。


## デプロイメントのスペック記述

### ポッドテンプレート
`.spec.template`は `.spec`の唯一の必須フィールドで、ポッドのテンプレートです。  これは、入れ子になっていて `apiVersion` や `kind` を持たない点を除いて、Podとまったく同じスキーマを持っています。 Podの必須フィールドに加えて、デプロイメントのポッドテンプレートには、適切なラベルを指定する必要があります。ラベルは、他のコントローラと重ならないようにしてください。

~~~
     1	apiVersion: extensions/v1beta1
     2	kind: Deployment
     3	metadata:
     4	  name: nginx-web-deployment
     5	spec:
     6	  replicas: 3
     7	  selector:
     8	    matchLabels:
     9	      app: nginx
    10	  template:         <--- 必須フィールドで、podのテンプレート
    11	    metadata:
    12	      labels:
    13	        app: nginx
    14	    spec:
    15	      containers:   <--- podの必須フィールドで、コンテナのイメージを指定
    16	      - name: nginx
    17	        image: registry.ng.bluemix.net/takara/web:v1
    18	        ports:
    19	        - containerPort: 80
~~~


## レプリカ
`.spec.replicas`は、必要なポッドの数を指定するオプションのフィールドです。デフォルトは1です。

~~~
     5	spec:
     6	  replicas: 3
~~~


### セレクター
`.spec.selector`は、このデプロイメントによってターゲットとされるポッドのラベルセレクタを指定するオプションのフィールドです。`.spec.selector`(A)は `.spec.template.metadata.labels`(B)と一致する必要があります。そうしないと、APIによって拒否されます。

~~~
     5	spec:
     6	  replicas: 3
     7	  selector:
     8	    matchLabels:
     9	      app: nginx      <--- (A)
    10	  template:         
    11	    metadata:
    12	      labels:
    13	        app: nginx    <--- (B) 
~~~

APIバージョンの `apps/v1beta2`では、``.spec.selector`および``.metadata.labels`は、設定されていない場合、`.spec.template.metadata.labels`にデフォルトではなくなりました。したがって、明示的に設定する必要があります。また、`.spec.selector`は、`apps/v1beta2`のデプロイメントの作成後に不変であることにも注意してください。

デプロイメントは、テンプレート・ポッドが '.spec.template'と異なる場合、またはそのようなポッドの総数が`.spec.replicas`を超える場合、ラベルがセレクタと一致するポッドを終了させることができます。ポッドの数が希望の数よりも少ない場合は、 '.spec.template'を使用して新しいポッドを表示します。

> 注：このセレクタとラベルが一致する他のポッドは、直接作成するか、別のデプロイメントを作成するか、ReplicaSetやReplicationControllerなどの別のコントローラを作成して作成しないでください。 そうした場合、最初の配置は、他のポッドを作成したと考えます。 Kubernetesはこれをやめることを止めません。

重複しているセレクタを持つコントローラが複数ある場合、コントローラは互いに戦い、正しく動作しません。



### ストラテジ
`.spec.strategy`は古いポッドを新しいポッドに置き換えるための戦略を指定します。 `.spec.strategy.type`は、「Recreate」または「RollingUpdate」です。 "RollingUpdate"がデフォルト値です。 下記は、`$ kubectl edit deployment` で編集している画面で、26行目からのフィールドです。

~~~
  1	apiVersion: extensions/v1beta1
  2	kind: Deployment
  3	metadata:
<中略>
 21 spec:
 22   replicas: 3
 23   selector:
 24     matchLabels:
 25       app: nginx
 26   strategy:
 27     rollingUpdate:
 28       maxSurge: 1
 29       maxUnavailable: 1
 30     type: RollingUpdate
 31   template:
<以下省略>
~~~ 

`.spec.strategy.type == Recreate`のときに、既存のポッドはすべて削除されます。

`.spec.strategy.type == RollingUpdate`のときにPodをローリングアップデートで更新します。 `maxUnavailable`と` maxSurge`を指定してローリングアップデートプロセスを制御することができます。

`.spec.strategy.rollingUpdate.maxUnavailable`は、更新処理中に利用できないポッドの最大数を指定するオプションのフィールドです。 値は絶対数（たとえば5）または希望のポッドのパーセンテージ（たとえば10％）にすることができます。 絶対数は、切り捨てによる割合から計算されます。 `.spec.strategy.rollingUpdate.maxSurge`が0の場合、値は0にできません。デフォルト値は25％です。

たとえば、この値を30％に設定すると、ローリングアップデートが開始されると直ちに古いReplicaSetを目的のPodの70％に縮小することができます。 新しいPodが準備されると、古いReplicaSetをさらに縮小し、新しいReplicaSetを拡大して、更新中に常に利用可能なPodの総数が希望のPodの70％以上になるようにします。

`.spec.strategy.rollingUpdate.maxSurge`は、必要な数のポッド上に作成できるポッドの最大数を指定するオプションのフィールドです。 値は絶対数（たとえば5）または希望のポッドのパーセンテージ（たとえば10％）にすることができます。 `MaxUnavailable`が0の場合は、値を0にすることはできません。絶対値は、切り上げによってパーセントから計算されます。 デフォルト値は25％です。

たとえば、この値を30％に設定すると、ローリングアップデートが開始されるとすぐに新しいレプリカセットを拡大して、古いポッドと新しいポッドの合計数が希望のポッドの130％を超えないようにすることができます。 古いPodが一度削除されると、新しいReplicaSetをさらに拡大して、更新中にいつでも実行されるPodの総数が、希望のPodの最大130％になるようにすることができます。



### Progress Deadline Seconds
`.spec.progressDeadlineSeconds`はオプションのフィールドで、Deploymentが進行していないことをシステムが報告する前に、展開が進むのを待つ秒数を指定します。これは、` Type=Progressing`、 `Status=False`となる。リソースのステータスではReason=ProgressDeadlineExceededです。デプロイメントコントローラはデプロイメントを再試行し続けます。将来、自動ロールバックが実装されると、デプロイメントコントローラは、そのような状態を観察するとすぐにデプロイメントをロールバックします。
指定されている場合、このフィールドは `.spec.minReadySeconds`より大きい必要があります。



### Min Ready Seconds
`.spec.minReadySeconds`は、新しく作成されたPodがコンテナがクラッシュせずに準備ができて、利用可能とみなされる最小秒数を指定するオプションのフィールドです。このデフォルトは0です（ポッドは準備ができ次第利用可能と見なされます）。

ポッドがいつ準備完了と見なされるかについては、「[コンテナプローブ](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)」を参照してください。



### Rollback To
`.spec.rollbackTo`はAPIバージョン` extensions/v1beta1`と `apps/v1beta1`では廃止され、APIバージョン`apps/v1beta2`ではサポートされなくなりました。代わりに`kubectl rollout undo`を使用する必要があります。



### Revision History Limit
`.spec.revisionHistoryLimit`は、ロールバックを許可するために保持する古いReplicaSetsの数を指定するオプションのフィールドです。その理想的な価値は、新しい展開の頻度と安定性に依存します。このフィールドが設定されていない場合、すべての古いReplicaSetsはデフォルトで保持され、 `etcd 'のリソースを消費し、` kubectl get rs`の出力を混雑させます。


### Paused
`.spec.paused`は、Deploymentの一時停止と再開のためのオプションのブール値フィールドです。中断されていないDeploymentと一時停止されていないDeploymentとの唯一の違いは、一時停止されたDeploymentのPodTemplateSpecへの変更が、一時停止されている限り、新しいロールアウトをトリガーしないことです。デプロイメントは、作成時にデフォルトで一時停止されません。


# まとめ
デプロイメントを勉強することで、k8sの素晴らしい部分の一部が解った様に思います。 頻繁にアプリをデプロイする必要に迫られ、課題を抱えている所に、ソリューションとして実装できる様になりたいですね。 どうも、k8sの能力を活かせるスケーラブルなデータベースが必須なんだと思いますね。