🤯

【やらかし共有】EKS マネージドノードへ移行する途中でやめたら、既存ノードが認識されなくなってSTGが落ちた…

に公開

この記事は LAPRAS アドベントカレンダー 2025 の 15 日目のエントリーです。
https://qiita.com/advent-calendar/2025/lapras

こんにちは。LAPRAS で SR をしている Kumamoto と言います。

SR チームに移籍して 1 年程ですが、最近になって弩級のボヤを STG 環境で発生させてしまいました。
この記事ではそのインシデント経緯と技術的な背景について説明していこうと思います

※本番/STG 環境とは別に作ったテスト環境の EKS クラスターでインシデント時の状態を再現した上で、技術的な内容の確認作業は行っています。
万一「ここの説明は違くないか?」等あればコメントでご指摘いただけると幸いです 🙇

1. インシデントが起こるまで 🔥

前提:マネージドノードへの移行背景

私のお勤め先ではインフラ基盤に Amazon EKS を使っているのですが、ワーカーノードは(一部例外を除き)セルフマネージドでの運用を行っています。

上記 EKS の 運用工数を減らす施策の一つとして、マネージドノード移行を試みていました。

移行にあたっては、 『Seamlessly migrate workloads from EKS self-managed node group to EKS-managed node groups』を含めた AWS 公式のドキュメントの一通りの調査は済ませていました。

また、テスト検証用に用意した EKS クラスターではダウンタイムなく移行が出来ることも確認済でした。

インシデント発生:移行中止から数分後の STG ダウン

「さぁ、STG 環境でマネージドノードに移行するぞー!」

STG 移行当日、terraform 側で定義したマネージドノードグループを適用した段階で、一部、インスタンスのタグの設定値にミスがあることに気が付きました。
※なお、このミス自体は今回のインシデントとは直接的な関係性がないので詳細は割愛します 🙏

「他にも設定ミスがあるかもしれないしなー…」ということで、ペアプロ作業者とも相談の上、大事を取ってこの日の STG での移行作業は中止すると決断しました。

ここまでは……ここまでは、よかったのです。

ですが私はここで、「Pod の移動もまだだし、問題ないか」と(深い考えもなく)新規に作ったマネージドノードグループの AWS リソースを消しました。

リソースを消した後は念のため、

(1)ブラウザから STG 環境にアクセス出来ること
(2)STG の pod に再起動など掛かっていないこと

の確認を行いました。ふぅ、一安心となって数分ほど後……。

急に STG 環境が動かなくなりました \(^o^)/

ブラウザから STG 環境にアクセスしようにも、「503 Service Temporarily Unavailable」のエラー表示が示されるだけ。

既存 Node の NotReady 化&新規追加も不可に

さらにこの段階で気づいたのですが、既存の(セルフマネージドな)ノードには何も変更を加えていないはずなのに、Node は全てNotReady状態になっていました。

$ kubectl get node
NAME                                              STATUS     ROLES    AGE   VERSION
ip-172-31-1-221.ap-northeast-1.compute.internal   NotReady   <none>   19d   v1.32.7-eks-3abbec1

「セルフマネージドの Node を増やして、そちらに Pod を移したら直るかもしれない!」

そう考えて、AWS のコンソール画面から ASG(Auto Scaling Group)のインスタンスの希望するキャパシティを増やしたのですが……

(1)EC2 としては新規に立ち上がっている(SSM セッションマネージャーでも入れる)
(2)しかし EKS 上では下記の添付にあるように、コンピューティングリソースとして認識されていない
(3)当然、ターミナル上で$kubectl get nodeをしても先程新規追加したはずのノードは出てこない

新規EC2立ち上げ後のAWSコンソール画面でのEKSクラスター
ステータス不明となっているノードは、インシデント発生前からある EC2

Pod の入れ替えも出来ない

Pod の方は…というと、どの namespace の Pod も、STATUS 上はRunningのままで問題は起こっているようには見えませんでした。

$ kubectl get po -A
NAMESPACE     NAME                                                              READY   STATUS    RESTARTS   AGE
kube-system   aws-load-balancer-controller-744b8df496-p4786                     1/1     Running   0          4h42m
kube-system   aws-load-balancer-controller-744b8df496-v8bcf                     1/1     Running   0          4h42m
(...省略)
test-base     busybox-64f54c5b5c-dpkjh                                          1/1     Running   0          2d1h
test-base     busybox-64f54c5b5c-p5ffq                                          1/1     Running   0          46h

しかし Pod のdeleteを試みてみると……。

-- delete表記が出たまま、ずっと処理が終わらない
$ kubectl delete po -n test-base busybox-64f54c5b5c-rbdw8
pod "busybox-64f54c5b5c-rbdw8" deleted
-- いつまで経っても新しいpodがpendingのままで、古いpodも死ねない
$ kubectl get po -n test-base
test-base     busybox-64f54c5b5c-dpkjh                                     1/1     Running       0          3h1m   172.31.0.245    ip-172-31-1-221.ap-northeast-1.compute.internal   <none>           <none>
test-base     busybox-64f54c5b5c-p5ffq                                     0/1     Pending       0          83s    <none>          <none>                                            <none>           <none>
test-base     busybox-64f54c5b5c-rbdw8                                     1/1     Terminating   0          14m    172.31.6.64     ip-172-31-1-221.ap-northeast-1.compute.internal   <none>           <none>

どれだけ待っても、terminate されませんし、新しい Pod も生まれてきません。

なぜ STG が落ちているのかもわからない。問題の起こっている Pod/Node ともに入れ替えることすら出来ない。なぜ…どうして……

どうしてこうなったアスキーアート

2. 復旧作業 🏥

調査作業:pod 内の認可エラー

pod のログを確認したところ、下記のようなエラーを何度も吐いていることが分かりました。

-- 生きているPodならどれでも同じようなエラーを吐いていた
$ kubectl stern -n kube-system aws-node-cp76l
+ aws-node-cp76l aws-eks-nodeagent
+ aws-node-cp76l aws-vpc-cni-init
+ aws-node-cp76l aws-node
- aws-node-cp76l aws-vpc-cni-init
failed to tail: Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy), will retry
+ aws-node-cp76l aws-vpc-cni-init
- aws-node-cp76l aws-eks-nodeagent
failed to tail: Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy), will retry

failed to tail: Internal error occurred: Authorization error (user=kube-apiserver-kubelet-client, verb=get, resource=nodes, subresource=proxy), will retry

つまり、kube-apiserver-kubelet-clientというユーザーがget操作をしようとしたがAuthorization error認可エラーを起こしているということです。

マネージドノード削除と aws-auth ConfigMap

「あれ、aws-auth ConfigMapのファイルは何もいじってないけどな?」と思いつつ、念の為 diff を取ってみると…。

$ kubectl diff aws-auth-configmap.yaml
diff -uN /tmp/LIVE-1111576147/v1.ConfigMap.kube-system.aws-auth /tmp/MERGED-478984027/v1.ConfigMap.kube-system.aws-auth
--- /tmp/LIVE-1111576147/v1.ConfigMap.kube-system.aws-auth	2025-12-12 22:54:12.147566895 +0900
+++ /tmp/MERGED-478984027/v1.ConfigMap.kube-system.aws-auth	2025-12-12 22:54:12.147566895 +0900
@@ -1,10 +1,17 @@
 apiVersion: v1
 data:
   mapRoles: |
+    - rolearn: arn:aws:iam::{AWSアカウントID}:role/eks-node-role
+      username: system:node:{{EC2PrivateDNSName}}
+      groups:
+        - system:bootstrappers
+        - system:nodes
(省略、他はインデントのズレがdiffに出たりくらいでした。)

まるっと、ノード向けの IAM ロールとsystem:bootstrapperssystem:nodesの権限のマッピングが消えていました。

急ぎ、上記の yaml ファイルをkubectl applyしたところ、STG 環境は復旧し、Node の状態もReadyへと回復しました。

復旧作業後に分かったことなのですが、どうやらマネージド型ノードグループを削除する際に、削除対象のノードグループで使われていた IAM ロールが他のマネージドノードグループで使用されていなければ、ロールとユーザーグループとのマッピングが aws-auth ConfigMap から削除されてしまうと言うの EKS の仕様なようです。
※今回のようにセルフマネージドなノードにその IAM ロールが使われていても関係なし

クラスターからマネージドノードグループを削除する際の注意書き
AWS 公式でしっかり注意書きされていた…… by クラスターからマネージドノードグループを削除する - Amazon EKS

3. 事象の深堀:なぜ STG のダウンにラグがあったのか? 🔎

インシデントで実際に私が体験したことベースの内容を時系列に書き連ねていく中で、いくつかのことがハッキリしました。

「ASG で新規に追加した EC2 が EKS のコンピューティングリソースとして認識されなかった」理由はsystem:bootstrappersが IAM ロールとのマッピングされなくなったことでクラスターに参加出来なかったためです。

また Pod を delete しようとしてもずっと delete 出来なかったのも Pod の Status を kubelet が伝えられない状態となっていたためだろうということも分かりました。

一方で、「なぜ STG は(マネージドノード削除から)ラグを空けてダウンしたのか?」については、EKS クラスター内部で何が起こっていたのか、をもう少し深堀しないと見えてこなそうです。

kubelet のハートビート

ハートビートとは何か?

各ノードで動作する kubelet は「ノードの状態」を定期的に コントロールプレーンの k8s API サーバーに送信しています。この死活監視の仕組みを「ハートビート」と呼びます。[1]

なお、このハートビートはデフォルト設定だと 10 秒に 1 回行われています。

実際に検証用のクラスター上のノードのハートビート頻度を見るためにkubectl get {ノード名} -o jsonpath='{.status.conditions[?(@.type=="Ready")].lastHeartbeatTime}';を確認してみると…

-- ハートビートは10秒ごとに行われている
$ while true; do date; kubectl get node ip-172-31-1-221.ap-northeast-1.compute.internal -o jsonpath='{.status.conditions[?(@.type=="Ready")].lastHeartbeatTime}'; echo; sleep 10; done
2025年 12月 12日 金曜日 15:27:10 JST
2025-12-12T06:27:08.598522Z
2025年 12月 12日 金曜日 15:27:21 JST
2025-12-12T06:27:18.792180Z
2025年 12月 12日 金曜日 15:27:31 JST
2025-12-12T06:27:28.922284Z
2025年 12月 12日 金曜日 15:27:42 JST
2025-12-12T06:27:39.187473Z
2025年 12月 12日 金曜日 15:27:53 JST
2025-12-12T06:27:49.228702Z
2025年 12月 12日 金曜日 15:28:03 JST
2025-12-12T06:27:59.359454Z
2025年 12月 12日 金曜日 15:28:14 JST
2025-12-12T06:28:09.374221Z
2025年 12月 12日 金曜日 15:28:24 JST
2025-12-12T06:28:19.667735Z

参考:ノード | Kubernetes

ここで重要なのが「kubelet の方 が API サーバーに対して能動的に情報を送る」ということです。
API サーバー側 が kubelet に問い合わせに行くわけではありません。あくまでも kubelet から情報をもらってそれを etcd に保存するだけです。

ここからハートビートは kubelet がリクエストを送る側なのだから、当然『kubelet は API サーバーから認証/認可された状態でないといけない』ということが分かります。

つまりマネージドノード消去に伴い、aws-auth CofigMap のマッピングが消えた時点で、kubelet は(API サーバーに情報を送れず)ハートビートが途絶えてしまったということです。

ハートビートが途絶えるとどうなるのか?

Node Controller は、一定時間ノードからのハートビートが途絶えると、「このノードは正常ではないな」と判断、つまり Node をUnknownと判定します。

ハートビートが途絶えてからこの判断が行われるまでの時間はnode-monitor-grace-periodの設定値によって決まります。
今回の対象である EKS(ver 1.32)の場合、デフォ値は 50 秒です[2]

参考:kube-controller-manager | Kubernetes

再現実験

テスト検証用のクラスターにて$ kubectl delete cm aws-auth -n kube-systemを行い IAM ロールとユーザーグループとのマッピングを消去しました。

上記実行後も数分ほどハートビートが続くのですが、kubelet をインスタンス上で再起動させてやれば即座に API サーバーからの認可が切れてハートビートが更新されなくなります。[3]

-- EC2インスタンス上にて
$ sudo systemctl restart kubelet
-- 再起動出来ているか念の為、確認
$ sudo systemctl status kubelet

上記作業実行後、ハートビートは即座に途切れます。
そしてその 50 秒ほど後に Node の STATUS はNotReadyとなります。

また Endpoint 側でもnotReadyAddressesに変化します。

$ kubectl get endpoints busybox -n test-base -o yaml
(...省略)
subsets:
- notReadyAddresses:
  - ip: 172.31.0.245
    nodeName: ip-172-31-1-221.ap-northeast-1.compute.internal
    targetRef:
      kind: Pod
      name: busybox-64f54c5b5c-dpkjh
      namespace: test-base
      uid: 71a6d91e-26fa-4545-91f2-0f639c60ec9c
  - ip: 172.31.11.198
    nodeName: ip-172-31-1-221.ap-northeast-1.compute.internal
    targetRef:
      kind: Pod
      name: busybox-64f54c5b5c-p5ffq
      namespace: test-base
      uid: 6e3bbb61-a84b-4cba-8f14-50c42b6cd7e6

さらに少しのラグを挟んで ASG 側でもターゲットからノードが外されます。

ASGのターゲット
ASG のターゲットでノードが Draining となっている

この段階で、ようやく(Pod が動かしている Web アプリなどの)サービスがダウンし始めるわけです。

4. まとめ 📖

今回の学び

  • マネージドノードグループを削除すると、その IAM ロールが他のマネージドノードグループで使われていない場合、aws-auth ConfigMap からマッピングが自動削除される
    • セルフマネージドノードで同じロールを使っていても関係なく消える
  • ハートビートが途絶えてから実際にサービスダウンするまでにはラグがあるので、「消した直後は動いてるから大丈夫」は危険

何より「Pod の移動もまだだし、問題ないかー」と調査もせずにリソースを消すのは本当によくなかったです。

感謝

今回のインシデントでは、SR チームのリード(@b7472)が冷静に対応してくれたおかげで、比較的早く STG 環境を復旧することができました。

私一人だったら「どうしてこうなった!?」状態のまま固まっていた時間がもっと長かったと思います。圧倒的感謝……ッ!!

今後の対応

ところで、aws-auth ConfigMap は現在 AWS から Deprecated(非推奨)とされています。

The aws-auth ConfigMap is deprecated. For the recommended method to manage access to Kubernetes APIs, see Grant IAM users access to Kubernetes with EKS access entries.
_by Grant IAM users access to Kubernetes with a ConfigMap - Amazon EKS

今後は EKS アクセスエントリーへの移行を進めて、今回のような「気づいたらマッピングが消えていた」系の事故を防いでいきたいところです。

参考ドキュメント

脚注
  1. ハートビートには二種類あるのですが、今回は軽量な Node Lease の方を見ています ↩︎

  2. ver 1.31 の時のデフォ値は 40 秒でした ↩︎

  3. ここは時間がなくて検証出来ていないのですが、おそらく EKS の認証トークンの TTL が残っていたのを再起動すれば消せるためかと思われます ↩︎

GitHubで編集を提案

Discussion