ServiceAccount: identity для Pods
ServiceAccount (SA) — это identity Pod при общении с kube-apiserver. Каждый Pod в кластере запускается с каким-то SA — нет варианта «без SA». Если в spec ничего не указано, используется default SA того namespace, где создан Pod. Этот SA каждому Pod даёт два конкретных артефакта: JWT-токен в файловой системе и список imagePullSecrets для скачивания образов. На CKAD ServiceAccount — это вход в RBAC: следующий урок без понимания SA не имеет смысла.
Пользователи и группы: /etc/passwd, /etc/shadow, /etc/group
SA как объект
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: production
imagePullSecrets:
- name: registry-creds
automountServiceAccountToken: false
Структура минимальная. Из примечательных полей:
imagePullSecrets— список Secrets типаkubernetes.io/dockerconfigjson. Применяется ко всем Pods, использующим этот SA. Удобный способ дать namespace доступ к приватному registry без повторения secret в каждом Pod.automountServiceAccountToken— отключить автоматический mount токена в Pods, использующих этот SA. По умолчаниюtrue.secrets— legacy поле для long-lived токенов, с v1.24 пустое по умолчанию.
Создать SA можно императивно:
kubectl create serviceaccount app-sa -n production
Что Pod получает от SA
Проверить можно изнутри Pod:
kubectl exec -it my-pod -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt namespace token
kubectl exec -it my-pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
# eyJhbGciOiJSUzI1NiIs...
Token projection: что изменилось с v1.21
До v1.22 K8s работал так: при создании SA автоматически создавался Secret типа kubernetes.io/service-account-token с долгоживущим (без exp) JWT. Этот секрет mountился в Pods. Проблемы:
- Токены жили вечно. Утекли — навсегда валидны до удаления Secret.
- Утечка в логах, environment variables, конфигах — критический инцидент.
- Один токен на много Pods — нет per-Pod audience.
С v1.21+ (BoundServiceAccountTokenVolume GA в v1.22) kubelet получает токен через TokenRequest API:
Преимущества:
- Срок жизни — токен валиден ~1 час, после exp — apiserver отвергает.
- Audience — токен ограничен конкретной audience (
apiпо умолчанию). Сторонние audiences для внешних систем (Vault, OIDC integrations). - Pod binding — токен невалиден после удаления Pod.
- Не Secret в etcd — нет долгоживущего объекта, который можно утечь.
Если кому-то нужен long-lived token (например, для CI-системы, которая не Pod) — можно явно создать Secret типа kubernetes.io/service-account-token с аннотацией kubernetes.io/service-account.name. Apiserver сгенерирует токен без exp. Но это anti-pattern для Pod: используйте projected token.
Default SA в каждом namespace
В каждом namespace есть SA default, создаваемый автоматически namespace controller’ом. Если в spec Pod не указан serviceAccountName, используется этот SA.
kubectl get sa -n default
# NAME SECRETS AGE
# default 0 5d
default SA имеет минимальные разрешения в RBAC: разрешено get для namespaces своего namespace и почти ничего больше (зависит от cluster). Но он НЕ имеет нулевых прав — на CKAD это критично. В production стандарт — создавать отдельный SA для каждого приложения и давать ему точечные разрешения через RBAC.
Использование SA в Pod
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
serviceAccountName: app-sa
containers:
- name: app
image: app:1.0
После создания Pod kubelet:
- Найдёт SA
app-saв namespace Pod. - Запросит JWT через TokenRequest.
- Смонтирует projected volume с token/ca.crt/namespace.
- Использует
imagePullSecretsиз SA при pull image.
Внутри Pod K8s-клиент будет:
# Python пример с kubernetes-python-client
from kubernetes import config, client
config.load_incluster_config() # читает token из /var/run/secrets/...
v1 = client.CoreV1Api()
pods = v1.list_namespaced_pod(namespace="default")
load_incluster_config() читает token, ca.crt, namespace и конструирует client.
automountServiceAccountToken: когда отключить
Большинство Pods не вызывают K8s API. Веб-сервер на Go, Python web app, nginx — им не нужен токен. Но он всё равно монтируется. Если container скомпрометирован — attacker имеет рабочий JWT и может разведать кластер (минимум listing своего namespace).
Отключение:
# На уровне SA — все Pods с этим SA не получают токен
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-sa
automountServiceAccountToken: false
---
# На уровне Pod — override SA setting
apiVersion: v1
kind: Pod
spec:
automountServiceAccountToken: false
serviceAccountName: web-sa
Pod-level automountServiceAccountToken переопределяет SA-level. Если в SA стоит false, а в Pod — true, токен будет смонтирован. Это сделано чтобы один SA мог обслуживать смешанные Pods, но в production обычно ставят false на SA и явно true только там, где нужно.