Connect S3 to EKS#
The PatientPing app serves a favicon and will store user uploads in S3. Our pods need to call the S3 API - but how do they get AWS credentials?
The lazy way is to bake an access key into the container image or paste it into a Deployment env var. Don’t. Long-lived keys in a pod spec end up in git, in kubectl describe output, and in your terminal history. And giving the worker nodes a broad S3 role is also wrong: any pod on the node (including one you didn’t write) inherits it.
The right tool is IRSA (IAM Roles for Service Accounts). It ties an IAM role to a Kubernetes ServiceAccount:
- The EKS control plane runs an OIDC identity provider for your cluster.
- You create an IAM role whose trust policy says “only this cluster’s
ServiceAccountnamed X may assume me”. - The pods using that
ServiceAccountget short-lived credentials via the STSAssumeRoleWithWebIdentityAPI - no keys stored anywhere.
sequenceDiagram
participant Pod
participant OIDC as EKS OIDC provider
participant STS as AWS STS
participant IAM as IAM role
participant S3
Pod->>OIDC: sa token (projected volume)
Pod->>STS: AssumeRoleWithWebIdentity(token)
STS->>OIDC: token geçerli mi?
OIDC-->>STS: evet
STS-->>Pod: geçici credentials
Pod->>S3: GetObject / PutObjectAssignment#
Give the patientping-web pods read/write access to the patientping-favicon-bucket S3 bucket using IRSA.
Cost check: IAM roles, service accounts, and STS calls are all free. S3 itself costs a fraction of a penny for a favicon-sized object.
- Create an IAM policy that allows access to only our bucket (least privilege - not
s3:*on*):
aws iam create-policy \
--policy-name patientping-s3-access \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::patientping-favicon-bucket",
"arn:aws:s3:::patientping-favicon-bucket/*"
]
}]
}'- Use
eksctlto create the IAM role and wire it to aServiceAccountin one step (it handles the OIDC trust policy for us):
eksctl create iamserviceaccount \
--cluster patientping-eks \
--namespace default \
--name patientping-sa \
--attach-policy-arn arn:aws:iam::ACCOUNT_ID:policy/patientping-s3-access \
--approve- Point the deployment at that service account. In
patientping-web.yaml, underspec.template.spec, add:
serviceAccountName: patientping-saThen re-apply:
kubectl apply -f patientping-web.yaml- Verify from inside a pod that credentials work:
kubectl exec deploy/patientping-web -- \
aws s3 ls s3://patientping-favicon-bucket/If
awsisn’t installed in the container, any S3 API call your app makes is the real test - load the app in a browser and check the favicon appears.
That’s it - pods now have scoped, short-lived S3 access. No keys in images, no node-wide role, and you can revoke everything by deleting one IAM role.
Not:
eksctl create iamserviceaccountServiceAccount’aeks.amazonaws.com/role-arnannotation’ı ekler. Pod başladığında AWS SDK bu annotation’ı görür ve token’ı STS’e yollar. Eski pod’larıkubectl rollout restart deployment patientping-webile yeniden başlatmayı unutma.