GitOps مع ArgoCD وFlux

مشروع: خط تسليم GitOps متكامل

18 دقيقة الدرس 10 من 30

مشروع: خط تسليم GitOps متكامل

غطّت الدروس التسعة السابقة المبادئ والأدوات والتقنيات الفردية. الآن تبني الشيء الحقيقي: خط تسليم GitOps كامل جاهز للإنتاج لخدمة متعددة البيئات. يسير هذا الدرس بدقة عبر كيفية تصميم المستودعات وموارد ArgoCD Application — القرارات التي تحدد ما إذا كان النظام سيتوسع بشكل نظيف أو ينهار تحت وطأة تعقيده.

السيناريو يعكس ما تواجهه في شركات التقنية الكبرى: خدمة payments-api يجب أن تمر عبر بيئات dev → staging → production عبر كلاسترَين من Kubernetes (staging و production يتشاركان كلاستراً في هذا المثال؛ الإنتاج معزول). فريق منصة منفصل يمتلك مستودع إعدادات GitOps؛ فرق التطبيقات تمتلك مستودعات تطبيقاتها. CI/CD هو GitHub Actions.

الخطوة 1: قرار تخطيط المستودع

في بداية كل مشروع GitOps، السؤال المعماري الأول هو: مستودع إعدادات واحد أم عدة؟ الجواب على نطاق واسع هو دائماً تقريباً mono-repo للإعدادات مع مستودعات منفصلة لكل خدمة للكود المصدري. يمنح مستودع الإعدادات الواحد فريق المنصة رؤية عالمية لحالة الكلاستر، ويبسّط RBAC، ويمنع انجراف الإعدادات بين الخدمات. إليك هيكل المستودع المستهدف:

# gitops-platform/ — مستودع الإعدادات الواحد (تملكه فريق المنصة) . ├── clusters/ │ ├── staging/ │ │ ├── argocd-apps/ │ │ │ └── payments-api.yaml # ArgoCD Application لـ staging │ │ └── cluster-config/ │ │ ├── namespaces.yaml │ │ └── network-policies.yaml │ └── production/ │ ├── argocd-apps/ │ │ └── payments-api.yaml # ArgoCD Application لـ production │ └── cluster-config/ │ ├── namespaces.yaml │ └── network-policies.yaml │ └── services/ └── payments-api/ ├── base/ │ ├── kustomization.yaml │ ├── deployment.yaml │ ├── service.yaml │ ├── hpa.yaml │ └── serviceaccount.yaml └── overlays/ ├── dev/ │ ├── kustomization.yaml # تعديل: replicas=1, موارد صغيرة │ └── image-tag.yaml # يحدّثه CI (بناءات فرع dev) ├── staging/ │ ├── kustomization.yaml # تعديل: replicas=2, مرجع secrets staging │ └── image-tag.yaml # يحدّثه CI (بناءات الفرع main) └── production/ ├── kustomization.yaml # تعديل: replicas=5, PodDisruptionBudget └── image-tag.yaml # يُحدَّث يدوياً عبر PR (release tag)
لماذا ملفات image-tag.yaml منفصلة؟ الاحتفاظ بعنوان الـ image في ملفه الخاص بدلاً من داخل deployment.yaml يعني أن CI يمكنه فتح PR بسطر واحد يسهل على المراجعين الموافقة عليه. كما يمنع CI من لمس المانيفستات الأساسية غير ذات الصلة. الملف image-tag.yaml هو تعديل Kustomize من نوع images: — الوحدة الأصغر والأكثر قابلية للتدقيق للتغيير لكل ترقية.

الخطوة 2: المانيفستات الأساسية

يحتوي مجلد base/ على التعريف المشترك للخدمة الذي لا يعتمد على البيئة. تُعدّل الطبقات الفوقية فقط ما يختلف. فيما يلي Deployment الأساسي الواقعي وملف تعديل Kustomize لعنوان الـ image الذي يحدّثه CI:

# services/payments-api/base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: payments-api labels: app: payments-api version: "unknown" # الطبقات الفوقية تضع label الإصدار الحقيقي spec: replicas: 1 # يُعاد تعريفه في كل طبقة فوقية selector: matchLabels: app: payments-api template: metadata: labels: app: payments-api annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" spec: serviceAccountName: payments-api securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 containers: - name: api image: ghcr.io/myorg/payments-api:latest # يُستبدل بتعديل image-tag ports: - containerPort: 8080 readinessProbe: httpGet: path: /healthz/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz/live port: 8080 initialDelaySeconds: 15 periodSeconds: 20 resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1000m" memory: "512Mi" envFrom: - secretRef: name: payments-api-secrets # External Secrets Operator يملأ هذا --- # services/payments-api/overlays/staging/image-tag.yaml # يكتب CI فوق حقل newTag فقط عبر: # yq e '.images[0].newTag = "'$IMAGE_TAG'"' -i overlays/staging/image-tag.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: ghcr.io/myorg/payments-api newTag: "sha-a1b2c3d" # CI يستبدل هذا عند كل دمج إلى main --- # services/payments-api/overlays/staging/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: payments-staging resources: - ../../base patches: - patch: |- - op: replace path: /spec/replicas value: 2 target: kind: Deployment name: payments-api components: - image-tag.yaml

الخطوة 3: موارد ArgoCD Application

تحصل كل بيئة على مانيفست Application خاص بها مخزّن في المجلد الخاص بالكلاستر في مستودع الإعدادات. ArgoCD يراقب المجلد ويدير نفسه بنفسه: إضافة ملف .yaml جديد إلى clusters/staging/argocd-apps/ ينشئ التطبيق تلقائياً دون أن يلمس أحد واجهة ArgoCD.

# clusters/staging/argocd-apps/payments-api.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: payments-api-staging namespace: argocd annotations: notifications.argoproj.io/subscribe.on-sync-succeeded.slack: devops-deploys notifications.argoproj.io/subscribe.on-sync-failed.slack: devops-alerts finalizers: - resources-finalizer.argocd.argoproj.io # حذف متتالي عند إزالة التطبيق spec: project: payments-team # AppProject يحدد نطاق RBAC source: repoURL: https://github.com/myorg/gitops-platform targetRevision: main path: services/payments-api/overlays/staging destination: server: https://kubernetes.default.svc # نفس الكلاستر حيث تعمل ArgoCD namespace: payments-staging syncPolicy: automated: prune: true # حذف الموارد المزالة من Git selfHeal: true # التراجع عن تغييرات kubectl اليدوية allowEmpty: false # عدم المزامنة مع حالة فارغة (شبكة أمان) syncOptions: - CreateNamespace=true - PrunePropagationPolicy=foreground - RespectIgnoreDifferences=true retry: limit: 3 backoff: duration: 5s factor: 2 maxDuration: 3m ignoreDifferences: - group: apps kind: Deployment jsonPointers: - /spec/replicas # تجاهل إذا كان HPA يدير عدد النسخ بشكل مباشر --- # clusters/production/argocd-apps/payments-api.yaml # الإنتاج: لا مزامنة تلقائية — يتطلب موافقة بشرية عبر ArgoCD UI أو CLI apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: payments-api-production namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: payments-team source: repoURL: https://github.com/myorg/gitops-platform targetRevision: main path: services/payments-api/overlays/production destination: server: https://prod-cluster-api.internal:6443 namespace: payments-production syncPolicy: syncOptions: - CreateNamespace=true - PrunePropagationPolicy=foreground retry: limit: 2 backoff: duration: 10s factor: 2 maxDuration: 5m # لا كتلة automated: — المزامنة في الإنتاج يدوية أو عبر Sync Windows
أفضل ممارسة الإنتاج — تعطيل المزامنة التلقائية، استخدام Sync Windows: لا تضبط أبداً automated: selfHeal: true على الإنتاج دون نافذة Sync Window لتجميد التغييرات على الأقل. قم بتكوين تعليقات argocd.argoproj.io/sync-wave للتحكم في ترتيب طرح الموارد (مثلاً: ConfigMap قبل Deployment، Deployment قبل HPA). استخدم ArgoCD Notifications لنشر رسالة Slack إلى قناة المناوبة لديك عند كل مزامنة إنتاج، متضمنةً رابط الفروق.

الخطوة 4: خط CI — إغلاق الحلقة

خط CI (GitHub Actions) يبني الـ image، يدفعه إلى سجل الحاويات، ثم يفتح pull request على مستودع الإعدادات لتحديث image-tag.yaml. هذه هي المهمة الوحيدة لـ CI في نظام GitOps فيما يتعلق بالنشر.

# .github/workflows/deploy.yml (في مستودع تطبيق payments-api) name: Build and Promote to Staging on: push: branches: [main] jobs: build-push: runs-on: ubuntu-latest outputs: image_tag: ${{ steps.meta.outputs.version }} steps: - uses: actions/checkout@v4 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/myorg/payments-api tags: | type=sha,prefix=sha-,format=short - name: Build and push uses: docker/build-push-action@v5 with: push: true tags: ${{ steps.meta.outputs.tags }} promote-staging: needs: build-push runs-on: ubuntu-latest steps: - name: Checkout config repo uses: actions/checkout@v4 with: repository: myorg/gitops-platform token: ${{ secrets.GITOPS_PAT }} path: gitops-platform - name: Update staging image tag working-directory: gitops-platform run: | TAG="${{ needs.build-push.outputs.image_tag }}" yq e '.images[0].newTag = "'$TAG'"' -i \ services/payments-api/overlays/staging/image-tag.yaml git config user.email "ci-bot@myorg.com" git config user.name "CI Bot" git checkout -b "promote/staging/$TAG" git add services/payments-api/overlays/staging/image-tag.yaml git commit -m "chore(staging): promote payments-api to $TAG" git push origin "promote/staging/$TAG" - name: Open PR to config repo env: GH_TOKEN: ${{ secrets.GITOPS_PAT }} run: | gh pr create \ --repo myorg/gitops-platform \ --title "Promote payments-api ${{ needs.build-push.outputs.image_tag }} to staging" \ --body "Automated promotion from CI. Merge to deploy." \ --base main \ --head "promote/staging/${{ needs.build-push.outputs.image_tag }}"

الخطوة 5: مخطط معمارية الخط الكامل

End-to-end GitOps delivery pipeline: app repo to multi-env cluster Developer git push App Repo payments-api GitHub Actions build + push image Container Registry ghcr.io/myorg open PR Config Repo (gitops-platform) services/ overlays/ clusters/ PR review + merge watches & reconciles ArgoCD App: staging + production auto-sync manual sync Staging Cluster namespace: payments-staging replicas=2 | auto-selfHeal=true Production Cluster namespace: payments-production replicas=5 | PDB enforced | manual sync image pull مزامنة GitOps تلقائية مزامنة بموافقة يدوية
خط GitOps الشامل من البداية إلى النهاية: دفع المطور يُشغّل CI الذي يفتح PR على مستودع الإعدادات؛ ArgoCD يصالح staging تلقائياً والإنتاج فقط عند موافقة يدوية.

الخطوة 6: أنماط الفشل الحرجة في الإنتاج وكيفية منعها

سيفشل هذا الخط بطرق متوقعة إذا لم تصمم ضدها من البداية. إليك أكثر أربعة أنماط فشل GitOps شيوعاً في الإنتاج وطرق التخفيف:

  1. CI يدفع مباشرةً إلى main في مستودع الإعدادات. يضع شخص ما قاعدة auto-merge لتسريع نشر staging. لاحقاً، خلل في CI يرقّي تلقائياً نفس العلامة المعطوبة إلى الإنتاج. الحل: اطلب دمج PR بموافقة بشرية لجميع البيئات، حتى staging. استخدم حماية فرع GitHub مع required_approvals: 1.
  2. ArgoCD selfHeal يتصارع مع HPA. HPA يوسّع Deployment إلى 8 نسخ تحت الحمل. ArgoCD يلاحظ أن الحالة المطلوبة في Git تقول 5 ويرتد. الحل: أضف كتلة ignoreDifferences لـ /spec/replicas على الـ Deployments التي يديرها HPA. هذا موضح في مانيفست Application لـ staging أعلاه.
  3. أسرار مُودَعة في مستودع الإعدادات. مهندس يضيف مانيفست Secret بكلمة مرور حقيقية بنص صريح. الحل: افرض هذا على مستوى المستودع بخطّاف pre-commit يبحث عن أسرار base64، واستخدم External Secrets Operator أو Sealed Secrets — أبداً مانيفستات Secret عادية في Git.
  4. ArgoCD لا يملك حدوداً للموارد وينهار بسبب نفاد الذاكرة أثناء مزامنة ضخمة. منصة كبيرة بمئات التطبيقات يمكنها إرباك Application Controller في ArgoCD. الحل: اضبط resource.requests وresource.limits على جميع مكونات ArgoCD، استخدم ApplicationSets لتجميع الإنشاء، وشغّل Application Controller مع ضبط --status-processors 20 --operation-processors 10 لحجم كلاسترك.
لا تخزّن أسراراً غير مشفّرة في مستودع إعدادات GitOps أبداً. حتى في مستودع خاص، أي نظام CI، أي متعاون يملك صلاحية القراءة، وأي PAT مسرّب يكشف تلك الأسرار بشكل دائم — تاريخ git أبدي. الحلول المعيارية في الصناعة هي External Secrets Operator (يسحب الأسرار من AWS Secrets Manager / Vault في وقت التشغيل) أو Sealed Secrets (يُشفّر السر بمفتاح خاص بالكلاستر بحيث يمكن للكلاستر وحده فكّ تشفيره). كلاهما يتكامل بشكل نظيف مع ArgoCD و Flux.

ما الذي بنيته

بتجميع القطع في هذا المشروع، أصبح لديك الآن خط تسليم GitOps كامل يعكس الممارسات الجاهزة للإنتاج:

  • مستودع إعدادات mono مع فصل واضح بين المانيفستات الأساسية، والطبقات الفوقية لكل بيئة، وموارد ArgoCD Application على مستوى الكلاستر.
  • CI يكتب فقط على Git — لا بيانات اعتماد كلاستر، لا استدعاءات kubectl مباشرة من الـ pipelines.
  • Staging يتزامن تلقائياً مع تصحيح الانجراف؛ الإنتاج يتطلب موافقة مزامنة بشرية.
  • تحديثات عناوين الـ image معزولة في ملف واحد لكل بيئة، مما يجعل PRs بسيطة وقابلة للمراجعة.
  • مانيفستات ArgoCD Application تعيش في مستودع الإعدادات — المنصة تصف نفسها بنفسها وقابلة للاسترداد من Git وحده.

هذا هو النمط الذي تستخدمه الفرق التي تشحن مئات عمليات النشر يومياً. تهانينا على إتمام دورة GitOps. الأساس الذي بنيته هنا — المصالحة المبنية على السحب، والإعدادات التصريحية، والتصحيح التلقائي للانجراف، وبوابات الترقية — هو نفس الأساس الذي يدعم بنية التحتية للنشر في أكبر المنظمات الهندسية في العالم.