Puppet هو نظام إدارة التكوين. يتم استخدامه لجلب المضيفين إلى الحالة المطلوبة والحفاظ على هذه الحالة.
لقد كنت أعمل مع Puppet منذ أكثر من خمس سنوات. هذا النص هو في الأساس عبارة عن مجموعة مترجمة ومعاد ترتيبها للنقاط الرئيسية من الوثائق الرسمية، والتي ستسمح للمبتدئين بفهم جوهر Puppet بسرعة.
المعلومات الأساسية
نظام تشغيل Puppet هو خادم عميل، على الرغم من أنه يدعم أيضًا التشغيل بدون خادم بوظائف محدودة.
يتم استخدام نموذج سحب للعملية: بشكل افتراضي، يتصل العملاء بالخادم مرة كل نصف ساعة للحصول على التكوين وتطبيقه. إذا كنت قد عملت مع Ansible، فإنهم يستخدمون نموذج دفع مختلف: يبدأ المسؤول عملية تطبيق التكوين، ولن يطبق العملاء أنفسهم أي شيء.
أثناء الاتصال بالشبكة، يتم استخدام تشفير TLS ثنائي الاتجاه: يمتلك الخادم والعميل مفاتيحهما الخاصة والشهادات المقابلة. عادةً ما يُصدر الخادم شهادات للعملاء، ولكن من حيث المبدأ من الممكن استخدام مرجع مصدق خارجي.
مقدمة للبيانات
في مصطلحات الدمية إلى الخادم الدمية يتصل العقد (العقد). تتم كتابة التكوين للعقد في البيانات بلغة برمجة خاصة - Puppet DSL.
Puppet DSL هي لغة تعريفية. وهو يصف الحالة المرغوبة للعقدة في شكل إعلانات للموارد الفردية، على سبيل المثال:
- الملف موجود ويحتوي على محتوى محدد.
- تم تثبيت الحزمة.
- بدأت الخدمة.
يمكن ربط الموارد فيما بينها:
- هناك تبعيات، فهي تؤثر على الترتيب الذي يتم به استخدام الموارد.
على سبيل المثال، "قم أولاً بتثبيت الحزمة، ثم قم بتحرير ملف التكوين، ثم ابدأ الخدمة." - توجد إشعارات - إذا تغير أحد الموارد، فإنه يرسل إشعارات إلى الموارد المشتركة فيه.
على سبيل المثال، إذا تغير ملف التكوين، فيمكنك إعادة تشغيل الخدمة تلقائيًا.
بالإضافة إلى ذلك، يحتوي Puppet DSL على وظائف ومتغيرات، بالإضافة إلى البيانات الشرطية والمحددات. يتم أيضًا دعم آليات القوالب المختلفة - EPP وERB.
الدمية مكتوبة بلغة روبي، لذا فإن العديد من التركيبات والمصطلحات مأخوذة من هناك. يتيح لك Ruby توسيع Puppet - إضافة منطق معقد وأنواع جديدة من الموارد والوظائف.
أثناء تشغيل Puppet، يتم تجميع بيانات كل عقدة محددة على الخادم في دليل. دليل هي قائمة بالموارد وعلاقاتها بعد حساب قيمة الدوال والمتغيرات وتوسيع الجمل الشرطية.
بناء الجملة ونمط التعليمات البرمجية
فيما يلي أقسام من الوثائق الرسمية التي ستساعدك على فهم بناء الجملة إذا كانت الأمثلة المقدمة غير كافية:
فيما يلي مثال على الشكل الذي يبدو عليه البيان:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}
لا تعد المسافة البادئة وفواصل الأسطر جزءًا مطلوبًا من البيان، ولكن هناك ما يوصى به
- المسافات البادئة ذات مسافتين، لا يتم استخدام علامات التبويب.
- يتم الفصل بين الأقواس المتعرجة بمسافة، ولا يتم فصل النقطتين بمسافة.
- الفواصل بعد كل معلمة، بما في ذلك المعلمة الأخيرة. كل معلمة على سطر منفصل. يتم إجراء استثناء للحالة التي لا تحتوي على معلمات ومعلمة واحدة: يمكنك الكتابة على سطر واحد وبدون فاصلة (أي:
resource { 'title': }
иresource { 'title': param => value }
). - يجب أن تكون الأسهم الموجودة على المعلمات على نفس المستوى.
- يتم كتابة أسهم علاقة الموارد أمامهم.
موقع الملفات على خادم Pappet
لمزيد من التوضيح، سأقدم مفهوم "الدليل الجذر". الدليل الجذر هو الدليل الذي يحتوي على تكوين الدمية لعقدة معينة.
يختلف الدليل الجذر وفقًا لإصدار Puppet والبيئات المستخدمة. البيئات عبارة عن مجموعات مستقلة من التكوينات التي يتم تخزينها في أدلة منفصلة. يُستخدم عادةً مع git، وفي هذه الحالة يتم إنشاء البيئات من فروع git. وفقا لذلك، تقع كل عقدة في بيئة واحدة أو أخرى. يمكن تكوين ذلك على العقدة نفسها، أو في ENC، وهو ما سأتحدث عنه في المقالة التالية.
- في الإصدار الثالث ("الدمية القديمة") كان الدليل الأساسي
/etc/puppet
. يعد استخدام البيئات أمرًا اختياريًا - على سبيل المثال، لا نستخدمها مع الدمية القديمة. إذا تم استخدام البيئات، فعادةً ما يتم تخزينها فيها/etc/puppet/environments
سيكون الدليل الجذر هو دليل البيئة. إذا لم يتم استخدام البيئات، فسيكون الدليل الجذر هو الدليل الأساسي. - بدءًا من الإصدار الرابع ("الدمية الجديدة")، أصبح استخدام البيئات إلزاميًا، وتم نقل الدليل الأساسي إلى
/etc/puppetlabs/code
. وفقا لذلك، يتم تخزين البيئات في/etc/puppetlabs/code/environments
الدليل الجذر هو دليل البيئة.
يجب أن يكون هناك دليل فرعي في الدليل الجذر manifests
، والذي يحتوي على واحد أو أكثر من البيانات التي تصف العقد. بالإضافة إلى ذلك، يجب أن يكون هناك دليل فرعي modules
، الذي يحتوي على الوحدات. سأخبرك ما هي الوحدات بعد قليل. بالإضافة إلى ذلك، قد تحتوي الدمية القديمة أيضًا على دليل فرعي files
الذي يحتوي على ملفات مختلفة نقوم بنسخها إلى العقد. في الدمية الجديدة، يتم وضع كافة الملفات في وحدات.
ملفات البيان لها الامتداد .pp
.
بضعة أمثلة قتالية
وصف العقدة والموارد الموجودة عليها
على العقدة server1.testdomain
يجب إنشاء ملف /etc/issue
مع المحتوى Debian GNU/Linux n l
. يجب أن يكون الملف مملوكًا لمستخدم والمجموعة root
، يجب أن تكون حقوق الوصول 644
.
نكتب البيان:
node 'server1.testdomain' { # блок конфигурации, относящийся к ноде server1.testdomain
file { '/etc/issue': # описываем файл /etc/issue
ensure => present, # этот файл должен существовать
content => 'Debian GNU/Linux n l', # у него должно быть такое содержимое
owner => root, # пользователь-владелец
group => root, # группа-владелец
mode => '0644', # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
}
}
العلاقات بين الموارد على العقدة
على العقدة server2.testdomain
يجب أن يكون nginx قيد التشغيل، ويعمل بتكوين مُعد مسبقًا.
دعونا نحلل المشكلة:
- يجب تثبيت الحزمة
nginx
. - من الضروري أن يتم نسخ ملفات التكوين من الخادم.
- يجب أن تكون الخدمة قيد التشغيل
nginx
. - إذا تم تحديث التكوين، يجب إعادة تشغيل الخدمة.
نكتب البيان:
node 'server2.testdomain' { # блок конфигурации, относящийся к ноде server2.testdomain
package { 'nginx': # описываем пакет nginx
ensure => installed, # он должен быть установлен
}
# Прямая стрелка (->) говорит о том, что ресурс ниже должен
# создаваться после ресурса, описанного выше.
# Такие зависимости транзитивны.
-> file { '/etc/nginx': # описываем файл /etc/nginx
ensure => directory, # это должна быть директория
source => 'puppet:///modules/example/nginx-conf', # её содержимое нужно брать с паппет-сервера по указанному адресу
recurse => true, # копировать файлы рекурсивно
purge => true, # нужно удалять лишние файлы (те, которых нет в источнике)
force => true, # удалять лишние директории
}
# Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
# подписаться на изменения ресурса, описанного выше.
# Волнистая стрелка включает в себя прямую (->).
~> service { 'nginx': # описываем сервис nginx
ensure => running, # он должен быть запущен
enable => true, # его нужно запускать автоматически при старте системы
}
# Когда ресурс типа service получает уведомление,
# соответствующий сервис перезапускается.
}
لكي يعمل هذا، تحتاج تقريبًا إلى موقع الملف التالي على الخادم الدمي:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.conf
أنواع الموارد
يمكن العثور على قائمة كاملة بأنواع الموارد المدعومة هنا
ملف
يدير الملفات والأدلة والارتباطات ومحتوياتها وحقوق الوصول.
خيارات:
- اسم المورد - المسار إلى الملف (اختياري)
- مسار - المسار إلى الملف (إذا لم يتم تحديده في الاسم)
- ضمان - نوع الملف:
absent
- حذف ملفpresent
— يجب أن يكون هناك ملف من أي نوع (إذا لم يكن هناك ملف، سيتم إنشاء ملف عادي)file
- ملف عاديdirectory
- الدليلlink
- الارتباط الرمزي
- محتوى — محتويات الملف (مناسبة فقط للملفات العادية، ولا يمكن استخدامها مع مصدر أو الهدف)
- مصدر - رابط للمسار الذي تريد نسخ محتويات الملف منه (لا يمكن استخدامه مع محتوى أو الهدف). يمكن تحديده كعنوان URI مع مخطط
puppet:
(ثم سيتم استخدام الملفات من خادم الدمية)، ومع المخططhttp:
(آمل أن يكون واضحا ما سيحدث في هذه الحالة)، وحتى مع الرسم التخطيطيfile:
أو كمسار مطلق بدون مخطط (ثم سيتم استخدام الملف من FS المحلي على العقدة) - الهدف - حيث يجب أن يشير الارتباط الرمزي (لا يمكن استخدامه مع محتوى أو مصدر)
- كاتوا ديلز - المستخدم الذي يجب أن يمتلك الملف
- رأس التجميع - المجموعة التي يجب أن ينتمي إليها الملف
- طريقة - أذونات الملف (كسلسلة)
- يعيد تنفيذ - تمكين معالجة الدليل العودي
- تطهير - يتيح حذف الملفات غير الموضحة في Puppet
- القوة - يتيح حذف الدلائل غير الموضحة في Puppet
صفقة
تثبيت وإزالة الحزم. قادر على التعامل مع الإشعارات - يعيد تثبيت الحزمة إذا تم تحديد المعلمة reinstall_on_refresh.
خيارات:
- اسم المورد - اسم الحزمة (اختياري)
- الاسم — اسم الحزمة (إذا لم يكن محددًا في الاسم)
- مزود - مدير الحزم للاستخدام
- ضمان - الحالة المرغوبة للحزمة:
present
,installed
- أي إصدار مثبتlatest
- تم تثبيت أحدث إصدارabsent
- تم الحذف (apt-get remove
)purged
- تم حذفه مع ملفات التكوين (apt-get purge
)held
- إصدار الحزمة مقفل (apt-mark hold
)любая другая строка
— تم تثبيت الإصدار المحدد
- reinstall_on_refresh - لو
true
، وبعد استلام الإشعار سيتم إعادة تثبيت الحزمة. مفيد للتوزيعات المستندة إلى المصدر، حيث قد تكون إعادة بناء الحزم ضرورية عند تغيير معلمات البناء. تقصيرfalse
.
الخدمة
يدير الخدمات. قادر على معالجة الإخطارات - إعادة تشغيل الخدمة.
خيارات:
- اسم المورد - الخدمة المراد إدارتها (اختياري)
- الاسم - الخدمة التي تحتاج إلى إدارتها (إذا لم تكن محددة في الاسم)
- ضمان - الحالة المرغوبة للخدمة:
running
- انطلقتstopped
- توقفت
- تمكين — التحكم في القدرة على بدء الخدمة:
true
- تم تمكين التشغيل التلقائي (systemctl enable
)mask
- متنكر (systemctl mask
)false
— التشغيل التلقائي معطل (systemctl disable
)
- إعادة تشغيل - الأمر لإعادة تشغيل الخدمة
- الحالة — الأمر للتحقق من حالة الخدمة
- hasrestart - وضح ما إذا كانت الخدمة initscript تدعم إعادة التشغيل. لو
false
ويتم تحديد المعلمة إعادة تشغيل - يتم استخدام قيمة هذه المعلمة. لوfalse
والمعلمة إعادة تشغيل غير محدد - تم إيقاف الخدمة وبدء إعادة التشغيل (ولكن يستخدم systemd الأمرsystemctl restart
). - hasstatus - وضح ما إذا كانت خدمة initscript تدعم الأمر
status
. إذاfalse
، ثم يتم استخدام قيمة المعلمة الحالة. تقصيرtrue
.
EXEC
تشغيل الأوامر الخارجية. إذا لم تقم بتحديد المعلمات يخلق, فقط اذا, ما لم أو منعش فقطسيتم تشغيل الأمر في كل مرة يتم فيها تشغيل Puppet. قادر على معالجة الإخطارات - يقوم بتشغيل أمر.
خيارات:
- اسم المورد - الأمر المطلوب تنفيذه (اختياري)
- أمر - الأمر المراد تنفيذه (إذا لم يتم تحديده في الاسم)
- مسار — المسارات التي تبحث فيها عن الملف القابل للتنفيذ
- فقط اذا — إذا اكتمل الأمر المحدد في هذه المعلمة برمز إرجاع صفر، فسيتم تنفيذ الأمر الرئيسي
- ما لم — إذا اكتمل الأمر المحدد في هذه المعلمة برمز إرجاع غير الصفر، فسيتم تنفيذ الأمر الرئيسي
- يخلق — إذا كان الملف المحدد في هذه المعلمة غير موجود، فسيتم تنفيذ الأمر الرئيسي
- منعش فقط - لو
true
، فلن يتم تشغيل الأمر إلا عندما يتلقى exec إشعارًا من موارد أخرى - cwd - الدليل الذي سيتم تشغيل الأمر منه
- المستخدم - المستخدم الذي سيتم تشغيل الأمر منه
- مزود - كيفية تشغيل الأمر:
- POSIX - يتم إنشاء عملية فرعية ببساطة، تأكد من تحديدها مسار
- قذيفة - يتم إطلاق الأمر في الصدفة
/bin/sh
، لا يجوز تحديدها مسار، يمكنك استخدام اللمعان والأنابيب وميزات الصدفة الأخرى. عادةً ما يتم اكتشافه تلقائيًا في حالة وجود أي أحرف خاصة (|
,;
,&&
,||
إلخ).
كرون
يتحكم في وظائف كرون.
خيارات:
- اسم المورد - مجرد نوع من المعرف
- ضمان - حالة التاج:
present
- إنشاء إذا لم يكن موجوداabsent
- احذف إذا كان موجودا
- أمر - ما الأمر للتشغيل
- بيئة — في أي بيئة يتم تشغيل الأمر (قائمة متغيرات البيئة وقيمها عبر
=
) - المستخدم - من أي مستخدم سيتم تشغيل الأمر
- دقيقة, ساعة, يوم من أيام الأسبوع, شهر, يوم الشهر - متى يتم تشغيل كرون. إذا لم يتم تحديد أي من هذه السمات، فستكون قيمتها في crontab
*
.
في دمية 6.0 كرون كما لو
حول الموارد بشكل عام
متطلبات تفرد الموارد
الخطأ الأكثر شيوعا الذي نواجهه هو إعلان مكرر. يحدث هذا الخطأ عند ظهور مصدرين أو أكثر من نفس النوع بنفس الاسم في الدليل.
ولذلك سأكتب مرة أخرى: يجب ألا تحتوي بيانات نفس العقدة على موارد من نفس النوع بنفس العنوان!
في بعض الأحيان تكون هناك حاجة لتثبيت حزم بنفس الاسم، ولكن مع مديري حزم مختلفين. في هذه الحالة، تحتاج إلى استخدام المعلمة name
لتجنب الخطأ:
package { 'ruby-mysql':
ensure => installed,
name => 'mysql',
provider => 'gem',
}
package { 'python-mysql':
ensure => installed,
name => 'mysql',
provider => 'pip',
}
تحتوي أنواع الموارد الأخرى على خيارات مماثلة للمساعدة في تجنب التكرار - name
у الخدمة, command
у EXEC، وما إلى ذلك وهلم جرا.
المعلمات الفوقية
يحتوي كل نوع من الموارد على بعض المعلمات الخاصة، بغض النظر عن طبيعته.
قائمة كاملة من المعلمات التعريفية
قائمة قصيرة:
- تطلب - تشير هذه المعلمة إلى الموارد التي يعتمد عليها هذا المورد.
- قبل - تحدد هذه المعلمة الموارد التي تعتمد على هذا المورد.
- الاشتراك — تحدد هذه المعلمة الموارد التي يتلقى هذا المورد الإشعارات منها.
- أبلغ - تحدد هذه المعلمة الموارد التي تتلقى إعلامات من هذا المورد.
تقبل كافة المعلمات الوصفية المدرجة إما رابط مورد واحد أو مجموعة من الروابط بين قوسين مربعين.
روابط للموارد
رابط المورد هو مجرد ذكر للمورد. يتم استخدامها بشكل أساسي للإشارة إلى التبعيات. سيؤدي الرجوع إلى مورد غير موجود إلى حدوث خطأ في الترجمة.
صيغة الرابط هي كما يلي: نوع المورد بحرف كبير (إذا كان اسم النوع يحتوي على نقطتين مزدوجتين، فكل جزء من الاسم بين النقطتين يكتب بحرف كبير)، ثم اسم المورد بين قوسين مربعين (حالة الاسم لم يتغير!). يجب ألا تكون هناك مسافات، حيث تتم كتابة الأقواس المربعة مباشرة بعد اسم النوع.
على سبيل المثال:
file { '/file1': ensure => present }
file { '/file2':
ensure => directory,
before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']
التبعيات والإخطارات
كما ذكرنا سابقًا، فإن التبعيات البسيطة بين الموارد تعتبر متعدية. بالمناسبة، كن حذرًا عند إضافة التبعيات - يمكنك إنشاء تبعيات دورية، مما سيؤدي إلى حدوث خطأ في الترجمة.
على عكس التبعيات، الإخطارات ليست متعدية. تنطبق القواعد التالية على الإخطارات:
- إذا تلقى المورد إشعارًا، فسيتم تحديثه. تعتمد إجراءات التحديث على نوع المورد - EXEC يدير الأمر، الخدمة إعادة تشغيل الخدمة، صفقة يعيد تثبيت الحزمة. إذا لم يكن لدى المورد إجراء تحديث محدد، فلن يحدث شيء.
- أثناء تشغيل Puppet مرة واحدة، لا يتم تحديث المورد أكثر من مرة. وهذا ممكن لأن الإشعارات تتضمن تبعيات ولا يحتوي الرسم البياني للتبعية على دورات.
- إذا قام Puppet بتغيير حالة أحد الموارد، فسيرسل المورد إشعارات إلى جميع الموارد المشتركة فيه.
- إذا تم تحديث أحد الموارد، فإنه يرسل إعلامات إلى كافة الموارد المشتركة فيه.
التعامل مع المعلمات غير المحددة
كقاعدة عامة، إذا لم يكن لبعض معلمات الموارد قيمة افتراضية ولم يتم تحديد هذه المعلمة في البيان، فلن تقوم Puppet بتغيير هذه الخاصية للمورد المقابل على العقدة. على سبيل المثال، إذا كان المورد من النوع ملف المعلمة غير محددة owner
، فلن يقوم Puppet بتغيير مالك الملف المقابل.
مقدمة إلى الفئات والمتغيرات والتعاريف
لنفترض أن لدينا العديد من العقد التي لها نفس الجزء من التكوين، ولكن هناك أيضًا اختلافات - وإلا فيمكننا وصفها كلها في كتلة واحدة node {}
. بالطبع، يمكنك ببساطة نسخ أجزاء متطابقة من التكوين، ولكن بشكل عام هذا حل سيء - ينمو التكوين، وإذا قمت بتغيير الجزء العام من التكوين، فسيتعين عليك تحرير نفس الشيء في العديد من الأماكن. في الوقت نفسه، من السهل ارتكاب خطأ، وبشكل عام، تم اختراع مبدأ DRY (لا تكرر نفسك) لسبب ما.
لحل هذه المشكلة هناك مثل هذا التصميم فئة.
فصول
أولاً يجب وصف الفصل. الوصف نفسه لا يضيف أي موارد في أي مكان. يتم وصف الفئة في البيانات:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}
بعد ذلك يمكن استخدام الفصل:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше
مثال من المهمة السابقة - لننقل تثبيت nginx وتكوينه إلى فئة:
class nginx_example {
package { 'nginx':
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => 'puppet:///modules/example/nginx-conf',
recure => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
include nginx_example
}
المتغيرات
الفئة من المثال السابق ليست مرنة على الإطلاق لأنها تأتي دائمًا بنفس تكوين nginx. لنقم بإنشاء المسار إلى متغير التكوين، ثم يمكن استخدام هذه الفئة لتثبيت nginx بأي تكوين.
يمكن إنجازه
تنبيه: المتغيرات في Puppet غير قابلة للتغيير!
بالإضافة إلى ذلك، لا يمكن الوصول إلى المتغير إلا بعد الإعلان عنه، وإلا ستكون قيمة المتغير undef
.
مثال على العمل مع المتغيرات:
# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"
دمية لديها مساحات الأسماء، والمتغيرات، وفقا لذلك، لديها مجال الرؤية: يمكن تعريف متغير بنفس الاسم في مساحات أسماء مختلفة. عند تحديد قيمة متغير، يتم البحث عن المتغير في مساحة الاسم الحالية، ثم في مساحة الاسم المتضمنة، وهكذا.
أمثلة على مساحة الاسم:
- عالمي - المتغيرات خارج وصف الفئة أو العقدة تذهب إلى هناك؛
- مساحة اسم العقدة في وصف العقدة؛
- مساحة اسم الفئة في وصف الفئة.
لتجنب الغموض عند الوصول إلى متغير، يمكنك تحديد مساحة الاسم في اسم المتغير:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var
دعونا نتفق على أن المسار إلى تكوين nginx يكمن في المتغير $nginx_conf_source
. ثم سيبدو الفصل كما يلي:
class nginx_example {
package { 'nginx':
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => $nginx_conf_source, # здесь используем переменную вместо фиксированной строки
recure => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
$nginx_conf_source = 'puppet:///modules/example/nginx-conf'
include nginx_example
}
ومع ذلك، فإن المثال المذكور سيء لأن هناك بعض "المعرفة السرية" أنه في مكان ما داخل الفصل يتم استخدام متغير يحمل اسم كذا وكذا. من الأصح بكثير جعل هذه المعرفة عامة - يمكن أن تحتوي الفئات على معلمات.
معلمات الطبقة هي متغيرات في مساحة اسم الفئة، ويتم تحديدها في رأس الفئة ويمكن استخدامها مثل المتغيرات العادية في نص الفئة. يتم تحديد قيم المعلمات عند استخدام الفئة في البيان.
يمكن تعيين المعلمة إلى قيمة افتراضية. إذا لم يكن للمعلمة قيمة افتراضية ولم يتم تعيين القيمة عند استخدامها، فسوف يتسبب ذلك في حدوث خطأ في الترجمة.
لنحدد معلمات الفئة من المثال أعلاه ونضيف معلمتين: الأول، مطلوب، هو المسار إلى التكوين، والثاني، اختياري، هو اسم الحزمة مع nginx (في دبيان، على سبيل المثال، هناك حزم nginx
, nginx-light
, nginx-full
).
# переменные описываются сразу после имени класса в круглых скобках
class nginx_example (
$conf_source,
$package_name = 'nginx-light', # параметр со значением по умолчанию
) {
package { $package_name:
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => $conf_source,
recurse => true,
purge => true,
force => true,
}
~> service { 'nginx':
ensure => running,
enable => true,
}
}
node 'server2.testdomain' {
# если мы хотим задать параметры класса, функция include не подойдёт* — нужно использовать resource-style declaration
# *на самом деле подойдёт, но про это расскажу в следующей серии. Ключевое слово "Hiera".
class { 'nginx_example':
conf_source => 'puppet:///modules/example/nginx-conf', # задаём параметры класса точно так же, как параметры для других ресурсов
}
}
في Puppet، يتم كتابة المتغيرات. يأكل
تتم كتابة النوع مباشرة قبل اسم المعلمة:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}
الفئات: تشمل اسم الفئة مقابل الفئة {'اسم الفئة':}
كل فئة هي مورد من النوع فئة. كما هو الحال مع أي نوع آخر من الموارد، لا يمكن أن يكون هناك مثيلان من نفس الفئة على نفس العقدة.
إذا حاولت إضافة فئة إلى نفس العقدة مرتين باستخدام class { 'classname':}
(لا يوجد فرق، مع معلمات مختلفة أو متطابقة)، سيكون هناك خطأ في الترجمة. ولكن إذا كنت تستخدم فئة في نمط المورد، فيمكنك على الفور تعيين جميع معلماتها بشكل صريح في البيان.
ومع ذلك، إذا كنت تستخدم include
، ثم يمكن إضافة الفصل عدة مرات حسب الرغبة. الحقيقة انه include
هي دالة غير فعالة تتحقق مما إذا كان قد تمت إضافة فئة إلى الدليل. إذا لم يكن الفصل موجودًا في الدليل، فإنه يضيفه، وإذا كان موجودًا بالفعل، فإنه لا يفعل شيئًا. لكن في حالة الاستخدام include
لا يمكنك تعيين معلمات الفئة أثناء إعلان الفئة - يجب تعيين كافة المعلمات المطلوبة في مصدر بيانات خارجي - Hiera أو ENC. وسنتحدث عنهم في المقال التالي.
يعرف
كما ذكرنا في الكتلة السابقة، لا يمكن أن تكون نفس الفئة موجودة على العقدة أكثر من مرة. ومع ذلك، في بعض الحالات، يجب أن تكون قادرًا على استخدام نفس كتلة التعليمات البرمجية مع معلمات مختلفة على نفس العقدة. بمعنى آخر، هناك حاجة إلى نوع مورد خاص به.
على سبيل المثال، لتثبيت وحدة PHP، نقوم بما يلي في Avito:
- قم بتثبيت الحزمة مع هذه الوحدة.
- لنقم بإنشاء ملف تكوين لهذه الوحدة.
- نقوم بإنشاء رابط رمزي لتكوين php-fpm.
- نقوم بإنشاء رابط رمزي للتكوين لـ php cli.
في مثل هذه الحالات، تصميم مثل $title
، حيث يذهب اسم المورد عندما يتم الإعلان عنه. كما هو الحال في حالة الفئات، يجب أولا وصف التعريف، وبعد ذلك يمكن استخدامه.
مثال مبسط مع وحدة PHP:
define php74::module (
$php_module_name = $title,
$php_package_name = "php7.4-${title}",
$version = 'installed',
$priority = '20',
$data = "extension=${title}.son",
$php_module_path = '/etc/php/7.4/mods-available',
) {
package { $php_package_name:
ensure => $version,
install_options => ['-o', 'DPkg::NoTriggers=true'], # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
}
-> file { "${php_module_path}/${php_module_name}.ini":
ensure => $ensure,
content => $data,
}
file { "/etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
ensure => link,
target => "${php_module_path}/${php_module_name}.ini",
}
file { "/etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
ensure => link,
target => "${php_module_path}/${php_module_name}.ini",
}
}
node server3.testdomain {
php74::module { 'sqlite3': }
php74::module { 'amqp': php_package_name => 'php-amqp' }
php74::module { 'msgpack': priority => '10' }
}
أسهل طريقة لاكتشاف خطأ التصريح المكرر هي في التعريف. يحدث هذا إذا كان التعريف يحتوي على مورد باسم ثابت، وهناك مثيلان أو أكثر لهذا التعريف في بعض العقد.
من السهل حماية نفسك من هذا: يجب أن يكون لكل الموارد الموجودة داخل التعريف اسمًا اعتمادًا على ذلك $title
. البديل هو الإضافة غير الفعالة للموارد؛ في أبسط الحالات، يكفي نقل الموارد المشتركة لجميع مثيلات التعريف إلى فئة منفصلة وإدراج هذه الفئة في التعريف - الوظيفة include
عاجز.
هناك طرق أخرى لتحقيق العجز عند إضافة الموارد، وهي استخدام الوظائف defined
и ensure_resources
لكن سأخبركم عنها في الحلقة القادمة.
التبعيات والإخطارات للفئات والتعريفات
تضيف الفئات والتعريفات القواعد التالية للتعامل مع التبعيات والإشعارات:
- التبعية على فئة/تعريف تضيف تبعيات على كافة موارد الفئة/التعريف؛
- تضيف تبعية الفئة/التعريف التبعيات إلى جميع موارد الفئة/التعريف؛
- يُعلم إشعار الفئة/التعريف جميع موارد الفئة/التعريف؛
- اشتراك class/define يشترك في جميع موارد class/define.
البيانات الشرطية والمحددات
if
كل شيء بسيط هنا:
if ВЫРАЖЕНИЕ1 {
...
} elsif ВЫРАЖЕНИЕ2 {
...
} else {
...
}
ما لم
ما لم يكن if معكوسًا: سيتم تنفيذ كتلة التعليمات البرمجية إذا كان التعبير خاطئًا.
unless ВЫРАЖЕНИЕ {
...
}
حقيبة
لا يوجد شيء معقد هنا أيضًا. يمكنك استخدام القيم العادية (السلاسل والأرقام وما إلى ذلك) والتعبيرات العادية وأنواع البيانات كقيم.
case ВЫРАЖЕНИЕ {
ЗНАЧЕНИЕ1: { ... }
ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
default: { ... }
}
محددات
المحدد هو بناء لغة مشابه لـ case
، ولكن بدلاً من تنفيذ كتلة من التعليمات البرمجية، فإنها تُرجع قيمة.
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }
وحدات
عندما يكون التكوين صغيرًا، يمكن بسهولة الاحتفاظ به في بيان واحد. ولكن كلما زاد عدد التكوينات التي وصفناها، زاد عدد الفئات والعقد الموجودة في البيان، ونموها، وأصبح العمل بها غير مناسب.
بالإضافة إلى ذلك، هناك مشكلة إعادة استخدام التعليمات البرمجية - عندما تكون جميع التعليمات البرمجية موجودة في بيان واحد، يكون من الصعب مشاركة هذا الرمز مع الآخرين. لحل هاتين المشكلتين، لدى Puppet كيان يسمى الوحدات النمطية.
وحدات - هذه مجموعات من الفئات والتعريفات وكيانات الدمى الأخرى الموضوعة في دليل منفصل. بمعنى آخر، الوحدة هي جزء مستقل من منطق الدمية. على سبيل المثال، قد تكون هناك وحدة نمطية للعمل مع nginx، وسوف تحتوي على ما هو مطلوب فقط للعمل مع nginx، أو قد تكون هناك وحدة نمطية للعمل مع PHP، وما إلى ذلك.
يتم إصدار الوحدات، ويتم أيضًا دعم تبعيات الوحدات على بعضها البعض. يوجد مستودع مفتوح للوحدات -
على خادم الدمية، توجد الوحدات النمطية في الدليل الفرعي للوحدات النمطية للدليل الجذر. يوجد داخل كل وحدة نظام دليل قياسي - البيانات والملفات والقوالب وlib وما إلى ذلك.
هيكل الملف في الوحدة النمطية
قد يحتوي جذر الوحدة على الدلائل التالية بأسماء وصفية:
manifests
- أنه يحتوي على البياناتfiles
- يحتوي على ملفاتtemplates
- يحتوي على قوالبlib
- أنه يحتوي على رمز روبي
هذه ليست قائمة كاملة بالأدلة والملفات، لكنها كافية لهذه المقالة في الوقت الحالي.
أسماء الموارد وأسماء الملفات في الوحدة
لا يمكن تسمية الموارد (الفئات والتعريفات) في الوحدة النمطية بأي اسم تريده. بالإضافة إلى ذلك، هناك تطابق مباشر بين اسم المورد واسم الملف الذي سيبحث فيه Puppet عن وصف لذلك المورد. إذا انتهكت قواعد التسمية، فلن تجد Puppet ببساطة وصف المورد، وستحصل على خطأ في الترجمة.
القواعد بسيطة:
- يجب أن تكون كافة الموارد الموجودة في الوحدة النمطية في مساحة اسم الوحدة النمطية. إذا تم استدعاء الوحدة
foo
، فيجب تسمية جميع الموارد الموجودة فيهfoo::<anything>
، أو فقطfoo
. - يجب أن يكون المورد الذي يحمل اسم الوحدة موجودًا في الملف
init.pp
. - بالنسبة للموارد الأخرى، يكون نظام تسمية الملف كما يلي:
- يتم تجاهل البادئة التي تحتوي على اسم الوحدة
- يتم استبدال كافة النقطتين المزدوجتين، إن وجدت، بخطوط مائلة
- يتم إضافة التمديد
.pp
سأوضح مع مثال. لنفترض أنني أكتب وحدة nginx
. ويحتوي على الموارد التالية:
- فئة
nginx
الموصوفة في البيانinit.pp
; - فئة
nginx::service
الموصوفة في البيانservice.pp
; - يُعرِّف
nginx::server
الموصوفة في البيانserver.pp
; - يُعرِّف
nginx::server::location
الموصوفة في البيانserver/location.pp
.
قوالب
من المؤكد أنك تعرف ما هي القوالب، ولن أصفها بالتفصيل هنا. لكنني سأترك الأمر فقط في حالة
كيفية استخدام القوالب: يمكن توسيع معنى القالب باستخدام وظيفة template
، والذي يتم تمرير المسار إلى القالب. للموارد من النوع ملف تستخدم جنبا إلى جنب مع المعلمة content
. على سبيل المثال، مثل هذا:
file { '/tmp/example': content => template('modulename/templatename.erb')
عرض المسار <modulename>/<filename>
يعني الملف <rootdir>/modules/<modulename>/templates/<filename>
.
بالإضافة إلى ذلك ، هناك وظيفة inline_template
- يتلقى نص القالب كمدخل، وليس اسم الملف.
ضمن القوالب، يمكنك استخدام كافة متغيرات Puppet في النطاق الحالي.
يدعم Puppet القوالب بتنسيق ERB وEPP:
باختصار عن ERB
جمل التحكم:
<%= ВЫРАЖЕНИЕ %>
- أدخل قيمة التعبير<% ВЫРАЖЕНИЕ %>
— حساب قيمة التعبير (دون إدراجه). عادةً ما يتم وضع العبارات الشرطية (إذا) والحلقات (كل منهما) هنا.<%# КОММЕНТАРИЙ %>
تتم كتابة التعبيرات في ERB باللغة Ruby (ERB هو في الواقع Embedded Ruby).
للوصول إلى المتغيرات من البيان، تحتاج إلى إضافة @
إلى اسم المتغير. لإزالة فاصل الأسطر الذي يظهر بعد إنشاء عنصر تحكم، تحتاج إلى استخدام علامة إغلاق -%>
.
مثال على استخدام القالب
لنفترض أنني أكتب وحدة للتحكم في ZooKeeper. تبدو الفئة المسؤولة عن إنشاء التكوين كما يلي:
class zookeeper::configure (
Array[String] $nodes,
Integer $port_client,
Integer $port_quorum,
Integer $port_leader,
Hash[String, Any] $properties,
String $datadir,
) {
file { '/etc/zookeeper/conf/zoo.cfg':
ensure => present,
content => template('zookeeper/zoo.cfg.erb'),
}
}
والقالب المقابل zoo.cfg.erb
- لذا:
<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>
dataDir=<%= @datadir %>
<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>
الحقائق والمتغيرات المضمنة
غالبًا ما يعتمد الجزء المحدد من التكوين على ما يحدث حاليًا على العقدة. على سبيل المثال، اعتمادًا على إصدار دبيان، ستحتاج إلى تثبيت إصدار أو آخر من الحزمة. يمكنك مراقبة كل هذا يدويًا، وإعادة كتابة البيانات إذا تغيرت العقد. ولكن هذا ليس نهجا جديا؛ فالأتمتة أفضل كثيرا.
للحصول على معلومات حول العقد، تمتلك الدمية آلية تسمى الحقائق. معطيات - هذه معلومات حول العقدة، وهي متاحة في البيانات على شكل متغيرات عادية في مساحة الاسم العامة. على سبيل المثال، اسم المضيف، وإصدار نظام التشغيل، وبنية المعالج، وقائمة المستخدمين، وقائمة واجهات الشبكة وعناوينها، وغير ذلك الكثير. تتوفر الحقائق في البيانات والقوالب كمتغيرات عادية.
مثال على العمل مع الحقائق:
notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог
من الناحية الرسمية، الحقيقة لها اسم (سلسلة) وقيمة (تتوفر أنواع مختلفة: السلاسل والمصفوفات والقواميس). يأكل
أثناء التشغيل، يقوم الوكيل العميل أولاً بنسخ جميع جامعي الحقائق المتاحين من خادم بابت إلى العقدة، وبعد ذلك يطلقهم ويرسل الحقائق المجمعة إلى الخادم؛ بعد ذلك، يبدأ الخادم في تجميع الكتالوج.
الحقائق في شكل ملفات قابلة للتنفيذ
يتم وضع هذه الحقائق في وحدات في الدليل facts.d
. وبطبيعة الحال، يجب أن تكون الملفات قابلة للتنفيذ. عند التشغيل، يجب أن تقوم بإخراج المعلومات إلى الإخراج القياسي إما بتنسيق YAML أو مفتاح = قيمة.
لا تنس أن الحقائق تنطبق على جميع العقد التي يتحكم فيها الخادم القفاز الذي تم نشر الوحدة الخاصة بك عليه. لذلك، في البرنامج النصي، احرص على التحقق من أن النظام لديه كافة البرامج والملفات اللازمة لكي تعمل حقيقتك.
#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'
حقائق روبي
يتم وضع هذه الحقائق في وحدات في الدليل lib/facter
.
# всё начинается с вызова функции Facter.add с именем факта и блоком кода
Facter.add('ladvd') do
# в блоках confine описываются условия применимости факта — код внутри блока должен вернуть true, иначе значение факта не вычисляется и не возвращается
confine do
Facter::Core::Execution.which('ladvdc') # проверим, что в PATH есть такой исполняемый файл
end
confine do
File.socket?('/var/run/ladvd.sock') # проверим, что есть такой UNIX-domain socket
end
# в блоке setcode происходит собственно вычисление значения факта
setcode do
hash = {}
if (out = Facter::Core::Execution.execute('ladvdc -b'))
out.split.each do |l|
line = l.split('=')
next if line.length != 2
name, value = line
hash[name.strip.downcase.tr(' ', '_')] = value.strip.chomp(''').reverse.chomp(''').reverse
end
end
hash # значение последнего выражения в блоке setcode является значением факта
end
end
حقائق النص
يتم وضع هذه الحقائق على العقد في الدليل /etc/facter/facts.d
في الدمية القديمة أو /etc/puppetlabs/facts.d
في الدمية الجديدة.
examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue
الوصول إلى الحقائق
هناك طريقتان للتعامل مع الحقائق:
- من خلال القاموس
$facts
:$facts['fqdn']
; - باستخدام اسم الحقيقة كاسم متغير:
$fqdn
.
من الأفضل استخدام القاموس $facts
أو حتى الأفضل الإشارة إلى مساحة الاسم العامة ($::facts
).
المتغيرات المضمنة
وإلى جانب الحقائق، هناك أيضا
- حقائق موثوقة - المتغيرات المأخوذة من شهادة العميل (نظرًا لأن الشهادة يتم إصدارها عادةً على خادم منبثق، لا يمكن للوكيل أخذ شهادته وتغييرها، وبالتالي فإن المتغيرات "موثوقة"): اسم الشهادة، اسم المضيف والمجال، ملحقات من الشهادة.
- حقائق الخادم - المتغيرات المتعلقة بالمعلومات حول الخادم - الإصدار والاسم وعنوان IP للخادم والبيئة.
- حقائق الوكيل - المتغيرات المضافة مباشرة بواسطة وكيل الدمية، وليس عن طريق العوامل - اسم الشهادة، إصدار الوكيل، إصدار الدمية.
- المتغيرات الرئيسية - متغيرات Pappetmaster (هكذا!). إنه نفس الشيء تقريبًا كما في حقائق الخادمبالإضافة إلى توفر قيم معلمات التكوين.
- متغيرات المترجم - متغيرات المترجم التي تختلف في كل نطاق: اسم الوحدة الحالية واسم الوحدة التي تم الوصول إلى الكائن الحالي من خلالها. يمكن استخدامها، على سبيل المثال، للتحقق من أن فئاتك الخاصة لا يتم استخدامها مباشرة من الوحدات الأخرى.
الإضافة 1: كيفية تشغيل وتصحيح كل هذا؟
تحتوي المقالة على العديد من الأمثلة على كود الدمية، لكنها لم تخبرنا على الإطلاق بكيفية تشغيل هذا الكود. حسنًا، أنا أصحح نفسي.
الوكيل يكفي لتشغيل Puppet، لكن في معظم الحالات ستحتاج أيضًا إلى خادم.
وكيل
على الأقل منذ الإصدار XNUMX، حزم وكيل الدمى من
في أبسط الحالات، لاستخدام تكوين الدمية، يكفي تشغيل الوكيل في وضع بدون خادم: بشرط أن يتم نسخ رمز الدمية إلى العقدة، قم بالتشغيل puppet apply <путь к манифесту>
:
atikhonov@atikhonov ~/puppet-test $ cat helloworld.pp
node default {
notify { 'Hello world!': }
}
atikhonov@atikhonov ~/puppet-test $ puppet apply helloworld.pp
Notice: Compiled catalog for atikhonov.localdomain in environment production in 0.01 seconds
Notice: Hello world!
Notice: /Stage[main]/Main/Node[default]/Notify[Hello world!]/message: defined 'message' as 'Hello world!'
Notice: Applied catalog in 0.01 seconds
من الأفضل، بالطبع، إعداد الخادم وتشغيل الوكلاء على العقد في الوضع الخفي - ثم سيطبقون التكوين الذي تم تنزيله من الخادم مرة واحدة كل نصف ساعة.
يمكنك تقليد نموذج الدفع للعمل - انتقل إلى العقدة التي تهتم بها وابدأ sudo puppet agent -t
. مفتاح -t
(--test
) يتضمن في الواقع العديد من الخيارات التي يمكن تمكينها بشكل فردي. تتضمن هذه الخيارات ما يلي:
- لا تعمل في الوضع الخفي (افتراضيًا، يبدأ الوكيل في الوضع الخفي)؛
- يتم إيقاف التشغيل بعد تطبيق الكتالوج (افتراضيًا، سيستمر الوكيل في العمل وتطبيق التكوين مرة واحدة كل نصف ساعة)؛
- كتابة سجل عمل مفصل؛
- إظهار التغييرات في الملفات.
لدى الوكيل وضع تشغيل بدون تغييرات - يمكنك استخدامه عندما لا تكون متأكدًا من أنك كتبت التكوين الصحيح وتريد التحقق مما سيغيره الوكيل بالضبط أثناء التشغيل. يتم تمكين هذا الوضع بواسطة المعلمة --noop
في سطر الأوامر: sudo puppet agent -t --noop
.
بالإضافة إلى ذلك، يمكنك تمكين سجل تصحيح الأخطاء للعمل - حيث تكتب الدمية عن جميع الإجراءات التي تقوم بها: حول المورد الذي تتم معالجته حاليًا، حول معلمات هذا المورد، حول البرامج التي تطلقها. بالطبع هذه معلمة --debug
.
الخادم
لن أفكر في الإعداد الكامل لخادم pappetserver ونشر التعليمات البرمجية عليه في هذه المقالة؛ سأقول فقط أنه يوجد إصدار جاهز للعمل بكامل طاقته من الخادم ولا يتطلب تكوينًا إضافيًا للعمل مع عدد صغير من العقد (على سبيل المثال، ما يصل إلى مائة). سيتطلب عدد أكبر من العقد ضبطًا - افتراضيًا، لا يقوم خادم الدمى بتشغيل أكثر من أربعة عمال، للحصول على أداء أفضل، تحتاج إلى زيادة عددهم ولا تنس زيادة حدود الذاكرة، وإلا فإن الخادم سوف يجمع القمامة في معظم الأوقات.
نشر التعليمات البرمجية - إذا كنت في حاجة إليها بسرعة وسهولة، فابحث (في r10k)[
الملحق 2: إرشادات الترميز
- ضع كل المنطق في الفصول والتعاريف.
- احتفظ بالفئات والتعريفات في الوحدات النمطية، وليس في البيانات التي تصف العقد.
- استخدم الحقائق.
- لا تقم بإنشاء ifs بناءً على أسماء المضيفين.
- لا تتردد في إضافة معلمات للفئات والتعريفات - وهذا أفضل من المنطق الضمني المخفي في نص الفئة/التعريف.
سأشرح لماذا أوصي بالقيام بذلك في المقالة التالية.
اختتام
لننتهي من المقدمة. في المقالة التالية سأخبرك عن Hiera وENC وPuppetDB.
يمكن للمستخدمين المسجلين فقط المشاركة في الاستطلاع.
في الواقع، هناك المزيد من المواد - يمكنني كتابة مقالات حول المواضيع التالية، والتصويت على ما قد ترغب في قراءته:
- 59,1%بنيات الدمى المتقدمة - بعض هراء المستوى التالي: الحلقات، ورسم الخرائط وتعبيرات لامدا الأخرى، وجامعي الموارد، والموارد المصدرة، والتواصل بين المضيفين عبر الدمى، والعلامات، والموفرين، وأنواع البيانات المجردة.13
- 31,8%"أنا مشرف والدتي" أو كيف قمنا في Avito بتكوين صداقات مع العديد من الخوادم القفازية ذات الإصدارات المختلفة، ومن حيث المبدأ، الجزء المتعلق بإدارة الخادم القفاز.7
- 81,8%كيف نكتب كود الدمية: الأجهزة، التوثيق، الاختبار، CI/CD.18
صوّت 22 مستخدمًا. امتنع 9 مستخدما عن التصويت.
المصدر: www.habr.com