誰も drain していないのに Pod が消えた
目標
NoScheduleとNoExecuteが、すでに起動しているPodにそれぞれ何をするのかを実際のエビクションで確認し、tolerationSecondsが決めるエビクション時刻をPodごとに計算して表にしたうえで、状態を持つワークロードに与える値を根拠とともに決めます。
なぜ重要なのか
運用担当者が呼び出される事故の半分は、誰もコマンドを実行していないのにPodが動いたケースです。ノードがハートビートを止めると、ノードコントローラーがnode.kubernetes.io/not-readyまたはnode.kubernetes.io/unreachableテイントをNoExecuteで付け、その瞬間から、各PodのtolerationSecondsが時計を進め始めます。問題は、この値を書いたことがないチームがほとんどだという点です。書かなければ、アドミッションが300秒を入れてくれます。つまり、すべてのワークロードが「5分後に移動する」という同じポリシーを使っているのです。ステートレスなフロントエンドには長すぎ、ローカル状態の大きいデータワークロードには短すぎます。この値を理解してワークロードごとに選ぶことが、ノード障害対応の半分です。
ステップ
- ネームスペース
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になるまで待ってください。 webのPodには、トレラレーションを1行も書いていません。しかし、実際のオブジェクトには付いています。webのPodのうち任意の1つを選んで-o jsonで確認し、そのトレラレーションを、/root/ops-eviction/default-tolerations.tsvに、<키>、<효과>、<tolerationSeconds>をタブ区切りで1行ずつ、キー名の昇順で保存してください(プレースホルダーはキー、効果、tolerationSecondsの値です)。ほかの行は入れないでください。/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になるようにしてください。lab-node-1にテイントmaint=planned:NoScheduleを付けてください。そのあと、/root/ops-eviction/noschedule.tsvに、brittleとpatientの現在の状態を、<파드이름>、<노드이름>、<phase>をタブ区切りで1行ずつ、Pod名の昇順で保存してください(プレースホルダーはPod名とノード名です)。- 同じノード
lab-node-1に、テイントmaint=planned:NoExecuteをさらに付けてください。brittleが消えるまで、固定のsleepではなく条件を待つループで待ったあと、結果を/root/ops-eviction/noexecute.tsvに2行で保存してください。brittleとgone、patientとRunningを、それぞれタブで区切ります。 /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になるようにしてください。- リンクが切れた状況を模擬します。
lab-node-2にテイントlinkdown=yes:NoExecuteを付けてください。shortwaitが消えるまで条件のループで待ったあと、/root/ops-eviction/partition.tsvに2行で保存してください。shortwaitとgone、longwaitとRunningを、それぞれタブで区切ります。 - まず、
/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に保存してください。 - ネームスペース
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文字以上の根拠の一文です)。
参考
- NoScheduleは新しい配置だけを防ぎ、NoExecuteはすでに起動しているPodも追い出します。
tolerationSecondsは、NoExecuteトレラレーションでのみ意味を持ちます。- 値が空のテイントは、
kubectl taint node <노드> <키>=:NoExecuteで付けます。 - 待つときは、固定のsleepではなく条件のループを使ってください。時間が、マシンごとに異なります。
- よくある間違い: NoScheduleを付けて、Podが消えるのを待ってしまうことです。
- よくある間違い: トレラレーションを自分で書いておきながら、アドミッションのデフォルトの300秒も一緒に付くと考えてしまうことです。
- 参考: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
手を付けないノードに比較群を立てる
ネームスペース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文字以上の根拠の一文です)。
値を増やすと、一時的に途切れたノードでむだに移動する事態は減りますが、本当に死んだノードでも、その分長くサービスが空いた状態になります。根拠には、何を得て何を失うのかが含まれている必要があります。ファイルに書いた数値と、クラスターに実際に適用された数値が一致していると、合格になります。