Skip to main content

ServiceAccount

A Kubernetes identity assigned to pods that controls what API operations the pod is permitted to perform within the cluster. Every pod runs under a ServiceAccount — if you don't specify one, Kubernetes automatically assigns the `default` ServiceAccount. ServiceAccounts are the foundation of pod-level RBAC: they are bound to Roles and ClusterRoles to grant or restrict cluster API access.

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.

◈ DIAGRAM
+------------------------------------------+
| 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.

◈ DIAGRAM
+------------------------------------------+
| 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
+------------------------------------------+
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:

YAML
# 1. Create the ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus-scraper
namespace: monitoring
---
# 2. Create a ClusterRole with exactly the permissions needed
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus-metrics-reader
rules:
- 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 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus-scraper-binding
subjects:
- kind: ServiceAccount
name: prometheus-scraper
namespace: monitoring
roleRef:
kind: ClusterRole
name: prometheus-metrics-reader
apiGroup: rbac.authorization.k8s.io
---
# 4. Assign the ServiceAccount to the pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: monitoring
spec:
template:
spec:
serviceAccountName: prometheus-scraper # <- This is what links pod to SA
containers:
- name: prometheus
image: prom/prometheus:v2.48.0

How the Token Gets Into the Pod

Kubernetes automatically mounts the ServiceAccount token into every pod as a projected volume:

◈ DIAGRAM
+------------------------------------------+
| Pod filesystem |
| |
| /var/run/secrets/ |
| kubernetes.io/ |
| serviceaccount/ |
| token <- JWT Bearer token |
| ca.crt <- API server CA cert |
| namespace <- Current namespace |
+------------------------------------------+
Bash
# Read the token from inside a running pod
kubectl 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 pod
TOKEN=$(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 | jq

Disabling 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:

YAML
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.2

Troubleshooting 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
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

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

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

Frequently Asked Questions

What happens if I never create a ServiceAccount for my pods?

Every pod gets the namespace's `default` ServiceAccount automatically, which typically has no RBAC permissions bound to it by default — so API calls from inside the pod (e.g., via kubectl or a client library) fail with a 403 unless something explicitly grants that ServiceAccount a Role. This is different from disabling API access entirely; the token is still mounted into the pod unless you set `automountServiceAccountToken: false`.

What's the recommended practice for assigning ServiceAccounts across a cluster?

Create a dedicated ServiceAccount per application (not per namespace-wide default) and bind it to the narrowest Role that covers what the app actually needs — read-only access to specific ConfigMaps, for instance, not cluster-admin. Also disable auto-mounting of the token for pods that never call the Kubernetes API at all; leaving the default token mounted unnecessarily is a common attack-surface mistake found in cluster security audits.