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

العوامل والبنيات الموزعة

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

العوامل والبنيات الموزعة

المتحكم في Jenkins (الذي كان يُعرف سابقاً بـ"master") هو العقل المدبّر لمنصة CI — يجدول المهام، ويدير الحالة، ويقدم واجهة المستخدم. يجب أن لا ينفّذ أعباء البناء أبداً. كل عملية بناء فعلية تجري على عامل (agent)، وهو عملية يتصل بها المتحكم عبر بروتوكول Remoting فوق TCP أو WebSocket، مما يوفر لك العزل الآمن، وقابلية التوسع الأفقي، والقدرة على مطابقة بيئات البناء مع متطلبات كل مهمة.

العوامل الثابتة

العامل الثابت هو آلة دائمة (جهاز VM أو خادم فعلي أو حاوية تعمل باستمرار) مسجّلة في Jenkins تحت Manage Jenkins → Nodes. يتصل بها المتحكم عبر SSH (أو تتصل العاملة بالمتحكم باستخدام agent.jar) وتحافظ على اتصال JNLP/TCP دائم.

يُضبط كل عقدة ثابتة بما يلي:

  • التسميات (Labels) — وسوم مفصولة بمسافات مثل linux وdocker وgpu-builder وwindows. يختار تعبير agent { label 'linux && docker' } في الـpipeline العقد المطابقة.
  • المنفّذون (Executors) — عدد البنيات المتزامنة التي تقبلها العقدة (عادةً 1–2× عدد المعالجات).
  • المجلد الجذري — مجلد مساحة العمل؛ استخدم قرص SSD محلياً سريعاً وليس NFS.
  • التوافر — دائم الاتصال أو عند الطلب (يُشغَّل عند الحاجة ويُقطع بعد الخمول).
العوامل الثابتة بسيطة لكنها مكلفة تشغيلياً. أنت مسؤول عن تحديث النظام، وتخطيط الطاقة الاستيعابية، والتنظيف الدوري. احتفظ بها للبنيات التي تحتاج فعلاً إلى حالة دائمة — مثل عقدة macOS لتوقيع كود iOS، أو عقدة Windows لمشاريع .NET Framework.

الإطلاق عبر SSH (الطريقة الموصى بها): يفتح Jenkins اتصال SSH إلى مضيف العامل ويشغّل java -jar agent.jar. تأكد من تخزين المفتاح الخاص لـSSH الخاص بالمتحكم في Jenkins Credentials ومن إمكانية الوصول إلى مضيف العامل على المنفذ 22.

العوامل الديناميكية

تُجهَّز العوامل الديناميكية عند الطلب وتُدمَّر بعد اكتمال البناء. أبرز الخيارات المتاحة:

  • إضافة Kubernetes — تُنشئ Pod لكل بناء، تُشغّل البناء داخل حاوية، ثم تُدمّره. هذا هو النموذج المعياري لـJenkins في البيئات الحديثة.
  • إضافات EC2 / Azure VM / Google Compute — تُجهّز جهاز VM سحابياً، تُجري البناء، ثم تُنهي الجهاز. مفيد حين تحتاج عزلاً كاملاً على مستوى نظام التشغيل.
  • إضافة Docker — تُشغّل حاوية لكل بناء على مضيف Docker. أبسط من Kubernetes لكنها مرتبطة بمضيف Docker واحد.

عوامل الحاويات: إضافة Kubernetes

على نطاق واسع، تُشغّل معظم المؤسسات الكبيرة عوامل Jenkins كـPods على Kubernetes. تُنشئ الإضافة PodTemplate — وهو جزء من مواصفات Pod — لكل نوع بيئة بناء. عندما يطلب الـpipeline تسمية مطابقة، تستدعي الإضافة Kubernetes API، يبدأ الـPod، يتصل حاوية jnlp بالمتحكم، وتُنفَّذ خطوات البناء داخل الحاويات المحددة.

Jenkins Distributed Build Fleet: Controller, Static Agents, and Kubernetes Dynamic Agents Jenkins Controller Scheduler / State / UI Static Agents macOS Node label: macos ios-build Windows Node label: windows dotnet SSH / JNLP Kubernetes Cluster Build Pod jnlp maven:3.9 label: java-build Build Pod jnlp node:20 label: node-build New Pod — provisioned on demand, destroyed after build completes k8s API agent dial-back
أسطول عوامل Jenkins: عقد ثابتة للبنيات الخاصة بالمنصة، وPods على Kubernetes تُجهَّز لكل بناء لجميع الأعباء المعيارية.

يبدو الحد الأدنى من PodTemplate على Kubernetes في pipeline تصريحي كما يلي:

// Declarative Pipeline مع عامل Kubernetes pipeline { agent { kubernetes { label 'java-build' defaultContainer 'maven' yaml """ apiVersion: v1 kind: Pod spec: serviceAccountName: jenkins-agent containers: - name: jnlp image: jenkins/inbound-agent:3256.v88a_f6e922152-1 resources: requests: { cpu: "100m", memory: "256Mi" } - name: maven image: maven:3.9.6-eclipse-temurin-21 command: ["sleep", "infinity"] resources: requests: { cpu: "500m", memory: "1Gi" } limits: { cpu: "2", memory: "2Gi" } - name: kaniko image: gcr.io/kaniko-project/executor:v1.23.0 command: ["sleep", "infinity"] """ } } stages { stage('Build') { steps { container('maven') { sh 'mvn -B -ntp package -DskipTests' } } } stage('Docker Build & Push') { steps { container('kaniko') { sh ''' /kaniko/executor \ --context=dir://${WORKSPACE} \ --destination=registry.example.com/myapp:${BUILD_NUMBER} \ --cache=true ''' } } } } }
استخدم Kaniko بدلاً من Docker-in-Docker (DinD) لبناء صور الحاويات داخل عوامل Kubernetes. يتطلب DinD Pod مُميَّزاً (privileged) — وهو خطر أمني بالغ في المجموعات متعددة المستأجرين. يبني Kaniko صور OCI كاملةً في فضاء المستخدم دون الحاجة إلى Docker daemon.

استراتيجية التسمية على نطاق واسع

التسميات هي الطريقة التي يُطابق بها المتحكم المهام مع الطاقة الاستيعابية. يمنع التصنيف المنهجي للتسميات فئة أعطال "يعمل على جهازي فقط":

  • نظام التشغيل / المنصةlinux، windows، macos-arm64
  • بيئة التشغيلjava17، node20، python311
  • القدرةdocker، gpu، large-mem (للبنيات الضخمة أو تعلم الآلة)
  • البيئةprod-deploy (عقد مقيّدة تحمل بيانات اعتماد السحابة)

في الـpipeline يمكن دمج التسميات بعوامل منطقية:

// تشغيل على عقدة Linux تمتلك Docker وbيئة python311 agent { label 'linux && docker && python311' } // تشغيل على macOS أو windows (مصفوفة اختبار متعددة المنصات) agent { label 'macos-arm64 || windows' }

إعادة استخدام PodTemplate عبر واجهة إضافة Kubernetes أو المكتبات المشتركة

تعريف yaml: """...""" داخل كل Jenkinsfile يؤدي إلى انجراف الإعداد. النمط الإنتاجي هو تعريف كائنات PodTemplate الرسمية إما في إعداد إضافة Kubernetes العام (Manage Jenkins → Clouds → Kubernetes → Pod Templates) أو — والأفضل — في مكتبة مشتركة كدالة مساعدة. تستدعي الـpipelines الفردية حينئذٍ agent { label 'java-build' } وتحصل على المواصفة المُدارة مركزياً.

عزل المتحكم: استخدم دائماً agent none

لا تسمح أبداً للـpipeline بالعمل افتراضياً على المتحكم. أعلن دائماً agent none على المستوى الأعلى وعيّن عوامل محددة لكل مرحلة. إذا تعطلت خطوة بناء أو سرّبت ملفات على المتحكم، فقد تُفسد بيانات البناء وتستنزف مساحة القرص وتُعطّل منصة CI بالكامل.
pipeline { agent none // المتحكم لا يُنفّذ أي خطوات بناء stages { stage('Build') { agent { label 'linux && docker' } steps { sh 'make build' } } stage('Deploy') { agent { label 'prod-deploy' } steps { sh './deploy.sh' } } } }

أنماط الأعطال في الإنتاج

  • انقطاع العامل أثناء البناء — يتوقف المهمة منتظرةً إعادة الاتصال؛ اضبط JNLP_TIMEOUT وهيّئ حدود إعادة المحاولة في إضافة السحابة.
  • تراكم مساحات العمل على العوامل الثابتة — تملأ مساحات العمل القديمة القرص؛ استخدم إضافة Workspace Cleanup ومهمة cleanWs() ليلية على كل عقدة.
  • طرد الـPod أثناء البناء — قد يطرد Kubernetes الـPod عند الضغط على الموارد؛ اضبط priorityClassName: system-cluster-critical للـpipelines الحرجة وهيّئ PodDisruptionBudgets على المجموعة.
  • بطء سحب الصور — الصور الكبيرة مثل maven:3.9 (500 ميغابايت+) تُسبب تأخيرات في الإطلاق البارد؛ اسحب الصور مسبقاً على العقد باستخدام DaemonSet أو سجل وسيط يُخزّن الصور مؤقتاً.

الحجم الصحيح للعوامل

اطلب فقط ما يحتاجه البناء فعلاً، لكن حدّد قيوداً لمنع مشكلة الجار المزعج. عيّن مواصفات البناء بـkubectl top pod أثناء تشغيل تمثيلي، ثم اضبط requests عند قيمة P50 المقاسة وlimits عند P99. هذا يمنع كلاً من نقص التوفير (OOMKilled) وزيادة التوفير (هدر في سعة المجموعة).