Di episode ini kita akan coba bahas Kubernetes ServiceAccount untuk Pod identity dan authentication. Kita akan mempelajari bagaimana ServiceAccount work, cara create dan use, token management, dan best practice untuk secure Pod authentication.

Catatan
Untuk kalian yang ingin membaca episode sebelumnya, bisa click thumbnail episode 31 di bawah ini
Di episode sebelumnya kita sudah belajar tentang Vertical Pod Autoscaler (VPA) untuk automatic resource sizing. Selanjutnya di episode 32 kali ini, kita akan coba bahas ServiceAccount, yang provide identity untuk Pod dan enable mereka untuk authenticate dengan Kubernetes API server.
Catatan: Disini saya akan menggunakan Kubernetes Cluster yang di install melalui K3s.
Seperti user need account untuk access system, Pod need ServiceAccount untuk interact dengan Kubernetes API. ServiceAccount enable secure, controlled access ke cluster resource, allowing application untuk query cluster state, create resource, atau perform operation based on assigned permission.
ServiceAccount adalah Kubernetes resource yang provide identity untuk process running di Pod. Dia enable Pod untuk authenticate dengan Kubernetes API server dan perform authorized operation.
Bayangkan ServiceAccount seperti employee badge - dia identify siapa kalian (authentication) dan determine pintu apa yang bisa kalian buka (authorization via RBAC). Setiap Pod dapat badge (ServiceAccount) yang grant specific access level.
Karakteristik kunci ServiceAccount:
Memahami key difference:
| Aspek | ServiceAccount | User Account |
|---|---|---|
| Purpose | Untuk Pod/application | Untuk human |
| Scope | Namespace-scoped | Cluster-wide |
| Management | Managed oleh Kubernetes | External (LDAP, OIDC, etc.) |
| Token | Stored di Secret | External auth system |
| Creation | kubectl create | External identity provider |
| Use Case | Application access | Human access |
| Lifecycle | Tied ke namespace | Independent |
ServiceAccount solve critical authentication dan authorization challenge:
Tanpa ServiceAccount, semua Pod akan gunakan same identity, making it impossible untuk implement proper access control atau audit siapa yang did what.
Setiap namespace automatically dapat default ServiceAccount:
kubectl get serviceaccountOutput:
NAME SECRETS AGE
default 0 10dSetiap Pod automatically gunakan default ServiceAccount kecuali specified otherwise.
ServiceAccount token adalah JWT (JSON Web Token) yang authenticate Pod:
Kubernetes 1.24+:
Sebelum Kubernetes 1.24:
Token automatically mounted di Pod di:
/var/run/secrets/kubernetes.io/serviceaccount/Contain:
token - JWT authentication tokenca.crt - CA certificate untuk API servernamespace - Current namespaceapiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: defaultCreate:
kubectl apply -f my-serviceaccount.ymlVerify:
kubectl get serviceaccount my-app-saapiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: default
annotations:
description: "ServiceAccount untuk my application"
owner: "platform-team"Untuk pulling image dari private registry:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
imagePullSecrets:
- name: docker-registry-secretapiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: nginx:1.25apiVersion: 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.25Prevent token dari being mounted:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.25ServiceAccount work dengan RBAC untuk control permission.
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-reader
namespace: defaultapiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]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.ioApply semua:
kubectl apply -f app-serviceaccount.yml
kubectl apply -f pod-reader-role.yml
kubectl apply -f pod-reader-binding.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 menggunakan ServiceAccount
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
serviceAccountName: app-reader
containers:
- name: app
image: nginx:1.25Inside Pod, access API menggunakan mounted token:
# 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/podsapiVersion: v1
kind: Pod
metadata:
name: kubectl-pod
spec:
serviceAccountName: app-reader
containers:
- name: kubectl
image: bitnami/kubectl:latest
command: ["sleep", "3600"]Inside Pod:
kubectl exec -it kubectl-pod -- /bin/bash
# Inside Pod
kubectl get pods
kubectl get servicesPython example:
from kubernetes import client, config
# Load in-cluster config (gunakan ServiceAccount)
config.load_incluster_config()
# Create API client
v1 = client.CoreV1Api()
# List pod
pods = v1.list_namespaced_pod(namespace="default")
for pod in pods.items:
print(f"Pod: {pod.metadata.name}")apiVersion: v1
kind: Secret
metadata:
name: my-app-sa-token
annotations:
kubernetes.io/service-account.name: my-app-sa
type: kubernetes.io/service-account-tokenGet token:
kubectl get secret my-app-sa-token -o jsonpath="{.data.token}" | base64 --decodeCreate short-lived token:
kubectl create token my-app-saCreate token dengan custom duration:
kubectl create token my-app-sa --duration=24hCheck token expiration:
kubectl create token my-app-sa | jwt decode -# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: readonly-app
namespace: production
---
# Role - Read pod dan service
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# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: cicd-deployer
namespace: default
---
# ClusterRole - Deploy application
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# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: monitoring-app
namespace: monitoring
---
# ClusterRole - Read metric
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# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: backup-job-sa
namespace: default
---
# Role - Access ke backup resource
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: OnFailureProblem: Default ServiceAccount tidak punya specific permission.
# Bad: Menggunakan default ServiceAccount
spec:
# No serviceAccountName specified
containers:
- name: app
image: myapp:latestSolusi: Create dedicated ServiceAccount:
# Good: Dedicated ServiceAccount
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: myapp:latestProblem: Granting cluster-admin ke ServiceAccount.
# Bad: Terlalu banyak permission
roleRef:
kind: ClusterRole
name: cluster-adminSolusi: Grant minimum necessary permission:
# Good: Specific permission
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]Problem: Multiple application menggunakan same ServiceAccount.
Solusi: Create separate ServiceAccount per application:
# App 1
serviceAccountName: app1-sa
# App 2
serviceAccountName: app2-saProblem: Mounting token di Pod yang tidak need API access.
Solusi: Disable ketika not needed:
spec:
automountServiceAccountToken: falseProblem: Token yang never expire adalah security risk.
Solusi: Gunakan short-lived token (1.24+):
kubectl create token my-app-sa --duration=1hGrant hanya necessary permission:
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # Hanya yang neededSeparate identity untuk setiap app:
# Frontend
serviceAccountName: frontend-sa
# Backend
serviceAccountName: backend-sa
# Database
serviceAccountName: database-saPrefer Role over ClusterRole ketika possible:
# Good: Namespace-scoped
kind: Role
metadata:
namespace: production
# Avoid kecuali necessary
kind: ClusterRolespec:
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.25 # Tidak need API accessmetadata:
name: my-app-sa
annotations:
description: "ServiceAccount untuk my-app dengan read-only access ke pod"
owner: "platform-team"
permissions: "pods:get,list,watch"Review ServiceAccount permission:
# List ServiceAccount
kubectl get serviceaccounts --all-namespaces
# Check permission
kubectl auth can-i --list --as=system:serviceaccount:default:my-app-saUntuk external access, gunakan time-bound token:
kubectl create token my-app-sa --duration=8hkubectl get pod my-pod
# Error: serviceaccount "my-app-sa" not foundSolusi: Create ServiceAccount dulu:
kubectl create serviceaccount my-app-sa# Inside Pod
kubectl get pods
# Error: pods is forbiddenSolusi: Check dan fix RBAC:
# Check permission
kubectl auth can-i get pods --as=system:serviceaccount:default:my-app-sa
# Create Role dan RoleBinding
kubectl create role pod-reader --verb=get,list --resource=pods
kubectl create rolebinding read-pods --role=pod-reader --serviceaccount=default:my-app-sa# Inside Pod
ls /var/run/secrets/kubernetes.io/serviceaccount/
# Directory not foundSolusi: Enable token mounting:
spec:
automountServiceAccountToken: true # Ensure ini true# ServiceAccount di namespace A, Pod di namespace B
# Error: serviceaccount not foundSolusi: Ensure same namespace:
# ServiceAccount
metadata:
namespace: production
# Pod
metadata:
namespace: production
spec:
serviceAccountName: my-app-sakubectl get serviceaccounts
kubectl get sa # Short form
kubectl get sa --all-namespaceskubectl describe serviceaccount my-app-sakubectl get serviceaccount my-app-sa -o yamlkubectl auth can-i --list --as=system:serviceaccount:default:my-app-sa# Kubernetes 1.24+
kubectl create token my-app-sa
# Pre-1.24
kubectl get secret <sa-token-secret> -o jsonpath="{.data.token}" | base64 --decodekubectl delete serviceaccount my-app-saWarning
Peringatan: Menghapus ServiceAccount akan cause Pod yang menggunakannya lose API access. Ensure no Pod menggunakannya sebelum deletion.
Pada episode 32 ini, kita telah membahas ServiceAccount di Kubernetes secara mendalam. Kita sudah belajar bagaimana ServiceAccount provide identity untuk Pod, enable API authentication, dan work dengan RBAC untuk fine-grained access control.
Key takeaway:
/var/run/secrets/kubernetes.io/serviceaccount/ServiceAccount fundamental untuk Kubernetes security dan access control. Dengan memahami ServiceAccount dan RBAC, kalian bisa implement proper authentication dan authorization untuk application, ensuring secure, controlled access ke cluster resource.
Bagaimana, makin jelas kan tentang ServiceAccount di Kubernetes? Jadi, pastikan tetap semangat belajar dan nantikan episode selanjutnya!
Catatan
Untuk kalian yang ingin melanjutkan ke episode selanjutnya, bisa click thumbnail episode 33 di bawah ini