Files
K3S/addons/external-secrets
Sergey Antropoff 38aaadbfb1 docs: sync addon docs with explicit external/internal service modes
Обновлена документация под новые аддоны (gitlab, redis, mongodb, kafka, kafka-ui, rabbitmq) и новую модель явного выбора зависимостей. Добавлены и унифицированы описания переключателей *_database_mode и *_redis_mode, обновлена таблица зависимостей аддонов, примеры конфигурации и список vault-секретов.
2026-04-29 23:21:04 +03:00
..

External Secrets Operator (ESO)

Синхронизирует секреты из внешних хранилищ (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager и др.) в нативные Kubernetes Secrets.

Когда использовать ESO вместо Vault Agent Injector:

  • Нужны стандартные k8s Secrets (secretRef, envFrom) — Helm чарты не менять
  • Секреты нужны до старта пода (init containers, admission webhooks)
  • Нужна синхронизация между namespace
  • Нет желания добавлять Vault annotations в каждый Deployment

Установка

# Базовая установка (настрой ClusterSecretStore вручную после)
make addon-external-secrets

# Сразу с настроенным ClusterSecretStore → Vault (если AppRole уже создан)
make addon-external-secrets ARGS="-e external_secrets_vault_role_id=<role-id>"

Настройка AppRole в Vault

ESO использует AppRole для аутентификации в Vault. Выполни один раз:

# 1. Port-forward к Vault
kubectl port-forward -n vault svc/vault 8200:8200 &
export VAULT_ADDR=http://localhost:8200
vault login <root_token>

# 2. Включить AppRole auth (если ещё не включён)
vault auth enable approle

# 3. Создать политику для ESO (read-only на все секреты)
vault policy write eso-policy - <<EOF
path "secret/data/*" {
  capabilities = ["read"]
}
path "secret/metadata/*" {
  capabilities = ["read", "list"]
}
EOF

# 4. Создать AppRole роль
vault write auth/approle/role/eso-role \
  secret_id_ttl="0"        \
  token_ttl="1h"           \
  token_max_ttl="24h"      \
  token_policies="eso-policy"

# 5. Получить Role ID (публичный, вставить в addons.yml)
vault read -field=role_id auth/approle/role/eso-role/role-id
# → Скопируй в external_secrets_vault_role_id в group_vars/all/addons.yml

# 6. Получить Secret ID (секретный, вставить в vault.yml)
vault write -f -field=secret_id auth/approle/role/eso-role/secret-id
# → Скопируй в vault_eso_approle_secret_id в group_vars/all/vault.yml

# 7. Применить ClusterSecretStore
make addon-external-secrets ARGS="-e external_secrets_vault_role_id=<role-id>"

Проверка подключения

# Статус ClusterSecretStore (должен быть Valid)
kubectl get clustersecretstore vault-backend

# Подробности
kubectl describe clustersecretstore vault-backend

Использование в YAML манифестах

Простой ExternalSecret — синхронизация одного секрета

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-db-secret
  namespace: myapp
spec:
  refreshInterval: 1h           # как часто синхронизировать
  secretStoreRef:
    name: vault-backend          # ClusterSecretStore
    kind: ClusterSecretStore
  target:
    name: myapp-db-credentials   # имя k8s Secret который будет создан
    creationPolicy: Owner        # ESO управляет Secret (авто-удаление)
  data:
    - secretKey: DB_PASSWORD     # ключ в k8s Secret
      remoteRef:
        key: myapp/database      # путь в Vault: secret/data/myapp/database
        property: password       # поле внутри секрета
    - secretKey: DB_USER
      remoteRef:
        key: myapp/database
        property: username

После применения ESO создаст k8s Secret myapp-db-credentials.

Использование в Deployment (через envFrom)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  template:
    spec:
      containers:
        - name: app
          image: myapp:latest
          envFrom:
            - secretRef:
                name: myapp-db-credentials  # создан ExternalSecret выше
          # ИЛИ выборочно:
          env:
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: myapp-db-credentials
                  key: DB_PASSWORD

Весь секрет целиком (все поля из Vault)

spec:
  dataFrom:
    - extract:
        key: myapp/config   # все поля из secret/data/myapp/config станут ключами в k8s Secret

ExternalSecret с template (форматирование)

spec:
  target:
    name: myapp-connection-string
    template:
      data:
        DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@postgres:5432/mydb"
  dataFrom:
    - extract:
        key: myapp/database

Использование в Helm чартах

Способ 1: ExternalSecret в том же Helm чарте

# templates/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ include "myapp.fullname" . }}-secrets
  namespace: {{ .Release.Namespace }}
spec:
  refreshInterval: {{ .Values.secrets.refreshInterval | default "1h" }}
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: {{ include "myapp.fullname" . }}-secrets
    creationPolicy: Owner
  dataFrom:
    - extract:
        key: {{ .Values.secrets.vaultPath }}   # задаётся в values.yaml
# values.yaml
secrets:
  vaultPath: "myapp/production/config"
  refreshInterval: "30m"

# Deployment использует стандартный envFrom:
envFrom:
  - secretRef:
      name: "{{ include "myapp.fullname" . }}-secrets"

Способ 2: ESO как внешняя зависимость (рекомендуется)

Секреты создаются заранее через отдельный ExternalSecret, Helm чарт просто ссылается на k8s Secret:

# 1. Создать ExternalSecret (один раз, в GitOps репозитории)
kubectl apply -f external-secret.yaml

# 2. Установить Helm чарт — он найдёт уже существующий Secret
helm install myapp ./mychart --set existingSecret=myapp-db-credentials
# values.yaml чарта
existingSecret: ""   # если задан — используется, иначе chart создаёт свой

# templates/deployment.yaml
{{- if .Values.existingSecret }}
envFrom:
  - secretRef:
      name: {{ .Values.existingSecret }}
{{- end }}

Способ 3: ArgoCD + ESO

# argocd Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
spec:
  source:
    helm:
      values: |
        # Секреты создаются ESO автоматически — чарт просто ссылается
        envFrom:
          - secretRef:
              name: myapp-vault-secrets

PushSecret — запись секретов в Vault из k8s

ESO поддерживает обратную синхронизацию (k8s → Vault):

apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
  name: push-to-vault
  namespace: myapp
spec:
  refreshInterval: 10s
  secretStoreRefs:
    - name: vault-backend
      kind: ClusterSecretStore
  selector:
    secret:
      name: my-local-secret    # существующий k8s Secret
  data:
    - match:
        secretKey: password
        remoteRef:
          remoteKey: myapp/config
          property: password

Управление несколькими SecretStore

Namespace-scoped SecretStore (для разных Vault paths/политик)

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-myapp          # только для namespace myapp
  namespace: myapp
spec:
  provider:
    vault:
      server: "http://vault.vault.svc.cluster.local:8200"
      path: "myapp"          # ограниченный путь
      version: "v2"
      auth:
        appRole:
          path: "approle"
          roleId: "<myapp-role-id>"
          secretRef:
            name: vault-approle-myapp
            key: secretId

Мониторинг

Если addon_prometheus_stack: true, метрики ESO доступны в Grafana:

  • external_secrets_sync_calls_total — количество синхронизаций
  • external_secrets_sync_call_errors_total — ошибки
  • Дашборд: импортируй Grafana Dashboard ID 21045
# Статус всех ExternalSecrets в кластере
kubectl get externalsecrets --all-namespaces

# Подробности (условие Ready/SecretSynced)
kubectl describe externalsecret myapp-db-secret -n myapp

Официальные ресурсы