Представь типичную картину. Релиз в прод идёт так: инженер запускает CI-пайплайн, тот собирает образ и в конце делает kubectl apply из раннера. Чтобы это сработало, у CI лежит kubeconfig с правами cluster-admin. Дальше начинается веселье. Кто-то ночью пофиксил инцидент руками через kubectl edit и забыл занести в репозиторий. Кто-то выкатил вручную из своей ветки. Через месяц никто не знает, что реально крутится в кластере и почему манифест в git не совпадает с тем, что работает. Откатить нельзя - непонятно, к чему откатывать. А ещё этот всемогущий kubeconfig в CI - лакомый кусок: утёк токен раннера, и у тебя угнали весь кластер.
GitOps переворачивает модель. Идея в одной фразе: git - единственный источник истины, а агент внутри кластера сам подтягивает (pull) желаемое состояние из git и приводит кластер к нему. Не CI пушит в кластер, а кластер тянет из git. Декларативный деплой из git стал отраслевым стандартом 2026 года, и держится он на двух инструментах - argo cd и flux. Разберём механику обоих, прогрессивные деплои, секреты и грабли, чтобы ты понимал не только как нажать кнопку Sync, но и что происходит под капотом.

Pull против push: почему направление меняет всё
В классическом push-CI/CD поток такой: git -> CI собрал -> CI применил в кластер. Кластер пассивен, инициатор снаружи. Проблемы: внешней системе нужен доступ внутрь периметра, дрейф (ручные правки) никто не ловит, состояние кластера нигде не зафиксировано целиком.
В pull-модели gitops kubernetes всё иначе. В кластере живёт контроллер-агент. Он постоянно сравнивает две вещи: desired state (что описано в git) и live state (что реально в etcd через API-сервер). Если они разошлись - агент это видит и (по настройке) выравнивает. Получаешь сразу несколько свойств даром:
- Нет входящего доступа в кластер. CI больше не нужен kubeconfig - он только собирает образ и пушит тег/манифест в git. Агент сам тянет наружу, инициатива изнутри периметра. Поверхность атаки резко сужается.
- Полный аудит. История git это и есть история деплоев. Кто, что, когда, через какой PR с каким ревью - всё в git log. Не нужен отдельный аудит-лог деплоев.
- Откат - это git revert. Откатываешь коммит, агент видит изменение desired state и возвращает кластер назад. Никаких магических кнопок.
- Drift-детект. Любая ручная правка через kubectl edit будет замечена и (опционально) затёрта обратно к состоянию из git. Кластер перестаёт расползаться.
Argo cd: Application, sync и drift на практике
Argo cd - это контроллер с удобным веб-UI, который показывает дерево ресурсов приложения и подсвечивает их статус цветом. Центральная сущность - кастомный ресурс Application. Он отвечает на два вопроса: откуда взять desired state (source) и куда катить (destination).
Код: Выделить всё
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop-backend
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.ru/infra/shop.git
targetRevision: main
path: deploy/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: shop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
Посмотрим, что видно через CLI:
Код: Выделить всё
$ argocd app get shop-backend
Name: argocd/shop-backend
Project: default
Sync Status: OutOfSync from main (a1b2c3d)
Health Status: Healthy
GROUP KIND NAMESPACE NAME STATUS HEALTH
apps Deployment shop shop-backend OutOfSync Healthy
Service shop shop-backend Synced Healthy
Чтобы выровнять руками, делаешь argocd app sync shop-backend, и argo прогоняет ресурсы через три фазы хуков - PreSync, Sync, PostSync. В PreSync удобно гонять миграции БД как Job, в PostSync - смоук-тесты. Если что-то упало, есть SyncFail-хуки для уборки.
App-of-Apps, ApplicationSet и масштаб
Когда приложений десятки, заводить каждое Application руками - боль. Тут argocd предлагает паттерн App-of-Apps: одно корневое Application указывает на каталог в git, где лежат манифесты других Application. Корень синкается, создаёт дочерние, те синкают свои приложения. Получается дерево, управляемое целиком из git - даже сам набор приложений теперь декларативен.
Для генерации однотипных приложений (например, одно и то же в десяти кластерах или для каждой команды) есть ApplicationSet с генераторами - list, cluster, git, matrix. Описываешь шаблон Application один раз, генератор размножает его по входным данным. Это убирает копипасту и держит парк приложений консистентным.
Flux: тот же gitops, но через набор CRD
flux kubernetes решает ту же задачу, но философия другая: вместо одного большого контроллера и UI - набор узких контроллеров GitOps Toolkit, каждый со своим CRD. UI из коробки нет (есть сторонние и интеграция с Weave GitOps), зато всё максимально декларативно и компонуемо.
Минимальный поток для flux: ресурс GitRepository (source-controller) описывает откуда тянуть git, а Kustomization (kustomize-controller) - что из этого репозитория собрать и применить.
Код: Выделить всё
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: shop
namespace: flux-system
spec:
interval: 1m
url: https://git.example.ru/infra/shop.git
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: shop-prod
namespace: flux-system
spec:
interval: 10m
prune: true
sourceRef:
kind: GitRepository
name: shop
path: ./deploy/overlays/prod
targetNamespace: shop
Главная киллер-фича flux - image automation, которой у argo из коробки нет. Три CRD группы image.toolkit.fluxcd.io/v1 работают в связке: ImageRepository сканирует реестр контейнеров, ImagePolicy задаёт правило выбора тега (например, semver-диапазон или последний по дате), а ImageUpdateAutomation, найдя новый подходящий образ, сам коммитит обновлённый тег обратно в git. Круг замыкается: собрал образ в CI -> flux заметил новый тег -> сам записал в git -> сам применил. CI вообще не касается кластера.
Прогрессивные деплои: Argo Rollouts
Обычный Deployment умеет только RollingUpdate - тупо подменяет поды пачками. Для продакшена этого мало: хочется выкатить новую версию на 5 процентов трафика, посмотреть метрики, и только потом катить дальше. Это делает Argo Rollouts - отдельный контроллер с ресурсом Rollout вместо Deployment, поддерживающий canary и blue-green.
Код: Выделить всё
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: shop-backend
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 5m }
- setWeight: 25
- pause: { duration: 10m }
- setWeight: 50
- pause: {}
Секреты в gitops: главная боль
GitOps требует, чтобы всё лежало в git. Но класть Secret с паролем в plaintext в репозиторий - катастрофа, даже в приватный. Два рабочих подхода в 2026:
- Sealed Secrets (Bitnami). Контроллер в кластере генерирует пару ключей. Ты шифруешь секрет утилитой kubeseal публичным ключом и получаешь ресурс SealedSecret, который безопасно коммитить в git - расшифровать его может только контроллер своим приватным ключом внутри кластера. В git лежит зашифрованный шифротекст, в кластере контроллер разворачивает его в обычный Secret.
- SOPS (часто с age или KMS). Шифрует значения внутри YAML, оставляя структуру читаемой - видно ключи, скрыты значения. flux умеет расшифровывать SOPS нативно через spec.decryption в Kustomization. Для argo нужен плагин или пайплайн-расшифровка.
Грабли и антипаттерны из боевой эксплуатации
- selfHeal воюет с HPA. Включил selfHeal, а HPA меняет replicas - argo видит дрейф и откатывает к числу из git, HPA снова масштабирует, и они дерутся бесконечно. Решение: убери поле replicas из манифеста (ignoreDifferences по /spec/replicas) и отдай его HPA.
- Плавающий тег latest. Если в манифесте image указан как тег latest или плавающий main, argo/flux считают, что desired state не менялся (строка та же), и нового образа не подтянут. Всегда иммутабельные теги - SHA или semver.
- Бесконечный sync из-за мутаций. Admission-вебхуки или операторы дописывают аннотации/поля в ресурс, argo видит это как дрейф и вечно ресинкает. Лечится ignoreDifferences на конкретные пути.
- Один монорепо-Application на всё. Сложил весь кластер в одно Application - любой мелкий PR ресинкает всё разом, blast radius огромный. Дроби по доменам, используй App-of-Apps.
- Откат образа, но не конфига. git revert вернёт манифест, но если миграция БД уже прошла в PreSync, откат схемы не делается автоматически. Думай об обратной совместимости миграций.
Разверни локальный кластер (kind, k3s или minikube) и пройди весь цикл:
- Установи argo cd: kubectl create namespace argocd, затем kubectl apply -n argocd -f с официальным install.yaml.
- Проброс UI: kubectl port-forward svc/argocd-server -n argocd 8080:443, логин admin, пароль достань из секрета argocd-initial-admin-secret.
- Заведи публичный git-репозиторий с простым Deployment и Service в каталоге deploy/.
- Создай Application (манифест из урока), включи automated + selfHeal, дождись Synced/Healthy в UI.
- Проверь drift: сделай kubectl scale deployment ... --replicas=5 и смотри, как argo откатывает к git. Затем поменяй число реплик в git, закоммить и увидь авто-sync.
- Сделай git revert последнего коммита и убедись, что кластер вернулся назад.
- Чем pull-модель gitops принципиально безопаснее push-CD, и какой именно доступ исчезает у CI?
- В чём разница между Sync Status и Health Status в argo cd, и может ли ресурс быть Synced и Degraded одновременно?
- Почему плавающий тег latest ломает gitops, и как это связано с понятием desired state?
- Какие два способа безопасно хранить секреты в git ты знаешь и чем SealedSecret отличается от SOPS?
GitOps - это не про инструмент, а про дисциплину: git как единственный источник истины и агент в кластере, который тянет состояние сам. argo cd даёт UI, дерево ресурсов и App-of-Apps; flux - набор узких CRD и нативную image automation. Поверх кладутся Argo Rollouts для canary/blue-green и SealedSecrets/SOPS для секретов. На выходе - честный аудит через git log, откат через git revert, защита от дрейфа и кластер без всемогущего kubeconfig в CI. Это и есть фундамент прод-эксплуатации Kubernetes в 2026.