Learning Platform
Глоссарий Troubleshooting
Урок 15.02 · 22 мин
Продвинутый
ServiceAccountJWTTokenRequestautomountServiceAccountTokenRBAC

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

ServiceAccount: что даётся Pod
JWT tokenJSON Web Token, подписанный kube-apiserver. Содержит claims: sub (system:serviceaccount:NS:NAME), aud (целевая audience), exp (срок жизни, default 1 час), iat. Pod использует его при API-запросах через Authorization: Bearer.
ca.crtCA certificate, которым подписан kube-apiserver. Используется client'ом для верификации серверного TLS-сертификата при HTTPS-вызовах к apiserver.
namespaceТекстовый файл с именем namespace, в котором запущен Pod. Удобно для библиотек, которые из контейнера хотят узнать свой namespace без env vars.
mountPathKubelet монтирует эти три файла как projected volume на стандартный путь. Все K8s client-libraries (client-go, kubernetes-python, fabric8) ищут токен именно там.
imagePullSecretsПомимо файлов в Pod, kubelet берёт imagePullSecrets из SA и использует их при pull image. Это не файл в Pod, это behavior kubelet/CRI.

Проверить можно изнутри 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:

Token projection flow
Pod созданkube-scheduler разместил Pod, kubelet получил Pod spec.
kubeletkubelet вызывает TokenRequest API на kube-apiserver, запрашивая JWT для SA Pod. В запросе указывает audience (по умолчанию api), expiration (default 1 час).
apiserverkube-apiserver генерирует JWT с указанным audience, expiration, bound к конкретному Pod (sub claim содержит ID Pod). Подписывает CA-ключом.
projected volumeKubelet монтирует токен в Pod как часть projected volume. Также монтирует ca.crt и namespace. Это конкретный VolumeProjection с serviceAccountToken источником.
rotationКогда до expiration остаётся менее 20% жизни (или явно прошло 80%) — kubelet запрашивает новый токен и обновляет файл в volume. Все K8s SDK читают файл при каждом запросе, так что ротация прозрачна.

Преимущества:

  • Срок жизни — токен валиден ~1 час, после exp — apiserver отвергает.
  • Audience — токен ограничен конкретной audience (api по умолчанию). Сторонние audiences для внешних систем (Vault, OIDC integrations).
  • Pod binding — токен невалиден после удаления Pod.
  • Не Secret в etcd — нет долгоживущего объекта, который можно утечь.
NOTE

Если кому-то нужен 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:

  1. Найдёт SA app-sa в namespace Pod.
  2. Запросит JWT через TokenRequest.
  3. Смонтирует projected volume с token/ca.crt/namespace.
  4. Использует 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
WARNING

Pod-level automountServiceAccountToken переопределяет SA-level. Если в SA стоит false, а в Pod — true, токен будет смонтирован. Это сделано чтобы один SA мог обслуживать смешанные Pods, но в production обычно ставят false на SA и явно true только там, где нужно.


Killer-моменты CKAD

SA: что важно помнить
default SAКаждый namespace имеет default SA. Если в Pod не указан serviceAccountName — используется default. Имеет минимальные, но не нулевые permissions.
namespacedServiceAccount — namespaced ресурс. SA в default не виден из production. RoleBinding/ClusterRoleBinding ссылаются на SA с указанием namespace.
token mount pathСтандартный путь не меняется — все клиенты библиотек ищут именно его. Это hard-coded путь в кодовой базе client-go и совместимых.
token v1.22+С v1.22 — projected token (bound, expiring). До v1.21 — Secret с долгоживущим JWT. На v1.35 — projected по умолчанию.
security riskЛюбой compromised container с примонтированным токеном получает доступ к API kubernetes с правами SA. Минимум: list своего namespace. Если SA имеет права широкие — катастрофа.
mitigationОтключить automountServiceAccountToken для apps без потребности в API, давать SA минимальные права через RBAC.

Проверка знанийKnowledge check
Pod создан без serviceAccountName в spec. Под каким SA он запустится и какие у него будут permissions?
ОтветAnswer
Под default SA своего namespace (создаётся automatically namespace controller'ом). У default минимальные permissions: get namespaces своего namespace и почти ничего больше. На production стандарт — создавать отдельный SA для каждого приложения и через RBAC давать ему точечные разрешения.
Проверка знанийKnowledge check
Чем projected token (v1.22+) отличается от legacy SA Secret token?
ОтветAnswer
Projected token — short-lived (~1 час), bound к конкретному Pod, имеет audience claim, ротируется kubelet прозрачно. Не хранится в etcd как Secret. Legacy Secret — долгоживущий JWT без exp, хранится в etcd, не привязан к Pod, утечка — постоянная проблема.
Проверка знанийKnowledge check
Приложение — обычный web server, не использует K8s API. Какая лучшая практика для его SA?
ОтветAnswer
Создать отдельный SA с automountServiceAccountToken: false. Это убирает токен из файловой системы Pod. Если container compromised — attacker не получит API access. Альтернатива — поставить automountServiceAccountToken: false на Pod-level.
Проверка знанийKnowledge check
Как дать всем Pods в namespace credentials к private registry без повторения imagePullSecrets в каждом Pod?
ОтветAnswer
Создать Secret типа kubernetes.io/dockerconfigjson с registry credentials, затем добавить его в imagePullSecrets секцию ServiceAccount, который будут использовать Pods. Если это default SA — все Pods namespace получают доступ автоматически.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Pod создан без поля serviceAccountName в spec. Под каким SA он запустится?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5