Kubernetes سطح: متوسط

GitOps با ArgoCD: استقرار خودکار و قابل ردیابی روی Kubernetes

GitOps چیست و چرا Push کردن مستقیم از Pipeline به کلاستر کافی نیست؟ راه‌اندازی ArgoCD، ساختار مخزن، ApplicationSet برای چند محیط و Rollback با git revert.

۵ دقیقه مطالعه

در مدل کلاسیک CI/CD، آخرین Job در Pipeline یک kubectl apply یا helm upgrade اجرا می‌کند. این روش کار می‌کند، اما سه مشکل جدی دارد:

  1. Runner باید دسترسی Admin به کلاستر داشته باشد؛ یعنی اگر Runner نفوذ شود، کلاستر هم از دست رفته است.
  2. اگر کسی دستی چیزی را در کلاستر تغییر دهد (kubectl edit)، هیچ‌کس متوجه نمی‌شود. به این وضعیت Configuration Drift می‌گویند.
  3. سؤال «الان دقیقاً چه چیزی روی Production است؟» جواب دقیقی ندارد.

GitOps این مشکلات را با یک ایده‌ی ساده حل می‌کند: وضعیت مطلوب کلاستر در Git نوشته می‌شود و یک Agent داخل کلاستر مدام آن را با وضعیت واقعی مقایسه و هماهنگ می‌کند. محبوب‌ترین این Agentها ArgoCD است.

Push در برابر Pull

ویژگی Push (CI مستقیم Deploy می‌کند) Pull (GitOps با ArgoCD)
دسترسی به کلاستر Runner دسترسی کامل دارد فقط ArgoCD داخل کلاستر
تشخیص Drift ندارد خودکار، همراه با Self-Heal
Rollback اجرای دوباره‌ی Pipeline قدیمی git revert
Audit پراکنده در لاگ Jobها تاریخچه‌ی Git
چند کلاستر پیچیده یک ArgoCD، چند کلاستر

نصب ArgoCD

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d; echo

ساختار مخزن: کد و Config را جدا کنید

بهترین الگو این است که دو مخزن داشته باشید:

  • مخزن کد (api): سورس برنامه، Dockerfile و .gitlab-ci.yml
  • مخزن Config (deploy-config): Helm Chart یا Manifestها و Valueهای هر محیط
deploy-config/
├── charts/
│   └── api/                 # Helm chart
└── envs/
    ├── dev/values.yaml
    ├── staging/values.yaml
    └── production/values.yaml

با این جداسازی، هر تغییر در Production یک Commit در deploy-config است که می‌شود برایش Merge Request و Approval گذاشت.

تعریف Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://gitlab.example.ir/devops/deploy-config.git
    targetRevision: main
    path: charts/api
    helm:
      valueFiles: [../../envs/dev/values.yaml]
  destination:
    server: https://kubernetes.default.svc
    namespace: dev
  syncPolicy:
    automated:
      prune: true      # delete resources removed from Git
      selfHeal: true   # revert manual changes in the cluster
    syncOptions: [CreateNamespace=true]

selfHeal: true یعنی اگر کسی دستی Replica را عوض کند، ArgoCD چند ثانیه بعد آن را به مقدار Git برمی‌گرداند. اول شاید عجیب باشد، ولی همین ویژگی است که Production را قابل پیش‌بینی می‌کند.

سه محیط با یک فایل: ApplicationSet

به‌جای سه Application تقریباً یکسان، از ApplicationSet استفاده کنید:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: api
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - list:
        elements:
          - env: dev
            autoSync: "true"
          - env: staging
            autoSync: "true"
          - env: production
            autoSync: "false"   # production syncs only after manual approval
  template:
    metadata:
      name: "api-{{.env}}"
    spec:
      project: default
      source:
        repoURL: https://gitlab.example.ir/devops/deploy-config.git
        targetRevision: main
        path: charts/api
        helm:
          valueFiles: ["../../envs/{{.env}}/values.yaml"]
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{.env}}"
  templatePatch: |
    {{- if eq .autoSync "true" }}
    spec:
      syncPolicy:
        automated: { prune: true, selfHeal: true }
    {{- end }}

اتصال CI به GitOps

حالا Pipeline دیگر Deploy نمی‌کند. فقط Image را می‌سازد و Tag جدید را در مخزن Config می‌نویسد:

update-dev:
  stage: deploy
  image: alpine:3.20
  needs: [docker-image]
  before_script:
    - apk add --no-cache git yq
  script:
    - git clone "https://ci-bot:${CONFIG_REPO_TOKEN}@gitlab.example.ir/devops/deploy-config.git"
    - cd deploy-config
    - yq -i ".image.tag = \"$CI_COMMIT_SHORT_SHA\"" envs/dev/values.yaml
    - git -c user.name=ci-bot -c user.email=ci@example.ir commit -am "api: dev -> $CI_COMMIT_SHORT_SHA"
    - git push origin main
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

ArgoCD تغییر را می‌بیند و Develop را به‌روز می‌کند. برای Staging و Production همین کار با یک Merge Request انجام می‌شود که تیم QA یا مدیر فنی Approve می‌کند.

Rollback: فقط یک Commit

git revert HEAD    # undo the last deployment
git push

ArgoCD نسخه‌ی قبلی را برمی‌گرداند و در تاریخچه‌ی Git دقیقاً ثبت می‌شود چه کسی، چه زمانی و چرا Rollback کرده است. در پروژه‌های بانکی همین تاریخچه برای Audit کافی است.

چند نکته از تجربه‌ی Production

  • Secretها را در Git ساده نگذارید. از Sealed Secrets، SOPS یا External Secrets همراه HashiCorp Vault استفاده کنید.
  • Sync Wave برای ترتیب استقرار: مثلاً Migration دیتابیس با argocd.argoproj.io/sync-wave: "-1" قبل از خود سرویس اجرا شود.
  • ArgoCD Notifications را به Mattermost یا تلگرام وصل کنید تا تیم از Sync ناموفق یا وضعیت Degraded باخبر شود.
  • AppProject برای محدود کردن دسترسی تیم‌ها به Namespaceها و مخزن‌های مشخص.

جمع‌بندی

GitOps فقط یک ابزار جدید نیست؛ تغییری در طرز فکر است: هر چیزی که در کلاستر هست باید در Git باشد. نتیجه‌اش امنیت بیشتر (CI دیگر کلید کلاستر را ندارد)، Rollback چندثانیه‌ای و پاسخ دقیق به سؤال «روی Production چه داریم؟» است.