TT Lab
はじめる
学ぶ 学習パス コース

Kubernetes運用実務

誰も drain していないのに Pod が消えた

TT Labで続きを見る

目標

NoScheduleとNoExecuteが、すでに起動しているPodにそれぞれ何をするのかを実際のエビクションで確認し、tolerationSecondsが決めるエビクション時刻をPodごとに計算して表にしたうえで、状態を持つワークロードに与える値を根拠とともに決めます。

なぜ重要なのか

運用担当者が呼び出される事故の半分は、誰もコマンドを実行していないのにPodが動いたケースです。ノードがハートビートを止めると、ノードコントローラーがnode.kubernetes.io/not-readyまたはnode.kubernetes.io/unreachableテイントをNoExecuteで付け、その瞬間から、各PodのtolerationSecondsが時計を進め始めます。問題は、この値を書いたことがないチームがほとんどだという点です。書かなければ、アドミッションが300秒を入れてくれます。つまり、すべてのワークロードが「5分後に移動する」という同じポリシーを使っているのです。ステートレスなフロントエンドには長すぎ、ローカル状態の大きいデータワークロードには短すぎます。この値を理解してワークロードごとに選ぶことが、ノード障害対応の半分です。

ステップ

  1. ネームスペースops-evictを作成し、/root/ops-eviction/web.yamlにDeploymentwebを書いてください。レプリカは3、ラベルはapp: web、イメージはnginx:1.27.3で、nodeSelectorでkubernetes.io/hostname: lab-node-0にだけ配置されるようにします。適用して、3つのPodがすべてRunningになるまで待ってください。
  2. webのPodには、トレラレーションを1行も書いていません。しかし、実際のオブジェクトには付いています。webのPodのうち任意の1つを選んで-o jsonで確認し、そのトレラレーションを、/root/ops-eviction/default-tolerations.tsvに、<키>、<효과>、<tolerationSeconds>をタブ区切りで1行ずつ、キー名の昇順で保存してください(プレースホルダーはキー、効果、tolerationSecondsの値です)。ほかの行は入れないでください。
  3. /root/ops-eviction/brittle.yamlと/root/ops-eviction/patient.yamlに、Podを2つ書いてください。どちらもネームスペースops-evict、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-1です。2つのPodは、トレラレーションの効果だけが異なります。brittleはラベルrole: brittleで、キーmaintをoperator: Exists・effect: NoScheduleで許容するトレラレーションが1つ(tolerationSecondsなし)、patientはラベルrole: patientで、キーmaintをoperator: Exists・effect: NoExecute・tolerationSeconds: 3600で許容するトレラレーションが1つです。どちらも適用して、lab-node-1でRunningになるようにしてください。
  4. lab-node-1にテイントmaint=planned:NoScheduleを付けてください。そのあと、/root/ops-eviction/noschedule.tsvに、brittleとpatientの現在の状態を、<파드이름>、<노드이름>、<phase>をタブ区切りで1行ずつ、Pod名の昇順で保存してください(プレースホルダーはPod名とノード名です)。
  5. 同じノードlab-node-1に、テイントmaint=planned:NoExecuteをさらに付けてください。brittleが消えるまで、固定のsleepではなく条件を待つループで待ったあと、結果を/root/ops-eviction/noexecute.tsvに2行で保存してください。brittleとgone、patientとRunningを、それぞれタブで区切ります。
  6. /root/ops-eviction/shortwait.yamlと/root/ops-eviction/longwait.yamlを書いてください。どちらもネームスペースops-evict、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-2です。2つのPodとも、キーlinkdownをoperator: Exists・effect: NoExecuteで許容しますが、shortwaitはtolerationSeconds: 20、longwaitはtolerationSeconds: 3600です。ラベルは、それぞれrole: shortwait、role: longwaitです。どちらもlab-node-2でRunningになるようにしてください。
  7. リンクが切れた状況を模擬します。lab-node-2にテイントlinkdown=yes:NoExecuteを付けてください。shortwaitが消えるまで条件のループで待ったあと、/root/ops-eviction/partition.tsvに2行で保存してください。shortwaitとgone、longwaitとRunningを、それぞれタブで区切ります。
  8. まず、/root/ops-eviction/forever.yamlにPodforeverを作成してください。ネームスペースops-evict、ラベルrole: forever、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-0で、トレラレーションはキーを書かずにoperator: Exists・effect: NoExecuteの1つだけです(tolerationSecondsなし)。そのあと、/root/ops-eviction/evict-plan.shを作成してください。ops-evictのすべてのPodを読み取り、PodごとにlinkdownのNoExecuteを何秒許容するかを、<파드이름>と<값>のタブ区切りで、標準出力にだけ出力します(プレースホルダーはPod名と値です)。値は、許容するトレラレーションがなければ0、あるがtolerationSecondsがなければnever、あればその秒数です。出力はPod名の昇順である必要があります。その出力を/root/ops-eviction/evict-plan.tsvに保存してください。
  9. ネームスペースops-ledgerを作成し、/root/ops-eviction/ledger.yamlにDeploymentledgerを書いてください。ネームスペースops-ledger、レプリカは2、ラベルはapp: ledger、イメージはnginx:1.27.3です。このワークロードはノードのローカル状態が大きいので、短い途絶には耐えるほうがよいです。node.kubernetes.io/not-readyとnode.kubernetes.io/unreachableの2つのキーをoperator: Exists・effect: NoExecuteで許容しますが、2つのトレラレーションのtolerationSecondsを同じ値で、900以上3600以下にしてください。適用してPodが2つともRunningになったら、/root/ops-eviction/decision.tsvに2行を書いてください。tolerationSecondsと<고른 값>のタブ区切り、reasonと<40자 이상의 근거 한 문장>のタブ区切りです(プレースホルダーは選んだ値と、40文字以上の根拠の一文です)。

参考

手を付けないノードに比較群を立てる

ネームスペースops-evictを作成し、/root/ops-eviction/web.yamlにDeploymentwebを書いてください。レプリカは3、ラベルはapp: web、イメージはnginx:1.27.3で、nodeSelectorでkubernetes.io/hostname: lab-node-0にだけ配置されるようにします。適用して、3つのPodがすべてRunningになるまで待ってください。

このラボでは、あとでlab-node-1とlab-node-2をわざと壊します。比較群がなければ、Podが消えた理由がテイントなのかほかの原因なのか区別できません。新しいネームスペースは、defaultサービスアカウントができるまで少し時間がかかるので、失敗したら数秒後にもう一度適用してください。

誰も書いていないトレラレーションがすでに付いている

webのPodには、トレラレーションを1行も書いていません。しかし、実際のオブジェクトには付いています。webのPodのうち任意の1つを選んで-o jsonで確認し、そのトレラレーションを、/root/ops-eviction/default-tolerations.tsvに、<키>、<효과>、<tolerationSeconds>をタブ区切りで1行ずつ、キー名の昇順で保存してください(プレースホルダーはキー、効果、tolerationSecondsの値です)。ほかの行は入れないでください。

アドミッションコントローラーが付けます。Podを作成するときにこの2つのキーを直接書かなければ自動的に補われ、直接書けば補われません。どのwebのPodを選んでも、答えは同じです。列の区切りはタブなので、jqの@tsvが便利です。

同じノードに、許容する時間だけが異なるPodを2つ置く

/root/ops-eviction/brittle.yamlと/root/ops-eviction/patient.yamlに、Podを2つ書いてください。どちらもネームスペースops-evict、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-1です。2つのPodは、トレラレーションの効果だけが異なります。brittleはラベルrole: brittleで、キーmaintをoperator: Exists・effect: NoScheduleで許容するトレラレーションが1つ(tolerationSecondsなし)、patientはラベルrole: patientで、キーmaintをoperator: Exists・effect: NoExecute・tolerationSeconds: 3600で許容するトレラレーションが1つです。どちらも適用して、lab-node-1でRunningになるようにしてください。

トレラレーションは、このテイントが付いても自分は耐えるという宣言です。operatorがExistsなら値は見ませんが、効果は必ず一致している必要があります。キーが同じでも、効果が異なれば許容できません。2つのPodの違いがまさにそれで、あとのステップで、その違いが何を生むのかを確認します。

NoScheduleは、すでに起動しているPodには手を触れない

lab-node-1にテイントmaint=planned:NoScheduleを付けてください。そのあと、/root/ops-eviction/noschedule.tsvに、brittleとpatientの現在の状態を、<파드이름>、<노드이름>、<phase>をタブ区切りで1行ずつ、Pod名の昇順で保存してください(プレースホルダーはPod名とノード名です)。

名前が「スケジュールするな」という意味なのには、理由があります。この効果は、新しく配置することだけを防ぎます。patientはこの効果を許容できないのに、そのまま残っているのが正常です。何も起こらないことを確認するのが、このステップの目的です。

NoExecuteを付けると、本当に消える

同じノードlab-node-1に、テイントmaint=planned:NoExecuteをさらに付けてください。brittleが消えるまで、固定のsleepではなく条件を待つループで待ったあと、結果を/root/ops-eviction/noexecute.tsvに2行で保存してください。brittleとgone、patientとRunningを、それぞれタブで区切ります。

NoExecuteは、すでに起動しているPodにも効果があります。brittleにもmaintのトレラレーションがありますが、効果がNoScheduleなので、このテイントは許容できません。待つためのループは、次のような形です。for i in $(seq 1 60); do kubectl -n ops-evict get pod brittle >/dev/null 2>&1 || break; sleep 2; done

ネットワークが切れたときに備えたPodを2つ用意する

/root/ops-eviction/shortwait.yamlと/root/ops-eviction/longwait.yamlを書いてください。どちらもネームスペースops-evict、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-2です。2つのPodとも、キーlinkdownをoperator: Exists・effect: NoExecuteで許容しますが、shortwaitはtolerationSeconds: 20、longwaitはtolerationSeconds: 3600です。ラベルは、それぞれrole: shortwait、role: longwaitです。どちらもlab-node-2でRunningになるようにしてください。

linkdownは、私たちが決めたカスタムキーです。組み込みのキー(not-ready・unreachable)を使わない理由があります。その2つのキーのテイントは、ノードコントローラーがノードのコンディションを見て直接管理しているため、Readyのノードに手で付けるとすぐに外されてしまいます(実際に確認しました)。エビクションのルール自体は、キーとは無関係に同じです。

ノードが応答を止めたように見せて、時刻を計測する

リンクが切れた状況を模擬します。lab-node-2にテイントlinkdown=yes:NoExecuteを付けてください。shortwaitが消えるまで条件のループで待ったあと、/root/ops-eviction/partition.tsvに2行で保存してください。shortwaitとgone、longwaitとRunningを、それぞれタブで区切ります。

本物のクラスターなら、ノードがハートビートを止めたあと、ノードコントローラーがnode.kubernetes.io/unreachableを付け、その時点から同じルールが動きます。ここのノードはkwokが作成した偽物なのでハートビートを止められず、組み込みのキーを手で付けるとノードコントローラーがすぐに外してしまうので、カスタムキーで同じことを行います。20秒が経過して初めて消えるので、条件のループで待ってください。

エビクション時刻表を機械に計算させる

まず、/root/ops-eviction/forever.yamlにPodforeverを作成してください。ネームスペースops-evict、ラベルrole: forever、イメージnginx:1.27.3、nodeSelectorはkubernetes.io/hostname: lab-node-0で、トレラレーションはキーを書かずにoperator: Exists・effect: NoExecuteの1つだけです(tolerationSecondsなし)。そのあと、/root/ops-eviction/evict-plan.shを作成してください。ops-evictのすべてのPodを読み取り、PodごとにlinkdownのNoExecuteを何秒許容するかを、<파드이름>と<값>のタブ区切りで、標準出力にだけ出力します(プレースホルダーはPod名と値です)。値は、許容するトレラレーションがなければ0、あるがtolerationSecondsがなければnever、あればその秒数です。出力はPod名の昇順である必要があります。その出力を/root/ops-eviction/evict-plan.tsvに保存してください。

キーが空のトレラレーションはすべてのキーを許容するという意味で、effectが空ならすべての効果を許容するという意味です。この2つの場合を見落とすと、foreverがneverとして出力されません。スクリプトがファイルを直接書き込むと、採点ツールが再実行するときに学習者の成果物を上書きしてしまうので、標準出力にだけ出力してください。

状態を持つワークロードに与える値を決めて、根拠を残す

ネームスペースops-ledgerを作成し、/root/ops-eviction/ledger.yamlにDeploymentledgerを書いてください。ネームスペースops-ledger、レプリカは2、ラベルはapp: ledger、イメージはnginx:1.27.3です。このワークロードはノードのローカル状態が大きいので、短い途絶には耐えるほうがよいです。node.kubernetes.io/not-readyとnode.kubernetes.io/unreachableの2つのキーをoperator: Exists・effect: NoExecuteで許容しますが、2つのトレラレーションのtolerationSecondsを同じ値で、900以上3600以下にしてください。適用してPodが2つともRunningになったら、/root/ops-eviction/decision.tsvに2行を書いてください。tolerationSecondsと<고른 값>のタブ区切り、reasonと<40자 이상의 근거 한 문장>のタブ区切りです(プレースホルダーは選んだ値と、40文字以上の根拠の一文です)。

値を増やすと、一時的に途切れたノードでむだに移動する事態は減りますが、本当に死んだノードでも、その分長くサービスが空いた状態になります。根拠には、何を得て何を失うのかが含まれている必要があります。ファイルに書いた数値と、クラスターに実際に適用された数値が一致していると、合格になります。