Обновлена документация под новые аддоны (gitlab, redis, mongodb, kafka, kafka-ui, rabbitmq) и новую модель явного выбора зависимостей. Добавлены и унифицированы описания переключателей *_database_mode и *_redis_mode, обновлена таблица зависимостей аддонов, примеры конфигурации и список vault-секретов.
294 lines
9.1 KiB
Markdown
294 lines
9.1 KiB
Markdown
# 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
|
||
|
||
## Установка
|
||
|
||
```bash
|
||
# Базовая установка (настрой 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. Выполни один раз:
|
||
|
||
```bash
|
||
# 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>"
|
||
```
|
||
|
||
## Проверка подключения
|
||
|
||
```bash
|
||
# Статус ClusterSecretStore (должен быть Valid)
|
||
kubectl get clustersecretstore vault-backend
|
||
|
||
# Подробности
|
||
kubectl describe clustersecretstore vault-backend
|
||
```
|
||
|
||
## Использование в YAML манифестах
|
||
|
||
### Простой ExternalSecret — синхронизация одного секрета
|
||
|
||
```yaml
|
||
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)
|
||
```yaml
|
||
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)
|
||
```yaml
|
||
spec:
|
||
dataFrom:
|
||
- extract:
|
||
key: myapp/config # все поля из secret/data/myapp/config станут ключами в k8s Secret
|
||
```
|
||
|
||
### ExternalSecret с template (форматирование)
|
||
```yaml
|
||
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 чарте
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
```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:
|
||
|
||
```bash
|
||
# 1. Создать ExternalSecret (один раз, в GitOps репозитории)
|
||
kubectl apply -f external-secret.yaml
|
||
|
||
# 2. Установить Helm чарт — он найдёт уже существующий Secret
|
||
helm install myapp ./mychart --set existingSecret=myapp-db-credentials
|
||
```
|
||
|
||
```yaml
|
||
# values.yaml чарта
|
||
existingSecret: "" # если задан — используется, иначе chart создаёт свой
|
||
|
||
# templates/deployment.yaml
|
||
{{- if .Values.existingSecret }}
|
||
envFrom:
|
||
- secretRef:
|
||
name: {{ .Values.existingSecret }}
|
||
{{- end }}
|
||
```
|
||
|
||
### Способ 3: ArgoCD + ESO
|
||
```yaml
|
||
# 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):
|
||
|
||
```yaml
|
||
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/политик)
|
||
```yaml
|
||
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
|
||
|
||
```bash
|
||
# Статус всех ExternalSecrets в кластере
|
||
kubectl get externalsecrets --all-namespaces
|
||
|
||
# Подробности (условие Ready/SecretSynced)
|
||
kubectl describe externalsecret myapp-db-secret -n myapp
|
||
```
|
||
## Официальные ресурсы
|
||
|
||
- Официальный сайт: [https://external-secrets.io/](https://external-secrets.io/)
|
||
- Официальная документация: [https://external-secrets.io/latest/](https://external-secrets.io/latest/)
|
||
- Версии Helm chart / ПО: [https://artifacthub.io/packages/helm/external-secrets-operator/external-secrets](https://artifacthub.io/packages/helm/external-secrets-operator/external-secrets)
|