Jenkins والتكامل المستمر المؤسسي

تشغيل Jenkins على نطاق واسع

18 دقيقة الدرس 9 من 28

تشغيل Jenkins على نطاق واسع

نسخة Jenkins تخدم خمسة مهندسين وعدداً محدوداً من المهام ليست سوى بيئة تجريبية. أما نسخة Jenkins تخدم 500 مهندس وتُشغّل 10,000 بناء يومياً فهي بنية تحتية تستوجب نفس الصرامة التشغيلية التي تُطبّقها على أي قاعدة بيانات إنتاجية أو كلاستر Kubernetes. يغطّي هذا الدرس أربعة محاور تُفرّق بين نشر Jenkins المُدار جيداً وذلك الذي ينهار تحت الضغط: استراتيجية النسخ الاحتياطي، وإدارة الإضافات، والإعداد كرمز (JCasC)، واعتبارات التوفر العالي.

استراتيجية النسخ الاحتياطي: ما الذي يستحق الحفظ فعلاً؟

يخزّن Jenkins كل شيء تقريباً على القرص ضمن $JENKINS_HOME. قبل كتابة سكريبتات النسخ الاحتياطي، افهم ما يقع في هذا المسار وتكلفة استرداد كل عنصر منه:

  • config.xml — ملف الإعداد الرئيسي (منظومة الأمان، التفويضات، إعدادات الأدوات العامة). خسارته تعني إعادة تكوين Jenkins من الصفر.
  • jobs/ — تعريف كل مهمة. خسارته تعني فقدان جميع إعدادات Pipeline ومشغّلات البناء وسجل البنائات.
  • credentials.xml ومجلد secrets/ — بيانات الاعتماد المشفّرة. خسارتها تُعطّل كل Pipeline تعتمد على أي مصادقة.
  • plugins/ — ملفات .jpi للإضافات المثبّتة. يمكن إعادة تثبيتها لكن العملية مُستهلِكة للوقت وحساسة للإصدار.
  • users/ — حسابات المستخدمين المحليين (إن كنت تستخدم قاعدة المستخدمين الداخلية لـ Jenkins).
  • مجلدات builds/ — سجلات البنائات السابقة والحزم المُنتَجة. تكون في الغالب أكبر البيانات حجماً وقد يكون قبول خسارتها مناسباً وفقاً لمتطلبات التدقيق.
مخاطر الإنتاج: كثير من الفرق تأخذ نسخة احتياطية من $JENKINS_HOME بأمر tar ساذج أثناء تشغيل Jenkins. النتيجة نسخة احتياطية تالفة. يكتب Jenkins باستمرار إلى ملفات عدة — خاصة قائمة انتظار البناء وقاعدة بيانات البصمات. خمّد Jenkins دائماً قبل أخذ اللقطة، أو استخدم لقطة على مستوى نظام الملفات (مثل LVM أو EBS snapshot) فهي لحظية.

سير العمل الموصى به للنسخ الاحتياطي عند الحجم الكبير يستخدم إضافة Thin Backup أو سكريبتاً مخصصاً يستدعي واجهة Jenkins للتهدئة قبل أخذ اللقطة:

#!/bin/bash # سكريبت نسخ احتياطي آمن لـ Jenkins # يُشغَّل عبر cron؛ يتطلب JENKINS_URL و JENKINS_TOKEN في متغيرات البيئة set -euo pipefail JENKINS_HOME=/var/lib/jenkins BACKUP_DIR=/mnt/backup/jenkins TIMESTAMP=$(date +%Y%m%d-%H%M%S) # 1. تهدئة Jenkins (لا تبدأ بنائات جديدة) curl -sf -X POST "${JENKINS_URL}/quietDown" \ --user "backup-bot:${JENKINS_TOKEN}" # انتظر حتى 5 دقائق لانتهاء البنائات الجارية for i in $(seq 1 30); do BUILDING=$(curl -sf "${JENKINS_URL}/computer/api/json?depth=2" \ --user "backup-bot:${JENKINS_TOKEN}" | \ python3 -c "import sys,json; d=json.load(sys.stdin); \ print(any(e['idle']==False for c in d['computer'] for e in c.get('executors',[])))") [ "$BUILDING" = "False" ] && break sleep 10 done # 2. خذ لقطة للمجلدات الحيوية فقط (تجاهل سجلات البناء الضخمة) tar -czf "${BACKUP_DIR}/jenkins-config-${TIMESTAMP}.tar.gz" \ --exclude="${JENKINS_HOME}/jobs/*/builds" \ --exclude="${JENKINS_HOME}/workspace" \ --exclude="${JENKINS_HOME}/caches" \ "${JENKINS_HOME}" # 3. إلغاء التهدئة حتى تستأنف البنائات فوراً curl -sf -X POST "${JENKINS_URL}/cancelQuietDown" \ --user "backup-bot:${JENKINS_TOKEN}" echo "اكتمل النسخ الاحتياطي: jenkins-config-${TIMESTAMP}.tar.gz"

عند التوسع الكبير، الأسلوب الأفضل هو معاملة $JENKINS_HOME كحجم تخزين دائم على طبقة تخزين سحابية (EBS أو Persistent Disk أو Azure Disk) وأخذ لقطات يومية للحجم. هذا لحظي ومتسق بعد الأعطال ومستقل عن داخليات Jenkins.

إدارة الإضافات: السبب الجذري لمعظم الانقطاعات

بيئة الإضافات في Jenkins هي أكبر مزاياه وأخطر نقاط هجومه. معظم انقطاعات إنتاج Jenkins ناجمة عن أحد ثلاثة أنماط فشل: تحديث إضافة يكسر واجهة برمجية تعتمد عليها إضافة أخرى، أو إضافة تُدخل تراجعاً في تنفيذ Pipeline، أو ثغرة أمنية في إضافة قديمة.

الممارسة الاحترافية: ثبّت كل إضافة على إصدار محدد في نظام التحكم بالإصدار. عامل ترقيات الإضافات كتغييرات كود يجب أن تمر ببيئة تجريبية. لا تضغط أبداً "Update All" على Jenkins الإنتاجي.

أداة Plugin Installation Manager Tool (PIMT)jenkins-plugin-cli — تتيح الإعلان عن الإضافات في ملف نصي وتثبيت مجموعة إصدارات دقيقة في صورة Docker أثناء البناء. هذا هو المعيار الإنتاجي:

# plugins.txt — قائمة إضافات محددة الإصدار (أضفها لـ git) # الصيغة: plugin-id:version workflow-aggregator:596.v8c21c963d92d git:5.2.1 credentials:1319.v7eb_51b_3a_c97b_ blueocean:1.27.9 job-dsl:1.87 configuration-as-code:1775.v810dc950b_514 kubernetes:4190.v0f7e7e pipeline-utility-steps:2.16.2 timestamper:1.26 # Dockerfile — دمج الإضافات عند بناء الصورة FROM jenkins/jenkins:2.440.3-lts-jdk21 USER root RUN apt-get update && apt-get install -y curl USER jenkins COPY plugins.txt /usr/share/jenkins/ref/plugins.txt RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt \ --latest false

عند الحاجة لتحديث إضافة، حدّث رقم الإصدار في plugins.txt، ابنِ صورة جديدة، انشرها على البيئة التجريبية، شغّل اختبارات Pipeline الأساسية، ثم أتح الترقية للإنتاج. الترقية الآن عملية مراجعة كود لا نقرة على واجهة رسومية.

Jenkins Configuration as Code (JCasC)

تحوّل إضافة Configuration as Code إعداد Jenkins المستند إلى XML إلى YAML مقروء يمكن تخزينه في git ومراجعته ومقارنته وتطبيقه تلقائياً عند الإقلاع. تحل هذه الإضافة أعقد مشاكل تشغيل Jenkins: الانجراف في حالة المتحكم — حيث يكون المتحكم الإنتاجي قد نُقّر إلى إعداد لا يستطيع أحد إعادة إنتاجه.

JCasC configuration flow Git Repository jenkins.yaml git push CI Pipeline validates YAML schema on merge Docker Image controller + plugins deploy Jenkins Controller auto-applies jenkins.yaml JCasC GitOps Loop Controller config is always reproducible from git Secrets Vault AWS SSM / Vault / k8s secrets env inject
حلقة JCasC GitOps: إعداد المتحكم قابل دائماً للاستعادة من git.

ملف JCasC الإنتاجي لنشر Jenkins مستند إلى Kubernetes يبدو هكذا:

# jenkins.yaml — مخزون في git؛ تُحمّله إضافة JCasC عند الإقلاع # الأسرار تُحقن عبر متغيرات البيئة؛ لا تُضمَّن أبداً هنا بنص صريح. jenkins: systemMessage: "مُدار بواسطة Configuration as Code — لا تعدّل عبر الواجهة" numExecutors: 0 # المتحكم لا يُنفّذ بنائات؛ العملاء فقط mode: EXCLUSIVE scmCheckoutRetryCount: 2 securityRealm: ldap: configurations: - server: ldaps://ldap.corp.example.com:636 rootDN: "dc=corp,dc=example,dc=com" userSearchBase: "ou=people" groupSearchBase: "ou=groups" managerDN: "cn=jenkins,ou=service-accounts,dc=corp,dc=example,dc=com" managerPasswordSecret: "${LDAP_MANAGER_PASSWORD}" # متغير بيئة authorizationStrategy: roleBased: roles: global: - name: "admin" permissions: - "Overall/Administer" assignments: - "jenkins-admins" # مجموعة LDAP - name: "developer" permissions: - "Overall/Read" - "Job/Build" - "Job/Read" assignments: - "engineers" clouds: - kubernetes: name: "kubernetes" serverUrl: "https://kubernetes.default.svc" namespace: "jenkins" jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080" podRetention: "Never" templates: - name: "default-agent" label: "k8s-agent" containers: - name: "jnlp" image: "jenkins/inbound-agent:3248.v65ecb_254c298-1" resourceLimitCpu: "1000m" resourceLimitMemory: "2Gi" resourceRequestCpu: "500m" resourceRequestMemory: "1Gi" unclassified: location: url: "https://jenkins.corp.example.com/" adminAddress: "jenkins-alerts@corp.example.com" credentials: system: domainCredentials: - credentials: - usernamePassword: scope: GLOBAL id: "artifactory-bot" description: "حساب خدمة Artifactory" username: "jenkins-bot" password: "${ARTIFACTORY_PASSWORD}" # لا تخزّن نصاً صريحاً هنا
المفهوم الأساسي: يفصل JCasC بين البنية (نوع المصادقة، السحابات، إعدادات المهام) والأسرار (كلمات المرور والرموز). البنية تذهب إلى git. الأسرار تأتي من خزنة خارجية أثناء التشغيل عبر حقن متغيرات البيئة. هذا النمط يعني أن jenkins.yaml آمن للإيداع في مستودع خاص.

اعتبارات التوفر العالي

Jenkins الكلاسيكي لديه قيد جوهري في التوفر العالي: المتحكم نقطة فشل وحيدة. عند إعادة تشغيله تنقطع جميع البنائات الجارية. عند تعطّله لا تبدأ بنائات جديدة. بالنسبة لمنظمة من 500 مهندس تعتمد دورة التطوير لديها على CI، فإن توقف المتحكم حادثة من الأولوية الأولى.

ثمة ثلاثة مستويات لنهج التوفر العالي، بترتيب تصاعدي للتعقيد والتكلفة:

  1. إعادة التشغيل السريعة (الأكثر شيوعاً): شغّل Jenkins كحاوية أو خدمة systemd مع إعادة تشغيل تلقائية عند الفشل. خزّن $JENKINS_HOME على حجم تخزين دائم. استهدف RTO أقل من دقيقتين. يغطي هذا 90% من الحوادث (نفاد الذاكرة، الانهيار، الترقية المتجددة).
  2. نشط/احتياطي دافئ: نسخة ثانية من المتحكم تُبقى دافئة وتُحمّل نفس حجم التخزين بوضع القراءة فقط. عند الفشل يُعاد تحميل الحجم بصلاحية الكتابة على النسخة الاحتياطية. يستلزم هذا طبقة تخزين كتل مشتركة (AWS EFS أو NFS أو حلول سحابية مخصصة). البنائات الجارية تنقطع لكن البنائات الجديدة تستأنف في أقل من 30 ثانية.
  3. Jenkins HA (CloudBees CI): التوزيع التجاري من CloudBees يدعم إعداد HA حقيقياً بتكوين نشط-نشط مع قائمة انتظار بناء موزعة ودون نقطة فشل وحيدة. هذا ما تستخدمه Netflix وGoldman Sachs وشركات مماثلة. Jenkins مفتوح المصدر لا يمتلك هذه القدرة.
الممارسة الاحترافية: عند النقطة التي يُطلق فيها توقف Jenkins تصعيداً، الإجابة الصحيحة في الغالب هي الانتقال إلى نظام CI سحابي أصلي (GitHub Actions أو Tekton أو Argo Workflows) بدلاً من الاستثمار أكثر في Jenkins HA. HA لـ Jenkins استثمار تشغيلي متناقص العائد. Jenkins يتفوق في المرونة؛ لا يتفوق في التوفر الصفري الصيانة.

بصرف النظر عن مستوى التوفر العالي، طبّق هذه الممارسات التشغيلية الأساسية في كل حجم:

  • شغّل المتحكم بـصفر منفّذين (numExecutors: 0 في JCasC). عملية المتحكم يجب أن تُنسّق فقط؛ جميع أعمال البناء تذهب للعملاء. هذا يُبقي المتحكم مستقراً ويمنع ضغط بناء الجيران الصاخبين من التأثير على الواجهة وواجهة API.
  • اضبط مُتلقفات البناء على كل مهمة — قيّد تاريخ البناء بالعدد و/أو العمر. تاريخ البناء غير المحدود سيملأ القرص ويُبطئ الواجهة.
  • راقب /metrics (إضافة Prometheus) وأنشئ تنبيهات على استخدام heap المتحكم فوق 80%، وعمق قائمة انتظار المنفّذين، وضغط القرص على $JENKINS_HOME.
  • شغّل تصدير الإعداد دورياً باستخدام JCasC: curl -X POST $JENKINS_URL/configuration-as-code/export وقارن الناتج بـ jenkins.yaml المثبّت. أي انجراف يعني أن أحداً نقر على الواجهة وملف IaC الخاص بك قديم.

يحوّل هذا الرباعي معاً — النسخ الاحتياطية المُختبَرة، والإضافات المُثبَّتة الإصدار، والإعداد المُدار بـ JCasC، وبنية التوفر العالي المناسبة — Jenkins من خدمة مشتركة هشّة إلى بنية CI تحتية موثوقة وقابلة للتدقيق، تستطيع الصمود أمام دوريات المناوبة ونمو الشركة دون تدخلات استثنائية.