--- title: 分散システムコーディネーターZooKeeperの実行 content_type: tutorial weight: 40 --- このチュートリアルでは、[StatefulSet](/ja/docs/concepts/workloads/controllers/statefulset/)、[PodDisruptionBudgets](/docs/concepts/workloads/pods/disruptions/#pod-disruption-budget)、[Podアンチアフィニティ](/ja/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity)を使って、Kubernetes上での[Apache Zookeeper](https://zookeeper.apache.org)の実行をデモンストレーションします。 ## {{% heading "prerequisites" %}} このチュートリアルを始める前に、以下のKubernetesの概念について理解しておく必要があります。 - [Pod](/ja/docs/concepts/workloads/pods/) - [クラスターDNS](/ja/docs/concepts/services-networking/dns-pod-service/) - [Headless Service](/ja/docs/concepts/services-networking/service/#headless-service) - [PersistentVolume](/ja/docs/concepts/storage/volumes/) - [PersistentVolume Provisioning](https://github.com/kubernetes/examples/tree/master/staging/persistent-volume-provisioning/) - [StatefulSet](/ja/docs/concepts/workloads/controllers/statefulset/) - [PodDisruptionBudgets](/docs/concepts/workloads/pods/disruptions/#pod-disruption-budget) - [Podアンチアフィニティ](/ja/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) - [kubectl CLI](/docs/reference/kubectl/kubectl/) 少なくとも4つのノードのクラスターが必要で、各ノードは少なくとも2つのCPUと4GiBのメモリが必須です。このチュートリアルでは、クラスターのノードをcordonおよびdrainします。 **つまり、クラスターがそのノードの全てのPodを終了して退去させ、ノードが一時的にスケジュールできなくなる、ということです。** このチュートリアル専用のクラスターを使うか、起こした破壊がほかのテナントに干渉しない確証を得ることをお勧めします。 このチュートリアルでは、クラスターがPersistentVolumeの動的なプロビジョニングが行われるように設定されていることを前提としています。 クラスターがそのように設定されていない場合、チュートリアルを始める前に20GiBのボリュームを3つ、手動でプロビジョニングする必要があります。 ## {{% heading "objectives" %}} このチュートリアルを終えると、以下の知識を得られます。 - StatefulSetを使ってZooKeeperアンサンブルをデプロイする方法。 - アンサンブルを一貫して設定する方法。 - ZooKeeperサーバーのデプロイをアンサンブルに広げる方法。 - 計画されたメンテナンス中もサービスが利用可能であることを保証するためにPodDisruptionBudgetsを使う方法。 ### ZooKeeper [Apache ZooKeeper](https://zookeeper.apache.org/doc/current/)は、分散アプリケーションのための、分散型オープンソースコーディネーションサービスです。 ZooKeeperでは、データの読み書き、および更新の監視ができます。 データは階層化されてファイルシステム内に編成され、アンサンブル(ZooKeeperサーバーのセット)内の全てのZooKeeperサーバーに複製されます。 データへの全ての操作はアトミックかつ逐次的に一貫性を持ちます。 ZooKeeperは、アンサンブル内の全てのサーバー間でステートマシンを複製するために[Zab](https://pdfs.semanticscholar.org/b02c/6b00bd5dbdbd951fddb00b906c82fa80f0b3.pdf)合意プロトコルを使ってこれを保証します。 アンサンブルはリーダーを選出するのにZabプロトコルを使い、選出が完了するまでデータを書き出しません。 完了すると、アンサンブルは複製するのにZabを使い、書き込みが承認されてクライアントに可視化されるより前に、全ての書き込みをクォーラムに複製することを保証します。 重み付けされたクォーラムでなければ、クォーラムは現在のリーダーを含むアンサンブルの過半数を占めるコンポーネントです。 例えばアンサンブルが3つのサーバーを持つ時、リーダーとそれ以外のもう1つのサーバーを含むコンポーネントが、クォーラムを構成します。 アンサンブルがクォーラムに達しない場合、アンサンブルはデータを書き出せません。 ZooKeeperサーバー群はそれらの全てのステートマシンをメモリに保持し、それぞれの変化をストレージメディア上の永続的なWAL(Write Ahead Log)に書き出します。 サーバーがクラッシュした時には、WALをリプレーすることで以前のステートに回復できます。 WALを際限のない増加から防ぐために、ZooKeeperサーバーは、メモリステートにあるものをストレージメディアに定期的にスナップショットします。 これらのスナップショットはメモリに直接読み込むことができ、スナップショットより前の全てのWALエントリは破棄され得ます。 ## ZooKeeperアンサンブルの作成 以下のマニフェストは[Headless Service](/ja/docs/concepts/services-networking/service/#headless-services)、[Service](/ja/docs/concepts/services-networking/service/)、[PodDisruptionBudget](/docs/concepts/workloads/pods/disruptions/#pod-disruption-budgets)、[StatefulSet](/ja/docs/concepts/workloads/controllers/statefulset/)を含んでいます。 {{< codenew file="application/zookeeper/zookeeper.yaml" >}} ターミナルを開き、マニフェストを作成するために [`kubectl apply`](/docs/reference/generated/kubectl/kubectl-commands/#apply)コマンドを使います。 ```shell kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml ``` これは`zk-hs` Headless Service、`zk-cs` Service、`zk-pdb` PodDisruptionBudget、 `zk` StatefulSetを作成します。 ``` service/zk-hs created service/zk-cs created poddisruptionbudget.policy/zk-pdb created statefulset.apps/zk created ``` StatefulSetのPodを作成するStatefulSetコントローラーを監視するため、[`kubectl get`](/docs/reference/generated/kubectl/kubectl-commands/#get)を使います。 ```shell kubectl get pods -w -l app=zk ``` `zk-2` PodがRunningおよびReadyになったら、`CTRL-C`でkubectlを終了してください。 ``` NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s ``` StatefulSetコントローラーが3つのPodを作成し、各Podは[ZooKeeper](https://archive.apache.org/dist/zookeeper/stable/)サーバー付きのコンテナを持ちます。 ### リーダーの選出のファシリテート {#facilitating-leader-election} 匿名のネットワークにおいてリーダー選出を終了するアルゴリズムがないので、Zabはリーダー選出のための明示的なメンバーシップ設定を要します。 アンサンブルの各サーバーはユニーク識別子を持つ必要があり、全てのサーバーは識別子のグローバルセットを知っている必要があり、各識別子はネットワークアドレスと関連付けられている必要があります。 `zk` StatefulSetのPodのホスト名を取得するために[`kubectl exec`](/docs/reference/generated/kubectl/kubectl-commands/#exec)を使います。 ```shell for i in 0 1 2; do kubectl exec zk-$i -- hostname; done ``` StatefulSetコントローラーは各Podに、その順序インデックスに基づくユニークなホスト名を提供します。 ホスト名は`-<順序インデックス>`という形をとります。 `zk` StatefulSetの`replicas`フィールドが`3`にセットされているので、このセットのコントローラーは、ホスト名にそれぞれ`zk-0`、`zk-1`、`zk-2`が付いた3つのPodを作成します。 ``` zk-0 zk-1 zk-2 ``` ZooKeeperアンサンブルのサーバーは、ユニーク識別子として自然数を使い、それぞれのサーバーの識別子をサーバーのデータディレクトリ内の`myid`というファイルに格納します。 各サーバーの`myid`ファイルの内容を調べるには、以下のコマンドを使います。 ```shell for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done ``` 識別子が自然数で順序インデックスは正の整数なので、順序に1を加算することで識別子を生成できます。 ``` myid zk-0 1 myid zk-1 2 myid zk-2 3 ``` `zk` StatefulSet内の各Podの完全修飾ドメイン名(FQDN)を取得するには、以下のコマンドを使います。 ```shell for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done ``` `zk-hs` Serviceは、全Podのためのドメイン`zk-hs.default.svc.cluster.local`を作成します。 ``` zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local ``` [Kubernetes DNS](/ja/docs/concepts/services-networking/dns-pod-service/)のAレコードは、FQDNをPodのIPアドレスに解決します。 KubernetesがPodを再スケジュールした場合、AレコードはPodの新しいIPアドレスに更新されますが、Aレコードの名前は変更されません。 ZooKeeperはそのアプリケーション設定を`zoo.cfg`という名前のファイルに格納します。 `zk-0` Pod内の`zoo.cfg`ファイルの内容を見るには、`kubectl exec`を使います。 ```shell kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg ``` ファイル末尾にある`server.1`、`server.2`、`server.3`のプロパティの、`1`、`2`、`3`はZooKeeperサーバーの`myid`ファイル内の識別子に対応します。 これらは`zk` StatefulSet内のPodのFQDNにセットされます。 ``` clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/log tickTime=2000 initLimit=10 syncLimit=2000 maxClientCnxns=60 minSessionTimeout= 4000 maxSessionTimeout= 40000 autopurge.snapRetainCount=3 autopurge.purgeInterval=0 server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 ``` ### 合意形成 {#achieving-consensus} 合意(consensus)プロトコルは、各参加者の識別子がユニークであることを要件としています。 Zabプロトコル内で同じユニーク識別子を主張する2つの参加者はないものとします。 これは、システム内のプロセスが、どのプロセスがどのデータをコミットしたかを同意できるようにするために必須です。 2つのPodが同じ順序値で起動されたなら、2つのZooKeeperサーバーはどちらもそれら自身を同じサーバーとして認識してしまうでしょう。 ```shell kubectl get pods -w -l app=zk ``` ``` NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s ``` 各PodのAレコードは、PodがReadyになった時に記入されます。そのため、ZooKeeperサーバー群のFQDNはある1つのエンドポイント、すなわち`myid`ファイルで設定された識別子を主張するユニークなZooKeeperサーバーに解決されます。 ``` zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local ``` これは、ZooKeeperの`zoo.cfg`ファイル内の`servers`プロパティが正しく設定されたアンサンブルを表していることを保証します。 ``` server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 ``` サーバーが値のコミットを試みるためにZabプロトコルを使う時、(リーダー選出が成功していて、少なくともPodのうちの2つがRunningおよびReadyならば)それぞれのサーバーは双方の合意をとって値をコミット、あるいは、(もし双方の状態が合わなければ)それを行うことに失敗します。 あるサーバーが別のサーバーを代行して書き込みを承認する状態は発生しません。 ### アンサンブルの健全性テスト {#sanity-testing-the-ensemble} 最も基本的な健全性テストは、データを1つのZooKeeperサーバーに書き込み、そのデータを別のサーバーで読み取ることです。 以下のコマンドは、`world`をアンサンブル内の`zk-0` Podのパス`/hello`に書き込むのに、`zkCli.sh`スクリプトを実行します。 ```shell kubectl exec zk-0 -- zkCli.sh create /hello world ``` ``` WATCHER:: WatchedEvent state:SyncConnected type:None path:null Created /hello ``` `zk-1` Podからデータを取得するには、以下のコマンドを使います。 ```shell kubectl exec zk-1 -- zkCli.sh get /hello ``` `zk-0`に作成したデータは、アンサンブル内の全てのサーバーで利用できます。 ``` WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 ``` ### 永続的なストレージの提供 [ZooKeeperの概要](#zookeeper)のセクションで言及したように、 ZooKeeperは全てのエントリを永続的なWALにコミットし、定期的にメモリ状態のスナップショットをストレージメディアに書き出します。 永続性を提供するためにWALを使用するのは、複製されたステートマシンを立てるために合意プロトコルを使うアプリケーションでよくあるテクニックです。 `zk` StatefulSetを削除するために、[`kubectl delete`](/docs/reference/generated/kubectl/kubectl-commands/#delete)コマンドを使います。 ```shell kubectl delete statefulset zk ``` ``` statefulset.apps "zk" deleted ``` StatefulSet内のPodの終了を観察します。 ```shell kubectl get pods -w -l app=zk ``` `zk-0`が完全に終了したら、`CTRL-C`でkubectlを終了します。 ``` zk-2 1/1 Terminating 0 9m zk-0 1/1 Terminating 0 11m zk-1 1/1 Terminating 0 10m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m ``` `zookeeper.yaml`のマニフェストを再適用します。 ```shell kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml ``` これは`zk` StatefulSetオブジェクトを作成しますが、マニフェストのその他のAPIオブジェクトはすでに存在しているので変更されません。 StatefulSetコントローラーがStatefulSetのPodを再作成するのを見てみます。 ```shell kubectl get pods -w -l app=zk ``` `zk-2` PodがRunningおよびReadyになったら、`CTRL-C`でkubectlを終了します。 ``` NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s ``` [健全性テスト](#sanity-testing-the-ensemble)で入力した値を`zk-2` Podから取得するには、以下のコマンドを使います。 ```shell kubectl exec zk-2 zkCli.sh get /hello ``` `zk` StatefulSet内の全てのPodを終了して再作成したにもかかわらず、アンサンブルは元の値をなおも供給します。 ``` WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 ``` `zk` StatefulSetの`spec`の`volumeClaimTemplates`フィールドは、各PodにプロビジョニングされるPersistentVolumeを指定します。 ```yaml volumeClaimTemplates: - metadata: name: datadir annotations: volume.alpha.kubernetes.io/storage-class: anything spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 20Gi ``` `StatefulSet`コントローラーは、`StatefulSet`内の各Podのために`PersistentVolumeClaim`を生成します。 `StatefulSet`の`PersistentVolumeClaims`を取得するために、以下のコマンドを使います。 ```shell kubectl get pvc -l app=zk ``` `StatefulSet`がそのPodを再作成した時、`StatefulSet`はPodのPersistentVolumeを再マウントします。 ``` NAME STATUS VOLUME CAPACITY ACCESSMODES AGE datadir-zk-0 Bound pvc-bed742cd-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-1 Bound pvc-bedd27d2-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-2 Bound pvc-bee0817e-bcb1-11e6-994f-42010a800002 20Gi RWO 1h ``` `StatefulSet`のコンテナ`template`の`volumeMounts`セクションは、ZooKeeperサーバーのデータディレクトリにあるPersistentVolumeをマウントします。 ```yaml volumeMounts: - name: datadir mountPath: /var/lib/zookeeper ``` `zk` `StatefulSet`内のPodが(再)スケジュールされると、ZooKeeperサーバーのデータディレクトリにマウントされた同じ`PersistentVolume`を常に得ます。 Podが再スケジュールされたとしても、全ての書き込みはZooKeeperサーバーのWALおよび全スナップショットに行われ、永続性は残ります。 ## 一貫性のある設定の保証 [リーダーの選出のファシリテート](#facilitating-leader-election)および[合意形成](#achieving-consensus)のセクションで記したように、ZooKeeperのアンサンブル内のサーバー群は、リーダーを選出しクォーラムを形成するための一貫性のある設定を必要とします。 また、プロトコルがネットワーク越しで正しく動作するために、Zabプロトコルの一貫性のある設定も必要です。 この例では、設定を直接マニフェストに埋め込むことで一貫性のある設定を達成します。 `zk` StatefulSetを取得しましょう。 ```shell kubectl get sts zk -o yaml ``` ``` … command: - sh - -c - "start-zookeeper \ --servers=3 \ --data_dir=/var/lib/zookeeper/data \ --data_log_dir=/var/lib/zookeeper/data/log \ --conf_dir=/opt/zookeeper/conf \ --client_port=2181 \ --election_port=3888 \ --server_port=2888 \ --tick_time=2000 \ --init_limit=10 \ --sync_limit=5 \ --heap=512M \ --max_client_cnxns=60 \ --snap_retain_count=3 \ --purge_interval=12 \ --max_session_timeout=40000 \ --min_session_timeout=4000 \ --log_level=INFO" … ``` このcommandでは、ZooKeeperサーバーを開始するためにコマンドラインパラメータで設定を渡しています。 設定をアンサンブルへ渡すのには環境変数を使うこともできます。 ### ログの設定 `zkGenConfig.sh`スクリプトで生成されたファイルの1つは、ZooKeeperのログを制御します。 ZooKeeperは[Log4j](https://logging.apache.org/log4j/2.x/)を使い、デフォルトではログの設定に基づいて、ログ設定に時間およびサイズベースでのローリングファイルアペンダー(ログのローテーション)を使用します。 `zk` `StatefulSet`内のPodの1つからログ設定を取得するには、以下のコマンドを使います。 ```shell kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties ``` 以下のログ設定は、ZooKeeperにログの全てを標準出力ファイルストリームに書き出す処理をさせます。 ``` zookeeper.root.logger=CONSOLE zookeeper.console.threshold=INFO log4j.rootLogger=${zookeeper.root.logger} log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold} log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n ``` これはログコンテナ内のログを安全にとるための、最もシンプルと思われる方法です。 アプリケーションはログを標準出力に書き出し、Kubernetesがログのローテーションを処理してくれます。 Kubernetesは、標準出力と標準エラー出力に書き出されるアプリケーションのログがローカルストレージメディアを使い尽くさないことを保証する、健全維持ポリシーも実装しています。 Podの1つから末尾20行を取得するために、[`kubectl logs`](/docs/reference/generated/kubectl/kubectl-commands/#logs)を使ってみます。 ```shell kubectl logs zk-0 --tail 20 ``` `kubectl logs`を利用するか、Kubernetes Dashboardから、標準出力または標準エラーに書き出されたアプリケーションログを参照できます。 ``` 2016-12-06 19:34:16,236 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740 2016-12-06 19:34:16,237 [myid:1] - INFO [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client) 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749 2016-12-06 19:34:26,156 [myid:1] - INFO [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client) 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750 2016-12-06 19:34:26,226 [myid:1] - INFO [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client) 2016-12-06 19:34:36,151 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client) 2016-12-06 19:34:36,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client) 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client) 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client) ``` Kubernetesは多くのログソリューションを統合しています。 クラスターおよびアプリケーションに最も適合するログソリューションを選べます。 クラスターレベルのロギングとアグリゲーションとして、ログをローテートおよび輸送するための[サイドカーコンテナ](/ja/docs/concepts/cluster-administration/logging#sidecar-container-with-logging-agent)をデプロイすることを検討してください。 ### 非特権ユーザーの設定 コンテナ内で特権ユーザーとしての実行をアプリケーションに許可するベストプラクティスは、議論の的です。 アプリケーションが非特権ユーザーとして動作することを組織で必須としているなら、エントリポイントがそのユーザーとして実行できるユーザーを制御する[セキュリティコンテキスト](/ja/docs/tasks/configure-pod-container/security-context/)を利用できます。 `zk` `StatefulSet`のPod `template`は、`SecurityContext`を含んでいます。 ```yaml securityContext: runAsUser: 1000 fsGroup: 1000 ``` Podのコンテナ内で、UID 1000はzookeeperユーザーに、GID 1000はzookeeperグループにそれぞれ相当します。 `zk-0` PodからのZooKeeperプロセス情報を取得してみましょう。 ```shell kubectl exec zk-0 -- ps -elf ``` `securityContext`オブジェクトの`runAsUser`フィールドが1000にセットされているとおり、ZooKeeperプロセスは、rootとして実行される代わりにzookeeperユーザーとして実行されています。 ``` F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S zookeep+ 1 0 0 80 0 - 1127 - 20:46 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground 0 S zookeep+ 27 1 0 80 0 - 1155556 - 20:46 ? 00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg ``` デフォルトでは、PodのPersistentVolumeがZooKeeperサーバーのデータディレクトリにマウントされている時、rootユーザーのみがそこにアクセス可能です。 この設定はZooKeeperのプロセスがそのWALに書き込んだりスナップショットに格納したりするのを妨げることになります。 `zk-0` PodのZooKeeperデータディレクトリのファイルパーミッションを取得するには、以下のコマンドを使います。 ```shell kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data ``` `securityContext`オブジェクトの`fsGroup`フィールドが1000にセットされているので、PodのPersistentVolumeの所有権はzookeeperグループにセットされ、ZooKeeperのプロセスがそのデータを読み書きできるようになります。 ``` drwxr-sr-x 3 zookeeper zookeeper 4096 Dec 5 20:45 /var/lib/zookeeper/data ``` ## ZooKeeperプロセスの管理 [ZooKeeperドキュメント](https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_supervision)では、「You will want to have a supervisory process that manages each of your ZooKeeper server processes (JVM).(各ZooKeeperサーバープロセス(JVM)を管理する監督プロセスを持ちたくなります)」と述べています。 分散型システム内で失敗したプロセスを再起動するのにwatchdog(監督プロセス)を使うのは、典型的パターンです。 アプリケーションをKubernetesにデプロイする時には、監督プロセスのような外部ユーティリティを使うよりもむしろ、アプリケーションのwatchdogとしてKubernetesを使うべきです。 ### アンサンブルのアップデート `zk` `StatefulSet`は`RollingUpdate`アップデート戦略を使うように設定されています。 サーバーに割り当てられる`cpus`の数を更新するのに、`kubectl patch`を利用できます。 ```shell kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]' ``` ``` statefulset.apps/zk patched ``` 更新の状況を見るには、`kubectl rollout status`を使います。 ```shell kubectl rollout status sts/zk ``` ``` waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... statefulset rolling update complete 3 pods at revision zk-5db4499664... ``` これはPod群を終了し、逆の順番で1つずつそれらを新しい設定で再作成します。 これはクォーラムがローリングアップデート中に維持されることを保証します。 履歴や過去の設定を見るには、`kubectl rollout history`コマンドを使います。 ```shell kubectl rollout history sts/zk ``` 出力は次のようになります: ``` statefulsets "zk" REVISION 1 2 ``` 変更をロールバックするには、`kubectl rollout undo`コマンドを使います。 ```shell kubectl rollout undo sts/zk ``` 出力は次のようになります: ``` statefulset.apps/zk rolled back ``` ### プロセスの失敗の取り扱い [再起動ポリシー](/ja/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy)は、Pod内のコンテナのエントリポイントへのプロセスの失敗をKubernetesがどのように取り扱うかを制御します。 `StatefulSet`内のPodにおいて唯一妥当な`RestartPolicy`はAlwaysで、これはデフォルト値です。 ステートフルなアプリケーションでは、このデフォルトポリシーの上書きは**絶対にすべきではありません**。 `zk-0` Pod内で実行されているZooKeeperサーバーのプロセスツリーを調査するには、以下のコマンドを使います。 ```shell kubectl exec zk-0 -- ps -ef ``` コンテナのエントリポイントとして使われるコマンドはPID 1、エントリポイントの子であるZooKeeperプロセスはPID 27となっています。 ``` UID PID PPID C STIME TTY TIME CMD zookeep+ 1 0 0 15:03 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground zookeep+ 27 1 0 15:03 ? 00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg ``` 別のターミナルで、以下のコマンドを使って`zk` `StatefulSet`内のPodを見てみます。 ```shell kubectl get pod -w -l app=zk ``` 別のターミナルで、以下のコマンドを使ってPod `zk-0`内のZooKeeperプロセスを終了します。 ```shell kubectl exec zk-0 -- pkill java ``` ZooKeeperプロセスの終了は、その親プロセスの終了を引き起こします。 コンテナの`RestartPolicy`はAlwaysなので、親プロセスが再起動(restart)されます。 ``` NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 21m zk-1 1/1 Running 0 20m zk-2 1/1 Running 0 19m NAME READY STATUS RESTARTS AGE zk-0 0/1 Error 0 29m zk-0 0/1 Running 1 29m zk-0 1/1 Running 1 29m ``` アプリケーションが、そのビジネスロジックを実装するプロセスを立ち上げるのにスクリプト(`zkServer.sh`など)を使っている場合、スクリプトは子プロセスとともに終了する必要があります。 これは、Kubernetesがアプリケーションのコンテナを、そのビジネスロジックを実装しているプロセスが失敗した時に再起動することを保証します。 ### 生存性(liveness)テスト 失敗したプロセスを再起動するための設定をアプリケーションに施すのは、分散型システムの健全さを保つのに十分ではありません。 システムのプロセスが生きていることもあれば無反応なこともあり、あるいはそうでなく不健全という状況もあります。 アプリケーションのプロセスが不健全で再起動すべきであることをKubernetesに通知するには、liveness probeを使うのがよいでしょう。 `zk` `StatefulSet`のPod `template`でliveness probeを指定します。 ```yaml livenessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 15 timeoutSeconds: 5 ``` プローブはサーバーの健全さをテストするのに、ZooKeeperの`ruok` 4文字コマンドを使うbashスクリプトを呼び出します。 ``` OK=$(echo ruok | nc 127.0.0.1 $1) if [ "$OK" == "imok" ]; then exit 0 else exit 1 fi ``` ターミナルウィンドウで、`zk` StatefulSet内のPodを見るのに以下のコマンドを使います。 ```shell kubectl get pod -w -l app=zk ``` 別のウィンドウで、Pod `zk-0`のファイルシステムから`zookeeper-ready`スクリプトを削除するために以下のコマンドを使います。 ```shell kubectl exec zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready ``` ZooKeeperプロセスの失敗のためにliveness probeを使う時、アンサンブル内の不健全なプロセスが再起動されることを保証するために、Kubernetesは自動的にプロセスを再起動します。 ```shell kubectl get pod -w -l app=zk ``` ``` NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 0/1 Running 0 1h zk-0 0/1 Running 1 1h zk-0 1/1 Running 1 1h ``` ### 準備性(readiness)テスト 準備性は生存性と同じではありません。 プロセスが生きているのであれば、スケジュールされ健全です。 プロセスの準備ができたら、入力を処理できます。 生存性はなくてはならないものですが、準備性の状態には十分ではありません。 プロセスは生きてはいるが準備はできていない時、特に初期化および終了の間がそのケースに相当します。 readiness probeを指定するとKubernetesは、準備性チェックに合格するまで、アプリケーションのプロセスがネットワークトラフィックを受け取らないことを保証します。 ZooKeeperサーバーにとって、健全性は準備性を意味します。 そのため、`zookeeper.yaml`マニフェストからのreadiness probeは、liveness probeと同一です。 ```yaml readinessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 15 timeoutSeconds: 5 ``` liveness probeとreadiness probeが同一だとしても、両方を指定することが重要です。 これは、ZooKeeperアンサンブル内の健全なサーバーだけがネットワークトラフィックを受け取ることを保証します。 ## ノードの失敗の許容 ZooKeeperはデータの変更を正しくコミットするのにサーバーのクォーラムを必要とします。 3つのサーバーのアンサンブルにおいては、書き込みの成功のために2つのサーバーは健全でなければなりません。 クォーラムベースのシステムにおいて、可用性を保証するために、メンバーは障害ドメインにデプロイされます。 個々のマシンの損失による障害を避けるためのベストプラクティスは、同じマシン上でアプリケーションの複数のインスタンスがコロケート(同じ場所に配置)されないようにすることです。 デフォルトでKubernetesは、同じノードの`StatefulSet`にPodをコロケートします。 3つのサーバーアンサンブルを作成していたとして、2つのサーバーが同じノードにあり、そのノードが障害を起こした場合、ZooKeeperサービスのクライアントは、少なくともPodの1つが再スケジュールされるまで障害に見舞われることになります。 クリティカルシステムのプロセスがノードの失敗イベントで再スケジュールできるよう、追加のキャパシティを常にプロビジョンしておくべきです。 そうしておけば、障害は単にKubernetesのスケジューラーがZooKeeperのサーバーの1つを再スケジュールするまでの辛抱です。 ただし、ダウンタイムなしでノードの障害への耐性をサービスに持たせたいなら、`podAntiAffinity`をセットすべきです。 `zk` `StatefulSet`内のPodのノードを取得するには、以下のコマンドを使います。 ```shell for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done ``` `zk` `StatefulSet`内の全てのPodは、別々のノードにデプロイされます。 ``` kubernetes-node-cxpk kubernetes-node-a5aq kubernetes-node-2g2d ``` これは`zk` `StatefulSet`内のPodに`PodAntiAffinity`の指定があるからです。 ```yaml affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - zk topologyKey: "kubernetes.io/hostname" ``` `requiredDuringSchedulingIgnoredDuringExecution`フィールドは、`topologyKey`で定義されたドメイン内で`app`ラベルの値が`zk`の2つのPodが絶対にコロケートすべきでないことを、Kubernetes Schedulerに指示します。 `topologyKey`の`kubernetes.io/hostname`は、ドメインが固有ノードであることを示しています。 異なるルール、ラベル、セレクターを使って、物理・ネットワーク・電源といった障害ドメイン全体に広がるアンサンブルにこのテクニックを広げることができます。 ## メンテナンス時の存続 このセクションでは、ノードをcordon(スケジュール不可化)およびdorain(解放)します。もし共有クラスターでこのチュートリアルを試しているのであれば、これがほかのテナントに有害な影響を及ぼさないことを確認してください。 前のセクションでは、計画外のノード障害に備えてどのようにPodをノード全体に広げるかを示しましたが、計画されたメンテナンスのため引き起こされる一時的なノード障害に対して計画する必要もあります。 クラスター内のノードを取得するために、以下のコマンドを使います。 ```shell kubectl get nodes ``` このチュートリアルでは、4つのノードのあるクラスターを仮定しています。 クラスターが4つよりも多くある場合には、4つのノード以外全てをcordonするために[`kubectl cordon`](/docs/reference/generated/kubectl/kubectl-commands/#cordon)を使ってください。 ノードを4つに制約することで、以下のメンテナンスシミュレーションにおいてzookeeper Podをスケジュールした時に、KubernetesがアフィニティとPodDisruptionBudget制約に遭遇することを保証します。 ```shell kubectl cordon <ノード名> ``` `zk-pdb`の`PodDisruptionBudget`を取得するために、以下のコマンドを使います。 ```shell kubectl get pdb zk-pdb ``` `max-unavailable`フィールドは、`zk` `StatefulSet`の最大で1つのPodがいつでも利用できなくなる可能性があるということを、Kubernetesに指示します。 ``` NAME MIN-AVAILABLE MAX-UNAVAILABLE ALLOWED-DISRUPTIONS AGE zk-pdb N/A 1 1 ``` 1つ目のターミナルで、`zk` `StatefulSet`内のPodを見るのに以下のコマンドを使います。 ```shell kubectl get pods -w -l app=zk ``` 次に別のターミナルで、Podが現在スケジュールされているノードを取得するために、以下のコマンドを使います。 ```shell for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done ``` 出力は次のようになります: ``` kubernetes-node-pb41 kubernetes-node-ixsl kubernetes-node-i4c4 ``` `zk-0` Podがスケジュールされているノードをcordonおよびdrainするには、[`kubectl drain`](/docs/reference/generated/kubectl/kubectl-commands/#drain)を使います。 ```shell kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data ``` 出力は次のようになります: ``` node "kubernetes-node-pb41" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz pod "zk-0" deleted node "kubernetes-node-pb41" drained ``` クラスターに4つのノードがあるので、`kubectl drain`は成功し、`zk-0`が別のノードに再スケジュールされます。 ``` NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m ``` 最初のターミナルで`StatefulSet`のPodを見守り、`zk-1`がスケジュールされたノードをdrainします。 ```shell kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data ``` 出力は次のようになります: ``` "kubernetes-node-ixsl" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74 pod "zk-1" deleted node "kubernetes-node-ixsl" drained ``` `zk` `StatefulSet`がPodのコロケーションを抑止する`PodAntiAffinity`ルールを含んでいるので、`zk-1` Podはスケジュールされず、またスケジュール可能なのは2つのノードだけなので、PodはPendingの状態のままになっています。 ```shell kubectl get pods -w -l app=zk ``` 出力は次のようになります: ``` NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s ``` StatefulSetのPodを見続け、`zk-2`がスケジュールされているノードをdrainします。 ```shell kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data ``` 出力は次のようになります: ``` node "kubernetes-node-i4c4" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4 There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget. pod/zk-2 ``` kubectlを終了するために`CTRL-C`を押します。 `zk-2`を退去させると`zk-budget`違反になってしまうので、3つ目のノードはdrainできません。ただし、ノードはcordonされたままとなります。 健全性テスト中に入力した値を`zk-0`から取得するには、`zkCli.sh`を使います。 ```shell kubectl exec zk-0 zkCli.sh get /hello ``` `PodDisruptionBudget`が遵守されているので、サービスはまだ利用可能です。 ``` WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x200000002 ctime = Wed Dec 07 00:08:59 UTC 2016 mZxid = 0x200000002 mtime = Wed Dec 07 00:08:59 UTC 2016 pZxid = 0x200000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 ``` 最初のノードをuncordon(スケジュール可能化)するには、[`kubectl uncordon`](/docs/reference/generated/kubectl/kubectl-commands/#uncordon)を使います。 ```shell kubectl uncordon kubernetes-node-pb41 ``` 出力は次のようになります: ``` node "kubernetes-node-pb41" uncordoned ``` `zk-1`はこのノードで再スケジュールされます。`zk-1`がRunningおよびReadyになるまで待ちます。 ```shell kubectl get pods -w -l app=zk ``` 出力は次のようになります: ``` NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 12m zk-1 0/1 ContainerCreating 0 12m zk-1 0/1 Running 0 13m zk-1 1/1 Running 0 13m ``` 試しに`zk-2`がスケジュールされているノードをdrainしてみます。 ```shell kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data ``` 出力は次のようになります: ``` node "kubernetes-node-i4c4" already cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog pod "heapster-v1.2.0-2604621511-wht1r" deleted pod "zk-2" deleted node "kubernetes-node-i4c4" drained ``` 今度は`kubectl drain`は成功しました。 `zk-2`の再スケジュールができるように、2つ目のノードをuncordonします。 ```shell kubectl uncordon kubernetes-node-ixsl ``` 出力は次のようになります: ``` node "kubernetes-node-ixsl" uncordoned ``` サービスがメンテナンス中も利用可能なままであることを保証するために、`PodDisruptionBudgets`とあわせて`kubectl drain`を利用できます。 メンテナンスでノードがオフラインになる前にノードをcordonして、Podを退去させるのにdrainが使われている場合、Disruption Budget(停止状態の予算)を表すサービスは遵守すべきバジェットを持ちます。 クリティカルサービスでは、Podをすぐに再スケジュールできるよう、追加のキャパティを常に割り当てておくべきです。 ## {{% heading "cleanup" %}} - クラスターの全てのノードをuncordonするために、`kubectl uncordon`を実行してください。 - このチュートリアルで使ったPersistentVolumeの永続的なストレージメディアを削除する必要があります。 全てのストレージが回収されたことを確実とするために、お使いの環境、ストレージ設定、プロビジョニング方法に基いて必要な手順に従ってください。