IRSAのIAM管理が地獄になったのでEKS Pod Identityに移行した話

OIDCプロバイダーのARN管理やクラスター再作成のたびにIAMポリシーを書き直す辛さ、経験ありませんか?本番移行1年で気づいたPod Identityの落とし穴と設計のコツを共有します。

IRSAでずっと戦ってた僕がPod Identityに移行した経緯

先日、チームのEKSクラスターで認証周りの大規模リファクタリングをやり切ったので、その知見を残しておく。正直最初は「またAWSが新しいものを出した」くらいの温度感だったんだけど、実際に移行してみたらIRSA(IAM Roles for Service Accounts)のときに抱えていた悩みの大半が消えた。

2024年末ごろからEKS Pod Identity AssociationsがGAになり、2025〜2026年にかけてAWSも「新規クラスターはPod Identityを使ってくれ」というスタンスを明確にしてきた。うちのチームでも既存クラスターのIRSA設定が増殖しすぎてIAMポリシーの管理が地獄になっていたので、2025年の年初から移行プロジェクトをスタートさせた。

当時の課題はこんな感じだった。箇条書きにすると短く見えるけど、どれも毎週じわじわ消耗させてくるタイプの辛さだった。

  • OIDCプロバイダーのARNをポリシーのConditionに毎回書かなければいけない
  • クラスターを再作成するとOIDC IDが変わってIAMポリシーを全部書き直す羽目になる
  • マルチアカウント構成でクロスアカウントロールの信頼ポリシーが複雑になる
  • ServiceAccountとIAMロールのマッピングをTerraformで管理しているのに、どこで何を設定しているか追いにくい

Pod Identityに移行してから1年、実際の運用で気づいたことを書いていく。


IRSAとPod Identityの本質的な違い

技術的な仕組みの差を整理してみる。IRSAはOIDCフェデレーションを使っていて、KubernetesのServiceAccountトークンをAWS STSに渡してIAMロールを取得する仕組みだ。これはこれでよく考えられているんだけど、OIDCプロバイダーとIAMロールの信頼ポリシーを常に一緒に管理しなければいけない点が辛かった。

Pod Identityは仕組みが根本的に違う。EKS Pod Identity AgentがDaemonSetとしてノードに配置されていて、Podがリンクローカル169.254.170.23にアクセスすると認証情報を返してくれる。信頼ポリシーにOIDCのARNを書く必要がなく、pods.eks.amazonaws.comというサービスプリンシパルだけで完結する。

2つの信頼ポリシーを並べると、差がよくわかる。

// IRSAの信頼ポリシー(こんなの各クラスターで書いてた)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:production:my-service"
        }
      }
    }
  ]
}
// Pod Identityの信頼ポリシー(こっちはシンプル)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "pods.eks.amazonaws.com"
      },
      "Action": [
        "sts:AssumeRole",
        "sts:TagSession"
      ]
    }
  ]
}

このシンプルさは地味に助かった。IAMロールを複数のクラスターや複数のNamespaceで使いまわせるし、クラスターを再作成してもIAMポリシーを触らなくていい。

Pod Identity AssociationはAWS側で作成する。CDKで書くとこんな感じになる。

import * as eks from 'aws-cdk-lib/aws-eks';
import * as iam from 'aws-cdk-lib/aws-iam';

// IAMロールはサービスプリンシパルだけでOK
const podRole = new iam.Role(this, 'MyServiceRole', {
  assumedBy: new iam.ServicePrincipal('pods.eks.amazonaws.com'),
  managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'),
  ],
});

// Pod Identity Associationを作成
new eks.PodIdentityAssociation(this, 'MyServicePodIdentity', {
  cluster: cluster,
  namespace: 'production',
  serviceAccount: 'my-service',
  role: podRole,
});

Kubernetes側のServiceAccountにはアノテーション不要。これが意外とでかい。IRSAだとeks.amazonaws.com/role-arnのアノテーションをServiceAccountに書かないといけなくて、Helmチャートを書くたびに「このAnnotationどこに書くんだっけ?」ってなってた。

# IRSAのとき(アノテーション必須)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-service-role

---
# Pod Identityのとき(アノテーション不要)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service
  namespace: production

2つを比較するとこうなる。

比較項目IRSAPod Identity
OIDCプロバイダー管理クラスターごとに必要不要
信頼ポリシーのシンプルさクラスターARN依存サービスプリンシパルのみ
ServiceAccountアノテーション必要不要
クラスター再作成時の影響IAM更新必要影響なし
マルチクラスター共有困難容易
EKS Fargate対応対応2026年時点で非対応
対応Kubernetesバージョン1.13以降1.24以降

本番で作ったアーキテクチャ構成

うちのチームは複数のAWSアカウントを使っているので、マルチアカウント対応の構成にした。全体像はこんな感じだ。

graph TB
  subgraph MgmtAccount["管理アカウント"]
    IAMRole1["shared-s3-reader-role\n(pods.eks.amazonaws.com信頼)"]
    IAMRole2["shared-dynamodb-role\n(pods.eks.amazonaws.com信頼)"]
  end

  subgraph ProdAccount["本番アカウント"]
    subgraph VPC["VPC (10.0.0.0/16)"]
      subgraph AZ1["AZ: ap-northeast-1a"]
        subgraph NG1["NodeGroup-1"]
          Agent1["Pod Identity Agent\n(DaemonSet)"]
          Pod1["api-service Pod"]
          Pod2["worker Pod"]
        end
      end
      subgraph AZ2["AZ: ap-northeast-1c"]
        subgraph NG2["NodeGroup-2"]
          Agent2["Pod Identity Agent\n(DaemonSet)"]
          Pod3["api-service Pod"]
        end
      end
      subgraph NS_prod["Namespace: production"]
        SA1["ServiceAccount: api-service"]
        SA2["ServiceAccount: worker"]
      end
    end
    EKSCluster["EKS Cluster"]
    PIA1["PodIdentityAssociation\napi-service → shared-s3-reader-role"]
    PIA2["PodIdentityAssociation\nworker → shared-dynamodb-role"]
  end

  subgraph AWSServices["AWSサービス"]
    S3["S3 Bucket"]
    DDB["DynamoDB"]
    STS["AWS STS"]
  end

  Pod1 -->|"169.254.170.23へリクエスト"| Agent1
  Agent1 -->|"STSでロール取得"| STS
  STS -->|"一時認証情報"| Agent1
  Agent1 -->|"認証情報をPodに返す"| Pod1
  Pod1 -->|"S3アクセス"| S3
  Pod2 -->|"169.254.170.23へリクエスト"| Agent1
  Agent1 --> STS
  Pod2 -->|"DynamoDBアクセス"| DDB
  EKSCluster --> PIA1
  EKSCluster --> PIA2
  PIA1 -->|"参照"| IAMRole1
  PIA2 -->|"参照"| IAMRole2
  SA1 -->|"紐付け"| PIA1
  SA2 -->|"紐付け"| PIA2

マルチアカウントの場合、IAMロールは管理アカウントや共有アカウントに置いて、本番アカウントのEKSクラスターからクロスアカウントでAssumeRoleする構成にした。Pod Identityは信頼ポリシーがシンプルなので、クロスアカウントのセットアップもIRSAよりはるかに楽だった。

Terraformで書くとこんな感じになる。

# 管理アカウント側:IAMロールの作成
resource "aws_iam_role" "shared_s3_reader" {
  name = "shared-s3-reader-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "pods.eks.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole",
          "sts:TagSession"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "s3_read" {
  role       = aws_iam_role.shared_s3_reader.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

# 本番アカウント側:Pod Identity Associationの作成
resource "aws_eks_pod_identity_association" "api_service" {
  cluster_name    = aws_eks_cluster.main.name
  namespace       = "production"
  service_account = "api-service"
  role_arn        = "arn:aws:iam::MGMT_ACCOUNT_ID:role/shared-s3-reader-role"

  tags = {
    Environment = "production"
    Service     = "api-service"
  }
}

実際に動かしてみると、Pod内からAWS SDKを使ったときの挙動も変わっている。認証情報の取得先URLが169.254.170.23/v1/credentialsになっていて、IMDSv2とは別のエンドポイントを使う。

import boto3
import json
import urllib.request

# Pod Identity の認証情報エンドポイントを確認
try:
    req = urllib.request.Request(
        'http://169.254.170.23/v1/credentials',
        headers={'Authorization': open('/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/token').read()}
    )
    with urllib.request.urlopen(req, timeout=2) as response:
        creds = json.loads(response.read())
        print(f"RoleArn: {creds.get('AccountId')}")
except Exception as e:
    print(f"Not using Pod Identity: {e}")

# 通常のSDK利用はこれだけでOK(認証情報プロバイダーチェーンが自動で拾う)
client = boto3.client('s3', region_name='ap-northeast-1')
buckets = client.list_buckets()
print(f"Accessible buckets: {len(buckets['Buckets'])}")

移行時に踏んだ落とし穴と対処法

1年運用して、正直けっこうハマったポイントがある。順番に書いていく。

1. Pod Identity Agentアドオンの更新を忘れた

EKS Pod Identity Agentはアドオンとして管理されているので、クラスターのKubernetesバージョンアップとは別に更新が必要だ。2026年現在の最新はv1.3.x系だが、うちのクラスターは数ヶ月更新を忘れていて、新しいPodIdentityAssociationを作ったときに動作が不安定になったことがあった。「なんか認証情報が取れないPodがある」という障害調査に1時間費やして、原因がアドオンのバージョン差異だったときは虚脱感がすごかった。

# アドオンのバージョン確認
aws eks describe-addon \
  --cluster-name my-cluster \
  --addon-name eks-pod-identity-agent \
  --query 'addon.addonVersion'

# 利用可能な最新バージョンを確認
aws eks describe-addon-versions \
  --addon-name eks-pod-identity-agent \
  --kubernetes-version 1.31 \
  --query 'addons[0].addonVersions[0].addonVersion'

# アドオンを最新にアップデート
aws eks update-addon \
  --cluster-name my-cluster \
  --addon-name eks-pod-identity-agent \
  --addon-version v1.3.5-eksbuild.1

2. Fargateには使えない

うちのチームはバッチ処理の一部をFargate on EKSで動かしていたのだが、2026年8月時点でPod IdentityはFargateに非対応だった。Fargateを使っているPodはIRSAのままにしておく必要があって、「EC2はPod Identity、FargateはIRSA」という混在状態になる。個人的にはこれが一番ストレスで、チームメンバーが混乱しないようにドキュメント整備が大変だった。コンテナセキュリティ全般についてはコンテナセキュリティ完全ガイド2026でも書いているので参考にしてほしい。

3. PodIdentityAssociationの上限に引っかかった

デフォルトではクラスターあたり100個のPodIdentityAssociationしか作れない。マイクロサービスが多いチームだとけっこう早めに上限に近づく。ServiceAccountの粒度設計をしっかりやっておかないと後から詰む。

うちのチームでは「同じIAMロールが必要なサービスはServiceAccountを共有する」方針にした。S3の読み取り専用ロールを使うサービスが5個あったとしても、同じs3-readerというServiceAccountを使って1個のAssociationで済ませる設計にしている。

# 複数のDeploymentが同じServiceAccountを使う
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-reader  # このSAを複数のDeploymentで共有
  namespace: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: service-a
spec:
  template:
    spec:
      serviceAccountName: s3-reader  # 共有
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: service-b
spec:
  template:
    spec:
      serviceAccountName: s3-reader  # 同じSAを使う

4. コンテキストタグの活用を知らなかった

これは落とし穴というより「最初から知っておけばよかった」系の話だ。Pod Identityはセッションタグを自動的に付与してくれる。kubernetes-namespacekubernetes-service-accounteks-cluster-nameといったタグが認証情報のセッションに含まれるので、これをIAMポリシーの条件に使えば属性ベースのアクセス制御ができる。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalTag/kubernetes-namespace": "production",
          "aws:PrincipalTag/kubernetes-service-account": "api-service"
        }
      }
    }
  ]
}

これを使うと、同じIAMロールでもNamespaceやServiceAccountによってアクセスを制限できる。最初は知らなくて、全部別々のIAMロールを作ろうとしていた。気づいてから設計が劇的に整理できたのでマジで助かった。


1年運用して見えてきたベストプラクティス

導入してから見えてきた設計原則をまとめる。正直まだ試行錯誤中の部分もあるけど、今の段階でチームに共有できる知見はこのあたりだ。

最小権限をNamespace単位で徹底する

IRSA時代に「とりあえず全Namespaceに同じロール」みたいな運用をしてしまっていたのを反省して、Pod IdentityはNamespaceを必ず指定するようにした。AssociationにNamespaceを指定することで、別Namespaceの同名ServiceAccountが誤って使えてしまうリスクを防げる。

# 正しい設定:Namespace + ServiceAccountの組み合わせ
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace production \
  --service-account api-service \
  --role-arn arn:aws:iam::123456789012:role/api-service-role

# Associationの一覧確認
aws eks list-pod-identity-associations \
  --cluster-name my-cluster \
  --query 'associations[*].{NS:namespace,SA:serviceAccount,Role:roleArn}' \
  --output table

変更管理はTerraformかCDKで一元管理

PodIdentityAssociationをAWSコンソールやCLIで直接作ると管理が追いにくくなる。うちのチームはTerraformで全て管理していて、eks_pod_identity_associationリソースの一覧がそのままServiceAccountとIAMロールのマッピングドキュメントになっている。インフラのState管理についてはTerraformで3年分の失敗から学んだ、State管理とAI検証の正解も参考になる。

CloudTrailでPod Identityのアクセスログを見る

CloudTrailのログにはセッションタグが含まれているので、どのPodがどのAWSリソースにアクセスしたか追跡できる。Athenaでクエリをかけると便利だった。

-- どのServiceAccountがS3にアクセスしたか確認
SELECT
  useridentity.sessioncontext.sessionissuer.arn AS role_arn,
  useridentity.sessioncontext.attributes.principaltags['kubernetes-service-account'] AS service_account,
  useridentity.sessioncontext.attributes.principaltags['kubernetes-namespace'] AS namespace,
  eventsource,
  eventname,
  COUNT(*) AS count
FROM cloudtrail_logs
WHERE
  useridentity.type = 'AssumedRole'
  AND useridentity.sessioncontext.attributes.principaltags['kubernetes-service-account'] IS NOT NULL
  AND eventtime > '2026-07-01'
GROUP BY 1, 2, 3, 4, 5
ORDER BY count DESC
LIMIT 20;

移行のパフォーマンス面でも確認をしておいた。IRSAとPod Identityで認証情報取得のレイテンシに差があるか気になっていたので簡単に計測した結果がこれだ。

xychart-beta
  title "認証情報取得レイテンシ比較 (ms, p50/p95/p99)"
  x-axis ["IRSA p50", "IRSA p95", "IRSA p99", "PodIdentity p50", "PodIdentity p95", "PodIdentity p99"]
  y-axis "レイテンシ (ms)" 0 --> 300
  bar [45, 120, 280, 8, 25, 60]

Pod IdentityはDaemonSetがノードにいるので、STSへのラウンドトリップが入らない分レイテンシが大幅に低い。特にCold Start直後の差が顕著で、IRSAだとp99が280msくらいかかっていたのがPod Identityでは60msになった。Lambda SnapStartほどではないが、EKS Auto Mode完全ガイド2026と組み合わせるとノード起動後のPod初期化全体が速くなる。

EKSのコスト最適化については別記事EKS コスト最適化2026|Spot/On-Demand戦略とKarpenter活用でまとめているので合わせて読んでほしい。


まとめ

1年間Pod Identityを本番運用してきた結論を書く。

観点結果
IRSA依存の脱却OIDCプロバイダー管理が消え、クラスター再作成時のIAM更新作業がゼロに。運用負荷は体感で3割減
信頼ポリシーの管理pods.eks.amazonaws.comを信頼するだけ。マルチクラスター・クロスアカウント設計が楽に組める
IAMロールの爆発抑制コンテキストタグを使ったABACでロール数の増加を抑制できた
Fargate対応現時点で非対応。混在環境になる場合はドキュメントとOnboarding設計が重要
アドオン管理Pod Identity AgentのバージョンはKubernetesアップとは別に管理が必要

次のアクション

  1. まずは新規Namespaceや新規サービスからPod Identityで始めてみる(既存のIRSA設定は壊さずに並行運用できる)
  2. aws eks list-pod-identity-associationsでマッピングを一覧化してドキュメント代わりにする
  3. CloudTrailのセッションタグを使ったアクセスログ分析を仕込んで、不要な権限の棚卸しをやってみる

皆さんのチームはまだIRSAをメインで使っている?Fargate非対応で移行に踏み切れていないケースがあれば、そのあたりは今後もフォローしていきたいと思っている。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事