🔖

【EKS】Amazon Linux 2 から Amazon Linux 2023 への移行(nodeadm / IMDSv2)

に公開

この記事は、kubernetes Advent Calendar 2025 の 15 日目です。
https://qiita.com/advent-calendar/2025/kubernetes

はじめに

本記事は、最近 EKS 上で EOL を迎えた Amazon Linux 2(以下、AL2)から、Amazon Linux 2023(以下、AL2023)へ移行した際の対応をまとめたものです。

EKS 上に AL2 が稼働している状態でアップグレードインサイトを見るとエラーステータスとなっており、EKS での AL2 は 2025/11/26 に EOL を迎たことがわかります。また、K8s 1.33 以降の Amazon Linux 2 AMI をリリースしないとも書いてあります。

Checks if any nodes in the cluster are running Amazon Linux 2. Support for Amazon Linux 2 on Amazon EKS will end on November 26, 2025. Additionally, Amazon EKS will not release Amazon Linux 2 AMIs for Kubernetes version 1.33 and higher

ちなみに、AL2 自体の EOL は 2026/6/30 でまだ少し余裕があります。
ref. https://aws.amazon.com/jp/amazon-linux-2/faqs/

上記の通り、Amazon EKS での AL2 のサポートはすでに終了(正確にはEKS Optimized AL2 AMI の更新・提供終了で動作はまだするはず)しているわけですが、対応を進めている環境があるかもしれないので何かの参考になれば幸いです。

基本的な方針は、公式がまとめている内容を一つずつ潰していく作業となります。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/al2023.html

ここでは、以下2点に絞って紹介します。

  1. 起動テンプレートの構成(nodeadm への変更)
  2. IMDSv2 デフォルト化及び Hop Limit 制限への対応

1. nodeadm への変更(Terraform 実装)

Self-Managed な Node Group があるなど、User Data を自分たちで管理している環境では、AL2023 向けの設定に変更する必要があります。これを行わないと、ノードが EKS クラスタに正常に参加しないという事象が発生しました。

AL2023 では、ノードの初期化プロセスが変更され、従来の User Data スクリプト(/etc/eks/bootstrap.sh を叩く方式)ではなく、nodeadm を使用する構成が推奨されています。

Terraform の aws_launch_template リソースを使って管理している場合、User Data の書き方を大きく変更する必要がありました。MIME マルチパート形式で設定を渡すのがポイントです。

config を作るにあたり参考にしたドキュメントはこちらです。
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/launch-templates.html
https://awslabs.github.io/amazon-eks-ami/nodeadm/

AL2023 nodeadm 用 Terraform 設定サンプル

  1. nodeadm NodeConfig テンプレート (nodeadm.yaml.tpl)
  ---
  apiVersion: node.eks.aws/v1alpha1
  kind: NodeConfig
  spec:
    cluster:
      name: ${cluster_name}
      apiServerEndpoint: ${cluster_endpoint}
      certificateAuthority: ${cluster_ca}
      cidr: ${service_cidr}
    kubelet:
      config:
        clusterDNS:
        - ${cluster_dns}
      # ※ label/taint が必要な場合のみ追加:
        # registerWithTaints:
        # - key: dedicated
        #   value: batch
        #   effect: NoSchedule
      # flags:
      # - --node-labels=workload=batch
  1. user_data 生成 (locals.tf)
  locals {
    boundary = "BOUNDARY"

    # base64encode して渡す必要がある
    user_data = base64encode(<<-EOF
  MIME-Version: 1.0
  Content-Type: multipart/mixed; boundary="${local.boundary}"

  --${local.boundary}
  Content-Type: application/node.eks.aws

  ${templatefile("${path.module}/nodeadm.yaml.tpl", {
    cluster_name     = aws_eks_cluster.this.name
    cluster_endpoint = aws_eks_cluster.this.endpoint
    cluster_ca       = aws_eks_cluster.this.certificate_authority[0].data
    service_cidr     = aws_eks_cluster.this.kubernetes_network_config[0].service_ipv4_cidr
    cluster_dns      = cidrhost(aws_eks_cluster.this.kubernetes_network_config[0].service_ipv4_cidr, 10)
  })}

  --${local.boundary}--
  EOF
    )
  }
  1. Launch Template (launch_template.tf)
  resource "aws_launch_template" "eks_node" {
    name     = "eks-node-al2023"
    image_id = data.aws_ssm_parameter.eks_ami.value  # AL2023 EKS Optimized AMI

    # <-- snip: instance_type, block_device_mappings, iam_instance_profile, etc. -->

    # ここが AL2023 nodeadm のポイント
    user_data = local.user_data
  }
項目 AL2 (bootstrap.sh) AL2023 (nodeadm)
設定形式 シェルスクリプト YAML (NodeConfig)
Content-Type text/x-shellscript application/node.eks.aws

実際に指定した起動テンプレートが渡っているのかは以下のコマンドで確認しました。
Terraform で適用された結果を確認するにあたり大変便利でした。

aws ec2 describe-launch-template-versions \
  --launch-template-id <lt-xxxxx> \
  --versions '$Latest' \
  --query 'LaunchTemplateVersions[0].LaunchTemplateData.UserData' \
  --output text | base64 -d

2. IMDSv2 デフォルト化及び Hop Limit 制限への対応

問題の発生

今回利用している EKS Managed Node Group では、AL2023 ベースのノードでデフォルトで IMDSv2 (Instance Metadata Service Version 2) が有効化されており、セキュリティ強化のために Hop Limit(HTTP 転送回数の制限)が 1 に設定されていました[1]。

これにより、ポッド内からノードの IAM ロール(Instance Profile)の権限を使って AWS リソースにアクセスしていた処理が、Hop Limit 制限に引っかかり通信できなくなる事象が発生しました。

┌──────────────────────────────┐
│     EC2 Instance (Host)      │
│                              │
│  ┌────────────┐              │
│  │   Pod A    │─ ✓ OK!       │ ← ホストと同じネットワーク層
│  └────────────┘   (hop=0)    │
│                              │
│  ┌─────────────────────┐     │
│  │  Pod B (Container)  │     │
│  │  ┌──────────────┐   │     │
│  │  │ Application  │─ ✗ NG!  │ ← コンテナ内部(hop=1 以上)
│  │  └──────────────┘   │     │
│  └─────────────────────┘     │
│         ↓ (hop count = 2)    │
│    169.254.169.254           │
└──────────────────────────────┘

つまり「IMDSv2 による影響調査」と「Hop Limit 制限の対応」の二つの問題を解決する必要があります。

対応方針:Hop Limit 緩和ではなく、IRSA を徹底する

公式ドキュメント等では「Hop Limit を 2 に上げる」という緩和策も紹介されていますが、AWS では IRSA を推奨しており、今回の HopLimit 問題を機に「Pod に必要な権限はすべて IRSA に集約する」という構成に移行しました。

  • Node IAM Role: 必要最小限(CNI用など)の権限のみにする。
  • Pod: 必要な権限はすべて ServiceAccount に紐づいた IAM Role から取得する。

これにより、ノード自体のセキュリティリスクを低減させ、より本来あるべき権限管理の状態に持っていくことができました。

どう調査するかの検討

冒頭の AWS による移設ガイドのドキュメントリンクにもあったのですが、IMDSv2 への移行ガイドも AWS から提供されています。AWS さんなんでもドキュメントにしてくれてありがたい。

https://aws.amazon.com/jp/blogs/security/get-the-full-benefits-of-imdsv2-and-disable-imdsv1-across-your-aws-infrastructure/

ここでは Amazon CloudWatch の MetadataNoToken メトリクスを確認する方法が記載されています。しかし、IMDSv1 の通信傾向はわかるものの、実際何が通信しているのか、そして IMDSv2 だったとしても Hop limit により影響を受けるのかはここから読み取ることができません。

そこで、いくつか方法を考え試しました。

ログによる影響調査(aws-imds-packet-analyzer)

これです。作った人に感謝を贈りたい🫶

https://github.com/aws/aws-imds-packet-analyzer

この aws-imds-packet-analyzer を DaemonSet として対象のクラスタにデプロイし、IMDS へのアクセスログを収集しました。
これを OSS としても用意しました。eBPF を使うため privileged: true、hostNetwork、hostPID が必要です。一時的な調査用とするなど、セキュリティにはお気をつけください。

https://github.com/ginbear/aws-imds-packet-analyzer-k8s/

ログサンプル

例えばこんな感じのログが取得できます(マスキング済み)。

[INFO] imdsv2 (pid:12345:argoexec argv:argoexec wait --loglevel info) called by -> (pid:12340:containerd-shim argv:/usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id abc123def456...) -> (pid:1:systemd argv:/usr/lib/systemd/systemd --switched-root --system --deserialize 21) req details: get /latest/meta-data/iam/security-credentials/my-eks-node-role http/1.1, host: 169.254.169.254, user-agent: aws-sdk-go/1.xx.xx (go1.xx.xx; linux; amd64), **token redacted**, accept-encoding: gzip,

分解していくとこのようなアクセスが見えてきます。

  systemd                   ←  Node の init
    └── containerd-shim     ←  Pod のコンテナ管理
          └── argoexec      ←  ★ IMDS にアクセスした張本人
                 │
                 │ GET /latest/meta-data/iam/security-credentials/<role-name>
                 ▼
           169.254.169.254 (IMDS)

このログを一定期間出力して、IMDSを経由する通信を精査しました。
IMDSv1の通信はログからすぐに判別できますが、Hop limit に関係するかはどのような通信であるか確認する必要があります。

この時発見したのは、kubectl logs ではなく直接 pod から転送するとタイムスタンプが付いてて便利でした。しかしログローテにより期間が短くなっていることもあるのでご注意下さい。

for pod in $(kubectl get pods -n default -l app=aws-imds-packet-analyzer -o name); do
  kubectl logs -n default $pod > ${pod##*/}-$(date +%Y%m%d-%H%M%S).log
done

↓ こっちだと timestamp 付でログ取得できます。ただし取得できるログの期間は(デフォルトでは)短めです。ログパスなどは環境に合わせて変更ください。

for pod in $(kubectl get pods -n default -l app=aws-imds-packet-analyzer -o name); do
    # 全ローテーションログを結合して取得
    kubectl exec -n default $pod -- sh -c 'cat /var/log/imds/imds-trace.log.* /var/log/imds/imds-trace.log 2>/dev/null | sort' > ${pod##*/}-$(date +%Y%m%d-%H%M%S).log
done

また、途中から気付いたのですが、Datadog Agent のログ収集を有効化していたため、DaemonSet として動作している aws-imds-packet-analyzer のログも Datadog に転送されていました。
これによりログの調査も Datadog の Logging で精査することができます。こちらなら timestamp もついていますし、様々な tag 情報も付与されていますので、調査の助けになります。

終わりに

AL2 から AL2023 に移設する時に調査・対応をした内容の一部をご紹介しました。
移設は完了しており、何のトラブルもなく無事に...! と言いたいところですが、実際にプロダクションで稼働すると、特定の Pod がまだ IMDS に依存していた箇所があり、そこは後追いで IRSA 化を行いました。完璧な対応は難しい!

脚注
  1. この記事を書きながら調査したところ、https://docs.aws.amazon.com/ja_jp/linux/al2023/ug/imdsv2.html を読むと AL2023 のデフォルトのホップ制限は 2 となっていました。恐らくEKS Managed Node Group で AL2023 AMI を使ったときに、ホップ制限が 1 となっていることが手元の環境では確認できました。 ↩︎

GitHubで編集を提案
Atrae Tech

Discussion