KubernetesLX · ServiceAccountsServiceAccounts
ServiceAccount anatomy — the workload identity
What you'll learn
- Explain what a ServiceAccount is and how it relates to projected tokens
- Inspect a SA's metadata and the projected token volume in a Pod
- Identify the production fields (name, namespace, labels, automountServiceAccountToken)
- Recognise the failure modes (over-broad SAs, default SA with broad bindings)
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
A ServiceAccount is the workload identity in Kubernetes. It is the unit at which RBAC bindings are anchored, the unit at which projected tokens are issued, and the unit at which external identity systems (IRSA, Workload Identity) integrate. This lesson covers the anatomy of a ServiceAccount, the relationship to Pods, and the production patterns.
The shape of a ServiceAccount
A ServiceAccount is a namespaced object with minimal fields:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-runner
namespace: ci
labels:
app.kubernetes.io/name: ci-runner
app.kubernetes.io/component: ci
annotations:
description: "ServiceAccount for CI pipeline runners"
automountServiceAccountToken: true
imagePullSecrets:
- name: regcred
secrets: [] # legacy; ignored when bound tokens are enabled
The fields:
metadata.name— the SA’s name. Required.metadata.namespace— the SA’s namespace. Required.metadata.labels— labels for selection. Common labels includeapp.kubernetes.io/nameandapp.kubernetes.io/component.metadata.annotations— annotations for tooling. Not used by the cluster.automountServiceAccountToken— controls whether Pods that use this SA have the token auto-mounted. Default istrue.imagePullSecrets— list of Secrets for pulling images from private registries. Used by the kubelet.secrets— list of Secret names for legacy tokens. Ignored when bound ServiceAccount tokens are enabled (default in 1.24+).
The relationship to Pods
A Pod references a SA via spec.serviceAccountName.
If the field is absent, the Pod uses the default SA
in its namespace.
apiVersion: v1
kind: Pod
metadata:
name: api
namespace: prod
spec:
serviceAccountName: api-sa
containers:
- name: api
image: myapp:v1.0
The kubelet projects a token for api-sa at
/var/run/secrets/kubernetes.io/serviceaccount/:
/var/run/secrets/kubernetes.io/serviceaccount/
├── ca.crt # the cluster CA bundle
├── namespace # the Pod's namespace
└── token # the projected JWT
The workload reads the token file, presents it as a
bearer token to the API server, and the API server
authenticates it as system:serviceaccount:prod:api-sa.
flowchart LR
A["Pod spec.serviceAccountName"] --> B["SA api-sa"]
B --> C["Projected token"]
C --> D["/var/run/secrets/..."]
D --> E["Workload reads token"]
E --> F["API server validates"]
F --> G["RBAC for SA"]
The default SA
Every namespace has a default SA, auto-created by
the namespace controller:
kubectl get sa -A | grep default
# NAMESPACE NAME SECRETS AGE
# default default 0 30d
# kube-public default 0 30d
# prod default 0 30d
# ci default 0 30d
The default SA is the implicit identity for any Pod
that does not specify serviceAccountName. It is also
the most dangerous SA in production, because:
- Every Pod that does not specify a SA uses it.
- If it has any RBAC binding, every such Pod inherits it.
- Its token is auto-mounted into every such Pod.
The right pattern is to bind default to nothing, set
automountServiceAccountToken: false on it (or on
every Pod that does not need a token), and require
every workload to specify its own SA.
Auto-creation by the namespace controller
When a namespace is created, the namespace controller
auto-creates the default SA. The cluster also has
three built-in SAs in kube-system:
kubectl get sa -n kube-system
# NAME SECRETS AGE
# default 0 30d
# kube-public 0 30d
# kubernetes-dashboard 0 30d (if installed)
The built-in SAs are reserved for control-plane and add-on workloads; production workloads should not use them.
Inspecting a SA
# List SAs in a namespace
kubectl get sa -n prod
# Describe a SA
kubectl describe sa api-sa -n prod
# Name: api-sa
# Namespace: prod
# Labels: app.kubernetes.io/name=api
# Annotations: ...
# Image pull secrets: regcred
# Mountable secrets: <none>
# Tokens: 1h # projected token expiry
# Events: <none>
# Inspect a Pod's projected token
kubectl exec api-pod -n prod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 60
# eyJhbGciOiJSUzI1NiIs...
# Check the token's claims
kubectl exec api-pod -n prod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | \
cut -d. -f2 | base64 -d 2>/dev/null | jq .
Production patterns
Three patterns:
- Per-workload SA. Every workload has its own SA.
apihasapi-sa;workerhasworker-sa. Each has minimum-surface RBAC.
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-sa
namespace: prod
automountServiceAccountToken: false # if the workload doesn't call the API
-
No
defaultSA bindings. ThedefaultSA in every namespace has no RBAC bindings andautomountServiceAccountToken: false. -
Image pull secrets. A workload that pulls from a private registry references the pull secret on the SA:
imagePullSecrets:
- name: regcred
The kubelet uses the secret to authenticate to the registry during pull.
Production failure modes
- Default SA with broad RBAC. Every Pod inherits
the binding. The fix is to bind
defaultto nothing. automountServiceAccountToken: trueeverywhere. Tokens are mounted even when not needed. The fix is to set it tofalseon SAs and Pods that do not call the API.- SA name as the only identifier. Two SAs with
the same name in different namespaces are different
identities. A workload that references
system:serviceaccount:prod:api-sadoes not matchsystem:serviceaccount:dev:api-sa. The fix is to verify the namespace in bindings. - Image pull secrets in plaintext. A Secret of
type
kubernetes.io/dockerconfigjsonin plaintext is readable by anyone withsecrets: getin the namespace. The fix is to scope the RBAC.
Cross-course references
- The Linux course covers JWT and JWKS — the primitives that projected tokens use.
- The Observability course covers the audit log entries for SA-based authentication.
Quiz
Knowledge check · 4 questions
Q1. What is the operational effect of `automountServiceAccountToken: false` on a ServiceAccount?
Q2. Every Kubernetes namespace has a `default` ServiceAccount that is auto-created by the namespace controller, and every Pod that does not specify a `serviceAccountName` uses it.
Q3. Your workload in `prod` calls the API server to read a ConfigMap. The Pod spec has `serviceAccountName: api-sa`. The workload reports `Unauthorized: invalid bearer token`. You log into the Pod and check the mount: the directory `/var/run/secrets/kubernetes.io/serviceaccount/` is empty. Why?
The SA `api-sa` has `automountServiceAccountToken: false` in its metadata. The Pod spec does not override the flag. The kubelet did not mount the projected token volume because the SA's flag is `false`. The workload has no token to present.
Q4. Name three ServiceAccount fields and what each one controls.
Passing score: 75%. Answers are checked in this browser.
Production discipline
A defensible ServiceAccount implementation creates one
SA per workload, sets automountServiceAccountToken: false on every SA that does not need a token, binds
the default SA to nothing, and audits every SA’s
RBAC. The projected token volume is the SA’s
interface to the API server; the workload reads the
token, presents it, and the API server authenticates.
A cluster whose SAs are minimum-surface has a
workload identity programme that is auditable; a
cluster whose SAs are over-broad has a workload
identity programme that is not.