imagePullSecrets во всех namespace через secretGenerator

Published: 2026-02-15

Приватные container registry требуют imagePullSecrets в каждом Pod в каждом namespace. Если у вас 15 namespace и каждый месяц добавляется новый, ручное управление учётными данными превращается в техдолг. Подход с secretGenerator в Kustomize решает задачу без операторов и естественно вписывается в GitOps.


Проблема

Pod должен скачать registry.example.com/myapp:1.0. Registry требует аутентификацию. imagePullSecret должен находиться в том же namespace, что и Pod. Секрет из другого namespace использовать нельзя.

Варианты:

  • Копировать секрет вручную во все namespace — при ротации токена придётся обновить 15 мест
  • Оператор репликации (Reflector, Kubernetes Replicator) — лишний компонент, который при баге молча перестаёт работать
  • secretGenerator — встроен в Kustomize, без внешних зависимостей, естественно ложится на GitOps

Подход с secretGenerator

В FluxCD GitOps-репозитории создайте Kustomization, которая генерирует Secret в каждом namespace:

yaml# fluxcd/custom/secrets/docker-registries/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

secretGenerator:
  - name: registry-example-com
    namespace: app
    type: kubernetes.io/dockerconfigjson
    files:
      - .dockerconfigjson=registry-credentials.dockerconfigjson
    options:
      disableNameSuffixHash: true

  - name: registry-example-com
    namespace: monitoring
    type: kubernetes.io/dockerconfigjson
    files:
      - .dockerconfigjson=registry-credentials.dockerconfigjson
    options:
      disableNameSuffixHash: true

  - name: registry-example-com
    namespace: db
    type: kubernetes.io/dockerconfigjson
    files:
      - .dockerconfigjson=registry-credentials.dockerconfigjson
    options:
      disableNameSuffixHash: true

disableNameSuffixHash: true сохраняет стабильное имя секрета (registry-example-com вместо registry-example-com-abc123). Без этого флага Kustomize добавляет к имени хэш содержимого: после каждой ротации учётных данных имя меняется, и все ссылки в HelmRelease ломаются.

Почему secretGenerator, а не копирование манифестов

secretGenerator идемпотентен: повторный kubectl apply обновит секрет во всех namespace, если содержимое изменилось. Новый namespace — это один блок и один коммит, остальное делает Flux. Никакого цикла «apply для каждого namespace».


Файл .dockerconfigjson

Сгенерируйте его один раз из учётных данных:

bashkubectl create secret docker-registry registry-example-com \
  --docker-server=registry.example.com \
  --docker-username=robot-ci \
  --docker-password=TOKEN \
  --dry-run=client -o jsonpath='{.data.\.dockerconfigjson}' \
  | base64 -d > registry-credentials.dockerconfigjson

Файл должен выглядеть так:

json{
  "auths": {
    "registry.example.com": {
      "username": "robot-ci",
      "password": "TOKEN",
      "auth": "BASE64_USER_PASS"
    }
  }
}

Не коммитьте этот файл в открытом виде. Варианты:

  • SOPS или SealedSecrets — хранить зашифрованным в Git
  • Vault + ESO — ESO синхронизирует dockerconfigjson из Vault в K8s Secret, обновляет при ротации
  • .gitignore + CI — файл в .gitignore, CI подставляет его при bootstrap

Для продакшена используем ESO: единый ExternalSecret с creationPolicy: Merge синхронизирует dockerconfigjson из Vault в каждый namespace. Для dev/test — файл в .gitignore, который подставляет CI.

Ротация учётных данных registry

При смене токена:

  1. Обновите токен в Vault — ESO обновит Secret в следующем цикле синхронизации
  2. Если используется файл: сгенерируйте новый registry-credentials.dockerconfigjson, закоммитьте, запушите. Flux синхронизирует и обновит Secret во всех namespace одновременно.

Никакого цикла kubectl apply по namespace вручную.


Добавление нового namespace

yaml  - name: registry-example-com
    namespace: newservice
    type: kubernetes.io/dockerconfigjson
    files:
      - .dockerconfigjson=registry-credentials.dockerconfigjson
    options:
      disableNameSuffixHash: true

Закоммитили, запушили, Flux синхронизировался — новый namespace получит секрет меньше чем за минуту.

Автоматизация списка namespace

Скрипт для генерации Kustomization из живого кластера:

bashkubectl get namespaces -o jsonpath='{.items[*].metadata.name}' \
  | tr ' ' '\n' \
  | grep -v 'kube-\|flux-\|kube$' \
  | while read ns; do
    cat <<EOF
  - name: registry-example-com
    namespace: $ns
    type: kubernetes.io/dockerconfigjson
    files:
      - .dockerconfigjson=registry-credentials.dockerconfigjson
    options:
      disableNameSuffixHash: true
EOF
  done

Использование секрета в HelmRelease

yamlvalues:
  imagePullSecrets:
    - name: registry-example-com
  image:
    repository: registry.example.com/myapp

Или глобально в Pod spec внутри Helm-чарта:

yamlspec:
  imagePullSecrets:
    {{- toYaml .Values.imagePullSecrets | nindent 4 }}

Патчинг default ServiceAccount

Можно пропатчить default ServiceAccount в каждом namespace — тогда каждый Pod автоматически получит pull-секрет, даже если Helm-чарт его не задаёт:

yamlapiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: app
imagePullSecrets:
  - name: registry-example-com

Admission-контроллер Kubernetes подставляет imagePullSecrets из ServiceAccount в каждый новый Pod в namespace. Менять Pod spec или Helm-чарт не нужно.


Отладка ошибок image pull

bashkubectl describe pod <pod> -n <namespace> | grep -A5 "Events:"

Типичные события:

  • ImagePullBackOff — загрузка образа не удалась, идут повторные попытки с экспоненциальным backoff
  • ErrImagePull — первичная ошибка загрузки образа
  • 403 Forbidden / 401 Unauthorized — неверные или отсутствующие учётные данные

Проверьте наличие и содержимое секрета:

bashkubectl get secret registry-example-com -n <namespace> \
  -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .

Если секрет отсутствует — Flux мог ещё не синхронизироваться:

bashflux get kustomization secrets -n flux-system