xenvman: بيئات اختبار الخدمة الدقيقة المرنة (والمزيد)

مرحبا بالجميع!


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


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


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


xenvman كتابة xenvman في Go xenvman HTTP بسيط ، والذي يسمح لك باستخدام جميع الوظائف المتاحة من أي لغة تتحدث هذا البروتوكول.


الشيء الرئيسي الذي يمكن أن يفعله xenvman هو:


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

البيئة


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



يوضح الشكل أعلاه خادم xenvman والبيئات النشطة التي تعمل فيها خدمات وقواعد بيانات مختلفة. تم إنشاء كل بيئة مباشرة من رمز اختبار التكامل ، وسيتم حذفها عند الانتهاء.


أنماط


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


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


النماذج قابلة للمعايير ، أي أنه يمكننا التحكم بشكل كامل في منطق القالب عن طريق تمرير معلمات معينة في طلب HTTP الخاص بنا.


إنشاء الصور على الطاير


في رأيي ، واحدة من أكثر ميزات xenvman الملائمة هي إنشاء صور Docker مباشرة في عملية تكوين البيئة. لماذا قد يكون هذا ضروريا؟
حسنًا ، على سبيل المثال ، في مشروعنا ، من أجل الحصول على صورة خدمة ، يلزمك الالتزام بالتغييرات في فرع منفصل ، والبدء والانتظار حتى يقوم Gitlab CI بتجميع وتعبئة الصورة.
إذا تغيرت خدمة واحدة فقط ، فيمكن أن يستغرق الأمر من 3-5 دقائق.


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


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


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


مثال صغير


من أجل الوضوح ، دعونا ننظر إلى المثال الجزئي.


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


لذلك سنحاول استخدام xenvman من أجل خلق بيئة مع Mongo قيد التشغيل وخادم wut الخاص بنا.


بادئ ذي بدء ، نحتاج إلى إنشاء دليل أساسي يتم فيه تخزين جميع القوالب:


$ mkdir xenv-templates && cd xenv-templates


بعد ذلك ، أنشئ نموذجين ، أحدهما لـ Mongo ، والآخر لخادمنا:


$ touch mongo.tpl.js wut.tpl.js


mongo.tpl.js


افتح mongo.tpl.js واكتب ما يلي هناك:


 function execute(tpl, params) { var img = tpl.FetchImage(fmt("mongo:%s", params.tag)); var cont = img.NewContainer("mongo"); cont.SetLabel("mongo", "true"); cont.SetPorts(27017); cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' }); } 

يجب أن يحتوي القالب على وظيفة execute () مع معلمتين.
الأول هو مثيل لكائن tpl الذي يتم من خلاله تكوين البيئة. الوسيطة الثانية (params) هي مجرد كائن JSON والذي سنقوم بتعديل قالبنا.


في الخط


 var img = tpl.FetchImage(fmt("mongo:%s", params.tag)); 

نطلب من xenvman تنزيل صورة docker mongo:<tag> ، حيث <tag> هو إصدار الصورة التي نريد استخدامها. من حيث المبدأ ، يكون هذا الخط مكافئًا لأمر docker pull mongo:<tag> ، مع وجود الفرق الوحيد في أن جميع وظائف كائن tpl معلن عنها بشكل أساسي ، أي أنه لن يتم تنزيل الصورة فعليًا إلا بعد تنفيذ xenvman لجميع القوالب المحددة في تكوين البيئة.


بعد أن لدينا الصورة ، يمكننا إنشاء حاوية:


 var cont = img.NewContainer("mongo"); 

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


بعد ذلك ، نضع ملصقًا على حاوياتنا:


 cont.SetLabel("mongo", "true"); 

يتم استخدام الاختصارات بحيث يمكن للحاويات العثور على بعضها البعض في بيئة ، على سبيل المثال ، لإدخال عنوان IP أو اسم المضيف في ملف التكوين.


نحتاج الآن إلى تعليق منفذ Mongo الداخلي (27017). يتم ذلك بسهولة مثل هذا:


  cont.SetPorts(27017); 

قبل أن يقدم لنا xenvman تقريراً حول الإنشاء الناجح للبيئة ، سيكون من الرائع التأكد من أن جميع الخدمات لا تعمل فقط ، ولكنها جاهزة بالفعل لقبول الطلبات. Xenvman لديه الشيكات الاستعداد لهذا الغرض.
أضف واحدة من هذا النوع لحاوية المنجو لدينا:


  cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' }); 

كما نرى ، يوجد هنا في شريط العناوين روتين حيث سيتم استبدال القيم اللازمة ديناميكيًا قبل إطلاق الحاويات.


بدلاً من {{.ExternalAddress}} سيتم استبدال العنوان الخارجي للمضيف الذي يقوم بتشغيل xenvman ، وبدلاً من {{.Self.ExposedPort 27017}} سيتم استبدال المنفذ الخارجي الذي تمت إعادة توجيهه إلى 27017 داخلي.


اقرأ المزيد عن الاستيفاء هنا .


كنتيجة لكل هذا ، يمكننا الاتصال بمتجر Mongo الذي يعمل في البيئة ، مباشرة خارج ، على سبيل المثال ، من المضيف الذي نجري اختبارنا عليه.


wut.tpl.js


لذا ، ج ، بعد التعامل مع wut لخادم wut الخاص بنا.
نظرًا لأننا نريد تجميع الصورة أثناء التنقل ، سيكون القالب مختلفًا بعض الشيء:


 function execute(tpl, params) { var img = tpl.BuildImage("wut-image"); img.CopyDataToWorkspace("Dockerfile"); // Extract server binary var bin = type.FromBase64("binary", params.binary); img.AddFileToWorkspace("wut", bin, 0755); // Create container var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true}); cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] }); } 

نظرًا لأننا BuildImage() الصورة هنا ، فإننا نستخدم BuildImage() بدلاً من FetchImage() :


  var img = tpl.BuildImage("wut-image"); 

لتجميع الصورة ، سنحتاج إلى عدة ملفات:
Dockerfile - في الواقع تعليمات حول كيفية تجميع صورة
config.toml - ملف التكوين لخادم wut لدينا


باستخدام أسلوب img.CopyDataToWorkspace("Dockerfile"); نقوم بنسخ Dockerfile من دليل بيانات القالب إلى دليل عمل مؤقت .


دليل البيانات هو دليل يمكننا من خلاله تخزين جميع الملفات اللازمة ليعمل القالب الخاص بنا.


في دليل العمل المؤقت ، نقوم بنسخ الملفات (باستخدام img.CopyDataToWorkspace ()) التي تدخل الصورة.


ما يلي:


  // Extract server binary var bin = type.FromBase64("binary", params.binary); img.AddFileToWorkspace("wut", bin, 0755); 

نمرر binar خادمنا مباشرة في المعلمات ، في شكل (base64) المشفرة. وفي القالب ، نقوم ببساطة بفك wut وحفظ السلسلة الناتجة في دليل العمل كملف تحت اسم wut .


ثم قم بإنشاء حاوية وقم بتثبيت ملف التكوين فيه:


  var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true}); 

استدعاء MountData() يعني أنه سيتم تحميل ملف config.toml من دليل البيانات داخل الحاوية تحت اسم /config.toml . تخبر علامة interpolate xenvman الخادم بأنه يجب استبدال جميع الدعائم قبل التركيب في الملف.


إليك ما قد يبدو عليه التكوين:


 {{with .ContainerWithLabel "mongo" "" -}} mongo = "{{.Hostname}}/wut" {{- end}} 

نحن هنا نبحث عن الحاوية التي تحمل ملصق mongo ، mongo اسم مضيفها ، أياً كان في هذه البيئة.


بعد الاستبدال ، قد يبدو الملف كما يلي:


 mongo = “mongo.0.mongo.xenv/wut” 

بعد ذلك ، ننشر مرة أخرى المنفذ ونبدأ في التحقق من جاهزية ، هذه المرة HTTP:


 cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] }); 

قوالبنا جاهزة لهذا ، ويمكننا استخدامها في كود اختبار التكامل:


 import "github.com/syhpoon/xenvman/pkg/client" import "github.com/syhpoon/xenvman/pkg/def" //  xenvman  cl := client.New(client.Params{}) //      env := cl.MustCreateEnv(&def.InputEnv{ Name: "wut-test", Description: "Testing Wut", // ,      Templates: []*def.Tpl{ { Tpl: "wut", Parameters: def.TplParams{ "binary": client.FileToBase64("wut"), "port": 5555, }, }, { Tpl: "mongo", Parameters: def.TplParams{"tag": “latest”}, }, }, }) //      defer env.Terminate() //     wut  wutCont, err := env.GetContainer("wut", 0, "wut") require.Nil(t, err) //      mongoCont, err := env.GetContainer("mongo", 0, "mongo") require.Nil(t, err) //    wutUrl := fmt.Sprintf("http://%s:%d/v1/wut/", env.ExternalAddress, wutCont.Ports[“5555”]) mongoUrl := fmt.Sprintf("%s:%d/wut", env.ExternalAddress, mongoCont.Ports["27017"]) // !      ,            ,   

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


في هذا المثال الصغير ، وبعيدًا عن عرض جميع ميزات xenvman ، يتوفر هنا دليل مفصل خطوة بخطوة .


الزبائن


يوجد حاليًا عملاء بلغتين:


اذهب
بيثون


لكن إضافة أخرى جديدة ليست صعبة ، لأن واجهة برمجة التطبيقات المقدمة بسيطة للغاية.


واجهة الويب


في الإصدار 2.0.0 ، تمت إضافة واجهة ويب بسيطة يمكنك من خلالها إدارة بيئاتك وعرض القوالب المتوفرة.





كيف يختلف xenvman عن عامل الإرساء؟


بالطبع ، هناك الكثير من أوجه التشابه ، لكن يبدو أن xenvman بالنسبة لي هو نهج أكثر مرونة وديناميكية ، بالمقارنة مع التكوين الثابت في الملف.
فيما يلي أهم السمات المميزة ، في رأيي:


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

المراجع


صفحة مشروع جيثب
مثال تفصيلي تفصيلي ، باللغة الإنجليزية.


الخاتمة


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


سأحاول الإجابة على أي أسئلة ، وسأكون سعيدًا إذا وجد أي شخص آخر هذا المشروع مفيدًا.

Source: https://habr.com/ru/post/ar439236/


All Articles