Skip to main content
RunBook Academy

KubernetesLX · ServiceAccountsServiceAccounts

ServiceAccount anatomy — the workload identity

Advanced⏱ ~14 minkubectl

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

Not yet marked complete on this device.

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 include app.kubernetes.io/name and app.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 is true.
  • 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:

  1. Per-workload SA. Every workload has its own SA. api has api-sa; worker has worker-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
  1. No default SA bindings. The default SA in every namespace has no RBAC bindings and automountServiceAccountToken: false.

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

  1. Default SA with broad RBAC. Every Pod inherits the binding. The fix is to bind default to nothing.
  2. automountServiceAccountToken: true everywhere. Tokens are mounted even when not needed. The fix is to set it to false on SAs and Pods that do not call the API.
  3. 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-sa does not match system:serviceaccount:dev:api-sa. The fix is to verify the namespace in bindings.
  4. Image pull secrets in plaintext. A Secret of type kubernetes.io/dockerconfigjson in plaintext is readable by anyone with secrets: get in 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

  1. Q1. What is the operational effect of `automountServiceAccountToken: false` on a ServiceAccount?

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

  3. 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.

  4. 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.