TL;DR

Use eksctl for cluster and node group management, aws eks update-kubeconfig to connect, and IRSA (IAM Roles for Service Accounts) to grant pods AWS permissions without static credentials. Use aws sts get-caller-identity to confirm which role your CLI is using.

EKS Cluster Basics

These are the daily commands you'll reach for when working with EKS — from connecting to a cluster to checking its version and add-ons.

basheks-basics.sh
# Connect to cluster
aws eks update-kubeconfig --region us-east-1 --name my-cluster
aws eks update-kubeconfig --region us-east-1 --name my-cluster --role-arn arn:aws:iam::123:role/eks-admin

# Cluster info
aws eks describe-cluster --name my-cluster --query 'cluster.{version:version,status:status,endpoint:endpoint}'
aws eks list-addons --cluster-name my-cluster
aws eks describe-addon --cluster-name my-cluster --addon-name vpc-cni

# Node groups
aws eks list-nodegroups --cluster-name my-cluster
aws eks describe-nodegroup --cluster-name my-cluster --nodegroup-name my-ng \
  --query 'nodegroup.{status:status,instanceType:instanceTypes,desired:scalingConfig.desiredSize,min:scalingConfig.minSize,max:scalingConfig.maxSize}'

# Scale node group
aws eks update-nodegroup-config --cluster-name my-cluster \
  --nodegroup-name my-ng \
  --scaling-config minSize=2,maxSize=10,desiredSize=5

# Verify caller identity (which IAM role/user is active)
aws sts get-caller-identity

IRSA — IAM Roles for Service Accounts

IRSA lets a Kubernetes ServiceAccount assume an IAM role, giving pods fine-grained AWS permissions without mounting static credentials. The pod receives temporary credentials via the projected token volume.

bashirsa.sh
# 1. Ensure OIDC provider is associated with the cluster
eksctl utils associate-iam-oidc-provider \
  --region us-east-1 --cluster my-cluster --approve

# 2. Create IAM role + SA with eksctl (simplest)
eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace my-namespace \
  --name my-service-account \
  --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --approve \
  --override-existing-serviceaccounts

# 3. Check the SA annotation (role ARN must be present)
kubectl describe sa my-service-account -n my-namespace | grep amazonaws

# 4. Verify pod is receiving credentials
kubectl exec -it <pod> -- aws sts get-caller-identity

# Manual trust policy (for custom IAM roles)
OIDC=$(aws eks describe-cluster --name my-cluster \
  --query 'cluster.identity.oidc.issuer' --output text | sed 's|https://||')
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
# Trust policy JSON: allow K8s SA to assume this role
cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Federated": "arn:aws:iam::${ACCOUNT}:oidc-provider/${OIDC}"},
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {"StringEquals": {
      "${OIDC}:sub": "system:serviceaccount:my-namespace:my-sa",
      "${OIDC}:aud": "sts.amazonaws.com"
    }}
  }]
}
EOF

ECR Operations

Common ECR commands for pushing images, managing lifecycle policies, and listing repositories. Use lifecycle policies to prevent unbounded image accumulation.

bashecr-ops.sh
REGION=us-east-1
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
REGISTRY=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com

# Authenticate docker to ECR
aws ecr get-login-password --region $REGION | \
  docker login --username AWS --password-stdin $REGISTRY

# Create repository
aws ecr create-repository --repository-name myapp --region $REGION

# Push image
docker build -t myapp:v1.2.3 .
docker tag myapp:v1.2.3 $REGISTRY/myapp:v1.2.3
docker push $REGISTRY/myapp:v1.2.3

# List images in a repo
aws ecr list-images --repository-name myapp --region $REGION

# Get image details (digest, size, tags)
aws ecr describe-images --repository-name myapp \
  --query 'sort_by(imageDetails, &imagePushedAt)[-5:]|[].{tag:imageTags[0],pushed:imagePushedAt,size:imageSizeInBytes}'

# Lifecycle policy: keep last 30 tagged images, delete untagged after 1 day
aws ecr put-lifecycle-policy --repository-name myapp --lifecycle-policy-text '{
  "rules": [
    {"rulePriority":1,"description":"remove untagged","selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":1},"action":{"type":"expire"}},
    {"rulePriority":2,"description":"keep 30","selection":{"tagStatus":"tagged","tagPrefixList":["v"],"countType":"imageCountMoreThan","countNumber":30},"action":{"type":"expire"}}
  ]
}'

IAM Debugging

When pods can't access AWS resources, work through the IAM chain: check the pod's assumed role, the attached policies, and whether the resource policy allows access.

bashiam-debug.sh
# Check what identity the pod has assumed
kubectl exec -it <pod> -- aws sts get-caller-identity

# List policies attached to a role
aws iam list-attached-role-policies --role-name my-role
aws iam list-role-policies --role-name my-role   # inline policies

# Simulate a policy (check if action is allowed without actually doing it)
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123:role/my-role \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-bucket/*

# Check aws-auth ConfigMap (maps IAM roles → K8s RBAC)
kubectl describe configmap aws-auth -n kube-system

# Add a role to aws-auth (eksctl method)
eksctl create iamidentitymapping \
  --cluster my-cluster \
  --arn arn:aws:iam::123456789:role/developer-role \
  --group system:masters \
  --username admin