はじめに
- こんにちは、新川です。先日執筆した「EKS Pod Identity」の記事では、Pod単位でIAM権限を安全に制御する方法をご紹介しました。今回はその続編として、EKSクラスタ内のアプリケーションを外部に公開するための重要なコンポーネント AWS Load Balancer Controller に焦点を当て、ハンズオンを行います。
[ハンズオン] EKS + Karpenter のデプロイ・トラブルシューティング演習
EKS Pod Identity:S3アクセスを例に学ぶPod単位のIAM制御
- 本記事では、AWS Load Balancer Controller を使ってアプリケーションを公開するだけでなく、ALB に証明書の設定、リスナールールの設定、ALB のアクセスログを有効化に設定します。また、AWS Load Balancer Controller が必要とするAWS リソースへのアクセスには、前回のテーマである EKS Pod Identity を使って権限を付与します。
- 本記事では、上記のハンズオンで作成したEKS と Karpenter環境があることを前提に進めます。
AWS Load Balancer Controller とは?
- AWS Load Balancer Controller は、EKS のアドオンの1つです。ALB の設定をYAMLで管理できます。
- Kubernetesや、特にKarpenter のオートスケーラーを利用する環境では、Podやノードは動的にスケールするため、アプリケーションが動作するPod のIPアドレスは定期的に変化します。もしAWS Load Balancer Controller の管理を使用せず、ALB を作成した場合、Pod が再作成されるたびにターゲットグループへIPアドレスの登録が必要になり、運用が破綻してしまいます。
- AWS Load Balancer Controller は、ALB の作成、リスナーやルーティングルールの設定、ターゲットグループへのPod IPアドレスの登録・解除まで、一連のライフサイクルを完全に自動化します。その結果、運用担当者の負荷を軽減しながら、安定したサービス提供を可能にします。
今回の検証シナリオ
- 今回のハンズオンでは、AWS Load Balancer Controller を使用してALBの作成、ルーティングルールの設定を検証します。
- パス /echo へのアクセス:正常なリクエストとして扱い、前回のハンズオンで作成したNginx が動作するPod にトラフィックを転送します。
- それ以外のパス (/, /test など) へのアクセス:バックエンドのPod には転送せず、「404 Not Found」の固定レスポンスを返します。
- ALB にはHTTPS リスナーを作成し、ACMで発行した証明書を登録します。
- ALB のアクセスログを有効に設定し、アクセスログが記録されることを確認します。
【ハンズオン】AWS Load Balancer Controller によるALB の管理とアクセス制御の手順
手順1:事前準備(ACM証明書とS3 バケット)
- WebサービスをHTTPS で公開するため、ACM で証明書を発行します。ACM の証明書発行手順はこちらのドキュメントを参照ください。
- 後ほど、証明書のARN を使用します。
- ALB のアクセスログを格納するための S3 バケットを作成します。S3 バケットは、ALB と同じリージョンに作成しましょう。
- S3 のバケットポリシーには、ALB アクセスログを書き込むための許可を設定します。ALB アクセスログを有効化する手順は、こちらのドキュメントを参照ください。
手順2:AWS Load Balancer Controller 用のIAM リソース作成
- 以下のAWS ドキュメントを参考に、AWS Load Balancer Controller のインストールを行います。
- GitHubからAWS Load Balancer Controller 用のIAM ポリシーをダウンロードします。最新のバージョンは、以下で確認ください。
- aws iam create-policy コマンドを使用して、AWS Load Balancer Controller 用のIAM ポリシーを作成します。
~ $ curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.4/docs/install/iam_policy.json % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 8446 100 8446 0 0 69759 0 --:--:-- --:--:-- --:--:-- 69801 ~ $ aws iam create-policy --policy-name niikawa-AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json { "Policy": { "PolicyName": "niikawa-AWSLoadBalancerControllerIAMPolicy", "PolicyId": "ANPATRPRKFUWELU322OFA", "Arn": "arn:aws:iam::111111111111:policy/niikawa-AWSLoadBalancerControllerIAMPolicy", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 0, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "CreateDate": "2025-08-07T15:08:18+00:00", "UpdateDate": "2025-08-07T15:08:18+00:00" } }
- コンソールで確認します。以下のIAM ポリシーが作成されました。
- 続いて、AWS Load Balancer Controller 用のIAM ロールを作成します。
- まずロールに使用する信頼ポリシーを準備します。以下のJSON(load-balancer-controller-trust-policy.json)を使用します。Pod Identity用に、Action に、「sts:TagSession」を指定する必要があります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": [ "sts:AssumeRole", "sts:TagSession" ] } ] }
- aws iam create-role コマンド、aws iam attach-role-policy コマンドでIAM ロールを作成します。
~ $ aws iam create-role --role-name niikawa-AmazonEKSLoadBalancerControllerRole --assume-role-policy-document file://load-balancer-controller-trust-policy.json { "Role": { "Path": "/", "RoleName": "niikawa-AmazonEKSLoadBalancerControllerRole", "RoleId": "AROATRPRKFUWH5X724VE6", "Arn": "arn:aws:iam::111111111111:role/niikawa-AmazonEKSLoadBalancerControllerRole", "CreateDate": "2025-08-07T15:11:27+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": [ "sts:AssumeRole", "sts:TagSession" ] } ] } } } ~ $ aws iam attach-role-policy --role-name niikawa-AmazonEKSLoadBalancerControllerRole --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/niikawa-AWSLoadBalancerControllerIAMPolicy
- コンソールで確認します。以下のIAM ロールが作成されました。
手順3:AWS Load Balancer Controller 用のPod Identity設定
- eks-pod-identity-agent のアドオンがインストールされていることを確認します。
~ $ aws eks list-addons --cluster-name ${CLUSTER_NAME} { "addons": [ "coredns", "eks-pod-identity-agent", "kube-proxy", "metrics-server", "vpc-cni" ] }
- 以下のeksctl create podidentityassociation コマンドを使用して、Pod Identity の設定を行います。
eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace kube-system \ --service-account-name aws-load-balancer-controller \ --role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/niikawa-AmazonEKSLoadBalancerControllerRole
- 以下は、コマンド実行後の出力です。
2025-08-07 22:02:42 [ℹ] 1 task: { create pod identity association for service account "kube-system/aws-load-balancer-controller" } 2025-08-07 22:02:43 [ℹ] created pod identity association for service account "aws-load-balancer-controller" in namespace "kube-system" 2025-08-07 22:02:43 [ℹ] all tasks were completed successfully
- コンソールで確認します。以下の通り、Pod Identity の関連付けが追加されました。
手順4:AWS Load Balancer Controller のインストール
- helm のコマンドを使用し、AWS Load Balancer controller をインストールします。helm が未インストールの場合は、前回のハンズオンを参照ください。
~ $ helm repo add eks https://aws.github.io/eks-charts "eks" already exists with the same configuration, skipping ~ $ helm repo update eks Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "eks" chart repository Update Complete. ⎈Happy Helming!⎈ ~ $ helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=${CLUSTER_NAME} \ --set serviceAccount.create=true \ --set serviceAccount.name=aws-load-balancer-controller NAME: aws-load-balancer-controller LAST DEPLOYED: Thu Aug 7 21:56:32 2025 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: AWS Load Balancer controller installed!
- コンソールで確認します。以下の通り、AWS Load Balancer controller のPod が追加されました。
手順5:EKSクラスタにIngressとService をデプロイする
- コンソールで確認します。現時点では、ALB はありません。
- 以下のコードを使用し、Ingress 作成用のYAML を作成します。(例:alb-deployment.yaml)
- Ingress とは、外部リクエストを適切なServiceへ振り分ける役割を担います。EKSでは、ロードバランサーが相当します。
- certificate-arn には、ACMで発行した証明書を指定します。
- load-balancer-attributes には、アクセスログを格納するS3 バケットを指定します。
- service は、後述のYAML で定義を行います。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: niikawa-testenv-ingress namespace: default annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip # --- HTTPS設定 --- alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' alb.ingress.kubernetes.io/certificate-arn: 'arn:aws:acm:us-west-2:111111111111:certificate/8794a86b-9c5b-4280-a313-a742c6eafea0' alb.ingress.kubernetes.io/ssl-redirect: '443' # --- アクセスログ設定 --- alb.ingress.kubernetes.io/load-balancer-attributes: access_logs.s3.enabled=true,access_logs.s3.bucket=alb-logs-us-west-2-niikawa-test,access_logs.s3.prefix=eks-handson-logs # # --- アクションの定義 --- # "response-404" という名前で、404エラーを返す固定レスポンスアクションを定義 alb.ingress.kubernetes.io/actions.response-404: > {"type":"fixed-response","fixedResponseConfig":{"contentType":"text/plain","statusCode":"404","messageBody":"The requested resource was not found."}} spec: ingressClassName: alb # --- デフォルトバックエンド --- # どのルールにもマッチしないリクエストは、上記で定義した "response-404" アクションに転送する defaultBackend: service: name: response-404 port: name: use-annotation # --- ルーティングルール --- rules: - http: paths: # "/echo" パスへのリクエストを "niikawa-testenv-service" に転送 - path: /echo pathType: Prefix backend: service: name: niikawa-testenv-service port: number: 80
- 以下のコードを使用し、Service 作成用のYAML を作成します。(例:service-deployment.yaml)
- Serviceは、Podへのアクセスを提供します。外部 → Ingress → Service → Pod という通信の流れになります。
- selector が指定しているapp の名前が前回のハンズオンで作成したNginx Pod のDeployment になります。
- ClusterIP は、Pod間で通信を行うための設定です。
# service.yaml apiVersion: v1 kind: Service metadata: name: niikawa-testenv-service # Ingressが指定している名前と一致させる namespace: default spec: # このselectorが、DeploymentのPodのラベルと一致することが重要 selector: app: niikawa-testenv ports: - protocol: TCP port: 80 # Serviceが受け付けるポート targetPort: 80 # Podコンテナが待ち受けているポート type: ClusterIP
- EKS クラスタに、IngressとService のリソースをデプロイします。
~ $ kubectl apply -f alb-deployment.yaml ingress.networking.k8s.io/niikawa-testenv-ingress created ~ $ kubectl apply -f service-deployment.yaml service/niikawa-testenv-service created
- kubectl get ingress コマンドで、ALB を確認します。
~ $ kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE niikawa-testenv-ingress alb * k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com 80 64m
- コンソールでも確認します。ALB が作成されました。
- 続けてALB のリスナーとリスナールールを確認します。HTTPS:443 のリスナーが作成され、証明書が登録されていることを確認します。
- リスナールールに2つのルールが作成されていることを確認します。
- ターゲットグループを確認します。ヘルスチェックがHealthy であることをチェックします。
手順6:疎通確認を行う
- curl コマンドを実行し、ALB にアクセスの結果、Nginx Pod から応答を確認します。
※Nginx Pod のコンテナイメージにおいて、/echo にhtmlファイルが配置されていない場合は、404 が返ります。
$ curl https://k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com/echo -k -vv * Trying 54.69.231.237:443... * Connected to k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com (54.69.231.237) port 443 (#0) ** 省略 ** > GET /echo HTTP/1.1 > Host: k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com > User-Agent: curl/7.81.0 > Accept: */* > * TLSv1.2 (IN), TLS header, Supplemental data (23): * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Fri, 08 Aug 2025 00:01:19 GMT < Content-Type: text/html < Content-Length: 11 < Connection: keep-alive < Server: nginx/1.29.0 < Last-Modified: Sat, 05 Jul 2025 13:35:02 GMT < ETag: "68692a06-b" < Accept-Ranges: bytes < Hello EKS! * Connection #0 to host k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com left intact $ curl https://k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com/ -k The requested resource was not found.
- S3 バケットを確認し、アクセスログが格納されていることを確認します。
トラブルシューティング
トラブルシューティング1:権限(ec2:DescribeRouteTables)の不足
- kubectl apply コマンドで、ALB が作成されませんでした。
~ $ kubectl apply -f alb-deployment.yaml ingress.networking.k8s.io/niikawa-testenv-ingress created ~ $ kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE niikawa-testenv-ingress alb * 80 66s
- kubectl logs コマンドでログを確認した結果、AWS Load Balancer ControllerがALBを配置するサブネットを自動検出するためにルートテーブルを調べる権限(ec2:DescribeRouteTables)が必要ですが、その権限が付与されていないことが分かりました。
~ $ kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller NAME READY STATUS RESTARTS AGE aws-load-balancer-controller-869f5cdf8b-9c2ng 1/1 Running 0 23m aws-load-balancer-controller-869f5cdf8b-zk9kc 0/1 CrashLoopBackOff 9 (77s ago) 23m ~ $ CONTROLLER_POD_NAME=$(kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller -o jsonpath='{.items[0].metadata.name}') ~ $ echo ${CONTROLLER_POD_NAME} aws-load-balancer-controller-869f5cdf8b-9c2ng ~ $ ~ $ kubectl logs -n kube-system ${CONTROLLER_POD_NAME} ** 省略 ** {"level":"error","ts":"2025-08-07T22:14:21Z","msg":"Reconciler error","controller":"ingress","object":{"name":"niikawa-testenv-ingress","namespace":"default"},"namespace":"default","name":"niikawa-testenv-ingress","reconcileID":"0e8683d3-75b7-4f38-aa63-43957f03f88e","error":"couldn't auto-discover subnets: failed to list subnets by reachability: operation error EC2: DescribeRouteTables, https response error StatusCode: 403, RequestID: 0cf54642-81ca-4ad5-8e34-5a0b27e4b351, api error UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:sts::111111111111:assumed-role/niikawa-AmazonEKSLoadBalancerControllerRole/eks-niikawa-ka-aws-load-b-a0052889-4d2f-4f15-aae9-544bf793b895 is not authorized to perform: ec2:DescribeRouteTables because no identity-based policy allows the ec2:DescribeRouteTables action"}
- 原因は、最新のAWS Load Balancer Controller 用のIAM ポリシーを使用しなかったからです。GitHubからAWS Load Balancer Controller 用のIAM ポリシーをダウンロードして使用しましょう。
- 今回は、コンソールから「ec2:DescribeRouteTables」を追加します。
- AWS Load Balancer Controller を再起動します。
~ $ kubectl rollout restart deployment aws-load-balancer-controller -n kube-system deployment.apps/aws-load-balancer-controller restarted ~ $ kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller NAME READY STATUS RESTARTS AGE aws-load-balancer-controller-54bccd4d9d-dvcmb 1/1 Running 0 14s aws-load-balancer-controller-54bccd4d9d-svr88 1/1 Running 0 27s
トラブルシューティング2:権限(DescribeListenerAttributes)の不足
- kubectl apply コマンドで、ALB とリスナーは作成されましたが、リスナールールが作成されませんでした。
~ $ kubectl apply -f alb-deployment.yaml ingress.networking.k8s.io/niikawa-testenv-ingress configured ~ $ kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE niikawa-testenv-ingress alb * 80 52m
- kubectl logs コマンドでログを確認した結果、AWS Load Balancer Controllerがリスナーを作成した後、その設定を確認する権限 (elasticloadbalancing:DescribeListenerAttributes)が必要ですが、その権限が付与されていないことが分かりました。
~ $ CONTROLLER_POD_NAME=$(kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller -o jsonpath='{.items[1].metadata.name}') ~ $ echo ${CONTROLLER_POD_NAME} aws-load-balancer-controller-54bccd4d9d-svr88 ~ $ kubectl logs -n kube-system ${CONTROLLER_POD_NAME} ** 省略 ** {"level":"error","ts":"2025-08-07T23:03:39Z","msg":"Reconciler error","controller":"ingress","object":{"name":"niikawa-testenv-ingress","namespace":"default"},"namespace":"default","name":"niikawa-testenv-ingress","reconcileID":"59c7165b-d893-45d1-aee7-66119a2d5b8a","error":"operation error Elastic Load Balancing v2: DescribeListenerAttributes, https response error StatusCode: 403, RequestID: 5d46a54c-b138-490d-b794-a7f611037a3b, api error AccessDenied: User: arn:aws:sts::111111111111:assumed-role/niikawa-AmazonEKSLoadBalancerControllerRole/eks-niikawa-ka-aws-load-b-160a37a7-9987-44e7-ba63-809b38c1a9df is not authorized to perform: elasticloadbalancing:DescribeListenerAttributes because no identity-based policy allows the elasticloadbalancing:DescribeListenerAttributes action"}
- 原因は、最新のAWS Load Balancer Controller 用のIAM ポリシーを使用しなかったからです。GitHubからAWS Load Balancer Controller 用のIAM ポリシーをダウンロードして使用しましょう。
- 今回は、コンソールから「elasticloadbalancing:DescribeListenerAttributes」を追加します。
トラブルシューティング3:疎通コマンドにてエラー(504 Gateway Time-out)
- kubectl apply コマンドでALBを作成後、疎通コマンドにて504 のエラーが発生しました。
$ curl https://k8s-default-niikawat-445ebde18b-1076184101.us-west-2.elb.amazonaws.com/echo -k504 Gateway Time-out 504 Gateway Time-out
- ALB がPod との通信に失敗している状態です。ALB からPod に対してリクエストを転送しましたが、Podから時間内に応答がなかったと思われます。
- kubectl logs コマンドでログを確認した結果、セキュリティグループの設定に関するエラーも見つかりました。
~ $ CONTROLLER_POD_NAME=$(kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller -o jsonpath='{.items[1].metadata.name}') ~ $ echo ${CONTROLLER_POD_NAME} aws-load-balancer-controller-54bccd4d9d-svr88 ~ $ kubectl logs -n kube-system ${CONTROLLER_POD_NAME} ** 省略 ** {"level":"error","ts":"2025-08-07T23:18:14Z","msg":"Requesting network requeue due to error from ReconcileForPodEndpoints","tgb":{"name":"k8s-default-niikawat-07a1084ffc","namespace":"default"},"error":"expected exactly one securityGroup tagged with kubernetes.io/cluster/niikawa-karpenter-demo for eni eni-0cf6a49f548acc20e, got: [] (clusterName: niikawa-karpenter-demo)"}
- EKS クラスタのセキュリティグループを特定し、以下のタグを追加して保存します。
- キー: kubernetes.io/cluster/クラスタ名
- 値: shared(変更前:owned)
- 続けてターゲットグループを確認します。ターゲットのヘルスチェックがUnhealthy からHealthy に変わるのを待ちます。
- ターゲットのヘルスチェックがUnhealthy から変化しない場合は、ノードに設定しているセキュリティグループを編集します。インバウンドに、ALB からのトラフィックを許可するルールを追加します。
まとめ
- 今回のハンズオンでは、以下の経験ができました。
- EKSクラスタに、AWS Load Balancer Controller を導入する。
- IngressリソースをYAMLで定義し、ALBをプロビジョニングできる。
- ACMを利用したHTTPS通信を実現、ALBのアクセスログを取得できる。
- Ingressリソースの定義に、パスベースのルーティングルールを設定する。
- EKS には他にも多くの機能があります。ぜひ公式ドキュメントやブログを参考に、チャレンジしてみてください。
参考資料
[ハンズオン] EKS + Karpenter のデプロイ・トラブルシューティング演習
[ハンズオン] EKS Pod Identity:S3アクセスを例に学ぶPod単位のIAM制御
Amazon EKS ユーザーガイド(AWS Load Balancer Controller)
https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/