Привет, Хабр!
В определенный момент мы приходим к понимаю, что процесс, который работает «хорошо», должен начать работать «правильно», особенно, если речь зашла про секреты приложений.
Дано: gitlab(onprem), облако (в моем случае Yandex Cloud), 10+ сервисов, которые нужно активно и часто деплоить (с возможностью быстрого наращивания кол-ва сервисов)
Требования к решению: деплои должны происходить без участия вмешательства инженера непосредственно в процессе деплоя, процесс прозрачен для разработчика и отвечает принятым в компании требованиям к качеству и безопасности.
Решение: Оговорюсь сразу — мое решение построено на Yandex Cloud, но справедливо для любого облачного или onprem решения.
Пропускаю путь от деплоя файлом локально через kubectl до Helm (можно в отдельную тему)
Итак есть репозитории с кодом, в них размещаем файлы .{environment}.values.yaml
При деплое указываем --values=/path/to/file
Возникает главный вопрос — если configmap хранить в репозитории (через helm переменные — это правильно), то как быть с секретами?
Вариант 1: использовать helm-secrets, sops и другие решения
Получаем в репозитории зашифрованные файлы с секретами. При деплое указываем путь к файлу, helm дефишрует и деплоит вместе с секретами приложение. Однако тут же прилетают накладные расходы в виде системы для передачи ключа с которым шифровали, обязательно не забыть в gitignore добавить *.dec, иначе в какой то момент секреты утекают в репозиторий и какой был смысл тогда? Ну и велик риск потерять ключ и секреты заодно. Главный минус подхода — при изменении секрета нужно заново пройти весь pipeline, а значит и проверки, и валидацию кода, и тесты — долго.
Вариант 2: использование управление секретами через внешние системы.
Дополнительно нам понадобится:
Выбор хранилища секретов ложиться на плечи системного инженера или архитектора. По опыту скажу — я использовал vault и lockbox. Первый, конечно более мощный, но для небольшого энтерпрайза хватит второго решения.
Далее разворачиваем в кластере External Secret Operator.
Создаем сервисный аккаунт с ролью lockbox.editor
Для работы с секретами так же развернем secretstore. Стоит обратить внимание, что secretstore работает в режиме namespace и изолирует секреты в рамках одного ns, ClusterSecretStore позволяет делить секреты между разными ns. Это следует помнить и не шарить лишний раз секреты, которые не будут использоваться в двух и более ns.
Предварительно создайте секрет ( в примере yc-auth) c ключами от сервисного аккаунта созданного ранее. Затем установите secretstore в нужный namespace
На данном этапе подготовительные работы выполнены.
В чарт приложения можно добавить шаблон для external-secret (или измените пример для использования, как обычный манифест)
В данном примере создается external-secret ресурс, который называется по названию чарта, вызывает создание одноименного k8s секрета из внешнего секрета.
Обратим внимание на два параметра:
После применения манифеста убедитесь, что ресурс перешел в состояние synced
На этом моменте мы получили управляемые секреты в к8s, меняем в lockbox — через какое то время — меняется в кубернетес. Но этого мало, деплоймент не узнает о том, что вы поменяли секрет или добавили новую переменную в него.
Тут на сцену выходит инструмент Kyverno (если будет необходимость — о настройке я напишу отдельный материал). В данный момент предположим, что он у вас уже стоит.
Данная политика не претендует на эталон, но она выполняет свою задачу — а именно выполнить restart деплоймента, если изменится контрольная сумма указанного секрета.
В итоге, какое решение мы получили.
При деплое приложения создается external secret, который занимается обновлением секрета k8s. Изменение последнего служит триггером для перезапуска приложения.

В определенный момент мы приходим к понимаю, что процесс, который работает «хорошо», должен начать работать «правильно», особенно, если речь зашла про секреты приложений.
Дано: gitlab(onprem), облако (в моем случае Yandex Cloud), 10+ сервисов, которые нужно активно и часто деплоить (с возможностью быстрого наращивания кол-ва сервисов)
Требования к решению: деплои должны происходить без участия вмешательства инженера непосредственно в процессе деплоя, процесс прозрачен для разработчика и отвечает принятым в компании требованиям к качеству и безопасности.
Решение: Оговорюсь сразу — мое решение построено на Yandex Cloud, но справедливо для любого облачного или onprem решения.
Пропускаю путь от деплоя файлом локально через kubectl до Helm (можно в отдельную тему)
Итак есть репозитории с кодом, в них размещаем файлы .{environment}.values.yaml
При деплое указываем --values=/path/to/file
Возникает главный вопрос — если configmap хранить в репозитории (через helm переменные — это правильно), то как быть с секретами?
Вариант 1: использовать helm-secrets, sops и другие решения
Получаем в репозитории зашифрованные файлы с секретами. При деплое указываем путь к файлу, helm дефишрует и деплоит вместе с секретами приложение. Однако тут же прилетают накладные расходы в виде системы для передачи ключа с которым шифровали, обязательно не забыть в gitignore добавить *.dec, иначе в какой то момент секреты утекают в репозиторий и какой был смысл тогда? Ну и велик риск потерять ключ и секреты заодно. Главный минус подхода — при изменении секрета нужно заново пройти весь pipeline, а значит и проверки, и валидацию кода, и тесты — долго.
Вариант 2: использование управление секретами через внешние системы.
Дополнительно нам понадобится:
- Место для хранения секретов. Я использую Yandex Cloud Lockbox, можно использовать например Vault или похожие системы
- External secret operator, как инструмент для синхронизации секретов
- Kyverno, как инструмент для выполнения политик к кластере k8s
Выбор хранилища секретов ложиться на плечи системного инженера или архитектора. По опыту скажу — я использовал vault и lockbox. Первый, конечно более мощный, но для небольшого энтерпрайза хватит второго решения.
Далее разворачиваем в кластере External Secret Operator.
Создаем сервисный аккаунт с ролью lockbox.editor
export HELM_EXPERIMENTAL_OCI=1 && \ helm pull oci://cr.yandex/yc-marketplace/yandex-cloud/external-secrets/chart/external-secrets \ --version 0.5.5 \ --untar && \ helm install \ --namespace external-secret-operator \ --create-namespace \ --set-file auth.json=sa-key.json \ external-secrets ./external-secrets/
Для работы с секретами так же развернем secretstore. Стоит обратить внимание, что secretstore работает в режиме namespace и изолирует секреты в рамках одного ns, ClusterSecretStore позволяет делить секреты между разными ns. Это следует помнить и не шарить лишний раз секреты, которые не будут использоваться в двух и более ns.
Предварительно создайте секрет ( в примере yc-auth) c ключами от сервисного аккаунта созданного ранее. Затем установите secretstore в нужный namespace
apiVersion: external-secrets.io/v1alpha1 kind: SecretStore metadata: name: secret-store spec: provider: yandexlockbox: auth: authorizedKeySecretRef: name: yc-auth key: authorized-key'
На данном этапе подготовительные работы выполнены.
В чарт приложения можно добавить шаблон для external-secret (или измените пример для использования, как обычный манифест)
apiVersion: external-secrets.io/v1alpha1 kind: ExternalSecret metadata: namespace: {{.Values.namespace }} name: {{ include "helm-chart.name" . }} spec: refreshInterval: {{ .Values.secretRefreshInterval }} secretStoreRef: name: secret-store kind: SecretStore target: name: {{ include "helm-chart.name" . }} data: - secretKey: DATABASE_URL remoteRef: key: {{ .Values.secretRemote }} property: DATABASE_URL
В данном примере создается external-secret ресурс, который называется по названию чарта, вызывает создание одноименного k8s секрета из внешнего секрета.
Обратим внимание на два параметра:
- secretRefreshInterval — интевал с которым secret-operator будет опрашивать lockbox на предмет изменения секрета (по умолчанию 1h0m0s)
- secretRemote — идентификатор секрета в lockbox (будет захвачена последняя актуальная версия секрета)
После применения манифеста убедитесь, что ресурс перешел в состояние synced
На этом моменте мы получили управляемые секреты в к8s, меняем в lockbox — через какое то время — меняется в кубернетес. Но этого мало, деплоймент не узнает о том, что вы поменяли секрет или добавили новую переменную в него.
Тут на сцену выходит инструмент Kyverno (если будет необходимость — о настройке я напишу отдельный материал). В данный момент предположим, что он у вас уже стоит.
apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: deployment-restart-policy namespace: test-ns annotations: policies.kyverno.io/title: Restart Deployment On Secret Change policies.kyverno.io/category: Other policies.kyverno.io/severity: medium policies.kyverno.io/subject: Deployment kyverno.io/kyverno-version: 1.9.0 policies.kyverno.io/minversion: 1.7.0 kyverno.io/kubernetes-version: "1.27" spec: schemaValidation: false rules: - name: update-secret match: any: - resources: kinds: - Secret names: - test-database-adapter namespaces: - sup preconditions: all: - key: "{{request.operation || 'BACKGROUND'}}" operator: Equals value: UPDATE mutate: targets: - apiVersion: apps/v1 kind: Deployment name: test-database-adapter namespace: test-ns patchStrategicMerge: spec: template: metadata: annotations: ops.corp.com/triggerrestart: "{{request.object.metadata.resourceVersion}}"
Данная политика не претендует на эталон, но она выполняет свою задачу — а именно выполнить restart деплоймента, если изменится контрольная сумма указанного секрета.
В итоге, какое решение мы получили.
При деплое приложения создается external secret, который занимается обновлением секрета k8s. Изменение последнего служит триггером для перезапуска приложения.
