あさのひとりごと

3日坊主にならないように、全力を尽くします。 記事は個人のひとりごとです。所属する組織の意見を代表するほど、仕事熱心じゃないです。

Kubernetesはクラスタで障害があったとき、どういう動きをするのか

Kubernetesは、コンテナアプリケーションをデプロイするためのオーケストレーションツールです。Kuberenetesは分散環境におけるスケーラブルなコンテナ実行環境をつくるための、さまざまな機能が提供されています。

もともとはGoogleが開発したBorgをもとにOSS化したものですが、今日ではマイクロソフトRedHatも積極的に開発に加わり、非常に早いスピートで機能拡張していて、追いかけるのも大変です。

Kubernetesの大きな特徴は宣言的設定にあります。

この宣言的設定とは、イミュータブルなインフラを作るための基本的な考え方で、「システムのあるべき姿」を設定ファイルにて宣言する!という考え方です。Kubernetes設定ファイルに書いたとおりのインフラを維持するように設計されています。

Kubernetesはコンテナを「Pod」という単位で管理します。このPodをKuberenetesクラスタ内でどのように稼働させるか、を維持する機能が「ReplicaSet」です。ReplicaSetをつかうと、テンプレートで定義した構成のPodが常に稼働している状態をつくれます。

ということは、、、、、たとえ何らかの理由でPodに障害が発生しても、なにも対処しなくても自動復旧するはずよね!!!が本当かどうか確かめるべく、AzureでKubernetesクラスタを構築し、ReplicaSetの簡単な実験をしてみます。

前提読者

実験準備

AzureのKubernetesのフルマネージドサービスであるAKSを使います。公式サイトをもとにKubernetesクラスタを構築しました。

クイック スタート:Azure Kubernetes Service クラスターをデプロイする | Microsoft Docs

ここでは、次の構成のクラスタで実験します。 f:id:dr_asa:20180402111911p:plain:w500

実験1:クラスタ内のコンテナが異常終了したらどうなるか?

まず、Nginxが動くたけのシンプルなPodを構成するために次の定義ファイルを作成します。ちなみにKuberenetesのPodはいくつかのコンテナをまとめたもので、今回はPodに1つのコンテナを入れたものを使います。

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で記述します。このマニュフェストで宣言した構成の意味をざっと図解すると、次のとおりです。

f:id:dr_asa:20180402122913p:plain:w800

このマニュフェストのポイントは以下の指定で、これは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

図であらわすと書くと次のようになっています。 f:id:dr_asa:20180402115227p:plain:w500

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 」が新しく生成されています。

現在の状態を図でかくと次のとおりです。

f:id:dr_asa:20180402115541p:plain:w500

結果1:クラスタ内のコンテナアプリが異常終了したらどうなるか?

実験したところ、マニュアルに書いてある通り、コンテナアプリが動作するある1つのPodが何らかの障害で利用できなくなったときも、ReplicaSetのマニュフェストで指定した、レプリカ数=5になるよう、新しい別のPodが自動で1つ生成されました。

つまり、KubernetesのReplicaSetは、設定ファイルに書いたとおりのインフラを維持するように動作することがわかります。

考察1:だれが、Podを復活させたのか?

KubernetesがPodを自動で復旧させたことがわかりましたが、これをKubernetesのオートヒーリング(Auto Healing)機能といいます。だれがいったいPodを復活させたのでしょうか?をみていきます。

Kuberenetesの主なコンポーネントたち

Kubernetesは分散環境でサーバ群が協調してそれぞれの処理を行います。このかたまりのことをKubernetesクラスタと呼びます。Kubernetesで動作しているサーバおよび主なコンポーネントは次のとおりです。

f:id:dr_asa:20180402121300p:plain:w600

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の数を調整します。

f:id:dr_asa:20180402113346p:plain:w500

kubernetes/controller_utils.go at master · kubernetes/kubernetes · GitHub

新しく生成されたPodは、kube-schedulerによって適切なNodeにスケジューリングされます。

このkube-schedulerの挙動は、@tkusumi さんの 「Kubernetes: スケジューラの動作」 がとても分かりやすく勉強になりました!こちらをぜひ!

qiita.com

この記事で解説されているように、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

図で書くと次のとおりです。

f:id:dr_asa:20180402114316p:plain:w500

なるほど!

きちんと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はノードの状態を監視します。Node Controllerは、ノードが到達不能になったとき(つまり、何らかの理由でNode Controllerがハートビートを受信しなくなったときなど)に、NodeStatusのNodeReadyをConditionUnknownまたはConditionFalseに更新し、ノードからすべてのPodを取り除きます。

Nodes - Kubernetes

よって、今回の実験では、Node01の停止を検知したNode ControllerがノードリストからNode0を外したため、正常に動作しているNode1にReplicaSetで定義したreplicasの値を維持するようPodが配置されました。

ただし、ReplicaSetは障害があったPodと同じ状態のものではなく、別の新しいPodが新規作成されるということを、きちんと理解しておく必要があります。

まとめ

Kubernetesは、分散環境でも高い耐障害性を保持するしくみを持っていることが確認できました。

すごい。

ただし、「24/365サービス無停止運用ウェーイ」とはいかず、、、後続のいくつかの他の実験を行ったところ、理論上無停止状態を維持できないケースも考えられましたので、脳内を整理して別途ブログにまとめます。

© 2017 ASA.