EKS でSecurity Groups for PodsとPod Identityを共存させるベストプラクティス
はじめに
- こんにちわ、新川です。これまで以下の記事を通して、EKS の構築方法をお伝えしてきました。今回は、EKS 構築の応用編として、「Security Groups for Pods」と「Pod Identity」を共存させるベストプラクティスをご紹介します。
なぜこの記事を書いたのか
- 今年度私が所属するチームで担当した案件において、EKS を使用して、「Security Groups for Pods」と「EKS Pod Identity」を組み合わせた構成に挑戦しました。この機能のメリットや採用した根拠については、後述します。
- いざ構築を始めてみると、公式ドキュメントや既存の技術ブログの手順通りに進めても、一筋縄ではいかず多大な時間を要しました。同じ構成を目指すエンジニアの皆さんが無駄な工数をかけずに済むよう、この記事を執筆しました。本記事では、私たちが実際に、GitHub の Issue を調査したり、トライアンドエラーでパラメータを変更しながら解決した、これらの機能を共存させるためのベストプラクティスを共有したいと思います。
「Security Groups for Pods」と「Pod Identity」を採用する目的
- 結論から記載すると、「Security Groups for Pods」と「Pod Identity」を採用する最大の目的は、AWS のベストプラクティスである”最小権限の原則”をネットワークとIAM権限の両面でPod単位に適用し、クラスター全体のセキュリティを向上させることにあります。どちらの機能もデフォルトで有効になっているわけではありません。2つの機能を組み合わせることで、ネットワークと権限の両面から強固なセキュリティを担保するアーキテクチャを目指しました。
EKS Pod Identity を採用する根拠とメリット
- 採用の根拠:従来の「ノードロール」の課題
- 従来、PodがAWSリソース(S3やDynamoDBなど)にアクセスする際は、Podが動作するEC2ノード自身のIAMロール(ノードロール)を利用していました。これには以下のセキュリティリスクがありました。
- 過剰な権限の付与:同一ノード上で動作する異なるPod(例:S3を使うPod AとDynamoDBを使うPod B)がある場合、ノードロールには両方の権限が必要になります。結果として、Pod AがDynamoDBを操作できてしまうといった不適切な権限の共有が発生します。
- 最小権限の原則に反する:万が一Pod Aが侵害された場合、攻撃者は本来不要なDynamoDBの操作も可能になり、被害が拡大する恐れがあります。
- メリット
- Pod単位の厳格な権限管理:IAMロールをKubernetesのサービスアカウントに直接関連付け、自分専用に許可された権限のみを利用できます。
- 管理の簡素化:アプリケーション(Pod)とIAMロールの対応が明確になり、ノードロールに大量のポリシーを追加し続ける複雑な管理から解放されます。
- コンプライアンスの強化:アクセス証跡の追跡や権限分離が容易になり、厳格なセキュリティ基準を満たすことが可能です。
- 従来、PodがAWSリソース(S3やDynamoDBなど)にアクセスする際は、Podが動作するEC2ノード自身のIAMロール(ノードロール)を利用していました。これには以下のセキュリティリスクがありました。
- EKS Pod Identity に関する資料は、以下を参照ください。
Security Groups for Pods を採用する根拠とメリット
- 採用の根拠:従来の「ノード単位SG」の課題
- 通常、EKSの各PodはノードのENI(ネットワークインターフェース)のセカンダリIPを使用し、ノードと同じセキュリティグループ(SG)を共有します。
- 同一ノード内の全てのPodが同じSGを持つため、「Pod Aだけにデータベース(RDS/ElastiCache)へのアクセスを許可する」といった個別のネットワーク制御ができませんでした。
- メリット
- AWSネイティブな制御:Amazon VPC CNI プラグインを使用し、特定のPod専用のENIをプロビジョニングすることで、Pod単位でSGを適用できます。
- ノードからの独立:Podはノードのネットワーク設定から完全に独立し、VPC内の独立したEC2インスタンスのように振る舞うことが可能です。
- DBアクセスの最適化:私が担当した案件では、RDSやElastiCacheなどのデータベースアクセスを特定のPodに限定し、ネットワークレベルでの安全性を担保しています。
- 通常、EKSの各PodはノードのENI(ネットワークインターフェース)のセカンダリIPを使用し、ノードと同じセキュリティグループ(SG)を共有します。
- Security Groups for Pods に関する資料は、以下を参照ください。
EKS の構築時に発生した問題について
問題の発生条件
- 目標とした構成は理想的でしたが、実際にリソースをデプロイすると、大きく分けて 2つの問題に直面しました。
- これらの問題は、単にアドオンをインストールしただけではなく、Amazon VPC CNI (aws-node) を使用し、SecurityGroupPolicy を適用した環境で Pod Identity を有効化したという特定の条件下で顕在化しました。
Security Groups for Pods を適用時にPod がReady にならない
- Podにセキュリティグループを割り当てた直後、PodはRunning状態になるものの、ヘルスチェック(Liveness/Readiness Probe)が失敗し続け、いつまでも 0/1 READY のままという事象に直面しました。
$ kubectl get securitygrouppolicy -A
No resources found
$ kubectl apply -f securitygrouppolicy.yaml
securitygrouppolicy.vpcresources.k8s.aws/niikawa-eksgp-01 created
$ kubectl get securitygrouppolicy -A
NAMESPACE NAME SECURITY-GROUP-IDS
niikawa-ekns-01 niikawa-eksgp-01 ["sg-01212121212121212","sg-03434343434343434"]
$ kubectl apply -f deployment.yaml
deployment.apps/niikawa-ekdep-01 created
$ kubectl get pods -n niikawa-ekns-01
NAME READY STATUS RESTARTS AGE
niikawa-ekdep-01-578744cf8f-9shft 0/1 Running 0 64s
niikawa-ekdep-01-578744cf8f-q8pc2 0/1 Running 0 64s
$
- 調査の過程で、以下のステップで問題の切り分けを行いました。
- Podイベントの確認:kubectl describe pod を実行したところ、以下のWarning が繰り返し発生しており、通信自体がPodに到達していないことが判明しました。
Warning Unhealthy 92s (x3 over 112s) kubelet Liveness probe failed: Get "http://10.100.0.70:8080/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Normal Killing 92s kubelet Container prod-qujpmain-ekct-01 failed liveness probe, will be restarted
Warning Unhealthy 1s (x15 over 115s) kubelet Readiness probe failed: Get "http://10.100.0.70:8080/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
- SGルールの検証:ノードからのインバウンドを許可するSG設定は正しいことを確認しましたが、プローブは失敗しました。これにより、SGではなく、さらに下層のネットワークスタックの問題が疑われました。
- VPC CNI設定の確認: aws-node の環境変数を確認し、Security Groups for Pods環境下でのプローブ通信を阻害する「TCP Early Demux」の影響や、ネットワークポリシーの適用モードを調査対象としました。
Pod Identity を設定後にcredentialsが取得できずタイムアウト
- 次に、ようやく Pod が起動したと思いきや、アプリケーションから AWS サービスへのアクセスに失敗しました。Pod にログインし、aws コマンドを実行すると、以下のエラーが発生していました。
- Connect timeout on endpoint URL: “http://169.254.170.23/v1/credentials"
- Pod Identity Agent のエンドポイントへの通信がタイムアウトしており、Pod が IAM 認証情報を取得できていないことが判明しました。
root@niikawa-ekdep-03-5bf7b5b95b-r8rxp:/# aws sts get-caller-identity
Error when retrieving credentials from container-role: Error retrieving metadata: Received error when attempting to retrieve container metadata: Connect timeout on endpoint URL: "http://169.254.170.23/v1/credentials"
- 原因を特定するために、以下のステップで調査を行いました。
- プロキシ設定の確認:最初に HTTP_PROXY 等の影響を疑いましたが、環境変数は設定されておらず、プロキシの問題ではないことがわかりました。
- Agent ログの確認:システム系の Pod(aws-node など)は正常に認証情報を取得できていましたが、アプリケーション Pod からのリクエストだけが Agent のログに一切記録されていませんでした。
- iptables の確認:ノードの iptables を確認したところ、Pod Identity に必要な転送ルールが完全に欠落していることが判明しました 。
- ネットワーク空間の検証:hostNetwork: true を設定すると通信が成功することを確認し、根本原因がSecurity Groups for Pods 設定時の Pod からノード内 Agent への疎通にあることを突き止めました。
構築時に起きた問題の原因について
- リソースデプロイ直後に直面したこの問題の原因を紐解くと、AWSマネージドサービスの利便性と、標準実装されるセキュリティが特定の構成下で引き起こすネットワークスタックの不整合に行き着きました。
Security Groups for Pods を適用時にPod がReady にならない原因について
- Security Groups for Pods を適用したPodは、ノードのIPを共有せず、専用の ENI (Branch ENI) を持ちます。この仕組みが以下の2点で通信を遮断していました。
- Linuxカーネルの最適化によるパケットドロップ (TCP Early Demux):
- Linuxカーネルの最適化機能である「Early Demux」が有効な場合、Branch ENI 経由で入ってくるプローブパケットが、カーネルレベルで正しくルーティングされずドロップされる事象が発生します 。これが原因で、SGで許可していてもプローブがタイムアウトしていました。
- デフォルトのネットワーク制御モード (strict):
- Amazon VPC CNI のデフォルト(strict モード)では、Podのセキュリティグループがリンクローカル通信を含めあらゆる通信を厳格に制限します。CoreDNSへの53番ポート経由のアウトバウンド通信なども許可が必要になり、適切な設定がないとPod内の通信が完全に孤立してしまいます 。
Pod Identity を設定後にcredentialsが取得できずタイムアウトする原因について
- Security Groups for Pods が有効な環境で Pod Identity の認証情報が取得できない根本的な理由は、Branch ENI を持つ Pod からノード上のリンクローカルアドレス(Agent)への通信が、VPC CNI によって遮断または正しくルーティングされないためです。調査の結果、GitHub の Issue や CNI の仕様から以下のメカニズムが判明しました。
- セキュリティグループによるノード内通信のブロック:
- デフォルトの POD_SECURITY_GROUP_ENFORCING_MODE: “strict" では、Pod の全通信が SG によって制限されます。Pod Identity Agent はノード上で動いているため、Pod から見ればローカル通信になりますが、このモードでは同一ノード内であっても SG によって通信がブロックされてしまいます。
- SNAT による Pod SG の無効化:
- 通常、Pod の通信はノードの IP で SNAT されますが、この過程で Pod 独自の SG 設定が無視されることがあります。AWS_VPC_K8S_CNI_EXTERNALSNAT: “true" を設定しないと、Pod の SG が正しく適用されず、Agent への到達に必要な経路が確保できません。
- VPC CNI の不整合:
- Security Groups for Pods と Pod Identity を併用する場合、VPC CNI が「Security Groups for Pods 用のネットワークスタック」と「Pod Identity 用のルーティング」を正しく組み合わせて処理できず、必要な iptables ルールの作成をスキップしてしまう不整合が発生していました。
- pods with attached security groups cannot reach Pod Identity Agent link local address
- https://github.com/aws/amazon-vpc-cni-k8s/blob/master/README.md
対応策について
- トライアンドエラーを経て確立した、安定稼働のための VPC CNI(aws-node)設定のベストプラクティスを記載します。これらは公式ドキュメントでは断片的な記載に留まりますが、いくつかの設定を組み合わせることで初めて Security Groups for Pods を正常に設定することができます。
EKS で Security Groups for Pods を適用する場合のベストプラクティス
- ネットワークポリシーモードを standard に変更
- NETWORK_POLICY_ENFORCING_MODE を standard に設定することで、ネットワーク制御の柔軟性を確保します。これにより、同一ノード内の特定通信が過度に制限されるのを防ぎます。
- TCP Early Demux の無効化
- ヘルスチェックパケットのドロップを防ぐため、DISABLE_TCP_EARLY_DEMUX を true に設定します。この設定は aws-node コンテナと aws-vpc-cni-init コンテナの両方に適用する必要があります。
- セキュリティグループの構成
- SecurityGroupPolicy で指定する ID には、アプリケーション固有の SG だけでなく、EKS クラスターのセキュリティグループ(eks-cluster-sg-xxx)を必ず含めます。これにより、ノードからのプローブ通信や CoreDNS へのアクセスが許可されます 。
# ネットワークポリシーモードの変更
# aws-nodeコンテナの環境変数を変更
kubectl set env daemonset aws-node -n kube-system NETWORK_POLICY_ENFORCING_MODE=standard
# aws-eks-nodeagentコンテナの引数を変更
kubectl patch daemonset aws-node -n kube-system --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/1/args/1","value":"--enable-network-policy=true"}]'
# aws-eks-nodeagentコンテナの環境変数を変更
kubectl patch daemonset aws-node -n kube-system -p '{"spec":{"template":{"spec":{"containers":[{"name":"aws-eks-nodeagent","env":[{"name":"NETWORK_POLICY_ENFORCING_MODE","value":"standard"}]}]}}}}'
# TCP Early Demux の無効化
# aws-nodeコンテナ
kubectl patch daemonset aws-node -n kube-system -p '{"spec":{"template":{"spec":{"containers":[{"name":"aws-node","env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}]}]}}}}'
# aws-vpc-cni-initコンテナ
kubectl patch daemonset aws-node -n kube-system -p '{"spec":{"template":{"spec":{"initContainers":[{"name":"aws-vpc-cni-init","env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}]}]}}}}'
# 設定の確認
# モード設定の確認 (3箇所がstandardになっていること)
kubectl describe daemonset aws-node -n kube-system | grep -i enforcing
# Demux設定の確認 (2箇所がtrueになっていること)
kubectl describe daemonset aws-node -n kube-system | grep -i demux
EKS でSecurity Groups for Pods + Pod Identity を設定する際のベストプラクティス
- Security Groups for Pods と Pod Identity を共存させるには、前述の設定に加えて、さらに踏み込んだ VPC CNI のパッチ適用が必要になります。
- Pod Identity の有効化:ENABLE_POD_IDENTITY を true に設定し、VPC CNI に Pod Identity 機能を認識させ、ノードに転送ルールを作成させます。
- 外部 SNAT の有効化:AWS_VPC_K8S_CNI_EXTERNALSNAT を true に変更します。これが抜けていると、Pod の SG が正しく適用されず、認証情報の取得に失敗します。
- エンフォースメントモードの再定義:POD_SECURITY_GROUP_ENFORCING_MODE を standard に設定します。これにより、同一ノード内の Pod Identity Agent への通信を SG の制限対象から外すことで、ブロックされずに疎通させることが可能になります。
なぜ設定が必要か?なんの設定か?を深堀り
- インターネット上に情報が少なかった、これらの追加設定が具体的に何をしているのかを詳しく解説します。
- POD_SECURITY_GROUP_ENFORCING_MODE: “standard"
- 役割:Pod の SG による制限を「標準的」なレベルに緩和するモードです。
- なぜ必要か:デフォルトの strict モードでは、Pod のあらゆる通信が SG で厳格に制限されます。しかし、Pod Identity Agent はノード上で動作しており、Pod から見れば「同一ノード内のローカル通信」です。standard モードにすることで、このノード内通信が SG のブロック対象から外れ、認証情報がスムーズに取得できるようになります。
- DISABLE_TCP_EARLY_DEMUX: “true"
- 役割:Linux カーネルの TCP 受信処理の最適化(Early Demux)を無効化します。
- なぜ必要か:Branch ENI 特有のルーティング問題を回避するためです。Security Groups for Pods 環境下では、この最適化機能が原因でプローブ通信などのパケットが正しくルーティングされず、ドロップされる事象が発生します。これを無効化することで、ノードからのヘルスチェックが Pod に確実に届くようになります。
- AWS_VPC_K8S_CNI_EXTERNALSNAT: “true"
- 役割:Pod の通信を SNAT(ソースネットワークアドレス変換)せずに、Pod の ENI から直接出すようにする設定です。
- なぜ必要か:通常、Pod の通信はノードの IP で SNAT されますが、これだと Pod に割り当てた SG が無視され、ノードの SG が使われてしまいます。この値を true にすることで、Pod のIP が維持され、外部通信に対してPod の SGが有効になります。その結果、Pod Identity Agent への通信制御も正しく行えるようになります。
【補足】構築中に遭遇する「無視していい」エラーメッセージについて
- 構築の過程で kubectl describe pod を実行した際、以下のようなエラーメッセージを経験することがあります。
Failed to create Pod sandbox: rpc error: code = Unknown desc = failed to set up sandbox container… failed to assign an IP address to container
- 実際のkubectl describe pod コマンドの出力結果抜粋です。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreatePodSandBox 2m13s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "aad50db96796df0532cbf31b97c757cf0e6171831cf7a4ec40e2d1b3d9a0fc9d": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to assign an IP address to container
Normal SecurityGroupRequested 2m13s vpc-resource-controller Pod will get the following Security Groups [sg-01212121212121212 sg-03434343434343434]
Normal ResourceAllocated 2m12s vpc-resource-controller Allocated [{"eniId":"eni-00bc8975a0123b456","ifAddress":"0a:c1:05:11:22:33","privateIp":"10.100.0.70","ipv6Addr":"","vlanId":1,"subnetCidr":"10.100.0.100/26","subnetV6Cidr":"","associationID":"trunk-assoc-a1234567"}] to the pod
Warning FailedCreatePodSandBox 2m12s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "abb48c5c34be62ec79778472f48aa0a44981d529330069f1835dc65155fd8552": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to assign an IP address to container
Normal Pulling 2m kubelet Pulling image "busybox:latest"
Normal Pulled 116s kubelet Successfully pulled image "busybox:latest" in 3.946s (3.946s including waiting). Image size: 2223686 bytes.
Normal Created 116s kubelet Created container: niikawa-container-01
Normal Started 116s kubelet Started container niikawa-container-01
- 特に Security Groups for Pods を適用していると、Pod が Running になるまでの間にこのログが記録されることがありますが、結論から言うと、これは無視して問題ありません。
- このメッセージは、Amazon VPC CNI が Pod 用のネットワークインターフェース(ENI)を作成し、IP アドレスを割り当てようとしている最中に発生する「一時的なタイムラグ」によるものです。Security Groups for Pods を利用する場合、通常の Pod よりもネットワークのセットアップに時間がかかるため、その準備が整うまでの間、Kubernetes 側がログを記録し続けます。 数分待って Pod が無事に Running 状態になれば、内部的には正常に処理が完了しています。この挙動については、AWS の公式ドキュメントにも記載があります。
AWS 公式ドキュメント – Security groups for Pods
まとめとハマらないためのチェックリスト
- 最後に、構築時に見落としがちなポイントをまとめました。
チェックリスト
- [ ] aws-node (VPC CNI) の環境変数が、Helm やマニフェストで上書きされていないか確認する。
- [ ] SecurityGroupPolicy にクラスター SG が含まれているか確認する。
- [ ] 設定変更後は必ず kubectl rollout restart daemonset aws-node -n kube-system を実行して反映させる。
- [ ] ノードの iptables を確認し、169.254.170.23 宛のルールが存在するかチェックする。
まとめ
- この記事が、EKS の高度なセキュリティ構成に挑むエンジニアの助けになれば幸いです。