ServiceAccount — Giving Pods Their Own Identity
Why Pods Need an Identity
Your application pods often need to talk to the Kubernetes API — to discover other services, watch ConfigMaps for config reloads, or scale other workloads. The cluster needs to know: who is this pod, and what is it allowed to do?
ServiceAccounts are the answer. Think of them as IAM roles for pods.
+------------------------------------------+| Pod (running your app) | <- "I am serviceaccount:| | payment-processor"+------------------------------------------+ | v+------------------------------------------+| Kubernetes API Server | <- Receives request with| | Bearer token from pod+------------------------------------------+ | v+------------------------------------------+| RBAC Authorization Check | <- Does payment-processor| | have GET on secrets?+------------------------------------------+ | | v v +------------+ +------------+ | ALLOWED | | DENIED | | 200 OK | | 403 Error | +------------+ +------------+The Default ServiceAccount Problem
Every namespace gets a default ServiceAccount automatically. If you don't set serviceAccountName in your pod spec, your pods use this default SA.
+------------------------------------------+| Namespace: payments-prod || || default ServiceAccount (auto-created) | <- ALL pods use this unless| | you specify otherwise| payments-api pod ──> default SA || fraud-checker pod ──> default SA | <- Both pods share the same| batch-job pod ──> default SA | identity and permissions+------------------------------------------+COMMON MISTAKE / WARNING**Security:** In older clusters, the `default` SA often has broad permissions inherited from cluster-admin bindings added during setup. In production, always create dedicated ServiceAccounts with the minimum permissions each workload actually needs — never rely on the default SA.
Creating a Scoped ServiceAccount — Real Example
This example sets up a ServiceAccount for a Prometheus pod that needs to scrape metrics endpoints across the cluster:
# 1. Create the ServiceAccountapiVersion: v1kind: ServiceAccountmetadata: name: prometheus-scraper namespace: monitoring# 2. Create a ClusterRole with exactly the permissions neededapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: prometheus-metrics-readerrules: - apiGroups: [""] resources: ["nodes", "pods", "services", "endpoints"] verbs: ["get", "list", "watch"] # Read-only — cannot create or delete - apiGroups: [""] resources: ["nodes/metrics"] verbs: ["get"]# 3. Bind the ClusterRole to the ServiceAccountapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: prometheus-scraper-bindingsubjects: - kind: ServiceAccount name: prometheus-scraper namespace: monitoringroleRef: kind: ClusterRole name: prometheus-metrics-reader apiGroup: rbac.authorization.k8s.io# 4. Assign the ServiceAccount to the podapiVersion: apps/v1kind: Deploymentmetadata: name: prometheus namespace: monitoringspec: template: spec: serviceAccountName: prometheus-scraper # <- This is what links pod to SA containers: - name: prometheus image: prom/prometheus:v2.48.0How the Token Gets Into the Pod
Kubernetes automatically mounts the ServiceAccount token into every pod as a projected volume:
+------------------------------------------+| Pod filesystem || || /var/run/secrets/ || kubernetes.io/ || serviceaccount/ || token <- JWT Bearer token || ca.crt <- API server CA cert || namespace <- Current namespace |+------------------------------------------+# Read the token from inside a running podkubectl exec -it api-server-7d9f8b -n production -- \ cat /var/run/secrets/kubernetes.io/serviceaccount/token # Use the token to call the Kubernetes API from inside the podTOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)curl -k -H "Authorization: Bearer $TOKEN" \ https://kubernetes.default.svc/api/v1/namespaces/production/pods # Inspect the token's contents (decoded JWT)kubectl create token prometheus-scraper -n monitoring --duration=1h | \ cut -d. -f2 | base64 -d 2>/dev/null | jqDisabling Auto-Mount for Non-API Pods
Most application pods don't need Kubernetes API access at all. Disabling the token mount reduces the attack surface — a compromised pod cannot use the token to probe the API:
spec: serviceAccountName: payments-api-sa automountServiceAccountToken: false # Don't mount the token — app doesn't need it containers: - name: payments-api image: registry.razorpay.in/payments-api:v3.1.2Troubleshooting Common ServiceAccount Problems
| Problem | Symptom | Fix |
|---|---|---|
| Pod gets 403 calling the API | Error: Forbidden in app logs |
SA lacks the required verb — check with kubectl auth can-i as the SA |
Pod stuck in Pending |
ServiceAccount not found event |
SA not created before the pod — create SA first, then deploy |
| Prometheus scraping fails | 403 Forbidden on /metrics |
ClusterRoleBinding is in wrong namespace or references wrong SA name |
| Token expired in long-running pods | API calls start failing after 24h | Use projected service account tokens with expirationSeconds: 86400 and enable token rotation |
| CI bot has too much access | Blast radius concern | Replace cluster-admin SA with a minimal ci-deployer Role bound only to deployments/patch |
PLACEMENT PRO TIP**Tip:** At Swiggy's scale, every microservice has its own dedicated ServiceAccount with the least-privilege permissions it actually needs. This means a compromised payment pod cannot read secrets from the delivery namespace — the blast radius is contained to one workload.
REMEMBER THIS**Remember:** `kubectl auth can-i get pods --as=system:serviceaccount:monitoring:prometheus-scraper` is the fastest way to verify what a ServiceAccount is allowed to do without deploying anything. Always test this before going to production.
COMMON MISTAKE / WARNING**Common Mistake:** Creating a ServiceAccount but forgetting to add `serviceAccountName` to the pod spec. The pod silently falls back to the `default` SA instead of the scoped one — and you won't notice until a permission error surfaces in production.
Quick Reference
| Command | Purpose |
|---|---|
kubectl get serviceaccounts -n <ns> |
List all SAs in a namespace |
kubectl describe sa <name> -n <ns> |
See mounted secrets and token references |
kubectl create token <sa-name> -n <ns> |
Generate a short-lived token for testing |
kubectl auth can-i get pods --as=system:serviceaccount:<ns>:<sa> |
Test what an SA is allowed to do |
kubectl get rolebindings -n <ns> -o wide |
See which SAs are bound to which roles |