نظرية وممارسة توحيد خدمات دوكر

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



مصدر الصورة:


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


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


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


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


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


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

- يتم التطوير في الشركة في 90 ٪ من الحالات في C # ؛
- ليست هناك حاجة للبدء من نقطة الصفر ، وهي جزء من المعايير والمقاربات والتقنيات المقبولة - وهذا نتيجة للتجربة أو مجرد إرث تاريخي ؛
- مستودعات مع مشاريع .NET ، على عكس الفرق ، العشرات (وسيكون هناك المزيد) ؛
- نود أن نستخدم خط أنابيب CI بسيطًا للغاية ، مع تجنب تأمين البائع قدر الإمكان ؛
- بالنسبة إلى مطور .NET العادي ، لا تزال الكلمات "الحاوية" و "docker" و "Linux" تسبب نوبات من الرعب الوجودي الطفيف ، لكنني لا أريد اختراق أي شخص عبر الركبة.

خلفية صغيرة


في ربيع عام 2017 ، قدمت Microsoft للعالم معاينة لـ .NET Core 2.0 ، وفي هذا العام سارع المنجمون على الفور إلى إعلان Linux Year ، لذلك ...



مصدر الصورة:


لبعض الوقت ، لم نثق في السحر ، وجمعنا واختبرنا كل شيء على كل من Windows و Linux ، نشرنا قطع أثرية مع بعض البرامج النصية SSH ، حاولنا تهيئة خطوط أنابيب CI / CD القديمة في وضع السكين السويسري. ولكن بعد بعض الوقت أدركوا أننا كنا نفعل شيئا خاطئا. بالإضافة إلى ذلك ، بدا أن الإشارات إلى الخدمات المجهرية والحاويات أصبحت أكثر فأكثر. لذلك قررنا أيضًا ركوب موجة الضجيج واستكشاف هذه الاتجاهات.


بالفعل في مرحلة التفكير في مستقبلنا المحتمل لخدمات micros ، ظهر عدد من الأسئلة ، متجاهلين ، والتي خاطرنا في هذه المشاكل الجديدة في المستقبل التي كنا سنخلقها لأنفسنا في مقابل المشاكل التي تم حلها.


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


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


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


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


CLI مقابل. واجهة المستخدم الرسومية


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


إذن ما ينبغي أن يكون مستودعنا:


  • تقع في أحد المشاريع ، حيث الاسم وحقوق الوصول والسياسات لقبول طلبات السحب ، إلخ ؛
  • تحتوي على الملفات والدلائل المطلوبة ، مثل:
    • ملف مع التكوين والمعلومات حول مستودع SolutionInfo.props (المزيد حول ذلك أدناه) ؛
    • رموز مصدر المشروع في دليل src ؛
    • README.md ، README.md ، وما إلى ذلك ؛
  • تحتوي على وحدات Git اللازمة ؛
  • يجب أن يستمد المشروع من أحد القوالب.

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


  • يحدد مشروع في Bitbucket للاختيار ؛
  • يتحقق من صحة الاسم وفقًا لاتفاقنا ؛
  • يجعل جميع الإعدادات اللازمة التي لا يمكن توريثها من المشروع ؛
  • يقوم بتحديث قائمة القوالب المخصصة (نستخدم templating dotnet ) للمشروع ويقترح الاختيار من بينها ؛
  • تعبئة الحد الأدنى من المعلومات الضرورية حول المستودع في ملف التكوين وفي مستندات *.md ؛
  • يربط وحدات فرعية مع تكوين خط أنابيب CI / CD (في حالتنا هو المواصفات الخيزران ) والبرامج النصية التجميع.

بمعنى آخر ، يقوم المطور ، الذي يبدأ مشروعًا جديدًا ، بإطلاق الأداة المساعدة ، ويملأ عدة مجالات ، ويحدد نوع المشروع ويتلقى ، على سبيل المثال ، "Hello world!" خدمة متصلة بالفعل بنظام CI ، حيث يمكن نشر الخدمة حتى إذا قمت بالتعهد بتغيير الإصدار إلى غير صفري.


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


هيكل


توحيد هيكل المستودع قد ترسخت معنا لفترة طويلة وكانت هناك حاجة لتبسيط التجميع والتكامل مع نظام CI وبيئة التطوير. في البداية ، بدأنا من فكرة أن خط الأنابيب في CI يجب أن يكون بسيطًا كما تعتقد ، هو المعيار الذي يضمن قابلية التجميع وقابلية إعادة إنتاجه. أي أنه يمكن الحصول على نفس النتيجة بسهولة في أي نظام CI وفي مكان عمل المطور. لذلك ، يتم تقديم كل ما لا يتعلق بميزات بيئة تكامل مستمر معينة إلى وحدة فرعية Git خاصة ونظام بناء مكتفٍ ذاتيًا. بتعبير أدق ، نظام توحيد التجميع. يجب أن يقوم خط الأنابيب نفسه ، على أقل تقدير تقريبًا ، بتشغيل البرنامج النصي build.sh فقط ، والتقاط تقرير عن الاختبارات وبدء عملية النشر ، إذا لزم الأمر. للتوضيح ، دعنا نرى ما يحدث إذا قمت بإنشاء مستودع SampleService في مشروع باسم Sandbox .


 . ├── [bamboo-specs] ├── [devops.build] │ ├── build.sh │ └── ... ├── [docs] ├── [.scripts] ├── [src] │ ├── [CodeAnalysis] │ ├── [Sandbox.SampleService] │ ├── [Sandbox.SampleService.Bootstrap] │ ├── [Sandbox.SampleService.Client] │ ├── [Sandbox.SampleService.Tests] │ ├── Directory.Build.props │ ├── NLog.config │ ├── NuGet.Config │ └── Sandbox.SampleService.sln ├── .gitattributes ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── README.md └── SolutionInfo.props 

الدلائل الأولين هي وحدات جيت. bamboo-specs هي "Pipeline as Code" لنظام Atlassian Bamboo CI (يمكن أن يكون هناك بعض Jenkinsfile في مكانه) ، devops.build هو نظام البناء الخاص بنا ، والذي devops.build بمزيد من التفاصيل أدناه. .scripts دليل .scripts أيضًا .scripts . يقع مشروع .NET نفسه في src : NuGet.Config يحتوي على تكوين مستودع NuGet الخاص ، NLog.config تكوين وقت تشغيل NLog . كما تعتقد ، فإن استخدام NLog في الشركة يعد أيضًا أحد المعايير. من الأشياء المثيرة للاهتمام هنا هو ملف Directory.Build.props السحري تقريبًا. لسبب ما ، قليل من الناس يعرفون مثل هذا الاحتمال في مشاريع .NET ، مثل تخصيص التجميع . باختصار ، Directory.Build.targets استيراد الملفات ذات الأسماء Directory.Build.props و Directory.Build.targets تلقائيًا إلى المشاريع الخاصة بك وتسمح لك بتكوين الخصائص الشائعة لجميع المشاريع في مكان واحد. على سبيل المثال ، هذه هي الطريقة التي نقوم بتوصيلها بمحلل StyleCop.Analyzers وتهيئته من دليل CodeAnalysis إلى جميع مشاريع نمط التعليمات البرمجية ، وتعيين قواعد الإصدار وبعض السمات الشائعة للمكتبات والحزم ( الشركة ، حقوق الطبع والنشر ، إلخ) ، وكذلك الاتصال عبر <Import> ملف SolutionInfo.props ، وهو بالضبط ملف تكوين المخزون ، الذي تمت مناقشته أعلاه. يحتوي بالفعل على الإصدار الحالي ، ومعلومات حول المؤلفين ، وعنوان URL الخاص بالمستودع ووصفه ، بالإضافة إلى العديد من الخصائص التي تؤثر على سلوك نظام التجميع والتحف الناتجة.


مثال `SolutionInfo.props`
 <?xml version="1.0"?> <Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="devops.build/SolutionInfo.xsd"> <PropertyGroup> <!-- Product name --> <Product>Sandbox.SampleService</Product> <!-- Product version. Version 0.0.0 wouldn't be published! --> <BaseVersion>0.0.0</BaseVersion> <!-- Project name which contains Main() --> <EntryProject>Sandbox.SampleService.Bootstrap</EntryProject> <!-- Exposed port --> <ExposedPort>4000/tcp</ExposedPort> <!-- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT value. See https://github.com/dotnet/corefx/blob/master/Documentation/architecture/globalization-invariant-mode.md --> <GlobalizationInvariant>false</GlobalizationInvariant> <!-- Project URL --> <RepositoryUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/</RepositoryUrl> <!-- Documentation URL --> <DocumentationUrl>https://bitbucket.contoso.com/projects/SND/repos/sandbox.sampleservice/browse/README.md</DocumentationUrl> <!-- Your name here --> <Authors>User Name &lt;username@contoso.com&gt;</Authors> <!-- Project description --> <Description>The sample service for demo purposes.</Description> <!-- Bamboo plan key (required for Bamboo Specs --> <BambooBlanKey>SMPL</BambooBlanKey> </PropertyGroup> </Project> 

مثال `Directory.Build.props`
 <Project> <Import Condition="Exists('..\SolutionInfo.props')" Project="..\SolutionInfo.props" /> <ItemGroup> <None Include="$(MSBuildThisFileDirectory)/CodeAnalysis/stylecop.json" Link="stylecop.json" CopyToOutputDirectory="Never"/> <PackageReference Include="StyleCop.Analyzers" Version="1.*" PrivateAssets="all" /> </ItemGroup> <PropertyGroup> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/CodeAnalysis/stylecop.ruleset</CodeAnalysisRuleSet> <!-- Enable XML docs generating--> <GenerateDocumentationFile>true</GenerateDocumentationFile> <!-- Enable C# 7.x features --> <LangVersion>latest</LangVersion> <!-- default base version --> <BaseVersion Condition="'$(BaseVersion)' == ''">0.0.0</BaseVersion> <!-- default build number and format --> <BuildNumber Condition="'$(BuildNumber)' == ''">0</BuildNumber> <BuildNumber>$([System.String]::Format('{0:0000}',$(BuildNumber)))</BuildNumber> <!-- default version suffix --> <VersionSuffix Condition="'$(VersionSuffix)' == ''">local</VersionSuffix> <!-- empty version suffix instead of 'prod' --> <VersionSuffix Condition="'$(VersionSuffix)' == 'prod'"></VersionSuffix> <!-- format version prefix --> <VersionPrefix>$(BaseVersion).$(BuildNumber)</VersionPrefix> <!-- disable IsPackable by default --> <IsPackable>false</IsPackable> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <Company>Contoso</Company> <Copyright>Copyright $([System.DateTime]::Now.Date.Year) Contoso Ltd</Copyright> </PropertyGroup> </Project> 

جمعية


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


بدأ اختيار الإطار المناسب. استنادًا إلى متطلبات استنساخ النتيجة على كلٍ من ماكينات الإنشاء التي تعمل بنظام Linux وعلى أجهزة Windows من أي مطور ، كان الشرط الأساسي هو نظام أساسي حقيقي عبر الحد الأدنى من التبعيات المحددة مسبقًا. في أوقات مختلفة ، تمكنت من التعرف على بعض أطر التجميع لمطوري .NET بشكل جيد: من MSBuild وتكوينات XML الشنيعة ، والتي تمت ترجمتها لاحقًا إلى Psake (Powershell) ، إلى FAKE الغريبة (F #). لكن هذه المرة أردت شيئًا جديدًا وخفيفًا. علاوة على ذلك ، فقد تقرر بالفعل أن التجميع والاختبار يجب أن يتم كليا في بيئة حاوية معزولة ، لذلك لم أخطط للتشغيل داخل أي شيء آخر غير أوامر Docker CLI و Git ، أي أنه كان يجب وصف معظم العملية في Dockerfile.
في ذلك الوقت ، كان كل من FAKE 5 و Cake for .NET Core لا يزالان غير جاهزين ، لذلك مع وجود نظام أساسي مشترك ، كانت هذه المشاريع جاهزة. ولكن تم بالفعل إطلاق إصدار PowerShell 6 Core المحبوب للغاية ، واعتدت استخدامه بالكامل. لذلك ، قررت أن أنتقل إلى Psake مرة أخرى ، وبينما التفتت ، تعثرت في مشروع Invoke-Build المثير للاهتمام ، والذي هو إعادة التفكير في Psake ، وكما يشير المؤلف نفسه ، هو نفسه ، فقط أفضل وأسهل. هكذا هو. لن أتطرق إليها بالتفصيل في إطار هذه المقالة ، إلا أنني سألاحظ أن الاكتناز يرشّها في حال توفر جميع الوظائف الأساسية لهذا الصنف من المنتجات:


  • يوصف تسلسل الإجراءات بمجموعة من المهام (المهام) المترابطة ، والتي يمكن التحكم فيها باستخدام ترابطها وشروطها الإضافية.
  • يوجد العديد من المساعدين المفيدين ، على سبيل المثال ، exec {} لمعالجة أكواد خروج تطبيق وحدة التحكم بشكل صحيح.
  • ستتم معالجة أي استثناء أو توقف عن استخدام Ctrl + C بشكل صحيح في كتلة Exit-Build خاصة مدمجة. على سبيل المثال ، يمكنك حذف جميع الملفات المؤقتة أو بيئة اختبار أو رسم تقرير يرضي العين.


عام Dockerfile


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


نمط متعدد المراحل والحجج


الخطوة الأولى هي تقسيم مراحل التجميع إلى مراحل أصغر وإضافة مراحل جديدة. لذلك ، يجدر تسليط الضوء على إطلاق dotnet build كمرحلة منفصلة ، لأنه بالنسبة للمشروعات التي تحتوي على مكتبات فقط ، لا معنى لتشغيل dotnet publish . الآن ، وفقًا لتقديرنا ، يمكننا فقط تشغيل مراحل التجميع المطلوبة باستخدام
dotnet build --target <name>
على سبيل المثال ، نجمع هنا مشروعًا يحتوي على مكتبات فقط. القطع الأثرية هنا ليست سوى حزم NuGet ، مما يعني أنه لا معنى لجمع صورة وقت التشغيل.



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



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


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG BUILD_BASE=mcr.microsoft.com/dotnet/core/sdk:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/runtime:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM ${BUILD_BASE} AS restore ... FROM ${RUNTIME_BASE} AS runtime ... 

لذلك حصلنا على الجديد والوهلة الأولى ، وليس فرصا واضحة. أولاً ، إذا أردنا إنشاء صورة باستخدام تطبيق ASP.NET Core ، فستحتاج صورة وقت التشغيل إلى صورة مختلفة: mcr.microsoft.com/dotnet/core/aspnet . يجب حفظ المعلمة مع صورة أساسية غير قياسية في تكوين مستودع SolutionInfo.props وتمريرها كوسيطة أثناء التجميع. لقد سهلنا أيضًا على المطور استخدام إصدارات أخرى من صور .NET Core: معاينات ، على سبيل المثال ، أو حتى تلك المخصصة (لا تعرف أبدًا!).


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


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/aspnet:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM node:alpine AS install WORKDIR /build COPY package.json . RUN npm install FROM install AS src COPY [".babelrc", ".eslintrc.js", ".stylelintrc", "./"] COPY ClientApp ./ClientApp FROM src AS publish RUN npm run build-prod FROM ${RUNTIME_BASE} AS appbase COPY --from=publish /build/wwwroot/ /app/wwwroot/ 

دعونا نجمع هذه الصورة مع العلامة ، والتي سننتقل إلى مرحلة تجميع صورة وقت تشغيل خدمة ASP.NET كوسيطة إلى RUNTIME_BASE. بحيث يمكنك توسيع التجميع بقدر ما تريد ، بما في ذلك ، يمكنك تحديد ما لا يمكنك فعله فقط في docker build . تريد المعلمة إضافة حجم؟ سهل:


 ARG DOTNETCORE_VERSION=2.2 ARG ALPINE_VERSION= ARG RUNTIME_BASE=mcr.microsoft.com/dotnet/core/aspnet:${DOTNETCORE_VERSION}-alpine${ALPINE_VERSION} FROM ${RUNTIME_BASE} AS runtime ARG VOLUME VOLUME ${VOLUME} 

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


تشغيل الاختبارات


بدلاً من إجراء الاختبارات مباشرةً في مراحل التجميع ، من الأصح والأكثر ملاءمة القيام بذلك في حاوية "Run Runner" خاصة. بإيجاز نقل جوهر هذا النهج ، ألاحظ أنه يتيح لك:


  • قم بإجراء جميع عمليات الإطلاق المجدولة ، حتى لو تعطل أحدها ؛
  • قم بتحميل دليل نظام ملفات المضيف في الحاوية لتلقي تقرير اختبار ، وهو أمر حيوي عند البناء في نظام CI ؛
  • قم بإجراء الاختبار في بيئة مؤقتة عن طريق تمرير اسم شبكتها إلى docker run --network <test_network_name> .

تعني الفقرة الأخيرة أنه لا يمكننا الآن تشغيل اختبارات الوحدة فقط ، ولكن أيضًا اختبارات التكامل. نحن نصف البيئة ، على سبيل المثال ، في docker-compose.yaml ، docker-compose.yaml للبناء بالكامل. يمكنك الآن التحقق من التفاعل مع قاعدة البيانات أو خدماتنا الأخرى ، وحفظ السجلات منها في حالة احتياجك للتحليل.


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


وألاحظ أيضًا أن النهج المتبع في حاويات العداء التي تم تجميعها في المرحلة باستخدام dotnet build سوف يخدم حينها أيضًا في إطلاق dotnet publish و dotnet nuget push dotnet pack و dotnet nuget push . سيتيح لنا ذلك حفظ عناصر التجميع محليًا.


Healthcheck وتبعيات نظام التشغيل


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


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


 .scripts ├── healthcheck.sh ├── run.sh └── runtime-deps.sh 

يحتوي البرنامج النصي healthcheck.sh على الأوامر اللازمة للتحقق من الحالة:


  • للويب باستخدام curl:


     #!/bin/ash set –e curl -sIf -o /dev/null -w "%{http_code}\n" 127.0.0.1/health || exit 1 

  • خدمات أخرى باستخدام أداة CLI الخاصة بنا:


     #!/bin/ash set –e healthcheck || exit 1 


باستخدام runtime-deps.sh ، يتم تثبيت التبعيات ، وإذا لزم الأمر ، يتم تنفيذ أي إجراءات أخرى على نظام التشغيل الأساسي والتي تكون ضرورية runtime-deps.sh الطبيعي للتطبيق داخل الحاوية. أمثلة نموذجية:


  • لتطبيق ويب:


     #!/bin/ash apk add --no-cache curl icu-libs 

  • لخدمة gRPC:


     #!/bin/ash apk add --no-cache libc6-compat 


وبالتالي ، فإن طريقة إدارة التبعيات والتحقق من الحالة موحدة ، ولكن هناك مجالًا لبعض المرونة. أما بالنسبة run.sh ، ثم هو أبعد من ذلك.


النصي نقطة الدخول


أنا متأكد من أن كل من كتب دوكرفيل على الأقل تساءل عن التوجيه الذي يجب استخدامه - CMD أو ENTRYPOINT . علاوة على ذلك ، لدى هذه الفرق أيضًا خياران في بناء الجملة ، مما يؤثر بشكل كبير على النتيجة. لن أشرح الفرق بالتفصيل ، وأكرر بعد أولئك الذين أوضحوا كل شيء بالفعل. أوصي فقط بتذكر أنه في 99٪ من المواقف ، من الصحيح استخدام ENTRYPOINT وبناء جملة exec:


مدخل ["/ path / to / قابل للتنفيذ"]


وإلا ، فلن يتمكن التطبيق الذي تم تشغيله من معالجة أوامر نظام التشغيل بشكل صحيح ، مثل SIGTERM ، وما إلى ذلك ، ويمكنك أيضًا أن تواجه مشكلة في شكل عمليات zombie وكل ما يتعلق بمشكلة PID 1 . ولكن ماذا لو كنت ترغب في بدء تشغيل الحاوية دون تشغيل التطبيق؟ نعم ، يمكنك تجاوز نقطة الدخول:
docker run --rm -it --entrypoint ash <image_name> <params>
لا تبدو مريحة جدا وبديهية ، أليس كذلك؟ ولكن هناك أخبار جيدة: يمكنك أن تفعل أفضل! وهي استخدام برنامج نصي نقطة الدخول . يسمح لك هذا البرنامج النصي بإجراء التهيئة التعسفية المعقدة ( مثال ) ومعالجة المعلمات وأي شيء تريده.


في حالتنا ، بشكل افتراضي ، يتم استخدام السيناريو الأكثر بساطة ، ولكن في نفس الوقت:


 #!/bin/sh set -e if [ ! -z "$1" ] && $(command -v $1 >/dev/null 2>&1) then exec $@ else exec /usr/bin/dotnet /app/${ENTRY_PROJECT}.dll $@ fi 

يسمح لك بالتحكم في إطلاق الحاوية بشكل حدسي للغاية:
docker run <image> env - فقط ينفذ env في الصورة ، مع عرض متغيرات البيئة.
docker run <image> -param1 value1 - بدء تشغيل الخدمة باستخدام الوسائط المحددة.


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


ماذا بعد


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


  • يتم تثبيت جميع تسميات الصور اللازمة: الإصدارات وأرقام المراجعة وارتباطات الوثائق والمؤلف وغيرها.
  • في حاوية وقت التشغيل ، يتم إعادة تعريف تكوين NLog بحيث يتم تقديم جميع السجلات فور نشرها في شكل منظم باستخدام json ، والتي يتم إصدار نسخة منها.
  • قواعد التحليل الثابت وأي معايير أخرى يتم تحديثها تلقائيًا.

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


استنتاج


الموصوفة هنا ليست سوى جزء من نهج متكامل للتوحيد القياسي. , , , , , . . , .


, Linux , - . , , . , , , Code Style, , .


, ! , « », , . , Docker .

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


All Articles