Представь типичный пайплайн CI: код собрался, тесты прошли, а в конце джоба прячется заветная строчка
Код: Выделить всё
kubectl apply -f manifests/Код: Выделить всё
helm upgradeДальше начинается дрейф. Кто-то ночью пофиксил инцидент через
Код: Выделить всё
kubectl edit deploymentИдея проста до неприличия: git - единственный источник правды. То, что описано в репозитории, и есть желаемое состояние кластера. А специальный контроллер внутри кластера сам тянет (pull) манифесты из git и приводит кластер в соответствие. CI больше не ходит в кластер - он только собирает образ и пишет новый тег в git. Креды от прода остаются внутри прода. Это и есть pull-модель, и в этом её главное преимущество перед push из CI.

Установка и первый вход в Argo CD
Argo CD ставится в отдельный namespace. Самый прямой способ - официальный манифест.
Код: Выделить всё
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
- argocd-application-controller - сердце системы. Это тот самый reconcile-loop, который сравнивает git с кластером и применяет diff.
- argocd-repo-server - клонирует git-репозитории, рендерит Helm/Kustomize в плоский YAML и кеширует результат.
- argocd-server - API и веб-UI, сюда же ходит CLI.
- argocd-applicationset-controller - генерирует Application'ы из шаблонов (о нём ниже).
- argocd-redis и argocd-dex - кеш и SSO-прокси.
Код: Выделить всё
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd -n argocd --create-namespace
Код: Выделить всё
argocdКод: Выделить всё
kubectl -n argocd port-forward svc/argocd-server 8080:443
argocd admin initial-password -n argocd
argocd login localhost:8080 --username admin --insecure
Код: Выделить всё
argocd-initial-admin-secretApplication: главный объект и его анатомия
Всё в Argo CD крутится вокруг CRD argocd application. Это и есть единица деплоя: "вот этот путь в этом репозитории должен быть раскатан в этот namespace этого кластера". Минимальный, но реальный манифест для деплоя приложения kubernetes выглядит так:
Код: Выделить всё
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop-api
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://gitlab.cyberlake.ru/team/shop-deploy.git
targetRevision: main
path: apps/shop-api/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: shop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
source описывает желаемое состояние.
Код: Выделить всё
repoURLКод: Выделить всё
pathКод: Выделить всё
targetRevisionКод: Выделить всё
targetRevision: HEADКод: Выделить всё
mainКод: Выделить всё
pathКод: Выделить всё
helmКод: Выделить всё
valuesКод: Выделить всё
valueFilesКод: Выделить всё
kustomization.yamldestination - куда катим.
Код: Выделить всё
serverКод: Выделить всё
https://kubernetes.default.svcКод: Выделить всё
argocd cluster addКод: Выделить всё
namespacefinalizers с
Код: Выделить всё
resources-finalizer.argocd.argoproj.ioSync, status и детект дрейфа
Теперь самое мясо - как именно идёт argo cd деплой. У Application два независимых измерения состояния, и их постоянно путают.
Sync status отвечает на вопрос "совпадает ли кластер с git":
Код: Выделить всё
SyncedКод: Выделить всё
OutOfSyncКод: Выделить всё
HealthyКод: Выделить всё
ProgressingКод: Выделить всё
DegradedКод: Выделить всё
MissingКонтроллер каждые несколько минут (плюс по вебхуку из git) делает reconcile: рендерит манифесты из repo-server и сравнивает с живыми объектами через three-way diff (git, live-состояние, last-applied). Расхождение - это и есть drift. Посмотреть его руками:
Код: Выделить всё
$ argocd app diff shop-api
===== apps/Deployment shop/shop-api ======
2c2
< image: registry.cyberlake.ru/shop-api:1.4.2
---
> image: registry.cyberlake.ru/shop-api:1.4.1
Код: Выделить всё
<Код: Выделить всё
>Ручной sync запускается командой:
Код: Выделить всё
$ argocd app sync shop-api
TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK
2026-... apps Deployment shop shop-api Synced Progressing
2026-... Service shop shop-api Synced Healthy
- prune: true - если ресурс убрали из git, Argo удалит его из кластера. Без prune удалённый из репозитория объект останется висеть в кластере навсегда. С prune git становится по-настоящему декларативным: нет в git - нет в кластере.
- selfHeal: true - если кто-то поправил объект руками (тот самый ночью), Argo откатит правку обратно к git-состоянию. Это убивает дрейф на корню.
Код: Выделить всё
kubectl edit
App-of-Apps и ApplicationSet: масштаб
Один Application на одно приложение - норм, пока их пять. Когда их пятьдесят, ты не хочешь создавать каждый руками. Тут два паттерна.
App-of-Apps - родительский Application, чей source указывает не на манифесты приложения, а на каталог с манифестами других Application'ов. Argo раскатывает родителя, тот создаёт детей, дети раскатывают свои приложения. Получается дерево, и весь кластер бутстрапится из одного корневого Application. Удобно для платформенного слоя: один root-app поднимает мониторинг, ingress-контроллер, cert-manager и так далее.
ApplicationSet - более мощная штука. Это отдельный CRD, который по шаблону генерит много Application'ов. Сердце его - генераторы:
- list - явный список значений, по одному Application на элемент.
- cluster - по одному Application на каждый зарегистрированный кластер. Тот самый мульти-кластерный деплой одной строкой.
- git - сканирует директории или файлы в репозитории и плодит Application'ы по находкам.
Код: Выделить всё
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: monitoring-fleet
namespace: argocd
spec:
generators:
- clusters: {}
template:
metadata:
name: 'monitoring-{{name}}'
spec:
project: platform
source:
repoURL: https://gitlab.cyberlake.ru/team/platform.git
targetRevision: main
path: monitoring
destination:
server: '{{server}}'
namespace: monitoring
syncPolicy:
automated: { prune: true, selfHeal: true }
Код: Выделить всё
argocd cluster addПорядок раската: sync waves и hooks
Реальные приложения требуют порядка. CRD должен появиться раньше ресурса, который его использует. Миграция БД должна пройти до запуска новых подов. За это отвечают две механики.
Sync waves - аннотация
Код: Выделить всё
argocd.argoproj.io/sync-waveResource hooks - аннотация
Код: Выделить всё
argocd.argoproj.io/hook- PreSync - до основного раската. Классика - миграция БД.
- Sync - вместе с основными ресурсами.
- PostSync - после того как всё стало Healthy. Прогрев кеша, smoke-тесты, нотификация.
- SyncFail - если sync упал, для отката или алерта.
Код: Выделить всё
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: registry.cyberlake.ru/shop-api:1.4.2
command: ["./migrate", "up"]
Код: Выделить всё
hook-delete-policy: HookSucceededОткат, история и AppProject
GitOps-откат идеологически правильный - это revert коммита в git. Но когда горит, есть быстрый путь через историю:
Код: Выделить всё
$ argocd app history shop-api
ID DATE REVISION
3 2026-06-14 22:10:00 1.4.2 (a1b2c3d)
2 2026-06-14 18:00:00 1.4.1 (f4e5d6c)
$ argocd app rollback shop-api 2
AppProject - механизм изоляции и RBAC. Дефолтный project разрешает всё со всех репозиториев во все кластеры - на бою это дыра. Project ограничивает, какие repoURL, кластеры и namespace разрешены для его Application'ов:
Код: Выделить всё
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: shop
namespace: argocd
spec:
sourceRepos:
- https://gitlab.cyberlake.ru/team/shop-deploy.git
destinations:
- server: https://kubernetes.default.svc
namespace: shop
clusterResourceWhitelist: []
Секреты в GitOps и интеграция с Rollouts
Главное правило: секреты в git открытым текстом не кладём никогда. Репозиторий часто читаем половиной компании, а git хранит историю вечно - удалить из истории сложно. Варианты:
- Sealed Secrets - шифруешь секрет публичным ключом контроллера, в git лежит зашифрованный SealedSecret, расшифровать может только контроллер в кластере.
- SOPS (часто с age/KMS) - шифрование значений в YAML, Argo расшифровывает через плагин.
- External Secrets Operator - в git только ссылка на секрет, реальное значение тянется из Vault, Yandex Lockbox или другого хранилища.
Грабли из практики
- Вечный OutOfSync из-за mutating-вебхуков. Service mesh или прочие admission-вебхуки добавляют в объект поля (sidecar, аннотации), которых нет в git. Argo видит расхождение и считает app OutOfSync навсегда. Лечится переходом на и при необходимости
Код: Выделить всё
ServerSideApply=trueпо конкретным полям.Код: Выделить всё
ignoreDifferences - selfHeal воюет с тобой. Дебажишь под через , а Argo через секунду откатывает правку. Это не баг - это фича. На время дебага временно отключи auto-sync или используй
Код: Выделить всё
kubectl editна отдельной копии.Код: Выделить всё
kubectl debug - Секрет в git. Закоммитил Secret в base64 (это НЕ шифрование, а кодирование) - считай, что утёк. base64 раскручивается за секунду.
- Слишком широкий AppProject. Все приложения в default project - значит любой Application может катить куда угодно. Делай project на команду или на окружение.
- targetRevision: HEAD на проде. Любой merge в ветку едет на бой без ревью раската. Пинуй теги.
Повтори руками, это полчаса:
- Подними k3s или kind, поставь Argo CD манифестом, пробрось порт, залогинься CLI, смени пароль.
- Создай git-репозиторий с простым Deployment + Service (nginx сойдёт), положи в .
Код: Выделить всё
apps/web - Создай Application через .
Код: Выделить всё
argocd app create web --repo <url> --path apps/web --dest-server https://kubernetes.default.svc --dest-namespace demo --sync-policy automated --auto-prune --self-heal - Проверь - увидишь Synced/Healthy.
Код: Выделить всё
argocd app get web - Поправь руками: . Подожди и посмотри, как selfHeal вернёт реплики к значению из git.
Код: Выделить всё
kubectl scale deploy web -n demo --replicas=5 - Поменяй образ в git, закоммить, пушни. Через минуту покажет, как Argo сам выкатил новую версию.
Код: Выделить всё
argocd app get web
- Чем отличаются sync status и health status, и может ли app быть Synced и Degraded одновременно?
- Что произойдёт с ресурсом, если убрать его из git при и при
Код: Выделить всё
prune: true?Код: Выделить всё
prune: false - Зачем нужен PreSync-хук и что будет с основным sync, если этот хук упадёт?
- Почему pull-модель Argo CD безопаснее push из CI, и что именно остаётся внутри кластера, а что снаружи?
Argo CD превращает git в пульт управления кластером. Application описывает желаемое состояние, контроллер постоянно сводит реальность к нему через reconcile, prune и selfHeal убивают дрейф, sync waves и hooks дают порядок раската, App-of-Apps и ApplicationSet масштабируют это на десятки приложений и кластеров, а AppProject и внешние секрет-менеджеры закрывают безопасность. CI теперь только собирает образ и коммитит тег - а выкатывает уже git. Это и есть gitops kubernetes в продакшене.