مساء الخير
هناك بالفعل العديد من المقالات حول Habré حول جنكينز و ci / cd و kubernetes ، لكن في هذا لا أريد التركيز على تحليل قدرات هذه التقنيات ، ولكن على أبسط مواصفاتها لبناء خط أنابيب ci / cd.
أعني أن القارئ لديه فهم أساسي لرسو السفن ، ولن أتطرق إلى مواضيع تثبيت وتكوين kubernetes. سيتم عرض جميع الأمثلة على minikube ، ولكن يمكن أيضًا تطبيقها على EKS أو GKE أو ما شابه ذلك بدون تغييرات كبيرة.

البيئة
أقترح استخدام البيئات التالية:
- اختبار - للنشر اليدوي والاختبار الفرع
- التدريج - بيئة يتم فيها تلقائيًا نشر جميع التغييرات التي تدخل في السجل الرئيسي
- الإنتاج - البيئة التي يستخدمها المستخدمون الحقيقيون ، حيث ستذهب التغييرات بعد تأكيد قابليتها للتشغيل على التدريج
سيتم تنظيم البيئات باستخدام مساحات أسماء kubernetes داخل كتلة واحدة. هذا النهج بسيط وسريع قدر الإمكان في البداية ، ولكن له أيضًا عيوبه: مساحات الأسماء ليست معزولة تمامًا عن بعضها البعض في kubernetes.
في هذا المثال ، سيكون لكل مساحة اسم نفس مجموعة ConfigMaps مع تكوينات هذه البيئة:
apiVersion: v1 kind: Namespace metadata: name: production --- apiVersion: v1 kind: ConfigMap metadata: name: environment.properties namespace: production data: environment.properties: | env=production
هيلم
Helm هو تطبيق يساعد في إدارة الموارد المثبتة على kubernetes.
تعليمات التثبيت يمكن العثور عليها هنا .
للبدء ، يجب تهيئة جراب tiller لاستخدام رأس المجموعة:
helm init
جنكينز
سأستخدم جنكينز لأنها منصة بسيطة ومرنة وشائعة لبناء المشاريع. سيتم تثبيته في مساحة اسم منفصلة لعزل نفسه عن البيئات الأخرى. نظرًا لأنني أخطط لاستخدام helm في المستقبل ، يمكنني تبسيط عملية تثبيت Jenkins باستخدام مخططات مفتوحة المصدر موجودة:
helm install --name jenkins --namespace jenkins -f jenkins/demo-values.yaml stable/jenkins
يحتوي demo-values.yaml على إصدار Jenkins ومجموعة من الإضافات المثبتة مسبقًا واسم النطاق والتهيئة الأخرى
القيم التجريبية Master: Name: jenkins-master Image: "jenkins/jenkins" ImageTag: "2.163-slim" OverwriteConfig: true AdminUser: admin AdminPassword: admin InstallPlugins: - kubernetes:1.14.3 - workflow-aggregator:2.6 - workflow-job:2.31 - credentials-binding:1.17 - git:3.9.3 - greenballs:1.15 - google-login:1.4 - role-strategy:2.9.0 - locale:1.4 ServicePort: 8080 ServiceType: NodePort HostName: jenkins.192.168.99.100.nip.io Ingress: Path: / Agent: Enabled: true Image: "jenkins/jnlp-slave" ImageTag: "3.27-1" #autoadjust agent resources limits resources: requests: cpu: null memory: null limits: cpu: null memory: null #to allow jenkins create slave pods rbac: install: true
يستخدم هذا التكوين admin / admin كاسم مستخدم وكلمة مرور لتسجيل الدخول ، ويمكن إعادة تكوينه لاحقًا. أحد الخيارات الممكنة هو google SSO (مكون google-login الإضافي مطلوب لذلك ، إعداداته موجودة في Jenkins> Manage Jenkins> تكوين Global Security> التحكم في الوصول> Security Security> تسجيل الدخول باستخدام Google).
سيتم تكوين Jenkins على الفور لإنشاء عبودية لمرة واحدة لكل بناء تلقائيًا. بفضل هذا ، لم يعد الفريق يتوقع وكيلًا مجانيًا للتجميع ، وستتمكن الشركة من توفير عدد الخوادم المطلوبة.

خارج المربع أيضًا ، يتم تكوين PersistenceVolume لحفظ خطوط الأنابيب عند إعادة التشغيل أو التحديث.
لكي تعمل برامج النشر التلقائية بشكل صحيح ، تحتاج إلى منح إذن مسؤول نظام المجموعة لـ Jenkins للحصول على قائمة بالموارد في kubernetes ومعالجتها.
kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default
في المستقبل ، يمكنك تحديث Jenkins باستخدام helm في حالة وجود إصدارات جديدة من الإضافات أو تغييرات التكوين.
helm upgrade jenkins stable/jenkins -f jenkins/demo-values.yaml
يمكن القيام بذلك من خلال واجهة جينكينز نفسها ، ولكن مع دفة القيادة ستتاح لك الفرصة للعودة إلى المراجعات السابقة باستخدام:
helm history jenkins helm rollback jenkins ${revision}
بناء التطبيق
على سبيل المثال ، سأقوم بإنشاء ونشر أبسط تطبيق تمهيد نابض. وبالمثل مع جنكينز سوف أستخدم الدفة.
سيتم التجمع في التسلسل التالي:
- الخروج
- تجميع
- اختبار الوحدة
- اختبار التكامل
- تجميع قطعة أثرية
- نشر قطعة أثرية في سجل عامل ميناء
- نشر قطعة أثرية على التدريج (فقط للفرع الرئيسي)
لهذا أنا استخدم ملف جنكينز . في رأيي ، هذه طريقة مرنة للغاية (ولكنها للأسف ليست أسهل) لتكوين تجميع المشروع. تتمثل إحدى ميزاته في القدرة على الاحتفاظ بتكوين مجموعة المشروع في المستودع مع المشروع نفسه.
الخروج

في حالة bitbucket أو مؤسسة github ، يمكنك تكوين Jenkins لإجراء مسح دوري للحساب بالكامل بحثًا عن مستودعات مع Jenkinsfile وإنشاء التجميعات تلقائيًا لهم. سوف جينكينز جمع كل من الماجستير والفروع. سيتم عرض طلبات السحب في علامة تبويب منفصلة. هناك خيار أكثر بساطة - إضافة مستودع بوابة منفصل ، بغض النظر عن مكان استضافته. في هذا المثال ، سأفعل ذلك تمامًا. كل ما يلزم هو في قائمة Jenkins> عنصر جديد> Multibranch Pipeline ، حدد اسم التجميع واربط مستودع git.
تجميع
نظرًا لأن Jenkins يقوم بإنشاء ملف تعريف جديد لكل مجموعة ، في حالة استخدام أدوات التجميع أو ما شابهها ، سيتم تنزيل التبعيات مرة أخرى في كل مرة. لتجنب ذلك ، يمكنك تخصيص PersistenceVolume لـ .m2 أو ذاكرة تخزين مؤقت مماثلة وتثبيتها في الحافظة التي تقوم بإنشاء المشروع.
apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "repository" namespace: "jenkins" spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi
في حالتي ، سمح هذا بتسريع خط الأنابيب من حوالي 4 إلى 1 دقيقة.
الإصدار
لكي يعمل CI / CD بشكل صحيح ، يحتاج كل بناء إلى إصدار فريد.
سيكون الخيار الجيد للغاية هو استخدام الإصدار الدلالي . سيسمح لك ذلك بتتبع التغييرات المتوافقة وغير المتوافقة مع الإصدارات السابقة ، ولكن من الصعب أتمتة مثل هذه الإصدارات.
في هذا المثال ، سوف أقوم بإنشاء إصدار من المعرف وتاريخ الالتزام ، وكذلك اسم الفرع ، إذا لم يكن سيدًا. على سبيل المثال 56e0fbdc-201802231623 أو b3d3c143-201802231548-PR-18 .
مزايا هذا النهج:
- سهولة الأتمتة
- من السهل الحصول على الكود المصدري ووقت إنشائه من الإصدار
- بصريا ، يمكنك التمييز بين نسخة الإصدار للمرشح (من المعالج) أو التجريبية (من الفرع)
لكن: - هذا الإصدار هو أصعب للاستخدام في التواصل الشفوي
- ليس من الواضح ما إذا كانت هناك تغييرات غير متوافقة.
نظرًا لأن صورة عامل ميناء يمكن أن تحتوي على عدة علامات في نفس الوقت ، يمكن دمج الأساليب: كل الإصدارات تستخدم الإصدارات التي تم إنشاؤها ، ويتم تمييز تلك التي تندرج تحت الإنتاج (يدويًا) بالإصدار الدلالي. هذا ، بدوره ، يرتبط بمزيد من التعقيد في التنفيذ والغموض الذي يجب أن يظهر عليه التطبيق.
التحف
ستكون نتيجة التجميع:
- صورة عامل ميناء مع تطبيق سيتم تخزينه وتحميله من سجل عامل ميناء. سيستخدم المثال السجل المدمج من minikube ، والذي يمكن استبداله بلوحة وصل أو سجل خاص من amazon (ecr) أو google (لا تنسَ تقديم بيانات اعتماد لهم باستخدام بنية withCredentials).
- مخططات helm مع وصف لنشر التطبيق (النشر ، الخدمة ، إلخ) في دليل helm. من الناحية المثالية ، يجب أن يتم تخزينها في مستودع منفصل من المصنوعات اليدوية ، ولكن لتبسيطها ، يمكن استخدامها عن طريق التحقق من الالتزام اللازم من بوابة.
جنكينفيل
نتيجة لذلك ، سيتم إنشاء التطبيق باستخدام Jenkinsfile التالية:
جنكينفيل def branch def revision def registryIp pipeline { agent { kubernetes { label 'build-service-pod' defaultContainer 'jnlp' yaml """ apiVersion: v1 kind: Pod metadata: labels: job: build-service spec: containers: - name: maven image: maven:3.6.0-jdk-11-slim command: ["cat"] tty: true volumeMounts: - name: repository mountPath: /root/.m2/repository - name: docker image: docker:18.09.2 command: ["cat"] tty: true volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: repository persistentVolumeClaim: claimName: repository - name: docker-sock hostPath: path: /var/run/docker.sock """ } } options { skipDefaultCheckout true } stages { stage ('checkout') { steps { script { def repo = checkout scm revision = sh(script: 'git log -1 --format=\'%h.%ad\' --date=format:%Y%m%d-%H%M | cat', returnStdout: true).trim() branch = repo.GIT_BRANCH.take(20).replaceAll('/', '_') if (branch != 'master') { revision += "-${branch}" } sh "echo 'Building revision: ${revision}'" } } } stage ('compile') { steps { container('maven') { sh 'mvn clean compile test-compile' } } } stage ('unit test') { steps { container('maven') { sh 'mvn test' } } } stage ('integration test') { steps { container ('maven') { sh 'mvn verify' } } } stage ('build artifact') { steps { container('maven') { sh "mvn package -Dmaven.test.skip -Drevision=${revision}" } container('docker') { script { registryIp = sh(script: 'getent hosts registry.kube-system | awk \'{ print $1 ; exit }\'', returnStdout: true).trim() sh "docker build . -t ${registryIp}/demo/app:${revision} --build-arg REVISION=${revision}" } } } } stage ('publish artifact') { when { expression { branch == 'master' } } steps { container('docker') { sh "docker push ${registryIp}/demo/app:${revision}" } } } } }
خطوط أنابيب جنكينز الإضافية لإدارة دورة حياة التطبيق
افترض أن المستودعات مرتبة بحيث:
- يحتوي على تطبيق منفصل في شكل صورة عامل ميناء
- يمكن نشرها باستخدام ملفات helm الموجودة في دليل helm
- يتم إصدارها باستخدام نفس النهج ولديها ملف helm / setVersion.sh لإعداد المراجعات في مخططات helm
ثم يمكننا بناء العديد من خطوط أنابيب Jenkinsfile لإدارة دورة حياة التطبيق ، وهي:
في Jenkinsfile من كل مشروع ، يمكنك إضافة استدعاء خط أنابيب النشر الذي سيتم تنفيذه في كل مرة يتم فيها ترجمة الفرع الرئيسي بنجاح أو عندما يطلب فرع النشر بيئة الاختبار بشكل صريح.
جنكينز ملف نشر خط أنابيب الدعوة ... stage ('deploy to env') { when { expression { branch == 'master' || params.DEPLOY_BRANCH_TO_TST } } steps { build job: './../Deploy', parameters: [ [$class: 'StringParameterValue', name: 'GIT_REPO', value: 'habr-demo-app'], [$class: 'StringParameterValue', name: 'VERSION', value: revision], [$class: 'StringParameterValue', name: 'ENV', value: branch == 'master' ? 'staging' : 'test'] ], wait: false } } ...
هنا يمكنك أن تجد Jenkinsfile مع كل الخطوات.
وبالتالي ، من الممكن إنشاء نشر مستمر على بيئة الاختبار أو القتال المحددة ، وأيضًا باستخدام jenkins أو إخطارات البريد الإلكتروني / slack / etc ، التي لها تدقيق في أي تطبيق ، وأي إصدار ، وممن وزمن ومكان تثبيته.
الخاتمة
باستخدام Jenkinsfile و helm ، يمكنك ببساطة إنشاء ci / cd للتطبيق الخاص بك. قد تكون هذه الطريقة أكثر صلة بالفرق الصغيرة التي بدأت مؤخرًا في استخدام kubernetes وغير قادرة (بغض النظر عن السبب) على استخدام الخدمات التي يمكن أن توفر مثل هذه الوظيفة خارج الصندوق.
يمكنك العثور على أمثلة التكوين للبيئات ، Jenkins وخط أنابيب لإدارة دورة حياة التطبيق هنا وتطبيق مثال مع Jenkinsfile هنا .