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
При смене токена:
- Обновите токен в Vault — ESO обновит Secret в следующем цикле синхронизации
- Если используется файл: сгенерируйте новый
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— загрузка образа не удалась, идут повторные попытки с экспоненциальным backoffErrImagePull— первичная ошибка загрузки образа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