نهج أفضل للنشر المستمر في Google Cloud
نشرت: 2022-03-11النشر المستمر (CD) هو ممارسة النشر التلقائي لتعليمات برمجية جديدة للإنتاج. تتحقق معظم أنظمة النشر المستمرة من أن الكود الذي سيتم نشره قابل للتطبيق عن طريق تشغيل اختبارات الوحدة والاختبارات الوظيفية ، وإذا كان كل شيء يبدو جيدًا ، فسيتم بدء النشر. عادةً ما يحدث الطرح نفسه على مراحل من أجل التمكن من التراجع إذا لم يعمل الرمز كما هو متوقع.
لا يوجد نقص في منشورات المدونة حول كيفية تنفيذ خط أنابيب القرص المضغوط الخاص بك باستخدام أدوات مختلفة مثل AWS stack و Google Cloud stack و Bitbucket pipeline وما إلى ذلك ، لكنني أجد أن معظمها لا يتناسب مع فكرتي عن ماهية خط أنابيب القرص المضغوط الجيد يجب أن يبدو بالشكل: الملف الذي يتم إنشاؤه أولاً ، ويختبر وينشر فقط هذا الملف المبني الفردي.
في هذه المقالة ، سأقوم ببناء خط أنابيب نشر مستمر مدفوع بالأحداث يتم إنشاؤه أولاً ثم إجراء اختبارات على الأداة النهائية للنشر. هذا لا يجعل نتائج الاختبار الخاصة بنا أكثر موثوقية فحسب ، بل يجعل أيضًا خط أنابيب CD قابل للتمديد بسهولة. سيبدو شيء هكذا:
- يتم الالتزام بمستودع المصدر الخاص بنا.
- هذا يؤدي إلى بناء الصورة المرتبطة.
- يتم إجراء الاختبارات على القطع الأثرية المبنية.
- إذا كان كل شيء يبدو جيدًا ، يتم نشر الصورة في الإنتاج.
تفترض هذه المقالة على الأقل معرفة عابرة بـ Kubernetes وتكنولوجيا الحاويات ، ولكن إذا كنت غير مألوف أو يمكنك استخدام أداة تنشيطية ، فراجع ما هو Kubernetes؟ دليل الحاوية والنشر.
المشكلة مع معظم برامج إعداد القرص المضغوط
إليك مشكلتي مع معظم خطوط أنابيب القرص المضغوط: عادةً ما يقومون بكل شيء في ملف الإنشاء. معظم منشورات المدونة التي قرأتها عن هذا سيكون لها بعض الاختلاف في التسلسل التالي في أي ملف بناء لديهم ( cloudbuild.yaml
لـ Google Cloud Build ، bitbucket-pipeline.yaml
لـ Bitbucket).
- إبدأ الاختبارات
- بناء الصورة
- دفع الصورة إلى حاوية إعادة الشراء
- تحديث البيئة بصورة جديدة
أنت لا تجري اختباراتك على القطع الأثرية النهائية.
من خلال القيام بالأشياء بهذا الترتيب ، تقوم بإجراء اختباراتك. إذا نجحوا ، فأنت تبني الصورة وتواصل بقية خط الأنابيب. ماذا يحدث إذا غيرت عملية الإنشاء صورتك بطريقة لا تجتاز فيها الاختبارات بعد الآن؟ في رأيي ، يجب أن تبدأ بإنتاج قطعة أثرية (صورة الحاوية النهائية) ويجب ألا تتغير هذه الأداة بين الإنشاء ووقت نشرها في الإنتاج. هذا يضمن أن البيانات التي لديك حول الأداة المذكورة (نتائج الاختبار ، الحجم ، إلخ) صالحة دائمًا.
تمتلك بيئة البناء الخاصة بك "مفاتيح المملكة".
باستخدام بيئة البناء الخاصة بك لنشر صورتك في مجموعة الإنتاج الخاصة بك ، فإنك تسمح لها بشكل فعال بتغيير بيئة الإنتاج الخاصة بك. أنا أعتبر هذا أمرًا سيئًا للغاية لأن أي شخص لديه حق الوصول للكتابة إلى مستودع المصدر الخاص بك يمكنه الآن فعل أي شيء يريده في بيئة الإنتاج الخاصة بك.
يجب عليك إعادة تشغيل خط الأنابيب بالكامل إذا فشلت الخطوة الأخيرة.
إذا فشلت الخطوة الأخيرة (على سبيل المثال ، بسبب مشكلة في الاعتماد) ، فيجب عليك إعادة تشغيل خط الأنابيب بالكامل ، واستهلاك الوقت والموارد الأخرى التي يمكن إنفاقها بشكل أفضل في القيام بشيء آخر.
هذا يقودني إلى نقطتي الأخيرة:
خطواتك ليست مستقلة.
بمعنى أكثر عمومية ، يسمح لك وجود خطوات مستقلة بمزيد من المرونة في خط الأنابيب الخاص بك. لنفترض أنك تريد إضافة اختبارات وظيفية إلى خط الأنابيب الخاص بك. من خلال وضع خطواتك في ملف بناء واحد ، فإنك تحتاج إلى جعل بيئة البناء الخاصة بك تقوم بتدوير بيئة اختبار وظيفية وإجراء الاختبارات فيها (على الأرجح بالتتابع). إذا كانت خطواتك مستقلة ، يمكن أن تبدأ اختبارات الوحدة والاختبارات الوظيفية من خلال حدث "إنشاء الصورة". ثم سيعملون بالتوازي في بيئتهم الخاصة.
الإعداد المثالي للقرص المضغوط الخاص بي
في رأيي ، أفضل طريقة للتعامل مع هذه المشكلة هي الحصول على سلسلة من الخطوات المستقلة مرتبطة ببعضها البعض بواسطة آلية الحدث.
هذا له عدة مزايا مقارنة بالطريقة السابقة:
يمكنك اتخاذ العديد من الإجراءات المستقلة في أحداث مختلفة.
كما هو مذكور أعلاه ، فإن البناء الناجح لصورة جديدة سينشر فقط حدث "بناء ناجح". في المقابل ، يمكننا تشغيل العديد من الأشياء عند بدء هذا الحدث. في حالتنا ، سنبدأ الوحدة والاختبارات الوظيفية. يمكنك أيضًا التفكير في أشياء مثل تنبيه المطور عند تشغيل حدث فشل في الإصدار أو إذا لم تنجح الاختبارات.
كل بيئة لها مجموعة الحقوق الخاصة بها.
من خلال إجراء كل خطوة في بيئتها الخاصة ، فإننا نلغي الحاجة إلى بيئة واحدة تتمتع بجميع الحقوق. الآن يمكن لبيئة البناء البناء فقط ، ويمكن لبيئة الاختبار فقط الاختبار ، ويمكن نشر بيئة النشر فقط. يتيح لك ذلك أن تكون واثقًا من أنه بمجرد إنشاء صورتك ، فلن تتغير. الأداة التي تم إنتاجها هي التي ستنتهي في كومة الإنتاج الخاصة بك. كما أنه يسمح بمراجعة أسهل لخطوة خط الأنابيب الخاص بك الذي يقوم بما يمكنك من ربط مجموعة واحدة من بيانات الاعتماد بخطوة واحدة.
هناك المزيد من المرونة.
هل تريد إرسال بريد إلكتروني إلى شخص ما في كل بناء ناجح؟ فقط أضف شيئًا يتفاعل مع هذا الحدث ويرسل بريدًا إلكترونيًا. إنه سهل — ليس عليك تغيير كود البناء الخاص بك ولا يتعين عليك ترميز البريد الإلكتروني لشخص ما في مستودع المصدر الخاص بك.
عمليات إعادة المحاولة أسهل.
وجود خطوات مستقلة يعني أيضًا أنك لست مضطرًا إلى إعادة تشغيل خط الأنابيب بالكامل إذا فشلت إحدى الخطوات. إذا كانت حالة الفشل مؤقتة أو تم إصلاحها يدويًا ، يمكنك فقط إعادة محاولة الخطوة التي فشلت. هذا يسمح لخط أنابيب أكثر كفاءة. عندما تستغرق خطوة الإنشاء عدة دقائق ، فمن الجيد ألا تضطر إلى إعادة بناء الصورة لمجرد أنك نسيت منح بيئة النشر الخاصة بك حق الوصول للكتابة إلى مجموعتك.
تنفيذ النشر المستمر في Google Cloud
يحتوي Google Cloud Platform على جميع الأدوات اللازمة لإنشاء مثل هذا النظام في فترة زمنية قصيرة وبقليل جدًا من التعليمات البرمجية.
تطبيق الاختبار الخاص بنا هو تطبيق Flask بسيط يقدم فقط جزءًا من النص الثابت. يتم نشر هذا التطبيق في مجموعة Kubernetes التي تخدمه على الإنترنت الأوسع.
سأقوم بتنفيذ نسخة مبسطة من خط الأنابيب الذي قدمته سابقًا. لقد أزلت خطوات الاختبار بشكل أساسي ، لذا يبدو الآن كما يلي:
- يتم إجراء التزام جديد بمستودع المصدر
- يؤدي هذا إلى إنشاء صورة. إذا نجحت ، يتم دفعها إلى مستودع الحاوية ويتم نشر حدث إلى موضوع Pub / Sub
- يتم الاشتراك في نص صغير في هذا الموضوع ويتحقق من معلمات الصورة - إذا كانت تتطابق مع ما طلبناه ، يتم نشرها في مجموعة Kubernetes.
هنا تمثيل رسومي لخط الأنابيب الخاص بنا.
التدفق على النحو التالي:
- شخص ما يلتزم بمستودعنا.
- يؤدي هذا إلى تشغيل إنشاء سحابي يقوم بإنشاء صورة Docker بناءً على مستودع المصدر.
- يدفع البناء السحابي الصورة إلى مستودع الحاوية وينشر رسالة إلى حانة / فرعي سحابي.
- يؤدي هذا إلى تشغيل وظيفة سحابية تتحقق من معلمات الرسالة المنشورة (حالة البناء ، واسم الصورة المبنية ، وما إلى ذلك).
- إذا كانت المعلمات جيدة ، تقوم وظيفة السحابة بتحديث نشر Kubernetes بالصورة الجديدة.
- تنشر Kubernetes حاويات جديدة مع الصورة الجديدة.
مصدر الرمز
كود المصدر الخاص بنا هو تطبيق Flask بسيط للغاية يقدم فقط بعض النصوص الثابتة. هنا هيكل مشروعنا:

├── docker │ ├── Dockerfile │ └── uwsgi.ini ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── LICENSE ├── Pipfile ├── Pipfile.lock └── src └── main.py
يحتوي دليل Docker على كل ما يلزم لإنشاء صورة Docker. تستند الصورة إلى صورة uWSGI و Nginx وتقوم فقط بتثبيت التبعيات ونسخ التطبيق إلى المسار الصحيح.
يحتوي دليل k8s على تهيئة Kubernetes. يتكون من خدمة واحدة ونشر واحد. يبدأ النشر حاوية واحدة بناءً على الصورة التي تم إنشاؤها من Dockerfile . تبدأ الخدمة بعد ذلك موازن تحميل له عنوان IP عام وتعيد التوجيه إلى حاوية (حاويات) التطبيق.
بناء سحابة
يمكن إجراء تكوين إنشاء السحابة نفسه من خلال وحدة التحكم السحابية أو سطر أوامر Google Cloud. اخترت استخدام وحدة التحكم السحابية.
هنا ، نقوم ببناء صورة لأي التزام على أي فرع ، ولكن يمكن أن يكون لديك صور مختلفة للتطوير والإنتاج ، على سبيل المثال.
إذا كان الإصدار ناجحًا ، فسينشر الإصدار السحابي الصورة إلى سجل الحاوية من تلقاء نفسه. ستقوم بعد ذلك بنشر رسالة إلى موضوع Pub / sub-builds cloud-builds.
ينشر الإصدار السحابي أيضًا الرسائل عندما يكون أحد المباني قيد التقدم وعندما يفشل أحدها ، لذلك يمكنك أيضًا جعل الأشياء تتفاعل مع تلك الرسائل.
توجد وثائق إشعارات الحانة / الفرعية الخاصة بالبنية السحابية هنا ويمكن العثور على تنسيق الرسالة هنا
Cloud Pub / Sub
إذا نظرت في علامة تبويب الحانة / الفرعية السحابية في وحدة التحكم السحابية ، فسترى أن إنشاء السحابة قد أنشأ موضوعًا يسمى الإنشاءات السحابية. هذا هو المكان الذي ينشر فيه الإصدار السحابي تحديثات الحالة الخاصة به.
وظيفة السحابة
ما سنفعله الآن هو إنشاء وظيفة سحابية يتم تشغيلها على أي رسالة يتم نشرها في موضوع الإنشاءات السحابية. مرة أخرى ، يمكنك إما استخدام وحدة التحكم السحابية أو أداة سطر أوامر Google Cloud المساعدة. ما فعلته في حالتي هو أنني أستخدم البنية السحابية لنشر وظيفة السحابة في كل مرة يحدث تغيير فيها.
الكود المصدري لوظيفة السحابة موجود هنا.
لنلقِ نظرة أولاً على الكود الذي ينشر وظيفة السحابة هذه:
steps: - name: 'gcr.io/cloud-builders/gcloud' id: 'test' args: ['functions', 'deploy', 'new-image-trigger', '--runtime=python37', '--trigger-topic=cloud-builds', '--entry-point=onNewImage', '--region=us-east1', '--source=https://source.developers.google.com/projects/$PROJECT_ID/repos/$REPO_NAME']
هنا ، نستخدم صورة Google Cloud Docker. هذا يسمح بتشغيل أوامر GCcloud بسهولة. ما ننفذه هو ما يعادل تشغيل الأمر التالي من محطة طرفية مباشرة:
gcloud functions deploy new-image-trigger --runtime=python37 --trigger-topic=cloud-builds --entry-point=onNewImage --region=us-east1 --source=https://source.developers.google.com/projects/$PROJECT_ID/repos/$REPO_NAME
نطلب من Google Cloud نشر وظيفة سحابية جديدة (أو استبدالها في حالة وجود وظيفة بهذا الاسم في تلك المنطقة بالفعل) والتي ستستخدم وقت تشغيل Python 3.7 وسيتم تشغيلها بواسطة رسائل جديدة في موضوع الإنشاءات السحابية. نخبر Google أيضًا بمكان العثور على الكود المصدري لتلك الوظيفة (هنا PROJECT_ID و REPO_NAME هما متغيرات البيئة التي يتم تعيينها بواسطة عملية الإنشاء). ونخبرها أيضًا ما هي الوظيفة التي يجب تسميتها كنقطة دخول.
كملاحظة جانبية ، لكي يعمل هذا ، تحتاج إلى منح حساب خدمة Cloudbuild الخاص بك "مطور وظائف السحابة" و "مستخدم حساب الخدمة" حتى يتمكن من نشر وظيفة السحابة.
فيما يلي بعض المقتطفات المعلقة من رمز وظيفة السحابة
ستحتوي بيانات نقطة الدخول على الرسالة المستلمة في موضوع عام / فرعي.
def onNewImage(data, context):
تتمثل الخطوة الأولى في الحصول على المتغيرات لهذا النشر المحدد من البيئة (حددناها عن طريق تعديل وظيفة السحابة في وحدة التحكم السحابية.
project = os.environ.get('PROJECT') zone = os.environ.get('ZONE') cluster = os.environ.get('CLUSTER') deployment = os.environ.get('DEPLOYMENT') deploy_image = os.environ.get('IMAGE') target_container = os.environ.get('CONTAINER')
سنتخطى الجزء الذي نتحقق فيه من أن بنية الرسالة هي ما نتوقعه ونتحقق من أن البناء كان ناجحًا وأنتجنا صورة واحدة.
الخطوة التالية هي التأكد من أن الصورة التي تم إنشاؤها هي الصورة التي نريد نشرها.
image = decoded_data['results']['images'][0]['name'] image_basename = image.split('/')[-1].split(':')[0] if image_basename != deploy_image: logging.error(f'{image_basename} is different from {deploy_image}') return
الآن ، نحصل على عميل Kubernetes ونسترجع النشر الذي نريد تعديله
v1 = get_kube_client(project, zone, cluster) dep = v1.read_namespaced_deployment(deployment, 'default') if dep is None: logging.error(f'There was no deployment named {deployment}') return
أخيرًا ، قمنا بتصحيح النشر بالصورة الجديدة ؛ سيهتم Kubernetes بطرحه.
for i, container in enumerate(dep.spec.template.spec.containers): if container.name == target_container: dep.spec.template.spec.containers[i].image = image logging.info(f'Updating to {image}') v1.patch_namespaced_deployment(deployment, 'default', dep)
خاتمة
هذا مثال أساسي جدًا عن الطريقة التي أحب بها هندسة الأشياء في خط أنابيب القرص المضغوط. يمكن أن يكون لديك المزيد من الخطوات فقط عن طريق تغيير حدث الحانة / الفرعي الذي يقوم بتشغيل ماذا.
على سبيل المثال ، يمكنك تشغيل حاوية تقوم بتشغيل الاختبارات داخل الصورة وتنشر حدثًا عن النجاح وآخر عن الفشل والرد على هؤلاء إما عن طريق تحديث النشر أو التنبيه اعتمادًا على النتيجة.
إن خط الأنابيب الذي أنشأناه بسيط جدًا ، ولكن يمكنك كتابة وظائف سحابية أخرى لأجزاء أخرى (على سبيل المثال ، وظيفة سحابية ترسل بريدًا إلكترونيًا إلى المطور الذي التزم بالشفرة التي حطمت اختبارات الوحدة الخاصة بك).
كما ترى ، لا يمكن لبيئة الإنشاء الخاصة بنا تغيير أي شيء في مجموعة Kubernetes الخاصة بنا ، ولا يمكن لكود النشر الخاص بنا (وظيفة السحابة) تعديل الصورة التي تم إنشاؤها. يبدو فصل الامتيازات الخاص بنا جيدًا ، ويمكننا أن ننام جيدًا مع العلم أن مطورًا محتالًا لن يؤدي إلى انهيار مجموعة الإنتاج لدينا. كما يمكننا أن نمنح مطورينا المهتمين أكثر بالعمليات الوصول إلى كود الوظيفة السحابية حتى يتمكنوا من إصلاحه أو تحسينه.
إذا كانت لديك أي أسئلة أو ملاحظات أو تحسينات ، فلا تتردد في التواصل معنا في التعليقات أدناه.