Kubernetesは、コンテナアプリケーションをデプロイするためのオーケストレーションツールです。Kuberenetesは分散環境におけるスケーラブルなコンテナ実行環境をつくるための、さまざまな機能が提供されています。
もともとはGoogleが開発したBorgをもとにOSS化したものですが、今日ではマイクロソフトやRedHatも積極的に開発に加わり、非常に早いスピートで機能拡張していて、追いかけるのも大変です。
Kubernetesの大きな特徴は宣言的設定にあります。
この宣言的設定とは、イミュータブルなインフラを作るための基本的な考え方で、「システムのあるべき姿」を設定ファイルにて宣言する!という考え方です。Kubernetesは設定ファイルに書いたとおりのインフラを維持するように設計されています。
Kubernetesはコンテナを「Pod」という単位で管理します。このPodをKuberenetesクラスタ内でどのように稼働させるか、を維持する機能が「ReplicaSet」です。ReplicaSetをつかうと、テンプレートで定義した構成のPodが常に稼働している状態をつくれます。
ということは、、、、、たとえ何らかの理由でPodに障害が発生しても、なにも対処しなくても自動復旧するはずよね!!!が本当かどうか確かめるべく、AzureでKubernetesクラスタを構築し、ReplicaSetの簡単な実験をしてみます。
前提読者
実験準備
AzureのKubernetesのフルマネージドサービスであるAKSを使います。公式サイトをもとにKubernetesクラスタを構築しました。
クイックスタート - Linux 用 Azure Kubernetes クラスター | Microsoft Docs
ここでは、次の構成のクラスタで実験します。
実験1:クラスタ内のコンテナが異常終了したらどうなるか?
まず、Nginxが動くたけのシンプルなPodを構成するために次の定義ファイルを作成します。
apiVersion: apps/v1 kind: ReplicaSet metadata: name: replicaset-exp spec: replicas: 5 selector: matchLabels: app: replicaset-temp template: metadata: labels: app: replicaset-temp ver: "1.0" spec: containers: - name: pod-sample image: nginx
Kubernetesでは定義ファイルはマニュフェストと呼ばれ、YAMLまたはJSONで記述します。このマニュフェストで宣言した構成の意味をざっと図解すると、次のとおりです。
このマニュフェストのポイントは以下の指定で、これはKubernetesクラスタ内に常に5つのPodを稼働させておくということを宣言しています。
replicas: 5
次のコマンドでreplicaset.yamlに定義したReplicaSetをクラスタ上に構成します。
$ kubectl create -f replicaset.yaml replicaset "replicaset-exp" created
Podの確認は、kubectl get podsコマンドで行います。 「宣言」した通り、5つのPod(コンテナ)がクラスタ上で起動しています。次の例では「replicaset-exp-58bp7」~「replicaset-exp-hh8qb」という名前のコンテナが起動(Running)しているのがわかります。
$ kubectl get pods NAME READY STATUS RESTARTS AGE replicaset-exp-58bp7 1/1 Running 0 14s replicaset-exp-6mj5z 1/1 Running 0 14s replicaset-exp-97z7t 1/1 Running 0 14s replicaset-exp-gxj9j 1/1 Running 0 14s replicaset-exp-hh8qb 1/1 Running 0 14s
これら5つのPodが2台のNodeにどう配置されているかを確認します。今回の実験ではNode0(aks-nodepool1-12354740-0)とNode1(aks-nodepool1-12354740-1)に次のようにpodが配置されていました。
Pod名 | 稼働しているNode |
---|---|
replicaset-exp-58bp7 | Node0 |
replicaset-exp-6mj5z | Node1 |
replicaset-exp-97z7t | Node0 |
replicaset-exp-gxj9j | Node0 |
replicaset-exp-hh8qb | Node1 |
図であらわすと書くと次のようになっています。
1つのPodをわざと削除してみるとどうなるか
ここでNode1上で稼働しているPod「replicaset-exp-hh8qb」をPodを以下のコマンドで削除します。
$ kubectl delete pod replicaset-exp-hh8qb
クラスタ内のPodがどういう状態であるかを次のコマンドで確認します。
$ kubectl get pods NAME READY STATUS RESTARTS AGE replicaset-exp-58bp7 1/1 Running 0 10m replicaset-exp-6mj5z 1/1 Running 0 10m replicaset-exp-97z7t 1/1 Running 0 10m replicaset-exp-gxj9j 1/1 Running 0 10m replicaset-exp-7d6gc 1/1 Running 0 1m
コマンドを注意深く見ると、削除したPod「replicaset-exp-hh8qb」が無くなりになり、代わりにPod「replicaset-exp-7d6gc 」が新しく生成されています。
現在の状態を図でかくと次のとおりです。
結果1:クラスタ内のコンテナアプリが異常終了したらどうなるか?
実験したところ、マニュアルに書いてある通り、コンテナアプリが動作するある1つのPodが何らかの障害で利用できなくなったときも、ReplicaSetのマニュフェストで指定した、レプリカ数=5になるよう、新しい別のPodが自動で1つ生成されました。
つまり、KubernetesのReplicaSetは、設定ファイルに書いたとおりのインフラを維持するように動作することがわかります。
考察1:だれが、Podを復活させたのか?
KubernetesがPodを自動で復旧させたことがわかりましたが、これをKubernetesのオートヒーリング(Auto Healing)機能といいます。だれがいったいPodを復活させたのでしょうか?をみていきます。
Kuberenetesの主なコンポーネントたち
Kubernetesは分散環境でサーバ群が協調してそれぞれの処理を行います。このかたまりのことをKubernetesクラスタと呼びます。Kubernetesで動作しているサーバおよび主なコンポーネントは次のとおりです。
1. Master
Kubernetesクラスタ内のコンテナを操作するためのサーバです。kubectlコマンドを使ってクラスタを構成したりリソースを操作したりする際は、マスターサーバがコマンドからのリクエストを受け取って処理を行います。複数台からなるKubernetesクラスタ内のノードのリソース使用状況を確認して、コンテナを起動するノードを自動的に選択します。Kubernetesがオーケストレーションツールと呼ばれるのも、このマスターサーバが複数台からなる分散したノードをまとめて管理することで、あたかも1台のサーバであるかのようにふるまいます。
■kube-apiserver
Kubernetesのリソース情報を管理するためのフロントエンドのREST APIです。各コンポーネントからリソースの情報を受け取りetcd上に格納します。他のコンポーネントはこのetcdの情報にkube-apiserverを介してアクセスします。このkube-apiserverにアクセスするには、GUIツールやkubebtlコマンドを使います。また、アプリケーション内からkube-apiserverを呼び出すことも可能です。kube-apiserverは認証/認可の機能も持っています。
■kube-scheduler
kube-schedulerはPodをどのNodeで動かすかを制御するコンポーネントです。kube-schedulerは、ノードに割り当てられていないPodに対して、Kubernetesクラスタの状態を確認し、空きスペースを持つNodeを探してPodを実行させるスケジューリングを行います。
■kube-controller-manager
kube-controller-managerはKubernetesクラスタの状態を常に監視するコンポーネントです。定義ファイルで指定したものと実際のNodeやコンテナで動作している状態をまとめて管理します。
2. Node
実際にDockerコンテナを動作させPodを稼働させるサーバです。AKSでは仮想マシン(VM)で構成され、通常は複数用意して、クラスタを構成します。ノードの管理は、マスターサーバが行います。何台ノードを用意するかは、システムの規模や負荷によって異なりますが台数が増えると可用性が向上します。なお、kubeproxyというコンポーネントも動作しますが、説明は別ブログで。
■kubelet
kubeletは、Podの定義ファイルに従ってコンテナを実行したり、ストレージをマウントしたりするエージェント機能を持ちます。またKubeletは、Nodeのステータスを定期的に監視する機能を持ちステータスが変わるとAPI Serverに通知します。
3. etcd
Kubernetesクラスタの構成を保持する分散KVSです。Key-Value型でデータを管理します。どのようなPodをどう配置するかなどの情報を持ち、API Serverから参照されます。
Podのオートヒーリング機能
クラスタの状態監視は、Masterのkube-controller-managerが行います。このkube-controller-managerには次の機能があります。
- ReplicationManager
- ReplicaSet/DaemonSet/Job controllers
- Deployment controller
- StatefulSet controller
- Node controller
- Service controller
- Endpoint controller
- Namespace controller
- etc
この中でPodの状態はReplicationManagerが監視しています。もし、実際に稼働しているPodの数とetcdで管理しているマニュフェストファイルで定義したreplicasの数が一致していない場合、Podの数を調整します。
kubernetes/controller_utils.go at master · kubernetes/kubernetes · GitHub
新しく生成されたPodは、kube-schedulerによって適切なNodeにスケジューリングされます。
このkube-schedulerの挙動は、@tkusumi さんの 「Kubernetes: スケジューラの動作」 がとても分かりやすく勉強になりました!こちらをぜひ!
この記事で解説されているように、kube-schedulerによって最適なノードにPodがスケジューリングされます。実際にノード上にPod、つまりDockerコンテナを実行させるのは、kubeletが行います。kubeletは自Nodeに割り当てられたPodのスケジューリングに従い、必要な数のPodを立ち上げます。
と、、、、このようにKuberenetesでは複数のコンポーネントがいい感じで協調しながらクラスタを維持しているのが分かりました。
実験2:クラスタ内のサーバ(VM)が異常終了したらどうなるか?
前の実験では、Pod(コンテナ)のアプリケーションが停止したときの動きをみましたが、次はKubernetesクラスタを構成するサーバ、つまりNode障害がおこったときにPodがどうなるかを確認します。クラスタを構成するサーバが、物理的にぱーーーんっと逝ってしまったときを想定しています。
注意:
これは実験です。メンテナンス等でNodeを意図的に停止させる場合は、kubectl drainコマンドでNode上のPodを安全に退避させてから停止をします。くれぐれも、本番機で実験しないようにお願いします。
意図的にNode0で障害を起こしてみる
Node0には3つのPodが起動した状態ですが、ここで強制的にNode0(aks-nodepool1-12354740-0/10.240.0.4)となっているVMを停止します。
kubectl get nodesコマンドで確認すると「NotReady」となり、KubernetesのMasterから利用できない状態になっていることが分かります。
$ kubectl get nodes NAME STATUS AGE VERSION aks-nodepool1-12354740-0 NotReady,agent 50m v1.9.2 aks-nodepool1-12354740-1 Ready,agent 38m v1.9.2
Podはどうなったか?
kubectl get podsコマンドを実行すると、8つのPod確認できます。うち5つが稼働(Running)し、3つのPodが不明(Unknown)な状態であることがわかります。
$ kubectl get pods NAME READY STATUS RESTARTS AGE replicaset-exp-58bp7 1/1 Unknown 0 18m replicaset-exp-6mj5z 1/1 Running 0 18m replicaset-exp-8p6xz 1/1 Running 0 40s replicaset-exp-97z7t 1/1 Unknown 0 18m replicaset-exp-gxj9j 1/1 Unknown 0 18m replicaset-exp-hh8qb 1/1 Running 0 18m replicaset-exp-qnztj 1/1 Running 0 40s replicaset-exp-wf7lt 1/1 Running 0 40s
これらの8つのPodがNodeにどう配置されているかを確認します。
Pod名 | 障害前に配置されたNode | 現在のNode |
---|---|---|
replicaset-exp-58bp7 | Node0 | - |
replicaset-exp-6mj5z | Node1 | Node1 |
replicaset-exp-8p6xz | - | Node1 |
replicaset-exp-97z7t | Node0 | - |
replicaset-exp-gxj9j | Node0 | - |
replicaset-exp-hh8qb | Node1 | Node1 |
replicaset-exp-qnztj | - | Node1 |
replicaset-exp-wf7lt | - | Node1 |
図で書くと次のとおりです。
なるほど!
きちんとKubernetesクラスタ内に5つのPodが稼働した状態を維持しています。
Podのたどった運命は、次の3つのパターンがあります。それぞれログを確認します。
1.もともとNode0で稼働していたPod(replicaset-exp-58bp7)
node-controllerによって、障害のあるNode0から立ち退き(NodeControllerEviction)されています。
Events: node-controller Normal NodeControllerEviction Marking for deletion Pod replicaset-exp-58bp7 from Node aks-nodepool1-12354740-0
2. もともとNode1で稼働していたPod(replicaset-exp-6mj5z)
Podに変更なしです。
Events: kubelet, aks-nodepool1-12354740-1 spec.containers{pod-sample} Normal Started Started container
3.新しく生成されたPod(replicaset-exp-8p6xz)
default-schedulerから指示を受けたNode1のkubeletによって新しいPodが生成されています。
Events: default-scheduler Normal Scheduled Successfully assigned replicaset-exp-8p6xz to aks-nodepool1-12354740-1 kubelet, aks-nodepool1-12354740-1 Normal SuccessfulMountVolume MountVolume.SetUp succeeded for volume "default-token-bn7zt" kubelet, aks-nodepool1-12354740-1 spec.containers{pod-sample} Normal Pulling pulling image "nginx" kubelet, aks-nodepool1-12354740-1 spec.containers{pod-sample} Normal Pulled Successfully pulled image "nginx" kubelet, aks-nodepool1-12354740-1 spec.containers{pod-sample} Normal Created Created container kubelet, aks-nodepool1-12354740-1 spec.containers{pod-sample} Normal Started Started container
結果2:クラスタ内のサーバ(VM)が異常終了したらどうなるか?
1つのNodeが何らかの障害で利用できなくなったとき、別の正常に動作しているNode上で、ReplicaSetのマニュフェストで指定したレプリカ数=5になるよう、Podが自動生成されました。
ただし、障害が発生したNodeで稼働していたPodがふたたび再配置されるわけではなく、新しい別のPodが正常なNode上に生成されることが分かりました。
というわけでやはりReplicaSetは、たとえNode障害が発生しても設定ファイルに書いたとおりのインフラを維持するように動作するよう実装されていることがわかります。
考察2:だれが、Podを復活させたのか?
Kubernetesクラスタ内でだれがNode障害を検知したのでしょうか?
Kubernetesでは、Masterで動作するController ManagerのNode ControllerがNodeの管理を行います。
Node Controllerは、Kubernetes内部のノードリストを最新の状態を保持する役割があります。クラスタ内のNodeが正常でない場合、そのNodeのVMがまだ使用可能かどうかを確認し、使用可能でない場合、Node Controllerは該当のNodeを、クラスタのNodeのリストから削除します。
また、Node Controllerはノードの状態を監視します。ノードコントローラは、ノードが到達不能になったとき(つまり、何らかの理由でノードコントローラが何らかの理由でハートビートを受信しなくなったときなど)に、NodeStatusのNodeReadyをConditionUnknownまたはConditionFalseに更新し、ノードからすべてのポッドを後で取り除きます。
よって、今回の実験では、Node01の停止を検知したNode ControllerがノードリストからNode0を外したため、正常に動作しているNode1にReplicaSetで定義したreplicasの値を維持するようPodが配置されました。
ただし、ReplicaSetは障害があったPodと同じ状態のものではなく、別の新しいPodが新規作成されるということを、きちんと理解しておく必要があります。
まとめ
Kubernetesは、分散環境でも高い耐障害性を保持するしくみを持っていることが確認できました。
すごい。
ただし、「24/365サービス無停止運用ウェーイ」とはいかず、、、後続のいくつかの他の実験を行ったところ、理論上無停止状態を維持できないケースも考えられましたので、脳内を整理して別途ブログにまとめます。