أنا مهندس DevOps رئيسي في Miro (RealtimeBoard السابقين). سوف أشارك كيف قام فريق DevOps الخاص بنا بحل مشكلة الإصدارات اليومية للخادم من تطبيق فعال متجانس وجعلها تلقائية وغير مرئية للمستخدمين وملائمة لمطوريهم.
بنيتنا التحتية
يتكون فريق التطوير لدينا من 60 شخصًا مقسمون إلى فرق Scrum ، ومن بينهم أيضًا فريق DevOps. تدعم معظم أوامر Scrum الوظيفة الحالية للمنتج وتقدم ميزات جديدة. تتمثل مهمة DevOps في إنشاء وصيانة بنية أساسية تساعد التطبيق على العمل بسرعة وموثوقية وتتيح للفرق تقديم وظائف جديدة بسرعة للمستخدمين.

طلبنا هو لوحة على الإنترنت لا نهاية لها. يتكون من ثلاث طبقات: موقع وعميل وخادم في Java ، وهو تطبيق ذو حالة متجانسة. يحتفظ التطبيق باتصال ثابت بمقبس الويب مع العملاء ، ويحتفظ كل خادم في ذاكرة التخزين المؤقت للوحات المفتوحة.
تقع البنية التحتية بالكامل - أكثر من 70 خادمًا - في أمازون: أكثر من 30 خادمًا من خلال تطبيق Java وخوادم الويب وخوادم قواعد البيانات والوسطاء وغير ذلك الكثير. مع نمو الأداء الوظيفي ، يجب تحديث كل هذا بانتظام ، دون تعطيل عمل المستخدمين.
يعد تحديث الموقع والعميل أمرًا بسيطًا: نقوم باستبدال الإصدار القديم بإصدار جديد ، وفي المرة التالية التي يصل فيها المستخدم إلى موقع جديد وعميل جديد. ولكن إذا قمنا بذلك عندما يتم إصدار الخادم ، فنحن نتوقف عن العمل. بالنسبة لنا ، هذا غير مقبول ، لأن القيمة الرئيسية لمنتجاتنا هي العمل المشترك للمستخدمين في الوقت الفعلي.
كيف تبدو عملية CI / CD لدينا
عملية CI / CD معنا هي git الالتزام ، git push ، ثم التجميع التلقائي ، الاختبار التلقائي ، النشر ، الإصدار والمراقبة.

للتكامل المستمر ، نستخدم Bamboo و Bitbucket. للاختبار التلقائي - Java و Python و Allure - لعرض نتائج الاختبار التلقائي. للتسليم المستمر - باكر ، Ansible وبيثون. تتم جميع عمليات الرصد باستخدام ELK Stack و Prometheus و Sentry.
يكتب المطورون رمزًا ، ويضيفونه إلى المستودع ، وبعد ذلك يتم إطلاق التجميع التلقائي والاختبار التلقائي. في نفس الوقت ، يجمع الفريق شكا من مطورين آخرين ويقوم بإجراء مراجعة الكود. عند الانتهاء من جميع العمليات المطلوبة ، بما في ذلك الاختبارات التلقائية ، يحتفظ الفريق بالبناء في الفرع الرئيسي ، ويبدأ إنشاء الفرع الرئيسي ويتم إرساله للاختبار التلقائي. يتم تصحيح العملية برمتها وتنفيذها من قبل الفريق من تلقاء نفسه.
صورة AMI
بالتوازي مع بنية الإنشاء والاختبار ، يبدأ إنشاء صورة AMI لـ Amazon. للقيام بذلك ، نستخدم Packer من HashiCorp ، وهي أداة رائعة مفتوحة المصدر تتيح لك إنشاء صورة لجهاز افتراضي. يتم تمرير جميع المعلمات إلى JSON مع مجموعة من مفاتيح التكوين. المعلمة الرئيسية هي بناة ، والتي تشير إلى أي مزود نقوم بإنشاء الصورة (في حالتنا ، أمازون).
"builders": [{ "type": "amazon-ebs", "access_key": "{{user `aws_access_key`}}", "secret_key": "{{user `aws_secret_key`}}", "region": "{{user `aws_region`}}", "vpc_id": "{{user `aws_vpc`}}", "subnet_id": "{{user `aws_subnet`}}", "tags": { "releaseVersion": "{{user `release_version`}}" }, "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "packer-board-ami_{{isotime \"2006-01-02_15-04\"}}" }],
من المهم ألا ننشئ صورة لجهاز ظاهري فحسب ، بل نهيئها مسبقًا باستخدام Ansible: قم بتثبيت الحزم اللازمة وإعداد إعدادات التكوين لبدء تشغيل تطبيق Java.
"provisioners": [{ "type": "ansible", "playbook_file": "./playbook.yml", "user": "ubuntu", "host_alias": "default", "extra_arguments": ["--extra_vars=vars"], "ansible_env_vars": ["ANSIBLE_HOST_KEY_CHECKING=False", "ANSIBLE_NOCOLOR=True"] }]
الأدوار الواضحة
اعتدنا على استخدام قواعد اللعبة Ansible المعتادة ، ولكن هذا أدى إلى الكثير من التعليمات البرمجية المتكررة ، التي أصبحت صعبة التحديث. لقد غيرنا شيئًا ما في أحد قواعد اللعبة ، ونسينا أن نفعل ذلك في آخر ، ونتيجة لذلك واجهنا مشاكل. لذلك بدأنا باستخدام الأدوار Ansible. لقد جعلناها متعددة الاستخدامات قدر الإمكان حتى نتمكن من إعادة استخدامها في أجزاء مختلفة من المشروع وعدم التحميل الزائد للكود في قطع كبيرة مكررة. على سبيل المثال ، نستخدم دور المراقبة لجميع أنواع الخوادم.
- name: Install all board dependencies hosts: all user: ubuntu become: yes roles: - java - nginx - board-application - ssl-certificates - monitoring
من جانب فرق Scrum ، تبدو هذه العملية بسيطة قدر الإمكان: يتلقى الفريق إعلامات في Slack عن تجميع البنية وصورة AMI.
ما قبل النشرات
لقد قدمنا الإصدارات السابقة لتقديم تغييرات المنتج للمستخدمين في أسرع وقت ممكن. في الواقع ، هذه هي إصدارات الكناري التي تتيح لك اختبار وظائف جديدة بأمان على نسبة صغيرة من المستخدمين.
لماذا تسمى الإصدارات الكناري؟ في السابق ، كان عمال المناجم ، عندما نزلوا إلى المنجم ، يأخذون الكناري معهم. إذا كان هناك غاز في المنجم ، مات الكناري ، وسرعان ما ارتفع عمال المناجم إلى السطح. لذا ، فهو معنا: إذا حدث خطأ ما في الخادم ، فإن الإصدار غير جاهز ويمكننا التراجع بسرعة ولن يلاحظ معظم المستخدمين أي شيء.
كيف يبدأ إصدار الكناري:- ينقر فريق التطوير في Bamboo على زر -> يسمى تطبيق Python الذي يطلق الإصدار التجريبي.
- يقوم بإنشاء مثيل جديد في Amazon من صورة AMI مُعدة مسبقًا مع إصدار جديد من التطبيق.
- تتم إضافة مثيل إلى المجموعات المستهدفة الضرورية وموازنات التحميل.
- باستخدام Ansible ، يتم تكوين تكوين فردي لكل مثيل.
- يعمل المستخدمون مع الإصدار الجديد من تطبيق Java.
على جانب أوامر Scrum ، تبدو عملية الإطلاق المسبق مرة أخرى بسيطة قدر الإمكان: يتلقى الفريق إخطارات في Slack بأن العملية قد بدأت ، وبعد 7 دقائق يعمل الخادم الجديد بالفعل. بالإضافة إلى ذلك ، يرسل التطبيق إلى Slack سجل التغييرات بالكامل في الإصدار.
لكي يعمل حاجز التحقق من الحماية والموثوقية هذا ، تقوم فرق Scrum بمراقبة الأخطاء الجديدة في Sentry. هذا هو تطبيق تتبع الأخطاء مفتوح المصدر في الوقت الحقيقي. ترقب يتكامل بسلاسة مع جافا ويحتوي على وصلات مع logback و log2j. عند بدء تشغيل التطبيق ، ننقل إلى Sentry الإصدار الذي يعمل عليه ، وعندما يحدث خطأ ، نرى في إصدار التطبيق الذي حدث فيه. هذا يساعد فرق سكروم على الاستجابة السريعة للأخطاء وحلها بسرعة.
يجب أن يعمل الإصدار التجريبي لمدة 4 ساعات على الأقل. خلال هذا الوقت ، يراقب الفريق عمله ويقرر ما إذا كان سيتم إصدار الإصدار لجميع المستخدمين.
يمكن للعديد من الفرق إصدار إصداراتها في وقت واحد . للقيام بذلك ، يتفقون فيما بينهم على ما يدخل في الإصدار التجريبي ومن المسؤول عن الإصدار النهائي. بعد ذلك ، تقوم الفرق إما بدمج جميع التغييرات في إصدار مسبق واحد ، أو إطلاق عدة إصدارات مسبقة في نفس الوقت. إذا كانت جميع الإصدارات السابقة صحيحة ، فسيتم إصدارها كإصدار واحد في اليوم التالي.
النشرات
نحن نصدر بيان يومي:
- نقدم خوادم جديدة للعمل.
- نحن نراقب نشاط المستخدم على خوادم جديدة باستخدام Prometheus.
- إغلاق الوصول للمستخدمين الجدد إلى الخوادم القديمة.
- ننقل المستخدمين من الخوادم القديمة إلى خوادم جديدة.
- قم بإيقاف تشغيل الخادم القديم.
تم تصميم كل شيء باستخدام تطبيقات بامبو وبيثون. يتحقق التطبيق من عدد الخوادم قيد التشغيل ويستعد لإطلاق نفس العدد من الخوادم الجديدة. إذا لم يكن هناك خوادم كافية ، يتم إنشاؤها من صورة AMI. يتم نشر إصدار جديد عليها ، ويتم تشغيل تطبيق Java ، ويتم تشغيل الخوادم.
عند المراقبة ، يتحقق تطبيق Python باستخدام API Prometheus من عدد اللوحات المفتوحة على خوادم جديدة. عندما يفهم أن كل شيء يعمل بشكل صحيح ، فإنه يغلق الوصول إلى الخوادم القديمة وينقل المستخدمين إلى خوادم جديدة.
import requests PROMETHEUS_URL = 'https://prometheus' def get_spaces_count(): boards = {} try: params = { 'query': 'rtb_spaces_count{instance=~"board.*"}' } response = requests.get(PROMETHEUS_URL, params=params) for metric in response.json()['data']['result']: boards[metric['metric']['instance']] = metric['value'][1] except requests.exceptions.RequestException as e: print('requests.exceptions.RequestException: {}'.format(e)) finally: return boards
يتم عرض عملية نقل المستخدمين بين الخوادم في Grafana. في النصف الأيسر من الرسم البياني ، يتم عرض الخوادم التي تعمل على الإصدار القديم ، في اليمين - على الجديدة. تقاطع المخططات هو لحظة نقل المستخدم.

يشرف الفريق على إطلاق سلاك. بعد الإصدار ، يتم نشر سجل التغييرات بالكامل في قناة منفصلة في Slack ، وفي Jira يتم إغلاق جميع المهام المرتبطة بهذا الإصدار تلقائيًا.
ما هو ترحيل المستخدم
نقوم بتخزين حالة السبورة التي يعمل عليها المستخدمون ، في ذاكرة التطبيق ، ونحفظ باستمرار جميع التغييرات في قاعدة البيانات. لنقل اللوحة على مستوى تفاعل الكتلة ، نقوم بتحميلها في الذاكرة على الخادم الجديد ونرسل إلى العميل أمرًا لإعادة الاتصال. في هذه المرحلة ، ينفصل العميل عن الخادم القديم ويتصل بالخادم الجديد. بعد بضع ثوانٍ ، يرى المستخدمون النقش - تمت استعادة الاتصال. ومع ذلك ، يواصلون العمل ولا يلاحظون أي إزعاج.

ما تعلمناه في حين جعل نشر غير مرئية
ماذا وصلنا بعد عشرات التكرارات:
- يتحقق فريق scrum من الكود نفسه.
- يقرر فريق scrum موعد إطلاق الإصدار التجريبي وتقديم بعض التغييرات إلى مستخدمين جدد.
- يقرر Scrum-team ما إذا كان إصداره جاهزًا للذهاب إلى جميع المستخدمين.
- يواصل المستخدمون العمل ولا يلاحظون أي شيء.
هذا لم يكن ممكنا على الفور ، صعدنا على نفس أشعل النار عدة مرات وملأنا الكثير من المخاريط. أريد أن أشارك الدروس التي تلقيناها.
أولا ، العملية اليدوية ، وعندها فقط أتمتة. لا تحتاج الخطوات الأولى إلى التعمق في التشغيل الآلي ، لأنه يمكنك أتمتة ما هو غير مفيد في النهاية.
Ansible جيد ، لكن الأدوار Ansible أفضل. لقد جعلنا أدوارنا عالمية قدر الإمكان: لقد تخلصنا من الكود المتكرر ، لذا فهي تحمل فقط الوظائف التي ينبغي أن تحملها. يتيح لك ذلك توفير الوقت بشكل كبير عن طريق إعادة استخدام الأدوار ، التي لدينا بالفعل أكثر من 50.
أعد استخدام الشفرة في Python وقسمها إلى مكتبات ووحدات منفصلة. هذا يساعدك على التنقل في المشاريع المعقدة وسرعان ما تغمر أشخاص جدد فيها.
الخطوات التالية
لم تنته بعد عملية النشر غير المرئية. فيما يلي بعض الخطوات التالية:
- السماح للفرق بإكمال ليس فقط الإصدارات المسبقة ، ولكن جميع الإصدارات.
- جعل التراجع التلقائي في حالة وجود أخطاء. على سبيل المثال ، يجب أن يتراجع إصدار ما قبل التشغيل تلقائيًا إذا تم اكتشاف أخطاء فادحة في Sentry.
- أتمتة الإصدار بالكامل في حالة عدم وجود أخطاء. إذا لم تكن هناك أخطاء في الإصدار التجريبي ، فهذا يعني أنه يمكن تلقائيًا تمديده.
- إضافة مسح رمز تلقائي لأخطاء الأمان المحتملة.