はじめに

  • こんにちは、新川です。先日執筆した「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 リソース作成

~ $ 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 -k

504 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/