Стадия жизненного цикла модуля: Предварительная версия (preview)

У модуля есть требования для установки

В этом руководстве показано, как аутентифицировать CI/CD-пайплайн в хранилище образов и пушить образы в проект. Примеры платформонезависимы (переносимый shell с docker); перенесите шаги на примитивы job’ов и секретов вашей CI-системы.

Хранилище аутентифицирует клиентов по токенам Kubernetes (проверка через TokenReview) и авторизует их через RBAC на виртуальном ресурсе payloadrepositoryaccesses — см. Аутентификация и авторизация. Поэтому пайплайну нужны: сервисный аккаунт робота, RBAC с правом push и токен, которым пайплайн логинится.

Предварительные условия

  • Адрес хранилища: payload-registry.${PUBLIC_DOMAIN}.

  • CI-раннер должен доверять TLS-сертификату хранилища. Если сертификат публично доверенный — делать ничего не нужно. Если используется собственный CA, добавьте CA на раннер, чтобы docker/crane могли его проверить, например:

    # Docker читает CA для конкретного registry по этому пути:
    install -d /etc/docker/certs.d/payload-registry.${PUBLIC_DOMAIN}
    cp ca.crt /etc/docker/certs.d/payload-registry.${PUBLIC_DOMAIN}/ca.crt

1. Создайте сервисный аккаунт робота и выдайте право push

Образы живут под неймспейсом проекта: первый сегмент пути в ссылке на образ — это неймспейс (например, project-1/app/... лежит в неймспейсе project-1). Создайте отдельный ServiceAccount для CI и выдайте ему create (push) на пути, в которые он может писать. Сужайте resourceNames максимально.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-robot
  namespace: project-1
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: payload-registry-ci-push
  namespace: project-1
rules:
- apiGroups: ["payload-registry.deckhouse.io"]
  resources: ["payloadrepositoryaccesses"]
  # Сузьте до путей, в которые CI может писать, например app/** для project-1/app/...
  resourceNames: ["app/*", "app/**"]
  # create = push, get = pull, list = список тегов, delete = удаление
  verbs: ["get", "list", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payload-registry-ci-push
  namespace: project-1
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: payload-registry-ci-push
subjects:
- kind: ServiceAccount
  name: ci-robot
  namespace: project-1

Соответствие глагол→операция (create = push, get = pull, list = список тегов, delete = удаление) описано в разделе Уровни доступа.

2. Получите токен для робота

Пайплайн логинится в хранилище с именем пользователя token и токеном Kubernetes в качестве пароля. Есть два надёжных способа получить токен; выберите по вашей политике безопасности.

Вариант A — долгоживущий токен ServiceAccount (рекомендуется для CI)

Создайте Secret типа токена ServiceAccount. Kubernetes наполнит его неистекающим токеном, привязанным к SA робота. Это самый простой надёжный вариант: токен не истекает посреди пайплайна, для его выпуска не нужны дополнительные права в кластере, а радиус поражения ограничен RBAC выше.

apiVersion: v1
kind: Secret
metadata:
  name: ci-robot-token
  namespace: project-1
  annotations:
    kubernetes.io/service-account.name: ci-robot
type: kubernetes.io/service-account-token

Прочитайте токен и положите его в маскированную / защищённую CI-переменную (например, REGISTRY_TOKEN):

kubectl -n project-1 get secret ci-robot-token -o jsonpath='{.data.token}' | base64 -d

Так как токен сам по себе не истекает, относитесь к нему как к любому долгоживущему секрету: храните маскированным/защищённым, не выводите в лог, периодически ротируйте и отзывайте, когда он больше не нужен (см. Отзыв и ротация креденшла).

Вариант B — короткоживущий токен на каждый job

Большинству пайплайнов это не нужно — предпочитайте вариант A. Используйте только если ваша политика безопасности запрещает долгоживущие токены. Выпускайте свежий токен в начале каждого job через TokenRequest API:

kubectl -n project-1 create token ci-robot --duration=2h

Нюансы:

  • Идентичности, выполняющей эту команду, нужно право create на serviceaccounts/token для ci-robot (у обычного cluster-admin kubeconfig его по умолчанию нет) — то есть CI всё равно хранит кластерный креденшл (kubeconfig) с этим правом.
  • Время жизни ограничено флагом apiserver --service-account-max-token-expiration. Запрашивайте не меньше максимальной длительности job, и перелогинивайтесь, если job может пережить токен.

Отзыв и ротация креденшла

Токен проверяется относительно ServiceAccount (а для варианта A — ещё и относительно его токен-Secret) при каждом логине. Отозвать можно двумя способами:

  • Ротация одного креденшла (вариант A): удалите токен-Secret и создайте новый, затем обновите CI-переменную. Старый токен перестаёт работать вскоре после этого (проверка токена кратко кэшируется apiserver, поэтому отзыв вступает в силу за десятки секунд, а не мгновенно).

    kubectl -n project-1 delete secret ci-robot-token
    kubectl -n project-1 apply -f ci-robot-token.yaml   # пересоздать, затем перечитать токен
  • Полностью отключить робота: удалите ServiceAccount. Это инвалидирует все его токены сразу (и вариант A, и вариант B — они больше не соответствуют существующему ServiceAccount) и убирает его как RBAC-subject.

    kubectl -n project-1 delete serviceaccount ci-robot

Удаление RoleBinding оставит токен валидным, но снимет авторизацию на push/pull (логин пройдёт, но любая операция будет запрещена).

3. Пайплайн: логин, сборка, push

Переносимый job (ложится напрямую в script: GitLab CI, шаги run: GitHub Actions и т.д.). REGISTRY_TOKEN — маскированная CI-переменная из шага 2.

set -euo pipefail

REGISTRY="payload-registry.${PUBLIC_DOMAIN}"
IMAGE="${REGISTRY}/project-1/app/myservice:${CI_COMMIT_SHORT_SHA}"

# 1. Аутентификация (имя пользователя всегда литерал "token").
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u token --password-stdin

# 2. Сборка и тег.
docker build -t "${IMAGE}" .

# 3. Push.
docker push "${IMAGE}"

Используйте --password-stdin (а не -p), чтобы токен не попал в список процессов и в лог job’а.

Квота хранилища в пайплайнах

Push проверяется на квоту хранилища проекта (ресурс PayloadRegistryQuota, если он задан в неймспейсе проекта). Когда проект превысил квоту, push отклоняется с HTTP 413 и кодом ошибки QUOTA_EXCEEDED; docker push завершается с ошибкой. Проверьте и при необходимости поднимите квоту:

kubectl -n project-1 get prquota default

Чтобы пайплайны не забивали проект, удаляйте устаревшие теги и полагайтесь на сборку мусора (GC) хранилища (учтите: квота считает физический футпринт, который освобождается именно GC, а не просто удалением манифеста).

Pull из CI

Тот же токен (с доступом хотя бы get к пути) работает и для pull:

echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u token --password-stdin
docker pull "${REGISTRY}/project-1/app/myservice:${TAG}"

Чтобы использовать образы внутри кластера (в Pod’ах), используйте imagePullSecrets — см. Использование образов в кластере.

Диагностика

Симптом Причина / решение
401 Unauthorized при логине Неверное имя пользователя (должно быть token), истёкший/невалидный токен, либо SA/Secret удалён.
denied / 403 при push У SA робота нет create на этот путь. Проверьте resourceNames в Role и RoleBinding.
x509: certificate signed by unknown authority Раннер не доверяет CA хранилища — см. Предварительные условия.
413 QUOTA_EXCEEDED при push Проект превысил квоту хранилища — см. Квота хранилища.