Kustomize и Argo CD: Dev и Prod без дублирования

Обновлено и опубликовано Опубликовано:

Используемые термины: 

В инструкции разберем, как хранить манифесты для двух окружений в одном GitOps-репозитории без копирования Deployment, Service и Ingress под каждое окружение отдельно. Используем Kustomize для наложения различий поверх общей базы и Argo CD для автоматической синхронизации из Git в кластер.

Каталоги GitOps-репозитория

Создадим структуру репозитория: общий base, overlay для dev, overlay для prod и отдельный каталог под манифесты Argo CD Application.

mkdir -p /opt/gitops-demo/argo/base /opt/gitops-demo/argo/overlays/dev /opt/gitops-demo/argo/overlays/prod /opt/gitops-demo/argo/applications

Перейдем в каталог репозитория, чтобы дальше работать с относительными путями:

cd /opt/gitops-demo

* где:

  • /opt/gitops-demo — фиксированный корень репозитория; все пути ниже относительно него.
  • argo/base — общие манифесты приложения.
  • argo/overlays/dev и argo/overlays/prod — отличия окружений (namespace, реплики, тег образа, Ingress host).
  • argo/applications — манифесты Argo CD Application; их не включают в sync path overlays, иначе Application начнет синхронизировать сам себя.

Каталог Base: общие манифесты

Начнем с Deployment. В base описываем только то, что одинаково для dev и prod, а окружение-специфичные значения вынесем в overlays.

vi argo/base/deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-web
  labels:
    app.kubernetes.io/name: demo-web
spec:
  replicas: 1
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: demo-web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: demo-web
    spec:
      terminationGracePeriodSeconds: 45
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: demo-web
          image: docker.dmosk.ru/demo/web:latest
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 128Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - sleep 10
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 15
            periodSeconds: 20
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}

* где:

  • maxUnavailable: 0 и maxSurge: 1 — сначала поднимается новый pod, старый гасится после Ready.
  • preStop + sleep 10 — окно на обновление Endpoints/Ingress до SIGTERM; terminationGracePeriodSeconds: 45 дает приложению доработать запросы.
  • memory requests = limits — Guaranteed QoS; для CPU задан только request.
  • readOnlyRootFilesystem — корень только на чтение; временные файлы в /tmp через emptyDir.
  • image — базовое имя образа; тег для окружений меняется в overlays через images.

Добавим Service, чтобы обращаться к подам по стабильному адресу внутри кластера:

vi argo/base/service.yml

apiVersion: v1
kind: Service
metadata:
  name: demo-web
  labels:
    app.kubernetes.io/name: demo-web
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: demo-web
  ports:
    - name: http
      port: 80
      targetPort: http

Добавим Ingress, чтобы пропускать внешний трафик к сервису. Host в base оставим заглушкой — реальные адреса для dev и prod зададим позже отдельными патчами:

vi argo/base/ingress.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-web
  labels:
    app.kubernetes.io/name: demo-web
spec:
  ingressClassName: nginx
  rules:
    - host: demo-web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: demo-web
                port:
                  name: http

* ingressClassName и host в base — заглушка; реальные хосты задаются патчами в overlays.

Свяжем три манифеста в один base через kustomization.yml — именно на этот каталог будут ссылаться оба overlay:

vi argo/base/kustomization.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yml
  - service.yml
  - ingress.yml

Каталоги Overlay

Отдельно рассмотрим примеры манифестов для dev и prod окружений

Dev окружение

Для dev нужен свой host в Ingress. Опишем его отдельным патчем, который наложится поверх base:

vi argo/overlays/dev/ingress-patch.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-web
spec:
  rules:
    - host: demo-web-dev.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: demo-web
                port:
                  name: http

Теперь соберем overlay: укажем namespace для dev, число реплик, тег образа и подключим патч Ingress:

vi argo/overlays/dev/kustomization.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: demo-web-dev
resources:
  - ../../base
replicas:
  - name: demo-web
    count: 1
images:
  - name: docker.dmosk.ru/demo/web
    newTag: dev
patches:
  - path: ingress-patch.yml

* где:

  • namespace — все ресурсы overlay уходят в demo-web-dev.
  • newTag: dev — тег образа для стенда; имя образа должно совпасть с полем image в base без тега.
  • replicas: 1 — для одной реплики anti-affinity не обязателен.

Манифесты Prod

Для prod также зададим свой host в Ingress — уже основной домен, а не поддомен для тестов:

vi argo/overlays/prod/ingress-patch.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-web
spec:
  rules:
    - host: demo-web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: demo-web
                port:
                  name: http

В prod держим несколько реплик, поэтому добавим patch с anti-affinity — он не даст планировщику разместить все поды на одной ноде:

vi argo/overlays/prod/affinity-patch.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-web
spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app.kubernetes.io/name: demo-web
              topologyKey: kubernetes.io/hostname

Соберем overlay prod: namespace, две реплики, прод-тег образа и оба патча — Ingress и affinity:

vi argo/overlays/prod/kustomization.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: demo-web-prod
resources:
  - ../../base
replicas:
  - name: demo-web
    count: 2
images:
  - name: docker.dmosk.ru/demo/web
    newTag: latest
patches:
  - path: ingress-patch.yml
  - path: affinity-patch.yml

* где:

  • newTag: latest — прод-тег для примера; на реальном проде вместо latest стоит указывать конкретный тег релиза или git SHA — так проще откатиться и понятно, какая сборка сейчас в кластере.
  • count: 2 — несколько реплик; requiredDuringSchedulingIgnoredDuringExecution разводит их по разным нодам.
  • affinity-patch.yml — strategic merge patch по имени Deployment из base.

Проверка рендера Kustomize

Прежде чем публиковать репозиторий, проверим локально, что Kustomize собирает оба overlay без ошибок и подставляет нужные значения:

kubectl kustomize argo/overlays/dev

kubectl kustomize argo/overlays/prod

* в выводе должны отличаться namespace, host Ingress, тег образа и число реплик; общие Deployment/Service/Ingress берутся из одного base. Если kubectl без поддержки Kustomize, ту же проверку сделает автономный бинарник: kustomize build argo/overlays/dev.

Application для Dev и Prod

Опишем Application для dev. Он указывает Argo CD, откуда брать манифесты (repoURL, targetRevision, path) и куда их применять (destination):

vi argo/applications/demo-web-dev.yml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo-web-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.example.com/infra/gitops-demo.git
    targetRevision: main
    path: argo/overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: demo-web-dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Аналогично опишем Application для prod — отличается только path до overlay и целевой namespace:

vi argo/applications/demo-web-prod.yml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo-web-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.example.com/infra/gitops-demo.git
    targetRevision: main
    path: argo/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: demo-web-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

* где:

  • path — каталог с kustomization.yml нужного overlay; Argo CD сам вызывает Kustomize.
  • CreateNamespace=true — namespace из overlay создается при первом sync.
  • prune / selfHeal — удаление лишнего из кластера и откат ручных правок к состоянию git.
  • argo/applications не указан в path — Application-манифесты применяются отдельно через kubectl и не синкаются как часть приложения.

* если Application-ов становится много, их удобнее не применять руками, а собрать в паттерн App of Apps — один корневой Application, который сам синкает каталог argo/applications из Git.

Применим оба Application в кластер — после этого Argo CD начнет следить за репозиторием и синхронизировать оба окружения:

kubectl apply -f argo/applications/demo-web-dev.yml

kubectl apply -f argo/applications/demo-web-prod.yml

После правок Application-файлов закоммитьте их в git для истории, но деплой Application в кластер делайте через kubectl apply (или отдельный bootstrap-Application на каталог applications), а не через path overlay самого demo-web.

Проверка sync

Проверим, что оба Application подтянулись, поды поднялись в своих namespace, а Ingress получил нужный host:

kubectl get applications -n argocd

kubectl get pods -n demo-web-dev

kubectl get pods -n demo-web-prod

kubectl get ingress -n demo-web-dev

kubectl get ingress -n demo-web-prod

Если статус Application вызывает вопросы, посмотрим детали через describe — там видны события и условия синхронизации:

kubectl describe application demo-web-dev -n argocd

* у Application статусы Synced и Healthy; если Unknown/Degraded — смотрите Events и Conditions в describe.

Типовое изменение без копипаста base

Дальнейшая работа с окружениями сводится к правке overlay, а не base. Например, обновим тег образа в dev:

vi argo/overlays/dev/kustomization.yml

* меняете только newTag / replicas / патч Ingress в нужном overlay, коммитите и пушите; automated sync подтянет изменение в соответствующий namespace.

git add argo/overlays/dev/kustomization.yml

git commit -m "Bump demo-web image tag in dev"

git push

Понаблюдаем за применением изменения в реальном времени:

kubectl get application demo-web-dev -n argocd -w

Возможные проблемы

Рассмотрим некоторые тимичные проблемы при работе с кастомайзером Argo CD.

Application не переходит в Synced

В kubectl get applications -n argocd статус висит на OutOfSync или Unknown, automated sync не срабатывает сам по себе.

Причина: path в манифесте Application не совпадает с реальным расположением kustomization.yml, либо в целевом namespace есть ресурс, измененный вручную и заблокировавший selfHeal.

Решение: сверьте path в Application с фактическим путем до overlay в репозитории. Откройте kubectl describe application demo-web-dev -n argocd и проверьте Events и Conditions — там указана точная причина рассинхронизации.

Второе окружение не разворачивается

Application для dev применен и синкается, а для prod поды в namespace не появляются, либо наоборот.

Причина: Application-манифесты не входят в sync path overlays и не применяются автоматически — каждый Application нужно применить в кластер отдельно.

Решение: убедитесь, что оба манифеста применены через kubectl apply -f (demo-web-dev.yml и demo-web-prod.yml). Для большого числа Application удобнее не применять их по одному вручную, а собрать в паттерн App of Apps — один корневой Application, который сам синкает каталог argo/applications из Git.

Изменение в base появилось не сразу в обоих окружениях

После правки base поды в dev обновились, а в prod — еще нет (или наоборот).

Причина: у каждого Application свой independent automated sync с собственным интервалом поллинга Git — окружения не обновляются синхронно друг с другом.

Решение: ждать не нужно дольше обычного интервала синхронизации Argo CD. Если требуется обновление немедленно, синхронизацию для конкретного Application можно запустить вручную из UI или через kubectl.

Ресурсы неожиданно исчезли из namespace

Под, Service или Ingress пропали из кластера без явного удаления вручную.

Причина: при prune: true Argo CD удаляет из кластера все, чего больше нет в Git — например, если overlay случайно стерли, переименовали или сломали путь в path.

Решение: перед изменениями в overlays для prod проверяйте diff в UI Argo CD или через kubectl describe application, прежде чем давать automated sync применить его. Так удаление можно заметить до того, как оно затронет прод.

Так base остается единственным источником общей конфигурации, а Kustomize и Argo CD берут на себя различия между dev и prod и их доставку в кластер.

# DevOps # Kubernetes
Дмитрий Моск — частный мастер
Была ли полезна вам эта инструкция?

Да            Нет

Дмитрий Моск
— IT-специалист.
Настройка серверов, услуги DevOps.

Заказать настройку контейнеризации

Нужна бесплатная консультация?

Мини-инструкции

Использование kustomize в Argo CD для описания манифестов под разные среды без дублирования

Как запустить llama.cpp на Linux-сервере: сборка, модель GGUF и API-сервер

OpenSSH Server на Windows: вход по SSH

Установка и запуск Harbor Registry на Kubernetes с помощью Helm Chart

Установка и запуск GitLab Runner в Kubernetes через Helm

Как установить и настроить ArgoCD в кластере Kubernetes

Использование Helm для установки Garage S3 в Kubernetes

Другие инструкции

Все статьи

Нужна помощь? Пишите:






Реклама