GitOps с ArgoCD: декларативный деплой в Kubernetes

gitopsargocdkubernetesdevops

Представьте: вся ваша инфраструктура описана в Git-репозитории. Хотите изменить версию приложения — делаете коммит. Хотите откатиться — git revert. Хотите понять, кто и когда поменял конфигурацию — git log. Не нужно запоминать, какую команду kubectl apply вы запускали два месяца назад. Не нужно гадать, соответствует ли кластер тому, что вы задумали. Git — единственный источник истины. Это и есть GitOps.

Принципы GitOps

GitOps — это не просто «храните YAML в Git» (хотя и это тоже). Это набор принципов:

  1. Декларативность — всё desired state описано декларативно (Kubernetes-манифесты, Helm values, Kustomize overlays).
  2. Версионирование — Git хранит полную историю изменений. Любое состояние кластера можно воспроизвести.
  3. Автоматическая синхронизация — агент в кластере постоянно сравнивает desired state (Git) с actual state (кластер) и устраняет дрифт.
  4. Pull-модель — кластер сам «вытягивает» изменения из Git, а не CI-пайплайн «проталкивает» их через kubectl. Не нужно давать CI-системе доступ к кластеру.

GitOps flow: от коммита в Git до состояния кластера через ArgoCD

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.

Процесс синхронизации ArgoCD: обнаружение дрифта и приведение к desired state

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 разделены:

  1. CI (GitHub Actions, GitLab CI) — собирает образ, прогоняет тесты, пушит в registry.
  2. Image Updater или CI-скрипт — обновляет тег образа в GitOps-репозитории.
  3. 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.

© 2026 Terminal Notes. Built with SvelteKit.