ServiceAccounts

ServiceAccounts provide an identity for pods and applications running inside your Kubernetes cluster. Think of a ServiceAccount as a work badge that identifies which application is making requests to the Kubernetes API.

What Are ServiceAccounts?

ServiceAccounts are Kubernetes resources that represent an identity for workloads. Unlike user accounts (which are managed outside Kubernetes), ServiceAccounts are namespace-scoped resources managed by Kubernetes itself.

Every pod runs with a ServiceAccount. If you don’t specify one, Kubernetes automatically assigns the default ServiceAccount in the pod’s namespace.

How ServiceAccounts Work

When a pod is created with a ServiceAccount:

  1. Kubernetes creates a token (stored as a Secret)
  2. The token is automatically mounted into the pod at /var/run/secrets/kubernetes.io/serviceaccount/
  3. The pod uses this token to authenticate to the Kubernetes API
  4. RBAC rules determine what the ServiceAccount can do
flowchart TD A[Pod Created] --> B{ServiceAccount Specified?} B -->|No| C[Use default ServiceAccount] B -->|Yes| D[Use Specified ServiceAccount] C --> E[Token Mounted in Pod] D --> E E --> F[Pod Makes API Request] F --> G[API Server Validates Token] G --> H{RBAC Check} H -->|Allowed| I[Request Succeeds] H -->|Denied| J[403 Forbidden]

Creating ServiceAccounts

Basic ServiceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production

This creates a ServiceAccount with no special permissions. It can authenticate but needs RBAC permissions to perform actions.

ServiceAccount with Image Pull Secrets

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
imagePullSecrets:
- name: registry-credentials

This ServiceAccount can pull images from private registries using the specified image pull secret.

Using ServiceAccounts in Pods

Explicit ServiceAccount Assignment

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: production
spec:
  serviceAccountName: my-app  # Use this ServiceAccount
  containers:
  - name: app
    image: nginx:latest

Automatic Token Mounting

Kubernetes automatically mounts three files into each pod:

  • token - The authentication token at /var/run/secrets/kubernetes.io/serviceaccount/token
  • ca.crt - The cluster’s CA certificate at /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • namespace - The pod’s namespace at /var/run/secrets/kubernetes.io/serviceaccount/namespace

You can disable automatic mounting:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  serviceAccountName: my-app
  automountServiceAccountToken: false  # Disable automatic mounting
  containers:
  - name: app
    image: nginx:latest

Token Management

Token Types

Kubernetes supports two types of ServiceAccount tokens:

  1. Legacy tokens - Long-lived tokens stored as Secrets (deprecated in Kubernetes 1.24+)
  2. TokenRequest API - Short-lived tokens obtained via the TokenRequest API (recommended)

Creating Tokens (Kubernetes 1.24+)

apiVersion: v1
kind: Secret
metadata:
  name: my-app-token
  namespace: production
  annotations:
    kubernetes.io/service-account.name: my-app
type: kubernetes.io/service-account-token

However, the recommended approach is to use the TokenRequest API programmatically or let pods automatically mount tokens.

Using Tokens in Applications

Applications can read the token from the mounted file:

# Inside a pod
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

# Make API request
curl -X GET \
  --cacert $CA_CERT \
  -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods

RBAC Integration

ServiceAccounts need RBAC permissions to perform actions. Here’s a complete example:

# 1. Create ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-reader
  namespace: production

---
# 2. Create Role with permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: config-reader-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]

---
# 3. Bind Role to ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: config-reader-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: config-reader
  namespace: production
roleRef:
  kind: Role
  name: config-reader-role
  apiGroup: rbac.authorization.k8s.io

---
# 4. Use ServiceAccount in Pod
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: production
spec:
  serviceAccountName: config-reader
  containers:
  - name: app
    image: nginx:latest

Best Practices

  1. One ServiceAccount per application - Don’t reuse ServiceAccounts across different applications
  2. Namespace isolation - Create ServiceAccounts in the same namespace as your workloads
  3. Minimal permissions - Grant only the permissions each application needs
  4. Naming convention - Use descriptive names like app-name or component-name
  5. Disable automount when not needed - Set automountServiceAccountToken: false if the pod doesn’t need API access
  6. Rotate tokens - Use short-lived tokens when possible (TokenRequest API)

Common Patterns

Read-Only ServiceAccount

For applications that only need to read resources:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: monitor-role
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: monitor-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: monitor
  namespace: production
roleRef:
  kind: Role
  name: monitor-role
  apiGroup: rbac.authorization.k8s.io

CI/CD ServiceAccount

For CI/CD systems that deploy applications:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: cicd-deployer
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: deployer-role
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployer-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: cicd-deployer
  namespace: production
roleRef:
  kind: Role
  name: deployer-role
  apiGroup: rbac.authorization.k8s.io

Troubleshooting

Check ServiceAccount exists:

kubectl get serviceaccount my-app -n production

View ServiceAccount details:

kubectl describe serviceaccount my-app -n production

Check what permissions a ServiceAccount has:

kubectl auth can-i --list \
  --as=system:serviceaccount:production:my-app \
  --namespace=production

Verify token is mounted in pod:

kubectl exec -it <pod-name> -n production -- \
  ls -la /var/run/secrets/kubernetes.io/serviceaccount/

Test API access from pod:

kubectl exec -it <pod-name> -n production -- \
  curl -k https://kubernetes.default.svc/api/v1/namespaces/production/pods

Security Considerations

  1. Token exposure - Tokens in pods can be read by anyone with pod exec access
  2. Token lifetime - Legacy tokens don’t expire; use TokenRequest API for short-lived tokens
  3. Permission scope - ServiceAccounts should have minimal permissions
  4. Namespace boundaries - ServiceAccounts are namespace-scoped, but ClusterRoles can grant cluster-wide access

See Also