Вернуться к главной странице, списку всех тем
9. Хранение секретных данных, таких как пароли. Автоматизация запуска приложений в Kubernetes. Простой способ запуска и конфигурации сложных систем из кучи приложений в Kubernetes
Что ты узнаешь: как хранить пароли так, чтобы их не было ни в коде, ни в манифестах; как сделать git единственным источником правды о том, что запущено в кластере; и как выкатывать новые версии, не рискуя всеми пользователями сразу
Что нужно знать заранее: темы 3 (git и PR), 5 (Kubernetes и Helm), 8 (метрики — без них не работает автоматический канареечный релиз)
Сколько времени займёт: 3 часа теории + 8 часов практики
Твой шаг в сквозном проекте: финал. Уберёшь пароль базы из репозитория, настроишь автоматический деплой «Заметок» из git и выкатишь новую версию канареечным релизом
Мы прошли восемь тем и оставили за собой три незакрытых долга. Пора вернуть их
Долг первый — пароль в открытом виде. В теме 4 мы написали в compose.yml строку POSTGRES_PASSWORD: notes_dev_password и честно пометили: «так делать нельзя». В теме 5 выяснили, что Secret в Kubernetes — это base64, то есть не защита вовсе
Долг второй — деплой руками. В теме 5 новые версии мы выкатывали командой kubectl set image, а в теме 7 — сценарием deploy.sh со своего ноутбука. Значит, чтобы обновить сервис, нужен человек с правами на кластер и с нужной версией кода на своей машине. Что именно сейчас запущено в кластере — знает только сам кластер
Долг третий — рискованные релизы. Rolling update из темы 5 не роняет сервис, но если новая версия работает и отвечает 200, а логика внутри сломана — обновление доедет до конца и сломанную версию получат все пользователи
Часть 1. Секреты: Vault
HashiCorp Vault — хранилище секретов: паролей, токенов, ключей, сертификатов. Работает как сервис с API, в который приложения ходят за своими секретами
Что он даёт помимо «положить пароль в надёжное место»:
- Шифрование при хранении. Данные лежат зашифрованными, ключ шифрования сам защищён и в открытом виде на диске не хранится
- Права доступа. Политики описывают, какое приложение к какому пути имеет доступ и на чтение или запись. Сервис
notesчитаетsecret/notes/*и не видит ничего другого - Аудит. Записывается каждое обращение: кто, когда, за каким секретом пришёл. При утечке видно, что именно скомпрометировано
- Динамические секреты. Самая ценная возможность. Vault сам создаёт в базе временного пользователя с паролем, выдаёт его приложению и удаляет через час. Постоянного пароля, который можно украсть, просто не существует
- Ротация. Пароли меняются автоматически, без похода к людям и правки конфигов
Как Vault устроен
Seal / Unseal (запечатан / распечатан). После запуска Vault запечатан: данные есть, но прочитать их нельзя даже ему самому. Мастер-ключ разделён на несколько частей (алгоритм Шамира), и чтобы распечатать хранилище, нужно собрать заданное число частей — например, 3 из 5. Идея в том, чтобы ни один человек в одиночку не мог получить доступ ко всем секретам компании. В облаке вместо ручной процедуры обычно используют auto-unseal через ключ облачного KMS
Движки секретов (secrets engines) — что именно Vault хранит или выдаёт:
kv— обычные пары «ключ-значение», версия 2 хранит историю измененийdatabase— динамические учётные записи для PostgreSQL, MySQL и другихpki— Vault сам работает удостоверяющим центром и выпускает TLS-сертификаты (вспомни тему 2)transit— шифрование как услуга: приложение отдаёт данные, получает зашифрованные, ключ никогда не покидает Vault
Методы аутентификации (auth methods) — как приложение доказывает, кто оно. Для Kubernetes используют метод kubernetes: под предъявляет свой ServiceAccount-токен, Vault проверяет его у API-сервера кластера и выдаёт свой токен с нужными правами. Пароля для доступа к паролям при этом не требуется — эта задача называется «проблемой нулевого секрета» и решается именно так
Как секрет попадает в под: три подхода
| Способ | Как работает | Когда выбирать |
|---|---|---|
| Vault Agent Injector | Sidecar-контейнер получает секрет и кладёт файлом в под | Нужны динамические секреты и обновление без перезапуска |
| External Secrets Operator | Оператор читает Vault и создаёт обычный Kubernetes Secret | Проще всего внедрить: приложение не знает про Vault вовсе |
| Sealed Secrets / SOPS | Секрет шифруется и в зашифрованном виде хранится в git | Когда Vault ставить не готовы, а секреты в git нужны |
Отдельно про последний вариант: SOPS шифрует значения в YAML-файле, оставляя ключи читаемыми — такой файл можно спокойно коммитить и осмысленно смотреть в Pull Request. Sealed Secrets работает похоже, но расшифровать может только контроллер в конкретном кластере. Оба решения дешевле Vault, но не дают ни динамических секретов, ни аудита
Часть 2. GitOps: Flux
Вернёмся ко второму долгу. Сейчас схема такая: человек берёт код, собирает, выполняет команду, кластер меняется. Проблемы очевидны: нужны права на кластер у каждого, кто катит; нет истории «кто и что выкатил»; ручная правка «на живую» никак не отслеживается; чтобы узнать, что запущено, надо идти в кластер и смотреть
GitOps переворачивает схему. Правила:
- Всё желаемое состояние системы описано в git. Манифесты, чарты, значения — всё
- Изменения вносятся только через git. Merge в основную ветку и есть развёртывание
- Агент внутри кластера сам забирает изменения. Не пайплайн стучится в кластер снаружи, а кластер тянет из репозитория. Значит, наружу не нужно открывать доступ к API-серверу и раздавать права
- Дрейф исправляется автоматически. Кто-то поправил ресурс руками — агент вернёт как записано в git
Получается ровно то же «желаемое состояние», что и в теме 5, но на уровень выше: желаемое состояние всего кластера лежит в репозитории
Flux — набор контроллеров, реализующих этот подход:
| Контроллер | Что делает |
|---|---|
source-controller |
Следит за источниками: git-репозиториями, Helm-репозиториями, бакетами |
kustomize-controller |
Применяет манифесты из источника в кластер |
helm-controller |
Устанавливает и обновляет Helm-релизы (объект HelmRelease) |
notification-controller |
Сообщает о результатах в Slack, Telegram, GitHub |
image-automation-controller |
Замечает новый образ в реестре и сам делает коммит с новой версией в git |
Последний пункт замыкает круг: пайплайн из темы 3 собрал образ notes:1.4.0 и запушил в реестр → Flux заметил новый тег → сделал коммит в репозиторий, поменяв версию → применил изменение в кластер. Никто не трогал кластер руками, но в git осталась полная история
Alternativa — Argo CD. Делает то же самое, отличается акцентами: у Argo CD сильный веб-интерфейс с наглядным деревом ресурсов, у Flux — более «родная» для Kubernetes архитектура из контроллеров и лучшая интеграция с Helm. Оба входят в CNCF и оба считаются зрелыми; выбор чаще определяется тем, нужна ли команде наглядная панель
Часть 3. Операторы Kubernetes
В теме 5 мы работали со встроенными типами объектов: Deployment, Service, ConfigMap. Но кластер можно научить новым типам
CRD (Custom Resource Definition) — описание собственного типа объекта. После его установки в кластере появляется, например, тип PostgresCluster, и kubectl get postgresclusters начинает работать так же, как kubectl get pods
Оператор — это CRD плюс контроллер, который знает, что с новым типом делать. Контроллер работает в цикле согласования (reconcile loop): смотрит желаемое состояние, смотрит фактическое, устраняет разницу. Ровно та же идея, что у всего Kubernetes, — просто теперь применённая к конкретной сложной системе
Смысл оператора в том, что он кодирует опыт эксплуатации. Развернуть PostgreSQL в кластере — это не только запустить контейнер: нужна репликация, резервные копии, переключение на реплику при отказе мастера, обновление версии без потери данных. Оператор содержит эти сценарии внутри, и вместо сотни манифестов и инструкции на десять страниц ты пишешь:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: notes-db
spec:
instances: 3 # три реплики с автоматическим переключением
storage:
size: 10Gi
backup:
barmanObjectStore:
destinationPath: s3://backups/notes-db
Известные операторы, которые встретятся в работе:
- Prometheus Operator — объекты
ServiceMonitorиPrometheusRule: добавить сервис в мониторинг из темы 8 становится вопросом одного манифеста - cert-manager — сам выпускает и обновляет TLS-сертификаты Let’s Encrypt (тема 2, но без ручного
certbot) - CloudNativePG, Zalando Postgres Operator — PostgreSQL с репликацией и резервным копированием
- Strimzi — Apache Kafka
- External Secrets Operator — тот самый мост между Vault и кластером
Свой оператор пишут редко и только для действительно нетривиальной внутренней системы; инструменты для этого — Operator SDK и Kubebuilder
Часть 4. Стратегии релизов
Третий долг. Rolling update из темы 5 хорош, но у него есть слепое пятно: он проверяет только то, что под отвечает на пробу. Логическая ошибка проедет до конца и достанется всем
| Стратегия | Как работает | Плюсы и минусы |
|---|---|---|
| Recreate | Погасить всё старое, поднять новое | Простая, но с простоем. Иногда неизбежна: например, при несовместимой миграции базы |
| Rolling update | Постепенная замена подов | Без простоя, стандарт по умолчанию. Не защищает от логических ошибок |
| Blue-Green | Рядом поднимается полная копия новой версии, трафик переключается разом | Мгновенный откат переключением обратно. Дорого: две полные копии одновременно |
| Canary (канареечный) | На новую версию пускают 5% трафика, смотрят метрики, постепенно увеличивают | Ошибку видит малая доля пользователей. Требует хорошего мониторинга |
| Feature flags | Код выкачен, но функция включается флагом для части пользователей | Развёртывание отделено от включения функции. Усложняет код |
Название «канареечный» — от канареек, которых шахтёры брали в забой: птица реагировала на газ раньше людей
Прогрессивная доставка (progressive delivery) — канареечный релиз, где решение принимает не человек, а автоматика по метрикам. Это и есть Flagger: он берёт метрики из Prometheus (тема 8), смотрит долю ошибок и задержку у канареечной версии и сам решает, увеличить ей долю трафика или откатить
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: notes
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: notes
analysis:
interval: 30s # как часто проверять метрики
threshold: 5 # сколько неудачных проверок подряд до отката
maxWeight: 50 # до какой доли трафика поднимать канарейку
stepWeight: 10 # шаг увеличения
metrics:
- name: request-success-rate
thresholdRange:
min: 99 # меньше 99% успешных ответов — откат
interval: 1m
- name: request-duration
thresholdRange:
max: 500 # медленнее 500 мс — откат
interval: 1m
Здесь сходится весь курс: метрики из темы 8 управляют релизом, описанным в git из темы 3, в кластере из темы 5, развёрнутом инфраструктурой из темы 7
Теоретические вопросы
-
Почему Secret в Kubernetes нельзя считать надёжным хранилищем пароля? Ответ: Его содержимое лишь закодировано base64 и по умолчанию не шифруется в etcd. Любой, у кого есть право читать секреты в пространстве имён, видит пароль открытым текстом, а история изменений и аудит обращений отсутствуют
-
Что такое seal / unseal в Vault и зачем такая сложность? Ответ: После запуска Vault запечатан и не может прочитать собственные данные. Мастер-ключ разделён на части, для распечатывания нужно собрать их заданное число (например, 3 из 5). Смысл — чтобы ни один человек в одиночку не имел доступа ко всем секретам компании
-
Что такое динамические секреты и чем они лучше статических? Ответ: Vault сам создаёт временную учётную запись в базе, выдаёт её приложению и удаляет по истечении срока. Постоянного пароля не существует, поэтому его нельзя украсть навсегда; при утечке ущерб ограничен временем жизни секрета
-
Как под доказывает Vault, что он — это он, без пароля? Ответ: Методом аутентификации
kubernetes: под предъявляет токен своего ServiceAccount, Vault проверяет его у API-сервера кластера и в ответ выдаёт свой токен с правами по политике. Так решается «проблема нулевого секрета» — секрет для доступа к секретам не нужен -
Чем External Secrets Operator отличается от Vault Agent Injector? Ответ: ESO читает Vault и создаёт обычный Kubernetes Secret — приложение не знает про Vault вовсе, внедрение проще. Injector добавляет к поду sidecar, который кладёт секрет файлом и обновляет его на лету; это нужно для динамических секретов, но требует изменения описания пода
-
Что такое SOPS и когда его достаточно? Ответ: Инструмент, шифрующий значения в YAML-файле с сохранением читаемых ключей. Такой файл можно хранить в git и осмысленно просматривать в Pull Request. Достаточен для небольших команд, где не нужны динамические секреты, ротация и аудит
-
Что такое GitOps и чем он отличается от обычного CI/CD? Ответ: В обычной схеме пайплайн снаружи стучится в кластер и применяет изменения. В GitOps агент внутри кластера сам забирает желаемое состояние из git. Наружу не нужно открывать API кластера, вся история изменений хранится в репозитории, а ручные правки автоматически откатываются
-
Что такое дрейф конфигурации и как GitOps его лечит? Ответ: Расхождение между тем, что записано в репозитории, и тем, что реально в кластере: кто-то поправил ресурс руками во время инцидента и забыл. Агент GitOps периодически сверяет состояния и возвращает описанное в git
-
Почему pull-модель безопаснее push-модели? Ответ: При push-модели у системы сборки должны быть постоянные права администратора кластера, а сам кластер должен быть доступен снаружи. При pull-модели агент работает внутри и ходит наружу только за чтением репозитория — прав на изменение кластера ни у кого снаружи нет
-
Из каких контроллеров состоит Flux и за что отвечает каждый? Ответ:
source-controllerследит за источниками (git, Helm-репозитории, бакеты),kustomize-controllerприменяет манифесты,helm-controllerуправляет Helm-релизами,notification-controllerрассылает уведомления,image-automation-controllerзамечает новые образы и коммитит обновление версии в git -
Чем Flux отличается от Argo CD? Ответ: Оба реализуют GitOps и входят в CNCF. У Argo CD сильный веб-интерфейс с наглядным деревом ресурсов, у Flux — архитектура из независимых контроллеров, ближе к идеологии Kubernetes, и более тесная работа с Helm. Выбор чаще определяется потребностью в панели управления
-
Что такое CRD? Ответ: Описание собственного типа объекта. После его установки новый тип работает наравне со встроенными: его можно создавать через
kubectl applyи смотреть черезkubectl get -
Что такое оператор и чем он отличается от Helm-чарта? Ответ: Helm-чарт разворачивает приложение один раз и на этом его работа заканчивается. Оператор — постоянно работающий контроллер, который дальше эксплуатирует систему: делает резервные копии, переключает мастера при отказе, обновляет версии. Чарт — установка, оператор — установка плюс эксплуатация
-
Что такое цикл согласования (reconcile loop)? Ответ: Бесконечный цикл: получить желаемое состояние, получить фактическое, устранить разницу. На нём построен весь Kubernetes, и операторы просто применяют тот же принцип к своей предметной области
-
Назови три известных оператора и задачи, которые они решают. Ответ: Prometheus Operator — управление мониторингом через объекты
ServiceMonitorиPrometheusRule; cert-manager — автоматический выпуск и обновление TLS-сертификатов; CloudNativePG — PostgreSQL с репликацией, резервным копированием и автоматическим переключением -
В чём разница между Blue-Green и Canary? Ответ: Blue-Green поднимает полную копию новой версии и переключает на неё весь трафик разом — откат мгновенный, но нужны двойные ресурсы. Canary пускает на новую версию малую долю трафика и постепенно увеличивает её, поэтому проблему видит небольшая часть пользователей, но нужен хороший мониторинг
-
Когда rolling update недостаточно? Ответ: Когда новая версия формально исправна (пробы проходят), но ведёт себя неправильно: выросла доля ошибок, упала конверсия, увеличилась задержка. Пробы этого не видят, поэтому нужен канареечный релиз с анализом метрик
-
Что такое прогрессивная доставка? Ответ: Канареечный релиз, в котором решение о продолжении или откате принимает автоматика по метрикам, а не человек. Реализуется, например, Flagger: он берёт метрики из Prometheus и сам управляет долей трафика
-
Почему для канареечного релиза обязателен мониторинг? Ответ: Решение принимается по метрикам: доля успешных ответов, задержка, бизнес-показатели. Без них «канареечный релиз» превращается в обычное обновление с ручным ожиданием, а вся идея — в автоматической проверке гипотезы «новая версия не хуже старой»
-
Что такое feature flag и как он связан с релизами? Ответ: Признак в конфигурации, включающий функцию для части пользователей. Позволяет отделить развёртывание кода от включения функциональности: код доехал до всех, но работает только там, где включён. Откат при этом не требует нового развёртывания
-
Как связаны git-репозиторий и состояние кластера в GitOps? Ответ: Репозиторий — единственный источник правды. Всё, что запущено, должно быть описано в нём; всё, что описано, должно быть запущено. Ответ на вопрос «что сейчас в проде» даёт
git log, а не поход в кластер
Вопросы с собеседований
Flux и GitOps на практике
-
Какие объекты создаёт Flux и как они связаны? Ответ: Источники —
GitRepository,HelmRepository,OCIRepository,Bucket: они только скачивают содержимое и проверяют, не изменилось ли оно. Применение —Kustomization(обычные манифесты) иHelmRelease(чарты); оба ссылаются на источник черезsourceRef. Отдельно живутImageRepository,ImagePolicyиImageUpdateAutomationдля обновления версий образов иAlert,Provider,Receiverдля уведомлений. Разделение на «скачать» и «применить» позволяет одному источнику питать несколько применений -
Что делает команда
flux bootstrap? Ответ: Три вещи сразу: устанавливает контроллеры Flux в кластер, коммитит их манифесты в указанный путь репозитория и настраивает Flux на слежение за этим путём. После неё Flux управляет в том числе собственным обновлением: чтобы обновить его версию, достаточно коммита в репозиторий -
Что произойдёт, если git станет недоступен? Ответ: Ничего страшного: приложения продолжают работать, кластер сохраняет последнее применённое состояние. Flux просто не сможет получить новые изменения и отметит источник как неготовый. Ничего не откатывается и не удаляется — распространённое заблуждение, что «упал git — упал прод»
-
Как Flux замечает и исправляет ручные правки? Ответ: Каждый интервал согласования он заново применяет манифесты из репозитория режимом server-side apply. Поля, которыми управляет Flux, возвращаются к описанным в git; изменённое вручную затирается. Полностью удалённые из репозитория ресурсы удаляются из кластера только при
prune: true— без него они остаются висеть -
Как задать порядок применения, если приложение зависит от базы? Ответ: Полем
dependsOnвKustomizationилиHelmRelease— следующий объект не применится, пока предыдущий не станет готов. ДополнительноhealthChecksсо списком ресурсов иwait: trueзаставляют Flux дождаться фактической готовности, а не просто факта применения манифеста -
Как в GitOps хранить секреты, если всё лежит в git? Ответ: Три рабочих варианта: SOPS — значения шифруются прямо в файле, а
kustomize-controllerрасшифровывает их при применении (decryption.provider: sops); Sealed Secrets — расшифровать может только контроллер конкретного кластера; External Secrets Operator — в git лежит лишь ссылка на секрет в Vault, а само значение никогда не попадает в репозиторий. Класть секрет открытым текстом нельзя ни при каких обстоятельствах -
Как организовать репозиторий под несколько окружений? Ответ: Распространённый подход — общая база манифестов и накладки Kustomize на каждое окружение (
base/иoverlays/dev,overlays/prod), а вclusters/<имя>/лежат объектыKustomization, указывающие на нужную накладку. Разница между окружениями сводится к нескольким строкам, а не к копии всех манифестов. Ветку на окружение обычно не заводят: сравнивать окружения через diff веток неудобно, и изменения начинают «терятся» при слияниях -
Приложение не обновляется после коммита — как искать причину? Ответ: По цепочке сверху вниз:
flux get sources git— скачался ли новый коммит;flux get kustomizations— применился ли он и что в колонке с сообщением;kubectl describe kustomization имя— подробная ошибка;flux logs --level=error. Частые причины: не тот путь или ветка в источнике, ошибка в манифесте, объект приостановлен черезflux suspend, не хватает прав у контроллера. Ускорить проверку, не дожидаясь интервала, —flux reconcile kustomization имя --with-source -
Зачем нужны
flux suspendиflux resume? Ответ:suspendвременно останавливает согласование объекта. Это единственный законный способ поправить что-то в кластере руками во время инцидента: иначе Flux вернёт всё обратно через минуту. После починки изменение переносят в git и делаютresume. Забытыйsuspend— классическая причина «почему мой коммит не приезжает» -
Чем
Kustomizationв Flux отличается отkustomization.yamlиз Kustomize? Ответ: Это разные вещи с похожим названием.kustomization.yaml— файл самого инструмента Kustomize, описывающий сборку манифестов.Kustomizationв Flux — объект Kubernetes, который говорит «возьми вот этот путь из вот этого источника и примени в кластер с такими параметрами». Второй использует первый, но объектом кластера является только он
Релизы и Flagger
-
Что Flagger делает с моим Deployment? Ответ: Берёт его под управление и создаёт рядом
-primary-копию, на которую и переводит рабочий трафик. Исходный Deployment становится канареечным и держится в нуле реплик, пока нет выкатки. При изменении образа канарейка поднимается, на неё постепенно переводят долю трафика, а по завершении анализа изменения переносятся в primary и канарейка снова сворачивается -
Что нужно, чтобы Flagger вообще заработал? Ответ: Два условия. Первое — чем разделять трафик: service mesh (Istio, Linkerd), Ingress-контроллер с поддержкой весов (NGINX, Traefik) или Gateway API. Второе — источник метрик, обычно Prometheus. Без разделения трафика невозможно отправить канарейке 10%, без метрик — принять решение
-
По каким метрикам принимается решение и можно ли добавить свои? Ответ: Встроенные — доля успешных ответов и длительность запроса. Свои задаются через
MetricTemplate: произвольный запрос PromQL с допустимым диапазоном. Именно так добавляют бизнес-проверки: доля успешных платежей, число ошибок в очереди. Технически исправная версия, которая роняет конверсию, отловится только такой метрикой -
Что такое webhooks в анализе Flagger? Ответ: Точки, где во время выкатки вызывается внешний сервис.
pre-rollout— проверка перед стартом (например, миграции применились),rollout— генерация нагрузки на канарейку, чтобы метрикам было на чём считаться,confirm-promotion— ручное подтверждение перед переводом всего трафика,post-rollout— уведомления. Без нагрузочного webhook на малопосещаемом сервисе анализ зависает: запросов слишком мало для статистики -
Что происходит при провале проверок? Ответ: Flagger возвращает трафик на primary, сворачивает канарейку в ноль реплик, помечает выкатку как failed и отправляет уведомление. Порог задаётся полем
threshold— сколько неудачных проверок подряд допустимо. Автоматический откат занимает секунды, а не время реакции дежурного -
Чем канареечный анализ отличается от A/B-тестирования в Flagger? Ответ: При канареечном режиме доля трафика растёт по весам и попадание случайно. В режиме A/B отбор идёт по условиям — заголовку или cookie, поэтому конкретный пользователь стабильно видит одну и ту же версию. Второй режим нужен, когда версия меняет интерфейс: случайное переключение между версиями посреди сессии выглядит как поломка
-
Как связаны Flux и Flagger? Ответ: Это разные инструменты одной экосистемы, работающие вместе. Flux доставляет изменение из git в кластер, Flagger решает, как безопасно перевести на него трафик. Flux меняет образ в Deployment — Flagger подхватывает изменение и начинает канареечную выкатку. Ни один не заменяет другой
Секреты и операторы
-
Как устроен жизненный цикл динамической учётной записи к базе? Ответ: Приложение просит доступ у Vault, тот по своим правам создаёт в базе нового пользователя с ограниченными привилегиями и сроком жизни, отдаёт логин и пароль приложению и запоминает аренду. Пока приложение работает, оно продлевает аренду; когда перестаёт — Vault сам удаляет пользователя из базы. Главное следствие: приложение должно уметь переподключаться с новыми данными, иначе после истечения срока оно отвалится
-
Что будет, если Vault станет недоступен? Ответ: Уже работающие приложения продолжат работать: секрет получен и лежит в памяти или в файле. Не смогут стартовать новые поды, не продлятся аренды динамических секретов и не пройдёт ротация. То есть Vault — критичный компонент, и его разворачивают в отказоустойчивом варианте, а не одним экземпляром
-
Что даёт движок transit? Ответ: Шифрование как услугу: приложение отправляет данные в Vault и получает зашифрованные, а ключ никогда не покидает хранилище. Приложению не нужно уметь работать с криптографией и хранить ключи, а ротация ключа не требует правок в коде. Так шифруют персональные данные перед записью в базу
-
Как менять пароль, не перезапуская приложение? Ответ: Секрет монтируют файлом, а не переменной окружения, и приложение перечитывает файл при изменении. Переменные окружения читаются один раз при старте — обновить их без перезапуска невозможно. Именно поэтому Vault Agent кладёт секрет файлом, а External Secrets Operator обновляет Secret, который смонтирован томом
-
Чем оператор отличается от контроллера? Ответ: Контроллер — общее понятие: цикл, приводящий фактическое состояние к желаемому; встроенные контроллеры Kubernetes управляют Deployment и другими стандартными объектами. Оператор — это контроллер, работающий со своим типом объекта (CRD) и содержащий знания об эксплуатации конкретной системы. Всякий оператор — контроллер, но не наоборот
-
Что такое finalizer? Ответ: Отметка на объекте, из-за которой Kubernetes не удаляет его сразу: объект помечается на удаление и ждёт, пока контроллер выполнит завершающие действия — снимет резервную копию, освободит внешний ресурс, отзовёт сертификат — и снимет отметку. Отсюда типичная авария: удалили оператор, а объекты «висят» в статусе Terminating навсегда, потому что снимать finalizer стало некому
-
Что такое admission-контроллеры и зачем нужны Kyverno или OPA? Ответ: Точки перехвата: перед сохранением объекта его можно проверить (validating) или изменить (mutating). Kyverno и OPA Gatekeeper позволяют описывать правила политиками: запретить образы с тегом
latest, требовать заданных ресурсов и меток, не пускать привилегированные контейнеры. Это дешевле, чем ловить нарушения ревью и напоминаниями -
Стоит ли писать свой оператор? Ответ: Обычно нет. Сначала проверяют, нет ли готового, и не решается ли задача Helm-чартом плюс CronJob. Свой оператор оправдан, когда есть нетривиальная логика эксплуатации внутренней системы, которую иначе приходится держать в головах дежурных. Цена — полноценный сервис, который нужно писать, тестировать и обновлять
-
Как выкатывать изменения, ломающие совместимость с базой? Ответ: В два этапа. Сначала изменение, совместимое со старым кодом: добавили колонку, но старая версия её не знает и продолжает работать. Выкатили новый код. Затем отдельным релизом убрали устаревшее. Правило «сначала расширить, потом сузить» позволяет откатиться в любой момент — иначе откат кода упрётся в изменённую схему и станет невозможным
-
Что не так с флагами функциональности, если их не убирать? Ответ: Каждый флаг — это ветвление в коде и два пути, которые нужно тестировать; десяток флагов даёт комбинаторику, которую никто не проверяет целиком. Поэтому у флага должен быть срок жизни и владелец: включили функцию всем — удалили флаг вместе со старой веткой кода. Иначе технический долг растёт быстрее пользы
-
Как понять, что GitOps действительно внедрён, а не «мы поставили Flux»? Ответ: По трём проверкам. Первая: можно ли ответить на вопрос «что сейчас в проде» через
git log, не заходя в кластер. Вторая: есть ли у людей права менять кластер напрямую — в зрелой схеме их нет. Третья: что произойдёт, если удалить кластер целиком — при настоящем GitOps он восстанавливается из репозитория за понятное время, и это регулярно проверяют
Практическая часть
Понадобится кластер из темы 5 (minikube или kind) и репозиторий «Заметок» из темы 3
Задание 1. Vault: положить и забрать секрет
Цель: понять модель работы Vault на самом простом сценарии
Шаги:
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Режим dev: Vault сразу распечатан и хранит данные в памяти.
# Только для обучения — при перезапуске всё пропадёт
helm install vault hashicorp/vault \
--set "server.dev.enabled=true" \
--set "injector.enabled=true"
kubectl get pods -l app.kubernetes.io/name=vault -w
Зайди внутрь пода и поработай с секретами:
kubectl exec -it vault-0 -- sh
export VAULT_TOKEN=root
vault status
# Записать секрет
vault kv put secret/notes db_password="НастоящийСложныйПароль123" api_key="abc123"
# Прочитать
vault kv get secret/notes
vault kv get -field=db_password secret/notes
# Версионирование: перезаписываем и смотрим историю
vault kv put secret/notes db_password="НовыйПароль456"
vault kv get -version=1 secret/notes
exit
Ожидаемый вывод: первая версия секрета доступна даже после перезаписи. Это KV версии 2 — он хранит историю, чем принципиально отличается от Kubernetes Secret
Типичные ошибки:
- Использовать
server.dev.enabled=trueгде-либо, кроме учёбы: хранилище в памяти, единый токенroot, всё распечатано. В настоящем кластере ставят обычный режим и настраивают auto-unseal
Задание 2. Политика доступа: приложение видит только своё
Цель: увидеть работу принципа минимальных привилегий
Шаги: внутри пода Vault:
kubectl exec -it vault-0 -- sh
export VAULT_TOKEN=root
# Политика: разрешено только читать свой путь
cat <<'EOF' > /tmp/notes-policy.hcl
path "secret/data/notes" {
capabilities = ["read"]
}
EOF
vault policy write notes /tmp/notes-policy.hcl
# Секрет чужого сервиса — «Заметки» не должны его видеть
vault kv put secret/billing card_key="очень-важный-ключ"
# Выпускаем токен с этой политикой и проверяем границы
NOTES_TOKEN=$(vault token create -policy=notes -field=token)
VAULT_TOKEN=$NOTES_TOKEN vault kv get secret/notes # получится
VAULT_TOKEN=$NOTES_TOKEN vault kv get secret/billing # permission denied
VAULT_TOKEN=$NOTES_TOKEN vault kv put secret/notes x=1 # permission denied — только чтение
exit
Ожидаемый вывод: два отказа с формулировкой permission denied. Так и должно быть: даже украденный токен приложения даёт доступ только к его собственному секрету и только на чтение
Задание 3. Сквозной проект: убрать пароль из репозитория
Цель: закрыть долг, оставленный в теме 4
Шаги:
-
Настрой аутентификацию Kubernetes в Vault:
kubectl exec -it vault-0 -- sh export VAULT_TOKEN=root vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" # Связываем ServiceAccount приложения с политикой vault write auth/kubernetes/role/notes \ bound_service_account_names=notes \ bound_service_account_namespaces=default \ policies=notes \ ttl=1h exit -
Создай ServiceAccount для приложения:
kubectl create serviceaccount notes -
Добавь в
Deploymentаннотации для Vault Agent Injector — секрет приедет файлом:spec: template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "notes" # Секрет окажется в файле /vault/secrets/db внутри пода vault.hashicorp.com/agent-inject-secret-db: "secret/data/notes" spec: serviceAccountName: notes -
Применяй и проверяй:
kubectl apply -f k8s/deployment.yaml kubectl get pods -l app=notes # теперь в поде 2/2: приложение и sidecar Vault kubectl exec deploy/notes -c notes -- cat /vault/secrets/db -
Убери пароль из репозитория:
grep -rn "notes_dev_password" . --include="*.yml" --include="*.yaml" # замени значение на чтение из файла /vault/secrets/db git add -A git commit -m "Убрать пароль базы из репозитория, брать из Vault" git pushПомни из темы 3: старый пароль остаётся в истории git навсегда. В реальной ситуации его после этого обязательно меняют — считай, что ты это сделал
Ожидаемый вывод: в поде появился второй контейнер, файл /vault/secrets/db содержит секрет, а в репозитории пароля больше нет
Типичные ошибки:
- Под остаётся в состоянии
Init— sidecar не может получить секрет. Смотриkubectl logs имя -c vault-agent-init: обычно не совпадает имя роли, ServiceAccount или пространство имён в настройке роли - Забыть
serviceAccountName— под пойдёт с учётной записьюdefault, и Vault откажет
Задание 4. Flux: развернуть GitOps
Цель: сделать так, чтобы кластер сам следил за репозиторием
Шаги:
-
Установи Flux и проверь готовность кластера:
curl -s https://fluxcd.io/install.sh | sudo bash flux check --pre -
Создай на GitHub токен с правами на репозитории (Settings → Developer settings → Personal access tokens) и подключи кластер к репозиторию:
export GITHUB_TOKEN=<твой_токен> export GITHUB_USER=<твой_логин> flux bootstrap github \ --owner=$GITHUB_USER \ --repository=notes \ --branch=main \ --path=./clusters/dev \ --personalКоманда сама создаст каталог
clusters/dev, закоммитит туда описание самого Flux и настроит кластер на слежение за этим путём -
Посмотри, что получилось:
flux get all kubectl get pods -n flux-system git pull # Flux сделал коммиты в твой репозиторий ls clusters/dev/flux-system/ -
Отдай «Заметки» под управление Flux. Создай
clusters/dev/notes.yaml:apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: notes namespace: flux-system spec: interval: 1m # как часто сверять состояние path: ./k8s # где в репозитории лежат манифесты приложения prune: true # удалённое из git удалять и из кластера sourceRef: kind: GitRepository name: flux-systemgit add clusters/dev/notes.yaml git commit -m "Отдать «Заметки» под управление Flux" git push flux reconcile kustomization flux-system --with-source # не ждать минуту flux get kustomizations
Ожидаемый вывод: flux get kustomizations показывает notes со статусом Applied revision: main@sha1:...
Задание 5. Главный эксперимент: попробуй сломать вручную
Цель: увидеть автоматическое исправление дрейфа
Шаги:
# 1. Меняем число реплик руками, «как во время инцидента»
kubectl scale deployment notes --replicas=10
kubectl get pods -l app=notes | wc -l
# 2. Ждём до минуты и смотрим снова
sleep 70
kubectl get deployment notes
Ожидаемый вывод: реплик снова три — столько, сколько записано в манифесте в git. Flux заметил расхождение и вернул как должно быть
# 3. Удаляем приложение целиком
kubectl delete deployment notes
sleep 70
kubectl get deployment notes
Ожидаемый вывод: Deployment вернулся. Кластер держит то состояние, которое описано в репозитории, а не то, которое кто-то задал последней командой
# 4. Теперь меняем правильно — через git
sed -i 's/replicas: 3/replicas: 5/' k8s/deployment.yaml
git commit -am "Увеличить число реплик до пяти"
git push
flux reconcile kustomization notes --with-source
kubectl get deployment notes
Ожидаемый вывод: пять реплик, и в git log видно, кто и когда это решил. Сравни с kubectl scale, после которого не остаётся никаких следов
Ответь письменно: почему в схеме GitOps разработчику вообще не нужны права на изменение кластера? Кому и какие права остаются нужны?
Задание 6. Автоматическое обновление образа
Цель: замкнуть цепочку от сборки до развёртывания
Шаги:
-
Включи автоматизацию образов при установке Flux (если не включал — переустанови с флагом):
flux bootstrap github \ --owner=$GITHUB_USER --repository=notes --branch=main \ --path=./clusters/dev --personal \ --components-extra=image-reflector-controller,image-automation-controller -
Опиши, за каким образом следить и какие теги считать подходящими:
flux create image repository notes \ --image=<твой_логин>/notes --interval=1m --export > clusters/dev/notes-image.yaml flux create image policy notes \ --image-ref=notes --select-semver='>=1.0.0' --export >> clusters/dev/notes-image.yaml flux create image update notes \ --git-repo-ref=flux-system --checkout-branch=main --push-branch=main \ --author-name=fluxcd --author-email=fluxcd@users.noreply.github.com \ --interval=1m --export >> clusters/dev/notes-image.yaml -
Пометь в манифесте строку, которую Flux имеет право менять:
image: твой_логин/notes:1.0.0 # {"$imagepolicy": "flux-system:notes"} -
Собери и запушь версию
1.1.0в реестр — и просто подожди:git log --oneline -5 # через пару минут появится коммит от fluxcd kubectl get deployment notes -o jsonpath='{..image}'
Ожидаемый вывод: коммит, сделанный не тобой, а автоматикой, и новая версия в кластере. Полная цепочка: пайплайн собрал образ → Flux заметил → закоммитил → применил
Задание 7. Канареечный релиз
Цель: выкатить версию так, чтобы ошибку увидели 10% пользователей, а не все
Шаги:
-
Установи Flagger (нужен Prometheus из темы 8 и Ingress-контроллер из темы 5):
helm repo add flagger https://flagger.app helm install flagger flagger/flagger \ --namespace flux-system \ --set meshProvider=nginx \ --set metricsServer=http://prometheus:9090 -
Создай объект
Canaryиз примера в теоретической части и примени его -
Выкати заведомо исправную версию и наблюдай:
kubectl set image deployment/notes notes=notes:2.0 kubectl -n default describe canary notes | tail -20Ожидаемый вывод: в событиях видно, как доля трафика растёт: 10% → 20% → 30% → … → продвижение завершено
-
Теперь сломай специально: собери версию, которая отвечает кодом 500 на каждый третий запрос, и выкати её
Ожидаемый вывод: доля успешных ответов падает ниже порога, Flagger останавливает продвижение и откатывает релиз сам. В событиях будет
Rolling back notes.default failed checks threshold reachedПользователи при этом видели ошибки только в те минуты, пока на канарейку шло 10% трафика
Типичные ошибки:
- Flagger не двигается дальше первого шага — нет метрик. Проверь, что Prometheus доступен по адресу из
metricsServerи действительно собирает метрики приложения - Порог поставлен нереалистично (например,
min: 100) — любой единичный сбой откатит нормальный релиз
Чек-лист зрелой эксплуатации
- Ни одного пароля, токена или ключа в репозитории — даже в истории
- Каждое приложение имеет доступ только к своим секретам
- Секреты ротируются автоматически, а не «когда вспомним»
- Состояние кластера полностью описано в git
- Изменения в прод идут только через Pull Request
- Ручные правки в кластере автоматически откатываются
- Ответ на вопрос «что сейчас в проде» даёт
git log - Права на изменение кластера есть у агента GitOps, а не у людей и не у системы сборки
- Критичные сервисы выкатываются канареечно с автоматическим анализом метрик
- Откат — одна команда или один revert-коммит, а не героические усилия
Проверь себя: тема освоена, если ты можешь
- Объяснить, почему Kubernetes Secret не защищает пароль, и назвать три способа это исправить
- Объяснить, как под доказывает Vault свою личность без пароля
- Сформулировать четыре принципа GitOps
- Объяснить, почему pull-модель безопаснее push-модели
- Показать, как кластер сам откатывает ручное изменение
- Объяснить разницу между Helm-чартом и оператором
- Выбрать стратегию релиза под задачу и обосновать выбор
- Объяснить, почему канареечный релиз бесполезен без мониторинга
Курс пройден. Что дальше
Оглянись на путь: в теме 1 ты запускал python3 app.py в терминале. Сейчас то же самое приложение живёт в кластере в нескольких копиях, разворачивается кодом, деплоится из git без участия человека, отдаёт метрики, защищено сертификатом, не хранит пароли в открытом виде и умеет откатываться само
Куда идти дальше:
- Пройди технические задания. Для инженера мониторинга и для SRE — это проверка в условиях, приближенных к рабочим
- Углубись в надёжность. SRE-практики: бюджеты ошибок, разборы инцидентов без поиска виноватых, дежурства
- Углубись в безопасность. Проверка образов на уязвимости, политики допуска в кластер, сетевые политики, цепочка поставки
- Возьми настоящую задачу. Разверни что-нибудь нужное себе или знакомым и веди это как настоящий сервис: с мониторингом, резервными копиями и дежурством. Опыт эксплуатации не заменяется чтением
Сохрани репозиторий «Заметок» — на собеседовании он покажет больше, чем список технологий в резюме