من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

في RIT 2019، قدم زميلنا ألكسندر كوروتكوف تقرير حول أتمتة التطوير في CIAN: لتبسيط الحياة والعمل، نستخدم منصة Integro الخاصة بنا. فهو يتتبع دورة حياة المهام، ويريح المطورين من العمليات الروتينية ويقلل بشكل كبير من عدد الأخطاء في الإنتاج. في هذا المنشور، سنكمل تقرير ألكساندر ونخبرك كيف انتقلنا من البرامج النصية البسيطة إلى الجمع بين المنتجات مفتوحة المصدر من خلال منصتنا الخاصة وما يفعله فريق الأتمتة المنفصل لدينا.
 

مستوى الصفر

"لا يوجد شيء اسمه مستوى الصفر، لا أعرف شيئًا كهذا"
المعلم شيفو من فيلم "كونغ فو باندا"

بدأت الأتمتة في CIAN بعد 14 عامًا من تأسيس الشركة. في ذلك الوقت كان هناك 35 شخصًا في فريق التطوير. من الصعب تصديق ذلك، أليس كذلك؟ وبطبيعة الحال، كانت الأتمتة موجودة بشكل ما، ولكن بدأ يتشكل اتجاه منفصل للتكامل المستمر وتسليم التعليمات البرمجية في عام 2015. 

في ذلك الوقت، كان لدينا كتلة ضخمة من لغات Python وC# وPHP، منتشرة على خوادم Linux/Windows. لنشر هذا الوحش، كان لدينا مجموعة من البرامج النصية التي قمنا بتشغيلها يدويًا. كان هناك أيضًا تجميع المونوليث، الذي جلب الألم والمعاناة بسبب الصراعات عند دمج الفروع وتصحيح العيوب وإعادة البناء "مع مجموعة مختلفة من المهام في البناء". تبدو العملية المبسطة كما يلي:

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

لم نكن سعداء بهذا، وأردنا إنشاء عملية إنشاء ونشر قابلة للتكرار وآلية وقابلة للإدارة. ولهذا السبب، كنا بحاجة إلى نظام CI/CD، واخترنا بين الإصدار المجاني من Teamcity والإصدار المجاني من Jenkins، حيث عملنا معهم وكلاهما يناسبنا من حيث مجموعة الوظائف. لقد اخترنا Teamcity كمنتج أحدث. في ذلك الوقت، لم نكن قد استخدمنا بعد بنية الخدمات المصغرة ولم نتوقع عددًا كبيرًا من المهام والمشاريع.

نأتي إلى فكرة نظامنا الخاص

أدى تنفيذ Teamcity إلى إزالة جزء فقط من العمل اليدوي: ما تبقى هو إنشاء طلبات السحب، وتعزيز المشكلات حسب الحالة في Jira، واختيار المشكلات للإصدار. لم يعد نظام Teamcity قادرًا على التعامل مع هذا. كان من الضروري اختيار مسار المزيد من الأتمتة. لقد درسنا خيارات العمل مع البرامج النصية في Teamcity أو التبديل إلى أنظمة التشغيل الآلي التابعة لجهات خارجية. ولكن في النهاية قررنا أننا بحاجة إلى أقصى قدر من المرونة، وهو ما يمكن أن يوفره لنا الحل الخاص بنا فقط. هكذا ظهرت النسخة الأولى من نظام الأتمتة الداخلي المسمى Integro.

تتعامل Teamcity مع الأتمتة على مستوى إطلاق عمليات البناء والنشر، بينما تركز Integro على أتمتة عمليات التطوير على أعلى مستوى. كان من الضروري الجمع بين العمل مع المشكلات في Jira ومعالجة كود المصدر المرتبط في Bitbucket. في هذه المرحلة، بدأت شركة Integro في إنشاء مسارات عمل خاصة بها للتعامل مع المهام ذات الأنواع المختلفة. 

نظرًا لزيادة الأتمتة في العمليات التجارية، زاد عدد المشاريع وعمليات التشغيل في Teamcity. لذا ظهرت مشكلة جديدة: لم يكن مثيل Teamcity المجاني واحدًا كافيًا (3 وكلاء و100 مشروع)، أضفنا مثيلًا آخر (3 وكلاء آخرين و100 مشروع)، ثم آخر. ونتيجة لذلك، انتهى بنا الأمر إلى نظام من عدة مجموعات، كان من الصعب إدارته:

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

عندما برزت مسألة الحالة الرابعة، أدركنا أنه لا يمكننا الاستمرار في العيش بهذه الطريقة، لأن إجمالي تكاليف دعم الحالات الأربع لم تعد ضمن أي حدود. نشأ السؤال حول شراء Teamcity المدفوعة أو اختيار Jenkins المجاني. لقد أجرينا حسابات على الحالات وخطط الأتمتة وقررنا أننا سنعيش في جنكينز. بعد بضعة أسابيع، قمنا بالتبديل إلى Jenkins وتخلصنا من بعض المتاعب المرتبطة بالحفاظ على مثيلات Teamcity المتعددة. لذلك، تمكنا من التركيز على تطوير Integro وتخصيص Jenkins لأنفسنا.

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

نحن أتمتة الاختبار

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

بسبب أتمتة الإصدارات، تسارعت عمليات التطوير، ويرجع ذلك جزئيًا إلى تخطي بعض مراحل الاختبار. وهذا أدى إلى خسارة مؤقتة للجودة. يبدو الأمر تافها، ولكن إلى جانب تسريع الإصدارات، كان من الضروري تغيير منهجية تطوير المنتج. كان من الضروري التفكير في أتمتة الاختبار، وغرس المسؤولية الشخصية (هنا نتحدث عن "قبول الفكرة في الرأس"، وليس الغرامات المالية) للمطور عن الكود الذي تم إصداره والأخطاء فيه، وكذلك قرار تحرير/عدم تحرير مهمة من خلال النشر التلقائي. 

للتخلص من مشكلات الجودة، توصلنا إلى قرارين مهمين: بدأنا في إجراء اختبار الكناري وقدمنا ​​مراقبة تلقائية لخلفية الخطأ مع الاستجابة التلقائية لفائضها. الحل الأول جعل من الممكن العثور على أخطاء واضحة قبل إطلاق الكود بالكامل في الإنتاج، أما الحل الثاني فقد قلل من وقت الاستجابة للمشاكل في الإنتاج. الأخطاء تحدث بالطبع، لكننا نقضي معظم وقتنا وجهدنا ليس في تصحيحها، بل في التقليل منها. 

فريق الأتمتة

لدينا حاليًا فريق عمل يضم 130 مطورًا، وما زلنا مستمرين لتنمو. يتكون فريق التكامل المستمر وتسليم التعليمات البرمجية (المشار إليه فيما يلي باسم فريق النشر والتكامل أو فريق DI) من 7 أشخاص ويعمل في اتجاهين: تطوير منصة Integro للأتمتة وDevOps. 

DevOps مسؤول عن بيئة Dev/Beta لموقع CIAN، وبيئة Integro، ويساعد المطورين على حل المشكلات وتطوير أساليب جديدة لتوسيع نطاق البيئات. يتعامل اتجاه تطوير Integro مع كل من Integro نفسه والخدمات ذات الصلة، على سبيل المثال، المكونات الإضافية لـ Jenkins وJira وConfluence، كما يقوم أيضًا بتطوير الأدوات المساعدة والتطبيقات لفرق التطوير. 

يعمل فريق DI بشكل تعاوني مع فريق النظام الأساسي، الذي يقوم بتطوير البنية والمكتبات وأساليب التطوير داخليًا. وفي الوقت نفسه، يمكن لأي مطور داخل CIAN المساهمة في الأتمتة، على سبيل المثال، إجراء أتمتة دقيقة لتناسب احتياجات الفريق أو مشاركة فكرة رائعة حول كيفية جعل الأتمتة أفضل.

طبقة كعكة الأتمتة في CIAN

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

يمكن تقسيم جميع الأنظمة المشاركة في الأتمتة إلى عدة طبقات:

  1. الأنظمة الخارجية (Jira، Bitbucket، إلخ). فرق التطوير تعمل معهم.
  2. منصة انتيغرو. في أغلب الأحيان، لا يتعامل المطورون معها بشكل مباشر، ولكنها هي التي تحافظ على تشغيل جميع عمليات الأتمتة.
  3. خدمات التسليم والتنسيق والاكتشاف (على سبيل المثال، Jeknins وConsul وNomad). وبمساعدتهم، نقوم بنشر التعليمات البرمجية على الخوادم والتأكد من أن الخدمات تعمل مع بعضها البعض.
  4. الطبقة المادية (الخوادم ونظام التشغيل والبرامج ذات الصلة). الكود الخاص بنا يعمل على هذا المستوى. يمكن أن يكون هذا خادمًا فعليًا أو خادمًا افتراضيًا (LXC، KVM، Docker).

واستنادًا إلى هذا المفهوم، نقوم بتقسيم مجالات المسؤولية داخل فريق شركة DI. يقع المستويان الأولان في مجال مسؤولية اتجاه تطوير Integro، والمستويان الأخيران موجودان بالفعل في مجال مسؤولية DevOps. يتيح لنا هذا الفصل التركيز على المهام ولا يتعارض مع التفاعل، لأننا قريبون من بعضنا البعض ونتبادل المعرفة والخبرة باستمرار.

تكاملية

دعونا نركز على Integro ونبدأ بمجموعة التكنولوجيا:

  • سينت أو إس 7
  • دوكر + نوماد + قنصل + قبو
  • Java 11 (ستبقى وحدة Integro القديمة في Java 8)
  • Spring Boot 2.X + تكوين Spring Cloud
  • بوستجرسكل 11
  • RabbitMQ 
  • اباتشي اشعال
  • كاموندا (مضمنة)
  • جرافانا + جرافيت + بروميثيوس + جايجر + ELK
  • واجهة مستخدم الويب: React (CSR) + MobX
  • SSO: عباءة المفاتيح

نحن نلتزم بمبدأ تطوير الخدمات الصغيرة، على الرغم من أننا نمتلك إرثًا في شكل وحدة متراصة من إصدار مبكر من Integro. تعمل كل خدمة صغيرة في حاوية Docker الخاصة بها، وتتواصل الخدمات مع بعضها البعض عبر طلبات HTTP ورسائل RabbitMQ. تجد الخدمات الصغيرة بعضها البعض من خلال Consul وتقدم طلبًا إليها، وتمرير التفويض من خلال SSO (Keycloak، OAuth 2/OpenID Connect).

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

كمثال واقعي، فكر في التفاعل مع Jenkins، والذي يتكون من الخطوات التالية:

  1. تريد الخدمة الصغيرة لإدارة سير العمل (المشار إليها فيما يلي باسم خدمة التدفق الصغيرة) تشغيل إصدار في Jenkins. للقيام بذلك، يستخدم Consul للعثور على IP: PORT الخاص بالخدمة الصغيرة للتكامل مع Jenkins (المشار إليها فيما يلي باسم Jenkins microservice) ويرسل إليها طلبًا غير متزامن لبدء الإنشاء في Jenkins.
  2. بعد تلقي الطلب، تقوم خدمة Jenkins الصغيرة بإنشاء معرف الوظيفة والاستجابة له، والذي يمكن بعد ذلك استخدامه لتحديد نتيجة العمل. وفي الوقت نفسه، يقوم بتشغيل الإنشاء في Jenkins عبر استدعاء REST API.
  3. ينفذ Jenkins الإنشاء، وبعد الانتهاء، يرسل خطافًا على الويب يتضمن نتائج التنفيذ إلى خدمة Jenkins الصغيرة.
  4. تقوم خدمة Jenkins الصغيرة، بعد تلقي خطاف الويب، بإنشاء رسالة حول اكتمال معالجة الطلب وإرفاق نتائج التنفيذ بها. يتم إرسال الرسالة التي تم إنشاؤها إلى قائمة انتظار RabbitMQ.
  5. من خلال RabbitMQ، تصل الرسالة المنشورة إلى خدمة Flow المصغرة، والتي تتعرف على نتيجة معالجة مهمتها من خلال مطابقة معرف الوظيفة من الطلب والرسالة المستلمة.

الآن لدينا حوالي 30 خدمة صغيرة، والتي يمكن تقسيمها إلى عدة مجموعات:

  1. إدارة التكوين.
  2. المعلومات والتفاعل مع المستخدمين (الرسل، البريد).
  3. العمل مع كود المصدر.
  4. التكامل مع أدوات النشر (جنكينز، البدوي، القنصل، الخ).
  5. المراقبة (الإصدارات والأخطاء وما إلى ذلك).
  6. أدوات الويب المساعدة (واجهة المستخدم لإدارة بيئات الاختبار، وجمع الإحصائيات، وما إلى ذلك).
  7. التكامل مع أجهزة تتبع المهام والأنظمة المماثلة.
  8. إدارة سير العمل للمهام المختلفة.

مهام سير العمل

يقوم Integro بأتمتة الأنشطة المتعلقة بدورة حياة المهمة. بعبارات مبسطة، سيتم فهم دورة حياة المهمة على أنها سير عمل المهمة في Jira. تحتوي عمليات التطوير لدينا على العديد من الاختلافات في سير العمل اعتمادًا على المشروع ونوع المهمة والخيارات المحددة في مهمة معينة. 

دعونا نلقي نظرة على سير العمل الذي نستخدمه في أغلب الأحيان:

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

في الرسم التخطيطي، يشير الترس إلى أن الانتقال يتم استدعاؤه تلقائيًا بواسطة Integro، بينما يشير الشكل البشري إلى أن الانتقال يتم استدعاؤه يدويًا بواسطة شخص ما. دعونا نلقي نظرة على المسارات المتعددة التي يمكن أن تتخذها المهمة في سير العمل هذا.

اختبار يدوي بالكامل على DEV+BETA بدون اختبارات الكناري (عادةً ما تكون هذه هي الطريقة التي نصدر بها وحدة متراصة):

من البرامج النصية إلى النظام الأساسي الخاص بنا: كيف قمنا بأتمتة التطوير في CIAN

قد تكون هناك مجموعات انتقالية أخرى. في بعض الأحيان يمكن تحديد المسار الذي ستتخذه المشكلة من خلال الخيارات الموجودة في Jira.

حركة المهمة

دعونا نلقي نظرة على الخطوات الرئيسية التي يتم تنفيذها عندما تنتقل مهمة ما عبر سير العمل "DEV Testing + Canary Tests":

1. يقوم المطور أو مدير المشروع بإنشاء المهمة.

2. يأخذ المطور المهمة للعمل. بعد الانتهاء، يتحول إلى حالة المراجعة.

3. يرسل Jira خطافًا عبر الويب إلى خدمة Jira الصغيرة (المسؤولة عن التكامل مع Jira).

4. ترسل خدمة Jira الصغيرة طلبًا إلى خدمة التدفق (المسؤولة عن سير العمل الداخلي الذي يتم تنفيذ العمل فيه) لبدء سير العمل.

5. داخل خدمة التدفق:

  • يتم تكليف المراجعين بالمهمة (خدمة المستخدمين الصغيرة التي تعرف كل شيء عن المستخدمين + خدمة Jira الصغيرة).
  • من خلال خدمة المصدر المصغرة (تعرف عن المستودعات والفروع ولكن لا تعمل بالكود نفسه) يتم البحث عن المستودعات التي تحتوي على فرع من اصدارنا (لتبسيط البحث يتطابق اسم الفرع مع المشكلة الرقم في جيرا). في أغلب الأحيان، تحتوي المهمة على فرع واحد فقط في مستودع واحد؛ وهذا يبسط إدارة قائمة انتظار النشر ويقلل الاتصال بين المستودعات.
  • لكل فرع تم العثور عليه، يتم تنفيذ التسلسل التالي من الإجراءات:

    ط) تحديث الفرع الرئيسي (Git microservice للعمل مع التعليمات البرمجية).
    XNUMX) تم حظر الفرع من التغييرات بواسطة المطور (Bitbucket microservice).
    ج) يتم إنشاء طلب سحب لهذا الفرع (خدمة Bitbucket الصغيرة).
    XNUMX) يتم إرسال رسالة حول طلب سحب جديد إلى محادثات المطورين (إخطار الخدمة الصغيرة للعمل مع الإشعارات).
    v) يتم بدء مهام البناء والاختبار والنشر على DEV (خدمة Jenkins الصغيرة للعمل مع Jenkins).
    XNUMX) إذا تم إكمال جميع الخطوات السابقة بنجاح، فإن Integro تضع موافقتها في طلب السحب (خدمة Bitbucket الصغيرة).

  • تنتظر شركة Integro الموافقة على طلب السحب من المراجعين المعينين.
  • بمجرد استلام جميع الموافقات اللازمة (بما في ذلك اجتياز الاختبارات الآلية بشكل إيجابي)، تقوم Integro بنقل المهمة إلى حالة الاختبار على Dev (Jira microservice).

6. يقوم المختبرون باختبار المهمة. إذا لم تكن هناك مشاكل، فسيتم نقل المهمة إلى حالة جاهز للبناء.

7. "يرى" Integro أن المهمة جاهزة للإصدار ويبدأ نشرها في وضع الكناري (Jenkins microservice). يتم تحديد الاستعداد للإصدار من خلال مجموعة من القواعد. على سبيل المثال، المهمة في الحالة المطلوبة، ولا توجد أقفال على المهام الأخرى، ولا توجد حاليًا أي تحميلات نشطة لهذه الخدمة الصغيرة، وما إلى ذلك.

8. يتم نقل المهمة إلى حالة Canary (Jira microservice).

9. يطلق Jenkins مهمة نشر من خلال Nomad في وضع الكناري (عادةً 1-3 حالات) ويبلغ خدمة مراقبة الإصدار (DeployWatch microservice) بشأن النشر.

10. تقوم خدمة DeployWatch الصغيرة بتجميع خلفية الخطأ والتفاعل معها، إذا لزم الأمر. إذا تم تجاوز خلفية الخطأ (يتم حساب معيار الخلفية تلقائيًا)، فسيتم إخطار المطورين عبر خدمة Notify الصغيرة. إذا لم يستجب المطور بعد 5 دقائق (النقر فوق "رجوع" أو "بقاء")، فسيتم تشغيل التراجع التلقائي لمثيلات Canary. إذا لم يتم تجاوز الخلفية، فيجب على المطور تشغيل نشر المهمة يدويًا إلى الإنتاج (بالنقر فوق زر في واجهة المستخدم). إذا لم يبدأ المطور في غضون 60 دقيقة عملية النشر إلى الإنتاج، فسيتم أيضًا التراجع عن مثيلات Canary لأسباب أمنية.

11. بعد إطلاق النشر إلى الإنتاج:

  • يتم نقل المهمة إلى حالة الإنتاج (Jira microservice).
  • تبدأ خدمة Jenkins الصغيرة عملية النشر وتقوم بإخطار خدمة DeployWatch الصغيرة حول النشر.
  • تتحقق خدمة DeployWatch الصغيرة من تحديث جميع الحاويات في مرحلة الإنتاج (كانت هناك حالات لم يتم فيها تحديث جميع الحاويات).
  • من خلال خدمة Notify الصغيرة، يتم إرسال إشعار حول نتائج النشر إلى الإنتاج.

12. سيكون لدى المطورين 30 دقيقة لبدء التراجع عن مهمة من الإنتاج إذا تم اكتشاف سلوك غير صحيح للخدمة الصغيرة. بعد هذا الوقت، سيتم دمج المهمة تلقائيًا في المهمة الرئيسية (Git microservice).

13. بعد الدمج الناجح في المهمة الرئيسية، سيتم تغيير حالة المهمة إلى مغلقة (Jira microservice).

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

ما هي الخطوة التالية

لدينا خطط كبيرة لتطوير الأتمتة، على سبيل المثال، التخلص من العمليات اليدوية أثناء الإصدارات المتراصة، وتحسين المراقبة أثناء النشر التلقائي، وتحسين التفاعل مع المطورين.

ولكن دعونا نتوقف هنا الآن. لقد تناولنا العديد من المواضيع في مراجعة الأتمتة بشكل سطحي، ولم يتم التطرق إلى بعضها على الإطلاق، لذلك سنكون سعداء بالإجابة على الأسئلة. نحن في انتظار اقتراحات حول ما يجب تغطيته بالتفصيل، اكتب في التعليقات.

المصدر: www.habr.com

إضافة تعليق