Skip to the content.

Вернуться к главной странице, списку всех тем

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, в который приложения ходят за своими секретами

Что он даёт помимо «положить пароль в надёжное место»:

Как Vault устроен

Seal / Unseal (запечатан / распечатан). После запуска Vault запечатан: данные есть, но прочитать их нельзя даже ему самому. Мастер-ключ разделён на несколько частей (алгоритм Шамира), и чтобы распечатать хранилище, нужно собрать заданное число частей — например, 3 из 5. Идея в том, чтобы ни один человек в одиночку не мог получить доступ ко всем секретам компании. В облаке вместо ручной процедуры обычно используют auto-unseal через ключ облачного KMS

Движки секретов (secrets engines) — что именно 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 переворачивает схему. Правила:

  1. Всё желаемое состояние системы описано в git. Манифесты, чарты, значения — всё
  2. Изменения вносятся только через git. Merge в основную ветку и есть развёртывание
  3. Агент внутри кластера сам забирает изменения. Не пайплайн стучится в кластер снаружи, а кластер тянет из репозитория. Значит, наружу не нужно открывать доступ к API-серверу и раздавать права
  4. Дрейф исправляется автоматически. Кто-то поправил ресурс руками — агент вернёт как записано в 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

Известные операторы, которые встретятся в работе:

Свой оператор пишут редко и только для действительно нетривиальной внутренней системы; инструменты для этого — 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

Теоретические вопросы

  1. Почему Secret в Kubernetes нельзя считать надёжным хранилищем пароля? Ответ: Его содержимое лишь закодировано base64 и по умолчанию не шифруется в etcd. Любой, у кого есть право читать секреты в пространстве имён, видит пароль открытым текстом, а история изменений и аудит обращений отсутствуют

  2. Что такое seal / unseal в Vault и зачем такая сложность? Ответ: После запуска Vault запечатан и не может прочитать собственные данные. Мастер-ключ разделён на части, для распечатывания нужно собрать их заданное число (например, 3 из 5). Смысл — чтобы ни один человек в одиночку не имел доступа ко всем секретам компании

  3. Что такое динамические секреты и чем они лучше статических? Ответ: Vault сам создаёт временную учётную запись в базе, выдаёт её приложению и удаляет по истечении срока. Постоянного пароля не существует, поэтому его нельзя украсть навсегда; при утечке ущерб ограничен временем жизни секрета

  4. Как под доказывает Vault, что он — это он, без пароля? Ответ: Методом аутентификации kubernetes: под предъявляет токен своего ServiceAccount, Vault проверяет его у API-сервера кластера и в ответ выдаёт свой токен с правами по политике. Так решается «проблема нулевого секрета» — секрет для доступа к секретам не нужен

  5. Чем External Secrets Operator отличается от Vault Agent Injector? Ответ: ESO читает Vault и создаёт обычный Kubernetes Secret — приложение не знает про Vault вовсе, внедрение проще. Injector добавляет к поду sidecar, который кладёт секрет файлом и обновляет его на лету; это нужно для динамических секретов, но требует изменения описания пода

  6. Что такое SOPS и когда его достаточно? Ответ: Инструмент, шифрующий значения в YAML-файле с сохранением читаемых ключей. Такой файл можно хранить в git и осмысленно просматривать в Pull Request. Достаточен для небольших команд, где не нужны динамические секреты, ротация и аудит

  7. Что такое GitOps и чем он отличается от обычного CI/CD? Ответ: В обычной схеме пайплайн снаружи стучится в кластер и применяет изменения. В GitOps агент внутри кластера сам забирает желаемое состояние из git. Наружу не нужно открывать API кластера, вся история изменений хранится в репозитории, а ручные правки автоматически откатываются

  8. Что такое дрейф конфигурации и как GitOps его лечит? Ответ: Расхождение между тем, что записано в репозитории, и тем, что реально в кластере: кто-то поправил ресурс руками во время инцидента и забыл. Агент GitOps периодически сверяет состояния и возвращает описанное в git

  9. Почему pull-модель безопаснее push-модели? Ответ: При push-модели у системы сборки должны быть постоянные права администратора кластера, а сам кластер должен быть доступен снаружи. При pull-модели агент работает внутри и ходит наружу только за чтением репозитория — прав на изменение кластера ни у кого снаружи нет

  10. Из каких контроллеров состоит Flux и за что отвечает каждый? Ответ: source-controller следит за источниками (git, Helm-репозитории, бакеты), kustomize-controller применяет манифесты, helm-controller управляет Helm-релизами, notification-controller рассылает уведомления, image-automation-controller замечает новые образы и коммитит обновление версии в git

  11. Чем Flux отличается от Argo CD? Ответ: Оба реализуют GitOps и входят в CNCF. У Argo CD сильный веб-интерфейс с наглядным деревом ресурсов, у Flux — архитектура из независимых контроллеров, ближе к идеологии Kubernetes, и более тесная работа с Helm. Выбор чаще определяется потребностью в панели управления

  12. Что такое CRD? Ответ: Описание собственного типа объекта. После его установки новый тип работает наравне со встроенными: его можно создавать через kubectl apply и смотреть через kubectl get

  13. Что такое оператор и чем он отличается от Helm-чарта? Ответ: Helm-чарт разворачивает приложение один раз и на этом его работа заканчивается. Оператор — постоянно работающий контроллер, который дальше эксплуатирует систему: делает резервные копии, переключает мастера при отказе, обновляет версии. Чарт — установка, оператор — установка плюс эксплуатация

  14. Что такое цикл согласования (reconcile loop)? Ответ: Бесконечный цикл: получить желаемое состояние, получить фактическое, устранить разницу. На нём построен весь Kubernetes, и операторы просто применяют тот же принцип к своей предметной области

  15. Назови три известных оператора и задачи, которые они решают. Ответ: Prometheus Operator — управление мониторингом через объекты ServiceMonitor и PrometheusRule; cert-manager — автоматический выпуск и обновление TLS-сертификатов; CloudNativePG — PostgreSQL с репликацией, резервным копированием и автоматическим переключением

  16. В чём разница между Blue-Green и Canary? Ответ: Blue-Green поднимает полную копию новой версии и переключает на неё весь трафик разом — откат мгновенный, но нужны двойные ресурсы. Canary пускает на новую версию малую долю трафика и постепенно увеличивает её, поэтому проблему видит небольшая часть пользователей, но нужен хороший мониторинг

  17. Когда rolling update недостаточно? Ответ: Когда новая версия формально исправна (пробы проходят), но ведёт себя неправильно: выросла доля ошибок, упала конверсия, увеличилась задержка. Пробы этого не видят, поэтому нужен канареечный релиз с анализом метрик

  18. Что такое прогрессивная доставка? Ответ: Канареечный релиз, в котором решение о продолжении или откате принимает автоматика по метрикам, а не человек. Реализуется, например, Flagger: он берёт метрики из Prometheus и сам управляет долей трафика

  19. Почему для канареечного релиза обязателен мониторинг? Ответ: Решение принимается по метрикам: доля успешных ответов, задержка, бизнес-показатели. Без них «канареечный релиз» превращается в обычное обновление с ручным ожиданием, а вся идея — в автоматической проверке гипотезы «новая версия не хуже старой»

  20. Что такое feature flag и как он связан с релизами? Ответ: Признак в конфигурации, включающий функцию для части пользователей. Позволяет отделить развёртывание кода от включения функциональности: код доехал до всех, но работает только там, где включён. Откат при этом не требует нового развёртывания

  21. Как связаны git-репозиторий и состояние кластера в GitOps? Ответ: Репозиторий — единственный источник правды. Всё, что запущено, должно быть описано в нём; всё, что описано, должно быть запущено. Ответ на вопрос «что сейчас в проде» даёт git log, а не поход в кластер

Вопросы с собеседований

Flux и GitOps на практике

  1. Какие объекты создаёт Flux и как они связаны? Ответ: Источники — GitRepository, HelmRepository, OCIRepository, Bucket: они только скачивают содержимое и проверяют, не изменилось ли оно. Применение — Kustomization (обычные манифесты) и HelmRelease (чарты); оба ссылаются на источник через sourceRef. Отдельно живут ImageRepository, ImagePolicy и ImageUpdateAutomation для обновления версий образов и Alert, Provider, Receiver для уведомлений. Разделение на «скачать» и «применить» позволяет одному источнику питать несколько применений

  2. Что делает команда flux bootstrap? Ответ: Три вещи сразу: устанавливает контроллеры Flux в кластер, коммитит их манифесты в указанный путь репозитория и настраивает Flux на слежение за этим путём. После неё Flux управляет в том числе собственным обновлением: чтобы обновить его версию, достаточно коммита в репозиторий

  3. Что произойдёт, если git станет недоступен? Ответ: Ничего страшного: приложения продолжают работать, кластер сохраняет последнее применённое состояние. Flux просто не сможет получить новые изменения и отметит источник как неготовый. Ничего не откатывается и не удаляется — распространённое заблуждение, что «упал git — упал прод»

  4. Как Flux замечает и исправляет ручные правки? Ответ: Каждый интервал согласования он заново применяет манифесты из репозитория режимом server-side apply. Поля, которыми управляет Flux, возвращаются к описанным в git; изменённое вручную затирается. Полностью удалённые из репозитория ресурсы удаляются из кластера только при prune: true — без него они остаются висеть

  5. Как задать порядок применения, если приложение зависит от базы? Ответ: Полем dependsOn в Kustomization или HelmRelease — следующий объект не применится, пока предыдущий не станет готов. Дополнительно healthChecks со списком ресурсов и wait: true заставляют Flux дождаться фактической готовности, а не просто факта применения манифеста

  6. Как в GitOps хранить секреты, если всё лежит в git? Ответ: Три рабочих варианта: SOPS — значения шифруются прямо в файле, а kustomize-controller расшифровывает их при применении (decryption.provider: sops); Sealed Secrets — расшифровать может только контроллер конкретного кластера; External Secrets Operator — в git лежит лишь ссылка на секрет в Vault, а само значение никогда не попадает в репозиторий. Класть секрет открытым текстом нельзя ни при каких обстоятельствах

  7. Как организовать репозиторий под несколько окружений? Ответ: Распространённый подход — общая база манифестов и накладки Kustomize на каждое окружение (base/ и overlays/dev, overlays/prod), а в clusters/<имя>/ лежат объекты Kustomization, указывающие на нужную накладку. Разница между окружениями сводится к нескольким строкам, а не к копии всех манифестов. Ветку на окружение обычно не заводят: сравнивать окружения через diff веток неудобно, и изменения начинают «терятся» при слияниях

  8. Приложение не обновляется после коммита — как искать причину? Ответ: По цепочке сверху вниз: flux get sources git — скачался ли новый коммит; flux get kustomizations — применился ли он и что в колонке с сообщением; kubectl describe kustomization имя — подробная ошибка; flux logs --level=error. Частые причины: не тот путь или ветка в источнике, ошибка в манифесте, объект приостановлен через flux suspend, не хватает прав у контроллера. Ускорить проверку, не дожидаясь интервала, — flux reconcile kustomization имя --with-source

  9. Зачем нужны flux suspend и flux resume? Ответ: suspend временно останавливает согласование объекта. Это единственный законный способ поправить что-то в кластере руками во время инцидента: иначе Flux вернёт всё обратно через минуту. После починки изменение переносят в git и делают resume. Забытый suspend — классическая причина «почему мой коммит не приезжает»

  10. Чем Kustomization в Flux отличается от kustomization.yaml из Kustomize? Ответ: Это разные вещи с похожим названием. kustomization.yaml — файл самого инструмента Kustomize, описывающий сборку манифестов. Kustomization в Flux — объект Kubernetes, который говорит «возьми вот этот путь из вот этого источника и примени в кластер с такими параметрами». Второй использует первый, но объектом кластера является только он

Релизы и Flagger

  1. Что Flagger делает с моим Deployment? Ответ: Берёт его под управление и создаёт рядом -primary-копию, на которую и переводит рабочий трафик. Исходный Deployment становится канареечным и держится в нуле реплик, пока нет выкатки. При изменении образа канарейка поднимается, на неё постепенно переводят долю трафика, а по завершении анализа изменения переносятся в primary и канарейка снова сворачивается

  2. Что нужно, чтобы Flagger вообще заработал? Ответ: Два условия. Первое — чем разделять трафик: service mesh (Istio, Linkerd), Ingress-контроллер с поддержкой весов (NGINX, Traefik) или Gateway API. Второе — источник метрик, обычно Prometheus. Без разделения трафика невозможно отправить канарейке 10%, без метрик — принять решение

  3. По каким метрикам принимается решение и можно ли добавить свои? Ответ: Встроенные — доля успешных ответов и длительность запроса. Свои задаются через MetricTemplate: произвольный запрос PromQL с допустимым диапазоном. Именно так добавляют бизнес-проверки: доля успешных платежей, число ошибок в очереди. Технически исправная версия, которая роняет конверсию, отловится только такой метрикой

  4. Что такое webhooks в анализе Flagger? Ответ: Точки, где во время выкатки вызывается внешний сервис. pre-rollout — проверка перед стартом (например, миграции применились), rollout — генерация нагрузки на канарейку, чтобы метрикам было на чём считаться, confirm-promotion — ручное подтверждение перед переводом всего трафика, post-rollout — уведомления. Без нагрузочного webhook на малопосещаемом сервисе анализ зависает: запросов слишком мало для статистики

  5. Что происходит при провале проверок? Ответ: Flagger возвращает трафик на primary, сворачивает канарейку в ноль реплик, помечает выкатку как failed и отправляет уведомление. Порог задаётся полем threshold — сколько неудачных проверок подряд допустимо. Автоматический откат занимает секунды, а не время реакции дежурного

  6. Чем канареечный анализ отличается от A/B-тестирования в Flagger? Ответ: При канареечном режиме доля трафика растёт по весам и попадание случайно. В режиме A/B отбор идёт по условиям — заголовку или cookie, поэтому конкретный пользователь стабильно видит одну и ту же версию. Второй режим нужен, когда версия меняет интерфейс: случайное переключение между версиями посреди сессии выглядит как поломка

  7. Как связаны Flux и Flagger? Ответ: Это разные инструменты одной экосистемы, работающие вместе. Flux доставляет изменение из git в кластер, Flagger решает, как безопасно перевести на него трафик. Flux меняет образ в Deployment — Flagger подхватывает изменение и начинает канареечную выкатку. Ни один не заменяет другой

Секреты и операторы

  1. Как устроен жизненный цикл динамической учётной записи к базе? Ответ: Приложение просит доступ у Vault, тот по своим правам создаёт в базе нового пользователя с ограниченными привилегиями и сроком жизни, отдаёт логин и пароль приложению и запоминает аренду. Пока приложение работает, оно продлевает аренду; когда перестаёт — Vault сам удаляет пользователя из базы. Главное следствие: приложение должно уметь переподключаться с новыми данными, иначе после истечения срока оно отвалится

  2. Что будет, если Vault станет недоступен? Ответ: Уже работающие приложения продолжат работать: секрет получен и лежит в памяти или в файле. Не смогут стартовать новые поды, не продлятся аренды динамических секретов и не пройдёт ротация. То есть Vault — критичный компонент, и его разворачивают в отказоустойчивом варианте, а не одним экземпляром

  3. Что даёт движок transit? Ответ: Шифрование как услугу: приложение отправляет данные в Vault и получает зашифрованные, а ключ никогда не покидает хранилище. Приложению не нужно уметь работать с криптографией и хранить ключи, а ротация ключа не требует правок в коде. Так шифруют персональные данные перед записью в базу

  4. Как менять пароль, не перезапуская приложение? Ответ: Секрет монтируют файлом, а не переменной окружения, и приложение перечитывает файл при изменении. Переменные окружения читаются один раз при старте — обновить их без перезапуска невозможно. Именно поэтому Vault Agent кладёт секрет файлом, а External Secrets Operator обновляет Secret, который смонтирован томом

  5. Чем оператор отличается от контроллера? Ответ: Контроллер — общее понятие: цикл, приводящий фактическое состояние к желаемому; встроенные контроллеры Kubernetes управляют Deployment и другими стандартными объектами. Оператор — это контроллер, работающий со своим типом объекта (CRD) и содержащий знания об эксплуатации конкретной системы. Всякий оператор — контроллер, но не наоборот

  6. Что такое finalizer? Ответ: Отметка на объекте, из-за которой Kubernetes не удаляет его сразу: объект помечается на удаление и ждёт, пока контроллер выполнит завершающие действия — снимет резервную копию, освободит внешний ресурс, отзовёт сертификат — и снимет отметку. Отсюда типичная авария: удалили оператор, а объекты «висят» в статусе Terminating навсегда, потому что снимать finalizer стало некому

  7. Что такое admission-контроллеры и зачем нужны Kyverno или OPA? Ответ: Точки перехвата: перед сохранением объекта его можно проверить (validating) или изменить (mutating). Kyverno и OPA Gatekeeper позволяют описывать правила политиками: запретить образы с тегом latest, требовать заданных ресурсов и меток, не пускать привилегированные контейнеры. Это дешевле, чем ловить нарушения ревью и напоминаниями

  8. Стоит ли писать свой оператор? Ответ: Обычно нет. Сначала проверяют, нет ли готового, и не решается ли задача Helm-чартом плюс CronJob. Свой оператор оправдан, когда есть нетривиальная логика эксплуатации внутренней системы, которую иначе приходится держать в головах дежурных. Цена — полноценный сервис, который нужно писать, тестировать и обновлять

  9. Как выкатывать изменения, ломающие совместимость с базой? Ответ: В два этапа. Сначала изменение, совместимое со старым кодом: добавили колонку, но старая версия её не знает и продолжает работать. Выкатили новый код. Затем отдельным релизом убрали устаревшее. Правило «сначала расширить, потом сузить» позволяет откатиться в любой момент — иначе откат кода упрётся в изменённую схему и станет невозможным

  10. Что не так с флагами функциональности, если их не убирать? Ответ: Каждый флаг — это ветвление в коде и два пути, которые нужно тестировать; десяток флагов даёт комбинаторику, которую никто не проверяет целиком. Поэтому у флага должен быть срок жизни и владелец: включили функцию всем — удалили флаг вместе со старой веткой кода. Иначе технический долг растёт быстрее пользы

  11. Как понять, что 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

Типичные ошибки:

Задание 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

Шаги:

  1. Настрой аутентификацию 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
    
  2. Создай ServiceAccount для приложения:

    kubectl create serviceaccount notes
    
  3. Добавь в 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
    
  4. Применяй и проверяй:

    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
    
  5. Убери пароль из репозитория:

    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 содержит секрет, а в репозитории пароля больше нет

Типичные ошибки:

Задание 4. Flux: развернуть GitOps

Цель: сделать так, чтобы кластер сам следил за репозиторием

Шаги:

  1. Установи Flux и проверь готовность кластера:

    curl -s https://fluxcd.io/install.sh | sudo bash
    flux check --pre
    
  2. Создай на 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 и настроит кластер на слежение за этим путём

  3. Посмотри, что получилось:

    flux get all
    kubectl get pods -n flux-system
    git pull                             # Flux сделал коммиты в твой репозиторий
    ls clusters/dev/flux-system/
    
  4. Отдай «Заметки» под управление 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-system
    
    git 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. Автоматическое обновление образа

Цель: замкнуть цепочку от сборки до развёртывания

Шаги:

  1. Включи автоматизацию образов при установке Flux (если не включал — переустанови с флагом):

    flux bootstrap github \
      --owner=$GITHUB_USER --repository=notes --branch=main \
      --path=./clusters/dev --personal \
      --components-extra=image-reflector-controller,image-automation-controller
    
  2. Опиши, за каким образом следить и какие теги считать подходящими:

    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
    
  3. Пометь в манифесте строку, которую Flux имеет право менять:

    image: твой_логин/notes:1.0.0 # {"$imagepolicy": "flux-system:notes"}
    
  4. Собери и запушь версию 1.1.0 в реестр — и просто подожди:

    git log --oneline -5     # через пару минут появится коммит от fluxcd
    kubectl get deployment notes -o jsonpath='{..image}'
    

Ожидаемый вывод: коммит, сделанный не тобой, а автоматикой, и новая версия в кластере. Полная цепочка: пайплайн собрал образ → Flux заметил → закоммитил → применил

Задание 7. Канареечный релиз

Цель: выкатить версию так, чтобы ошибку увидели 10% пользователей, а не все

Шаги:

  1. Установи 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
    
  2. Создай объект Canary из примера в теоретической части и примени его

  3. Выкати заведомо исправную версию и наблюдай:

    kubectl set image deployment/notes notes=notes:2.0
    kubectl -n default describe canary notes | tail -20
    

    Ожидаемый вывод: в событиях видно, как доля трафика растёт: 10% → 20% → 30% → … → продвижение завершено

  4. Теперь сломай специально: собери версию, которая отвечает кодом 500 на каждый третий запрос, и выкати её

    Ожидаемый вывод: доля успешных ответов падает ниже порога, Flagger останавливает продвижение и откатывает релиз сам. В событиях будет Rolling back notes.default failed checks threshold reached

    Пользователи при этом видели ошибки только в те минуты, пока на канарейку шло 10% трафика

Типичные ошибки:

Чек-лист зрелой эксплуатации

Проверь себя: тема освоена, если ты можешь


Курс пройден. Что дальше

Оглянись на путь: в теме 1 ты запускал python3 app.py в терминале. Сейчас то же самое приложение живёт в кластере в нескольких копиях, разворачивается кодом, деплоится из git без участия человека, отдаёт метрики, защищено сертификатом, не хранит пароли в открытом виде и умеет откатываться само

Куда идти дальше:

  1. Пройди технические задания. Для инженера мониторинга и для SRE — это проверка в условиях, приближенных к рабочим
  2. Углубись в надёжность. SRE-практики: бюджеты ошибок, разборы инцидентов без поиска виноватых, дежурства
  3. Углубись в безопасность. Проверка образов на уязвимости, политики допуска в кластер, сетевые политики, цепочка поставки
  4. Возьми настоящую задачу. Разверни что-нибудь нужное себе или знакомым и веди это как настоящий сервис: с мониторингом, резервными копиями и дежурством. Опыт эксплуатации не заменяется чтением

Сохрани репозиторий «Заметок» — на собеседовании он покажет больше, чем список технологий в резюме