Module 7: Eks Specifics For Production

5. IRSA and its successor, EKS Pod Identity

Description

Two sibling guides in this ecosystem already solved a version of this exact problem: cicd-and-gitops-on-aws-guide (Module 4) gave a GitHub Actions job a temporary AWS identity, with no long-lived secret stored anywhere, using OIDC. cloud-security-and-guardrails-guide (Module 2) built that same pattern in depth — the OIDC provider, the trust policy, exchanging a short-lived JWT for temporary credentials — as the correct replacement for a hardcoded AWS_ACCESS_KEY_ID. This lesson takes that exact same underlying idea and applies it somewhere new: not a CI job running for a few minutes and finishing, but a Kubernetes Pod running indefinitely, inside your cluster, needing to talk to AWS with an identity of its own.

Connection to the module

Before this lesson, andes-cargo-status-api never needed to talk to any AWS service in this lab — all it does is serve HTTP and read a table that, on kind, lives on LocalStack. On a real EKS, against DynamoDB's real Shipments table, that Pod needs a legitimate AWS identity to do dynamodb:GetItem — exactly the permission aws-serverless-and-containers-guide already designed, without ever executing it, under the name StatusApiTaskRole. This lesson closes that loop.


The same OIDC pattern, a new context

Review the underlying idea, without repeating the complete mechanics the two sibling guides already built: instead of distributing a long-lived AWS credential to something that needs it (a CI job, a Pod), that "something" presents a signed, short-lived token, issued by a system AWS already trusts — GitHub, in the sibling guides' case; the Kubernetes cluster itself, in this lesson's case — and AWS exchanges it for temporary credentials, with no static secret ever having existed.

        THE SAME PRINCIPLE, THREE DIFFERENT CONTEXTS

  cicd-and-gitops-on-aws-guide (M4)     cloud-security-and-guardrails
  ──────────────────────────────         -guide (M2)
  A GitHub Actions job                    ────────────────────────────
  presents a GitHub JWT                   The same GitHub JWT,
  → AWS STS AssumeRoleWithWebIdentity     evaluated in more depth
  → temporary credentials                 (trust policy conditions,
                                            least privilege)

                    kubernetes-and-eks-in-production-guide (M7, this one)
                    ──────────────────────────────────────────────────
                    A Pod presents a JWT issued by the
                    EKS cluster itself (IRSA) or receives
                    credentials from an agent on its node
                    (EKS Pod Identity)
                    → AWS STS / EKS Auth Service
                    → temporary credentials

The JWT's issuer changes — from GitHub to the Kubernetes cluster itself — and the exact exchange mechanism changes depending on whether you choose IRSA or EKS Pod Identity (the next section distinguishes them precisely), but the underlying principle is identical to what you already know: never a static credential, always a short-lived token, exchanged at the exact moment it's needed.


IRSA: the traditional mechanism, still current

IRSA (IAM Roles for Service Accounts) is EKS's original mechanism for this problem, built directly on OIDC — the same standard you already used with GitHub Actions. Every EKS cluster can have its own OIDC provider, unique per cluster (you confirmed it in this module's lesson 2: identity.oidc.issuer in describe-cluster's output). Once that provider exists, IRSA links an IAM role to a Kubernetes ServiceAccount via an annotation:

With eksctl (representative) — creates the ServiceAccount and the IAM role in a single command, attaching an existing policy:

eksctl create iamserviceaccount \
  --name status-api-service-account \
  --namespace andes-cargo \
  --cluster andes-cargo-cluster \
  --role-name StatusApiTaskRole \
  --attach-policy-arn arn:aws:iam::000000000000:policy/StatusApiReadOnlyPolicy \
  --approve

The result, verified against official AWS documentation, is a ServiceAccount with a specific annotation the AWS SDK automatically recognizes inside the Pod:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: status-api-service-account
  namespace: andes-cargo
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::000000000000:role/StatusApiTaskRole

And the role's trust policy — the piece that answers "who's allowed to assume this role?" — specifically trusts this cluster's OIDC provider, conditioned on the token coming exactly from this ServiceAccount, in this namespace:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::000000000000:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:andes-cargo:status-api-service-account",
          "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

Notice the :sub condition — it's, with a different provider, the exact same idea you already saw in cloud-security-and-guardrails-guide M2 restricting GitHub Actions' trust policy to a specific repository and branch: here, instead of a GitHub repository, the condition ties the role to an exact namespace and ServiceAccount in this cluster. A Pod in a different namespace, or a Pod using a different ServiceAccount, could never assume this role, even running on the same cluster.


EKS Pod Identity: the recommended successor for new workloads

EKS Pod Identity is the more recent mechanism, and AWS explicitly documents it as simpler than IRSA — the official documentation says it plainly:

"EKS Pod Identity is a simpler method than IAM roles for service accounts, as this method doesn't use OIDC identity providers."

The underlying technical difference: instead of every cluster having its own OIDC provider that IAM must individually learn to trust, EKS Pod Identity introduces a single service principal (pods.eks.amazonaws.com) that any role can trust once, regardless of which cluster the Pod runs on:

{
  "Principal": {
    "Service": "pods.eks.amazonaws.com"
  }
}

And instead of every AWS SDK, inside every Pod, negotiating the token exchange directly against AWS STS, an agent (Amazon EKS Pod Identity Agent, a DaemonSet running on every node) centralizes that work — the documentation describes it this way: "Each set of temporary credentials is assumed by the EKS Auth service in EKS Pod Identity, instead of each AWS SDK that you run in each pod... the load is reduced to once for each node and isn't duplicated in each pod."

With eksctl (representative) — the equivalent syntax for EKS Pod Identity:

eksctl create podidentityassociation \
  --cluster andes-cargo-cluster \
  --namespace andes-cargo \
  --service-account-name status-api-service-account \
  --role-name StatusApiTaskRole \
  --permission-policy-arns arn:aws:iam::000000000000:policy/StatusApiReadOnlyPolicy

Notice what disappears compared to IRSA: there's no OIDC provider to create once per cluster, no :sub/:aud condition to write by hand in the trust policy — the association between the ServiceAccount and the role lives on EKS's side, not encoded inside the IAM policy.


The exact comparison, verified against AWS Docs

IRSAEKS Pod Identity
Needs an OIDC provider per clusterYesNo
Trust policy principalUnique per cluster (that OIDC provider's issuer)Unique and reusable: pods.eks.amazonaws.com, the same for any cluster
Who negotiates the credential exchangeEvery AWS SDK, inside every PodA centralized agent (DaemonSet), once per node
Compatible with FargateYesNo — restricted to Linux EC2 nodes (verified: "Linux and Windows pods that run on AWS Fargate aren't supported")
Who administers whatSeparate teams can administer the OIDC provider (infrastructure) and the IAM policies (security) independentlyAssociation configuration lives in EKS; IAM permissions live in IAM — "clean separation of duties," in AWS's words
Announced deprecation date for IRSANone — verified against docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html, complete and current documentation, with no deprecation notice anywhereNot applicable
Practical industry guidance (2026)Recommended for existing clusters already using it, and required for FargateRecommended by AWS for new workloads on EC2 nodes

The row most worth keeping precise, because it's easy to draw the wrong conclusion: IRSA has no announced deprecation date. AWS's official documentation describing IRSA, at the time this lesson was written, remains complete, current, with no "deprecated" or "legacy" notice anywhere visible. The practical guidance — "Pod Identity for new workloads on EC2, IRSA for Fargate and for existing clusters already running it" — is a reasonable synthesis of how the industry and AWS's own documentation structure present the choice in 2026, not a single verbatim citation from an AWS paragraph saying that exact phrase in those exact words. The two mechanisms coexist with no conflict: nothing prevents a cluster from having some ServiceAccounts using IRSA (legacy) and others using EKS Pod Identity (new) at the same time.


Closing the loop: StatusApiTaskRole, from ECS-documented-only to a ServiceAccount's role

aws-serverless-and-containers-guide, Module 1, designed StatusApiTaskRole with an exact, bounded purpose: read-only permission (dynamodb:GetItem, optionally dynamodb:Query) over the Shipments table — no write, no delete, nothing on any other resource. That role was created, in practice, only as representative JSON: the trust policy trusted ecs-tasks.amazonaws.com, because the original intent was for an ECS task to assume it. ECS never got to run in that lab (LocalStack Hobby doesn't cover it), so that role never had, until now, a real consumer to assume it.

In this module, that same name and that same purpose get picked back up, with a different consumer: not an ECS task, but a Kubernetes ServiceAccount, via IRSA or EKS Pod Identity. The only thing that changes is the trust policy — from ecs-tasks.amazonaws.com to pods.eks.amazonaws.com (Pod Identity) or the cluster's OIDC provider (IRSA) — the permissions (the permission policy, dynamodb:GetItem over Shipments's exact ARN) are, in spirit, exactly the same ones the previous guide already designed:

        StatusApiTaskRole — SAME NAME, SAME PURPOSE,
                            DIFFERENT CONSUMER

  aws-serverless-and-containers-guide (never applied)
  ──────────────────────────────────────────────────────
  trust policy:  Principal.Service = "ecs-tasks.amazonaws.com"
  consumer:      an ECS task (never ran)
  permissions:   dynamodb:GetItem over Shipments

  kubernetes-and-eks-in-production-guide (M7, representative)
  ──────────────────────────────────────────────────────
  trust policy:  Principal.Service = "pods.eks.amazonaws.com" (Pod Identity)
                 or Federated = the cluster's OIDC provider (IRSA)
  consumer:      andes-cargo-status-api's ServiceAccount
  permissions:   dynamodb:GetItem over Shipments  ── UNCHANGED

This is the same narrative pattern this guide's Module 1 already established with andes-cargo-cluster and status-api-service: a name the previous guide left documented, never executed, picked back up here with its purpose intact — except that, this time, not even this guide gets to execute it against a real account, for the same reason declared in this module's lesson 1. What does change, and is this lesson's real point, is who assumes the role: it's no longer container infrastructure centrally managed by ECS, it's a Kubernetes ServiceAccount, with the same least-privilege discipline.


Common mistakes

Thinking EKS Pod Identity "replaces" IRSA mandatorily, and that IRSA needs to be migrated right away (miscalibrated urgency). What happens: someone, learning a "simpler" mechanism exists, assumes they have to immediately migrate any existing IRSA ServiceAccount. How to spot it: if your work plan includes "migrate all IRSA to Pod Identity" with no real deprecation date backing it. How to fix it: this lesson confirmed, against current official documentation, that there is no announced deprecation date for IRSA — the two mechanisms coexist with no conflict. The practical guidance is to use Pod Identity for new workloads, not to retroactively migrate something already working with no concrete technical reason (for example, simplifying multi-account administration).

Trying to use EKS Pod Identity with Fargate profiles, without checking the restriction first (technical). What happens: a team that decided to use Fargate profiles (this module's lesson 3) for andes-cargo-status-api also tries to configure EKS Pod Identity for that same Pod. How to spot it: if your plan combines Fargate profiles with EKS Pod Identity without having checked this restriction. How to fix it: AWS's documentation explicitly confirms EKS Pod Identity doesn't support Pods running on Fargate — a service using Fargate profiles needs to use IRSA for its AWS identity, not Pod Identity. It's one of the few combinations where IRSA remains, not just valid, but the only option.

Confusing StatusApiTaskRole with a Fargate profile's Pod execution role (overlap of neighboring concepts, picked back up from lesson 3). What happens: someone mixes up the role your code needs (StatusApiTaskRole, via IRSA/Pod Identity) with the role Fargate's infrastructure needs to boot the Pod (lesson 3's Pod execution role). How to spot it: if you try to use StatusApiTaskRole as a Fargate profile's Pod execution role, or vice versa. How to fix it: it's, again, the same "Question 1 / Question 2" distinction you already saw with ECS (EcsTaskExecutionRole vs StatusApiTaskRole) and with Fargate's Pod execution role in this module's lesson 3 — one role so the infrastructure can boot the Pod, a completely different role for what that Pod's code can do once running. It's never the same role.


Exercises

Exercise 1 — Complete the table from memory. Without looking at this lesson's comparison section, answer: which of the two mechanisms needs an OIDC provider per cluster? Which is compatible with Fargate? Which has an announced deprecation date?

See solution

IRSA needs its own OIDC provider per cluster; EKS Pod Identity doesn't need one, it uses a single reusable service principal (pods.eks.amazonaws.com). Only IRSA is compatible with Fargate — EKS Pod Identity is restricted to Linux EC2 nodes. Neither has an announced deprecation date: both are fully supported and current in AWS's official documentation.

Exercise 2 — Explain the mechanism choice for a real case, without using the word "simple." A colleague asks you which of the two mechanisms to choose for a new ServiceAccount, on a new EKS cluster, with no Fargate profile involved. Answer them in one sentence, without using the word "simple" or "simplicity."

See solution

A complete answer sounds, roughly, like this: "For completely new workloads, with no Fargate restriction, EKS Pod Identity is the path AWS documents as recommended — you don't need to create or maintain an OIDC provider, and the association between the ServiceAccount and the IAM role lives centralized in EKS instead of encoded inside the role's trust policy."

Exercise 3 — Predict which mechanism andes-cargo-status-api would use if the team had chosen Fargate profiles in lesson 3. Based on this lesson's exact restriction, predict which identity mechanism andes-cargo-status-api would have to use if, instead of a managed node group, the team had chosen to run it on a Fargate profile.

See solution

IRSA, with no alternative. AWS's official documentation confirms EKS Pod Identity doesn't support Pods running on AWS Fargate (Linux or Windows) — so a service running on a Fargate profile mandatorily needs IRSA's OIDC provider mechanism to get AWS credentials, regardless of EKS Pod Identity generally being the recommended path for new EC2 workloads.


Summary and next step

This lesson applied, for the first time in this ecosystem, the federated-identity OIDC pattern to a Kubernetes Pod instead of a CI job — the same underlying principle cicd-and-gitops-on-aws-guide M4 and cloud-security-and-guardrails-guide M2 already built for GitHub Actions. You compared, with real eksctl syntax and exact citations from AWS Docs, IRSA (the traditional mechanism, with an OIDC provider per cluster, still with no deprecation date) against EKS Pod Identity (the recommended successor for new workloads, no OIDC provider, with the real restriction of not supporting Fargate). You closed the loop with StatusApiTaskRole: the same name, the same least-privilege purpose aws-serverless-and-containers-guide designed for ECS and never applied, picked back up here with a new consumer — a Kubernetes ServiceAccount, not an ECS task.

Before moving on you should be able to: explain, without confusing them, what each mechanism needs (OIDC provider vs. single principal); cite EKS Pod Identity's real restriction with Fargate; and describe what changes and what doesn't change about StatusApiTaskRole when moving from ECS to a ServiceAccount.

Next lesson: the AWS Load Balancer Controller — the real production Ingress. There you're going to see the direct contrast with ingress-nginx, which you actually installed and ran in Module 4: same Ingress object, different controller underneath.

Resources

  1. Amazon EKS — IAM roles for service accounts — the official reference for IRSA, verified to confirm it announces no deprecation date.
  2. Amazon EKS — Learn how EKS Pod Identity grants pods access to AWS services — the exact source for the "simpler method than IAM roles for service accounts" citation and the Fargate restriction.
  3. Amazon EKS — Assign IAM roles to Kubernetes service accounts — complete syntax for eksctl create iamserviceaccount, the source for this lesson's representative example.
  4. cicd-and-gitops-on-aws-guide (NIEVA), Module 4 — the original OIDC pattern applied to GitHub Actions, this lesson's conceptual foundation.
  5. cloud-security-and-guardrails-guide (NIEVA), Module 2 — the deeper dive into that same pattern (conditioned trust policy, least privilege) this lesson reapplies to a Pod.
  6. aws-serverless-and-containers-guide (NIEVA), Module 1, lesson 6 — StatusApiTaskRole's original design, picked back up here with no changes to its permissions.