GitOps с ArgoCD: декларативный деплой в Kubernetes
Представьте: вся ваша инфраструктура описана в Git-репозитории. Хотите изменить версию приложения — делаете коммит. Хотите откатиться — git revert. Хотите понять, кто и когда поменял конфигурацию — git log. Не нужно запоминать, какую команду kubectl apply вы запускали два месяца назад. Не нужно гадать, соответствует ли кластер тому, что вы задумали. Git — единственный источник истины. Это и есть GitOps.
Принципы GitOps
GitOps — это не просто «храните YAML в Git» (хотя и это тоже). Это набор принципов:
- Декларативность — всё desired state описано декларативно (Kubernetes-манифесты, Helm values, Kustomize overlays).
- Версионирование — Git хранит полную историю изменений. Любое состояние кластера можно воспроизвести.
- Автоматическая синхронизация — агент в кластере постоянно сравнивает desired state (Git) с actual state (кластер) и устраняет дрифт.
- Pull-модель — кластер сам «вытягивает» изменения из Git, а не CI-пайплайн «проталкивает» их через
kubectl. Не нужно давать CI-системе доступ к кластеру.
ArgoCD vs Flux
Два основных инструмента для GitOps в Kubernetes — ArgoCD и Flux. Оба решают одну задачу, но подход различается:
ArgoCD: мощный веб-интерфейс, визуализация ресурсов и зависимостей, встроенный RBAC, Application CRD как центральная абстракция. Лучше для команд, где важна наглядность и контроль.
Flux: toolkit-подход, набор контроллеров без единого UI (есть Weave GitOps как опция), больше «Kubernetes-native». Лучше для автоматизации и multi-tenancy.
Я использую ArgoCD — веб-интерфейс экономит массу времени при отладке, а визуализация зависимостей между ресурсами бесценна, когда что-то идёт не так.
Установка ArgoCD на k3s
Установка стандартная — применяем манифест и ждём:
kubectl create namespace argocd
kubectl apply -n argocd -f
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Ждём, пока все поды поднимутся
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s Получаем первоначальный пароль админа:
kubectl -n argocd get secret argocd-initial-admin-secret
-o jsonpath="{.data.password}" | base64 -d Для доступа к UI через Traefik (который уже есть в k3s) создаём Ingress:
# argocd-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server
namespace: argocd
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
rules:
- host: argocd.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443
tls:
- hosts:
- argocd.example.com
secretName: argocd-tls Также ставим CLI для управления из терминала:
curl -sSL -o argocd
https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd
sudo mv argocd /usr/local/bin/
argocd login argocd.example.com
argocd account update-password Подключаем Git-репозиторий
Структура репозитория для GitOps выглядит примерно так:
k8s-manifests/
├── apps/
│ ├── myapp/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── ingress.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── staging/
│ │ │ ├── kustomization.yaml
│ │ │ └── patch-replicas.yaml
│ │ └── production/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── monitoring/
│ └── ...
└── infrastructure/
├── cert-manager/
└── sealed-secrets/ Подключаем репозиторий к ArgoCD:
argocd repo add https://github.com/user/k8s-manifests.git
--username git-user
--password ghp_token Для приватных репозиториев лучше использовать SSH-ключ или deploy token.
Application CRD
Центральный объект ArgoCD — Application. Он описывает, какой манифест из какого репозитория деплоить в какой namespace:
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/user/k8s-manifests.git
targetRevision: main
path: apps/myapp/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: myapp-prod
syncPolicy:
automated:
prune: true # удалять ресурсы, которых нет в Git
selfHeal: true # исправлять ручные изменения в кластере
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3m Ключевые настройки sync policy:
automated— ArgoCD автоматически синхронизирует кластер при изменении в Git. Без этого потребуется ручная синхронизация через UI или CLI.prune: true— если вы удалили ресурс из Git, ArgoCD удалит его из кластера. Без prune «мёртвые» ресурсы будут висеть вечно.selfHeal: true— если кто-то вручную изменил ресурс в кластере (черезkubectl edit), ArgoCD откатит изменение к состоянию из Git. Это суть GitOps — Git is the source of truth.
Kustomize overlays для staging/prod
Kustomize — встроенный в kubectl инструмент для кастомизации манифестов без темплейтинга. ArgoCD поддерживает его нативно.
Базовые манифесты:
# apps/myapp/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
commonLabels:
app: myapp Overlay для staging:
# apps/myapp/overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: staging-
namespace: myapp-staging
patches:
- path: patch-replicas.yaml
images:
- name: myapp
newTag: develop-latest # apps/myapp/overlays/staging/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
template:
spec:
containers:
- name: app
resources:
limits:
memory: 256Mi Overlay для production:
# apps/myapp/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: myapp-prod
patches:
- path: patch-replicas.yaml
images:
- name: myapp
newTag: v1.4.2 # apps/myapp/overlays/production/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: app
resources:
limits:
memory: 512Mi Один и тот же базовый манифест, разные параметры для разных сред. Обновление версии в проде — это изменение newTag в kustomization.yaml и push в Git.
Управление секретами
Хранить секреты в открытом виде в Git-репозитории — очевидно плохая идея. Два основных подхода:
Sealed Secrets
Bitnami Sealed Secrets шифрует секреты публичным ключом кластера. В Git хранится зашифрованный SealedSecret, который может расшифровать только контроллер в кластере:
# Устанавливаем контроллер
kubectl apply -f
https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.25.0/controller.yaml
# Устанавливаем CLI
brew install kubeseal
# Шифруем секрет
kubectl create secret generic db-creds
--from-literal=password=supersecret
--dry-run=client -o yaml |
kubeseal --format yaml > sealed-db-creds.yaml Результат — YAML, который безопасно коммитить в Git:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-creds
namespace: myapp-prod
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO... SOPS + age
Альтернативный подход — шифрование файлов через Mozilla SOPS с ключами age:
# Генерируем ключ
age-keygen -o age.key
# Шифруем
sops --encrypt --age $(age-keygen -y age.key) secrets.yaml > secrets.enc.yaml ArgoCD поддерживает SOPS через плагин ksops. Этот подход гибче, но требует больше настройки.
ApplicationSet для масштабирования
Когда приложений становится много, создавать Application для каждого вручную утомительно. ApplicationSet генерирует Application-ы по шаблону:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-apps
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/user/k8s-manifests.git
revision: main
directories:
- path: apps/*/overlays/production
template:
metadata:
name: "{{'path[1]'}}-production"
spec:
project: default
source:
repoURL: https://github.com/user/k8s-manifests.git
targetRevision: main
path: "{{'path'}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{'path[1]'}}-prod"
syncPolicy:
automated:
prune: true
selfHeal: true Этот ApplicationSet автоматически создаст Application для каждого приложения в apps/*/overlays/production. Добавили новый сервис в репозиторий — ArgoCD подхватит его без ручного вмешательства.
Health checks и rollbacks
ArgoCD отслеживает health-статус каждого ресурса. Deployment считается healthy, когда все реплики запущены и проходят readiness probe. Если деплой завис — ArgoCD покажет это в UI и при необходимости можно откатиться:
# Посмотреть историю синхронизаций
argocd app history myapp-production
# Откатиться к конкретной ревизии
argocd app rollback myapp-production 3
# Или через Git — просто revert коммита
git revert HEAD
git push
# ArgoCD автоматически синхронизирует Второй вариант (через Git revert) — канонический для GitOps. Откат через ArgoCD CLI — это ручное вмешательство, которое selfHeal потом перезатрёт, если в Git останется новая версия.
CI/CD пайплайн с GitOps
В GitOps-модели CI и CD разделены:
- CI (GitHub Actions, GitLab CI) — собирает образ, прогоняет тесты, пушит в registry.
- Image Updater или CI-скрипт — обновляет тег образа в GitOps-репозитории.
- ArgoCD (CD) — видит изменение и деплоит.
Пример шага в GitHub Actions, который обновляет тег образа:
- name: Update image tag in GitOps repo
run: |
git clone https://github.com/user/k8s-manifests.git
cd k8s-manifests
cd apps/myapp/overlays/production
kustomize edit set image myapp=${IMAGE_REPO}:${GITHUB_SHA::7}
git add .
git commit -m "deploy: myapp ${GITHUB_SHA::7}"
git push Либо используйте ArgoCD Image Updater — он отслеживает registry и автоматически обновляет теги.
Итог
GitOps с ArgoCD — это не overhead, а упрощение. Вся конфигурация — в Git. Все изменения — через PR с code review. Все откаты — через git revert. Аудит — через git log. ArgoCD берёт на себя синхронизацию и визуализацию, а вам остаётся только писать манифесты и пушить коммиты. Для кластера на k3s это работает отлично — ресурсов ArgoCD потребляет немного (~300 МБ RAM), а пользы приносит на порядок больше, чем ручные kubectl apply.