Learning Kubernetes - Episode 32 - Introduction and Explanation of ServiceAccount

Learning Kubernetes - Episode 32 - Introduction and Explanation of ServiceAccount

In this episode, we'll discuss Kubernetes ServiceAccount for Pod identity and authentication. We'll learn how ServiceAccounts work, how to create and use them, token management, and best practices for secure Pod authentication.

Arman Dwi Pangestu
Arman Dwi PangestuApril 7, 2026
0 views
9 min read

Introduction

Note

If you want to read the previous episode, you can click the Episode 31 thumbnail below

Episode 31Episode 31

In the previous episode, we learned about Vertical Pod Autoscaler (VPA) for automatic resource sizing. In episode 32, we'll discuss ServiceAccount, which provides identity for Pods and enables them to authenticate with the Kubernetes API server.

Note: Here I'll be using a Kubernetes Cluster installed through K3s.

Just like users need accounts to access systems, Pods need ServiceAccounts to interact with the Kubernetes API. ServiceAccounts enable secure, controlled access to cluster resources, allowing applications to query cluster state, create resources, or perform operations based on assigned permissions.

What Is a ServiceAccount?

ServiceAccount is a Kubernetes resource that provides an identity for processes running in Pods. It enables Pods to authenticate with the Kubernetes API server and perform authorized operations.

Think of ServiceAccount like an employee badge - it identifies who you are (authentication) and determines what doors you can open (authorization via RBAC). Each Pod gets a badge (ServiceAccount) that grants specific access levels.

Key characteristics of ServiceAccount:

  • Pod identity - Provides identity for applications in Pods
  • API authentication - Enables Pods to authenticate with API server
  • Token-based - Uses JWT tokens for authentication
  • Namespace-scoped - Belongs to specific namespace
  • RBAC integration - Works with Role-Based Access Control
  • Automatic mounting - Token automatically mounted in Pods
  • Default ServiceAccount - Every namespace has default ServiceAccount
  • Custom ServiceAccounts - Create specific accounts for different needs

ServiceAccount vs User Account

Understanding the key differences:

AspectServiceAccountUser Account
PurposeFor Pods/applicationsFor humans
ScopeNamespace-scopedCluster-wide
ManagementManaged by KubernetesExternal (LDAP, OIDC, etc.)
TokenStored in SecretsExternal auth system
Creationkubectl createExternal identity provider
Use CaseApplication accessHuman access
LifecycleTied to namespaceIndependent

Why Use ServiceAccount?

ServiceAccount solves critical authentication and authorization challenges:

  • Secure API access - Controlled access to Kubernetes API
  • Principle of least privilege - Grant only necessary permissions
  • Application identity - Each app can have unique identity
  • Audit trail - Track which ServiceAccount performed actions
  • Token rotation - Refresh credentials without redeployment
  • Namespace isolation - Limit access to specific namespaces
  • RBAC enforcement - Fine-grained permission control
  • Automation - Enable CI/CD and automation tools

Without ServiceAccounts, all Pods would use the same identity, making it impossible to implement proper access control or audit who did what.

How ServiceAccount Works

Default ServiceAccount

Every namespace automatically gets a default ServiceAccount:

Kubernetesbash
kubectl get serviceaccount

Output:

Kubernetesbash
NAME      SECRETS   AGE
default   0         10d

Every Pod automatically uses the default ServiceAccount unless specified otherwise.

ServiceAccount Token

ServiceAccount tokens are JWT (JSON Web Tokens) that authenticate Pods:

Kubernetes 1.24+:

  • Tokens are time-bound (default 1 hour)
  • Automatically rotated
  • Projected into Pods via TokenRequest API

Before Kubernetes 1.24:

  • Tokens stored in Secrets
  • Never expire
  • Manually managed

Token Mounting

Tokens are automatically mounted in Pods at:

plaintext
/var/run/secrets/kubernetes.io/serviceaccount/

Contains:

  • token - JWT authentication token
  • ca.crt - CA certificate for API server
  • namespace - Current namespace

Creating ServiceAccount

Basic ServiceAccount

Kubernetesmy-serviceaccount.yml
apiVersion: v1
kind: ServiceAccount
metadata:
    name: my-app-sa
    namespace: default

Create:

Kubernetesbash
kubectl apply -f my-serviceaccount.yml

Verify:

Kubernetesbash
kubectl get serviceaccount my-app-sa

ServiceAccount with Annotations

Kubernetesannotated-sa.yml
apiVersion: v1
kind: ServiceAccount
metadata:
    name: my-app-sa
    namespace: default
    annotations:
        description: "ServiceAccount for my application"
        owner: "platform-team"

ServiceAccount with Image Pull Secrets

For pulling images from private registries:

Kubernetessa-with-imagepull.yml
apiVersion: v1
kind: ServiceAccount
metadata:
    name: my-app-sa
imagePullSecrets:
    - name: docker-registry-secret

Using ServiceAccount in Pods

Specify ServiceAccount in Pod

Kubernetespod-with-sa.yml
apiVersion: v1
kind: Pod
metadata:
    name: my-app
spec:
    serviceAccountName: my-app-sa
    containers:
        - name: app
          image: nginx:1.25

Specify ServiceAccount in Deployment

Kubernetesdeployment-with-sa.yml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: my-app
spec:
    replicas: 3
    selector:
        matchLabels:
            app: my-app
    template:
        metadata:
            labels:
                app: my-app
        spec:
            serviceAccountName: my-app-sa
            containers:
                - name: app
                  image: nginx:1.25

Disable Automatic Token Mounting

Prevent token from being mounted:

Kubernetespod-no-token.yml
apiVersion: v1
kind: Pod
metadata:
    name: my-app
spec:
    serviceAccountName: my-app-sa
    automountServiceAccountToken: false
    containers:
        - name: app
          image: nginx:1.25

ServiceAccount with RBAC

ServiceAccounts work with RBAC to control permissions.

Create ServiceAccount

Kubernetesapp-serviceaccount.yml
apiVersion: v1
kind: ServiceAccount
metadata:
    name: app-reader
    namespace: default

Create Role

Kubernetespod-reader-role.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    name: pod-reader
    namespace: default
rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list", "watch"]

Create RoleBinding

Kubernetespod-reader-binding.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: read-pods
    namespace: default
subjects:
    - kind: ServiceAccount
      name: app-reader
      namespace: default
roleRef:
    kind: Role
    name: pod-reader
    apiGroup: rbac.authorization.k8s.io

Apply all:

Kubernetesbash
kubectl apply -f app-serviceaccount.yml
kubectl apply -f pod-reader-role.yml
kubectl apply -f pod-reader-binding.yml

Complete Example

Kubernetescomplete-rbac-example.yml
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
    name: app-reader
    namespace: default
---
# Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    name: pod-reader
    namespace: default
rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list", "watch"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: read-pods
    namespace: default
subjects:
    - kind: ServiceAccount
      name: app-reader
      namespace: default
roleRef:
    kind: Role
    name: pod-reader
    apiGroup: rbac.authorization.k8s.io
---
# Pod using ServiceAccount
apiVersion: v1
kind: Pod
metadata:
    name: app-pod
spec:
    serviceAccountName: app-reader
    containers:
        - name: app
          image: nginx:1.25

Accessing Kubernetes API from Pod

Using ServiceAccount Token

Inside a Pod, access the API using the mounted token:

Kubernetesbash
# Get token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
 
# Get namespace
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
 
# Get CA certificate
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
 
# Call API
curl --cacert $CACERT \
     -H "Authorization: Bearer $TOKEN" \
     https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods

Using kubectl in Pod

Kuberneteskubectl-pod.yml
apiVersion: v1
kind: Pod
metadata:
    name: kubectl-pod
spec:
    serviceAccountName: app-reader
    containers:
        - name: kubectl
          image: bitnami/kubectl:latest
          command: ["sleep", "3600"]

Inside the Pod:

Kubernetesbash
kubectl exec -it kubectl-pod -- /bin/bash
 
# Inside Pod
kubectl get pods
kubectl get services

Using Client Libraries

Python example:

python
from kubernetes import client, config
 
# Load in-cluster config (uses ServiceAccount)
config.load_incluster_config()
 
# Create API client
v1 = client.CoreV1Api()
 
# List pods
pods = v1.list_namespaced_pod(namespace="default")
for pod in pods.items:
    print(f"Pod: {pod.metadata.name}")

Token Management

Creating Long-Lived Token (Pre-1.24)

Kubernetessa-token-secret.yml
apiVersion: v1
kind: Secret
metadata:
    name: my-app-sa-token
    annotations:
        kubernetes.io/service-account.name: my-app-sa
type: kubernetes.io/service-account-token

Get token:

Kubernetesbash
kubectl get secret my-app-sa-token -o jsonpath="{.data.token}" | base64 --decode

Creating Token (1.24+)

Create short-lived token:

Kubernetesbash
kubectl create token my-app-sa

Create token with custom duration:

Kubernetesbash
kubectl create token my-app-sa --duration=24h

Token Expiration

Check token expiration:

Kubernetesbash
kubectl create token my-app-sa | jwt decode -

Practical Examples

Example 1: Read-Only Application

Kubernetesreadonly-app.yml
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
    name: readonly-app
    namespace: production
---
# Role - Read pods and services
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    name: readonly-role
    namespace: production
rules:
    - apiGroups: [""]
      resources: ["pods", "services"]
      verbs: ["get", "list", "watch"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: readonly-binding
    namespace: production
subjects:
    - kind: ServiceAccount
      name: readonly-app
      namespace: production
roleRef:
    kind: Role
    name: readonly-role
    apiGroup: rbac.authorization.k8s.io
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
    name: readonly-app
    namespace: production
spec:
    replicas: 2
    selector:
        matchLabels:
            app: readonly-app
    template:
        metadata:
            labels:
                app: readonly-app
        spec:
            serviceAccountName: readonly-app
            containers:
                - name: app
                  image: myapp:latest

Example 2: CI/CD ServiceAccount

Kubernetescicd-serviceaccount.yml
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
    name: cicd-deployer
    namespace: default
---
# ClusterRole - Deploy applications
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
    name: deployer-role
rules:
    - apiGroups: ["apps"]
      resources: ["deployments", "replicasets"]
      verbs: ["get", "list", "create", "update", "patch", "delete"]
    - apiGroups: [""]
      resources: ["services", "configmaps", "secrets"]
      verbs: ["get", "list", "create", "update", "patch", "delete"]
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
    name: cicd-deployer-binding
subjects:
    - kind: ServiceAccount
      name: cicd-deployer
      namespace: default
roleRef:
    kind: ClusterRole
    name: deployer-role
    apiGroup: rbac.authorization.k8s.io

Example 3: Monitoring Application

Kubernetesmonitoring-app.yml
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
    name: monitoring-app
    namespace: monitoring
---
# ClusterRole - Read metrics
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
    name: metrics-reader
rules:
    - apiGroups: [""]
      resources: ["nodes", "pods", "services"]
      verbs: ["get", "list", "watch"]
    - apiGroups: ["metrics.k8s.io"]
      resources: ["nodes", "pods"]
      verbs: ["get", "list"]
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
    name: monitoring-binding
subjects:
    - kind: ServiceAccount
      name: monitoring-app
      namespace: monitoring
roleRef:
    kind: ClusterRole
    name: metrics-reader
    apiGroup: rbac.authorization.k8s.io
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
    name: monitoring-app
    namespace: monitoring
spec:
    replicas: 1
    selector:
        matchLabels:
            app: monitoring
    template:
        metadata:
            labels:
                app: monitoring
        spec:
            serviceAccountName: monitoring-app
            containers:
                - name: prometheus
                  image: prom/prometheus:latest

Example 4: Job with ServiceAccount

Kubernetesjob-with-sa.yml
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
    name: backup-job-sa
    namespace: default
---
# Role - Access to backup resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    name: backup-role
    namespace: default
rules:
    - apiGroups: [""]
      resources: ["persistentvolumeclaims"]
      verbs: ["get", "list"]
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list", "create"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: backup-binding
    namespace: default
subjects:
    - kind: ServiceAccount
      name: backup-job-sa
      namespace: default
roleRef:
    kind: Role
    name: backup-role
    apiGroup: rbac.authorization.k8s.io
---
# Job
apiVersion: batch/v1
kind: Job
metadata:
    name: backup-job
spec:
    template:
        spec:
            serviceAccountName: backup-job-sa
            containers:
                - name: backup
                  image: backup-tool:latest
                  command: ["./backup.sh"]
            restartPolicy: OnFailure

Common Mistakes and Pitfalls

Mistake 1: Using Default ServiceAccount

Problem: Default ServiceAccount has no specific permissions.

Kubernetesyml
# Bad: Using default ServiceAccount
spec:
    # No serviceAccountName specified
    containers:
        - name: app
          image: myapp:latest

Solution: Create dedicated ServiceAccount:

Kubernetesyml
# Good: Dedicated ServiceAccount
spec:
    serviceAccountName: my-app-sa
    containers:
        - name: app
          image: myapp:latest

Mistake 2: Overly Permissive Roles

Problem: Granting cluster-admin to ServiceAccount.

Kubernetesyml
# Bad: Too many permissions
roleRef:
    kind: ClusterRole
    name: cluster-admin

Solution: Grant minimum necessary permissions:

Kubernetesyml
# Good: Specific permissions
rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list"]

Mistake 3: Sharing ServiceAccounts

Problem: Multiple applications using same ServiceAccount.

Solution: Create separate ServiceAccount per application:

Kubernetesyml
# App 1
serviceAccountName: app1-sa
 
# App 2
serviceAccountName: app2-sa

Mistake 4: Not Disabling Token Mounting

Problem: Mounting tokens in Pods that don't need API access.

Solution: Disable when not needed:

Kubernetesyml
spec:
    automountServiceAccountToken: false

Mistake 5: Using Long-Lived Tokens

Problem: Tokens that never expire are security risks.

Solution: Use short-lived tokens (1.24+):

Kubernetesbash
kubectl create token my-app-sa --duration=1h

Best Practices

Principle of Least Privilege

Grant only necessary permissions:

Kubernetesyml
rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list"]  # Only what's needed

One ServiceAccount Per Application

Separate identity for each app:

Kubernetesyml
# Frontend
serviceAccountName: frontend-sa
 
# Backend
serviceAccountName: backend-sa
 
# Database
serviceAccountName: database-sa

Use Namespace-Scoped Roles

Prefer Role over ClusterRole when possible:

Kubernetesyml
# Good: Namespace-scoped
kind: Role
metadata:
    namespace: production
 
# Avoid unless necessary
kind: ClusterRole

Disable Token Mounting When Not Needed

Kubernetesyml
spec:
    automountServiceAccountToken: false
    containers:
        - name: app
          image: nginx:1.25  # Doesn't need API access

Document ServiceAccount Purpose

Kubernetesyml
metadata:
    name: my-app-sa
    annotations:
        description: "ServiceAccount for my-app with read-only access to pods"
        owner: "platform-team"
        permissions: "pods:get,list,watch"

Regular Audit

Review ServiceAccount permissions:

Kubernetesbash
# List ServiceAccounts
kubectl get serviceaccounts --all-namespaces
 
# Check permissions
kubectl auth can-i --list --as=system:serviceaccount:default:my-app-sa

Use Token Expiration

For external access, use time-bound tokens:

Kubernetesbash
kubectl create token my-app-sa --duration=8h

Troubleshooting ServiceAccount

ServiceAccount Not Found

Kubernetesbash
kubectl get pod my-pod
# Error: serviceaccount "my-app-sa" not found

Solution: Create ServiceAccount first:

Kubernetesbash
kubectl create serviceaccount my-app-sa

Permission Denied

Kubernetesbash
# Inside Pod
kubectl get pods
# Error: pods is forbidden

Solution: Check and fix RBAC:

Kubernetesbash
# Check permissions
kubectl auth can-i get pods --as=system:serviceaccount:default:my-app-sa
 
# Create Role and RoleBinding
kubectl create role pod-reader --verb=get,list --resource=pods
kubectl create rolebinding read-pods --role=pod-reader --serviceaccount=default:my-app-sa

Token Not Mounted

Kubernetesbash
# Inside Pod
ls /var/run/secrets/kubernetes.io/serviceaccount/
# Directory not found

Solution: Enable token mounting:

Kubernetesyml
spec:
    automountServiceAccountToken: true  # Ensure this is true

Wrong Namespace

Kubernetesbash
# ServiceAccount in namespace A, Pod in namespace B
# Error: serviceaccount not found

Solution: Ensure same namespace:

Kubernetesyml
# ServiceAccount
metadata:
    namespace: production
 
# Pod
metadata:
    namespace: production
spec:
    serviceAccountName: my-app-sa

Viewing ServiceAccount Details

Get ServiceAccounts

Kubernetesbash
kubectl get serviceaccounts
kubectl get sa  # Short form
kubectl get sa --all-namespaces

Describe ServiceAccount

Kubernetesbash
kubectl describe serviceaccount my-app-sa

View ServiceAccount YAML

Kubernetesbash
kubectl get serviceaccount my-app-sa -o yaml

Check ServiceAccount Permissions

Kubernetesbash
kubectl auth can-i --list --as=system:serviceaccount:default:my-app-sa

Get Token

Kubernetesbash
# Kubernetes 1.24+
kubectl create token my-app-sa
 
# Pre-1.24
kubectl get secret <sa-token-secret> -o jsonpath="{.data.token}" | base64 --decode

Deleting ServiceAccount

Kubernetesbash
kubectl delete serviceaccount my-app-sa

Warning

Warning: Deleting a ServiceAccount will cause Pods using it to lose API access. Ensure no Pods are using it before deletion.

Conclusion

In episode 32, we've explored ServiceAccount in Kubernetes in depth. We've learned how ServiceAccounts provide identity for Pods, enable API authentication, and work with RBAC for fine-grained access control.

Key takeaways:

  • ServiceAccount provides identity for Pods
  • Enables API authentication with JWT tokens
  • Namespace-scoped - belongs to specific namespace
  • Default ServiceAccount exists in every namespace
  • Token automatically mounted at /var/run/secrets/kubernetes.io/serviceaccount/
  • Works with RBAC for authorization
  • Kubernetes 1.24+ uses time-bound tokens
  • Create dedicated ServiceAccount per application
  • Follow principle of least privilege
  • Use Role over ClusterRole when possible
  • Disable token mounting when not needed
  • Document ServiceAccount purpose and permissions
  • Regular audit of ServiceAccount permissions
  • Token expiration improves security
  • Different from User Accounts (for humans)

ServiceAccount is fundamental to Kubernetes security and access control. By understanding ServiceAccounts and RBAC, you can implement proper authentication and authorization for your applications, ensuring secure, controlled access to cluster resources.

Are you getting a clearer understanding of ServiceAccount in Kubernetes? Keep your learning momentum going and look forward to the next episode!

Note

If you want to continue to the next episode, you can click the Episode 33 thumbnail below

Episode 33Episode 33

Related Posts