يعد Ansible أداة لأتمتة المهام التقنية البسيطة ويتميز ببنية دفع push على عكس الأدوات المشابهة له والتي تعتمد على نموذج السحب pull الشائع.
يتكون Ansible من أدلة التشغيل playbooks والوحدات modules، يحتوي دليل التشغيل على تسلسل من الوحدات لتنفيذها، أما الوحدات فهي من يقوم بالعمل المطلوب.
الوحدات modules
هي عبارة عن برامج صغيرة تُرسل إلى العقد بعد الاتصال بها ليتم تنفيذها عن بُعد وعند انتهاء مهمتها يتم إزالتها، تحتوي هذه البرامج على إعدادات الضبط ويتم دفعها إلى خوادم بلا وكلاء. تتخصص كل وحدة بتنفيذ مهمة معينة، فمثلًا تقوم وحدة بإنشاء ملف جديد وتقوم أخرى بتثبيت برمجية ما كما تقوم أخرى بتشغيل برنامج ما وهكذا لذا يوجد عدد كبير من الوحدات والتي تُضمَّن بواسطة نموذج وحدات Ansible الشامل.
تمكننا الوحدات من إدارة كل ما يمتلك واجهة برمجة تطبيقات API أو واجهة سطر الأوامر CLI أو ملف ضبط إعدادات قابل للتعديل يمكن التفاعل معه، ويشمل ذلك أجهزة الشبكة مثل موازِن الحمل والمبدل وجدار الحماية ومنسق الحاويات والحاوية نفسها وحتى نُسَخ الآلات الوهمية في مراقب Hypervisor أو في سحابة عامة cloud مثل AWS و GCE و Azure أو السحابة الخاصة مثل OpenStack و CloudStack، بالإضافة إلى أجهزة التخزين والأمان وضبط النظام.
طريقة بناء الوحدات
تُكتب إعدادات الضبط بشكل سكربتات صغيرة في ملفات YAML وتُرسل عبر الشبكة بواسطة SSH / WinRM أو أي ملحق اتصال آخر إلى الخوادم التي يُراد تنفيذها عليها، ويمكن الكتابة باستخدام أي لغة قادرة على إرجاع صيغة JSON على الرغم من أن معظم وحدات Ansible (باستثناء Windows PowerShell) مكتوبة بلغة بايثون Python باستخدام واجهة برمجة تطبيقات Ansible وهذا ما يسهل تطوير وحدات جديدة.
الملحقات
تعد الملحقات إحدى طرق توسيع قدرات Ansible ويمكن للبدائل الأخرى مثل الملحقات والقوائم الديناميكية أن تزيد أيضًا من قوة وفعالية Ansible وتنقسم الملحقات إلى عدة فئات ذات أهداف مختلفة لكل فئة ومن الملحقات الأكثر شيوعًا:
- ملحقات الاتصال Connection: تنفذ هذه الملحقات طريقة للتواصل مع الخوادم الموجودة في قائمة الخوادم، أي كيفية نقل شيفرة التشغيل الآلي عبر الشبكة ليتم تنفيذها مثل: SSH و WinRM و Telnet.
- ملحقات المرشحات Filters: تسمح للمستخدم بمعالجة البيانات داخل دليل التشغيل الخاص به، وهذه ميزة Jinja2 وتستخدم لحل مشاكل عملية IaC وهي اختصار infrastructure-as-code أي بنية تحتية كشيفرة والتي يتم فيها إدارة وتوفير البنية التحتية من خلال الشيفرة البرمجية بدلًا من العمليات اليدوية.
- ملحقات البحث Lookup: تجلب البيانات من مصدر خارجي مثل ملف أو قاعدة بيانات أو ملفات بيئة env، أو نظام البحث هيرا Hiera أو أداة تخزين واسترجاع البيانات السرية فاولت Vault من شركة HashiCorp أو غيرها.
يوجد أمثلة أخرى للملحقات وهي Action و Cache و Callback و Vars، ويمكن الاستعانة بتوثيق Ansible الرسمي لتطوير الملحقات.
تطوير الوحدات
على الرغم من امتلاك Ansible لمئات الوحدات إلا أنه قد يوجد مشكلة ما لم تتم تغطيتها بعد أو أنها شيء محدد للغاية كحل محلي في مؤسسة معينة، عندها يمكن تطوير وحدة جديدة تناسب الاحتياجات ولكن يجب التحقق بدايةً من وجود هذه الوحدة مسبقًا أو أنها قد التطوير حاليًا، وذلك من خلال التواصل مع المطورين في قناة (IRC/Freenode) في ansible-devel# أو البحث في قائمة التطوير أو مجموعات العمل الحالية.
قد تتساءل متى يجب تطوير وحدة جديدة؟ الحالات التالية تدل على أنك بحاجة إلى تطوير وحدة جديدة بدلًا من الاعتماد على وحدة حالية موجودة بالفعل:
- طرق إدارة الضبط التقليدية لا تحل مشكلة ما بشكل صحيح مثل القوالب والملف وتعديل سطر محدد في ملف الحصول على محدِّد الموارد المُوحَّد get_url
- لتحقيق الهدف المطلوب يلزم استخدام مجموعة معقدة من الأوامر وبيئات سطر الأوامر والمرشحات ومعالجة النصوص باستخدام magic regexes واستدعاءات واجهة برمجة التطبيقات API باستخدام curl.
- أدلة التشغيل الخاصة بالمستخدم ضرورية ولكنها معقدة وغير مستقرة وتعطي نتائج مختلفة مع كل تنفيذ.
تحتوي الأداة أو الخدمة في الحالة المثالية على واجهة برمجة تطبيقات أو واجهة سطر الأوامر CLI للإدارة وتقوم بإرجاع نوع من البيانات المُهيكلة مثل JSON أو XML أو YAML.
التأكد من فعالية دليل التشغيل
تعد أدلة التشغيل مهمة بنفس أهمية الوحدات حيث يؤثر دليل التشغيل السيء سلبًا على عملية الأتمتة فبدلًا من من تسخير أدوات إدارة الضبط لزيادة السرعة، تجعلنا نواجه نفس المشكلات مثل نهج التشغيل الآلي الأساسي القائم على السكربتات وتنفيذ الأوامر، ويصبح الأمر عبارة عن استخدام Ansible كوسيلة لتسليم السكربتات القديمة ونسخ ما هو موجود مسبقًا إلى ملفات YAML، وفيما يلي مثال على دليل تشغيل سيء:
- name: Read a remote resource command: "curl -v http://xpto/resource/abc" register: resource changed_when: False - name: Create a resource in case it does not exist command: "curl -X POST http://xpto/resource/abc -d '{ config:{ client: xyz, url: http://beta, pattern: *.* } }'" when: "resource.stdout | 404" # Leave it here in case I need to remove it hehehe #- name: Remove resource # command: "curl -X DELETE http://xpto/resource/abc" # when: resource.stdout == 1
يعد هذا الدليل سيء لأنه ضعيف ويتطلب شيفرة إضافية ليكون مستقرًا ولا يستطيع تحديث المورد عندما تتغير حالته.
لا تحقق أدلة التشغيل المكتوبة بهذه الطريقة العديد من معايير نهج IaC حيث يصعب على المطور قراءتها وإعادة استخدامها وتحديد متغيراتها ولا تلتزم بالنموذج التصريحي المُتبع من قبل معظم أدوات إدارة الضبط، كما تعد غير مستقرة ولا تتقارب مع الحالة المعلنة.
أما كتابة دليل التشغيل وفق معايير نهج IaC تجعله يتصف بما يلي:
- يسهل على المطور قراءته
- يسهل تحديد المتغيرات أو إعادة استخدامه
- تصريحي يتم تمثيل الموارد بشكل صحيح في YAML
- مستقر
- يتقارب من الحالة المعلنة إلى الحالة الحالية
لذا سنعيد كتابة المثال السابق لاتباع معايير نهج IaC كما يلي:
- name: XPTO xpto: name: abc state: present config: client: xyz url: http://beta pattern: "*.*"
تنفيذ وحدة مخصصة
سنستخدم WildFly وهو خادم تطبيق جافا مفتوح المصدر كمثال لتقديم وحدة مخصصة لدليل التشغيل غير الجيد:
- name: Read datasource command: "jboss-cli.sh -c '/subsystem=datasources/data-source=DemoDS:read-resource()' " register: datasource - name: Create datasource command: "jboss-cli.sh -c '/subsystem=datasources/data-source=DemoDS:add(driver-name=h2, user-name=sa, password=sa, min-pool-size=20, max-pool-size=40, connection-url=.jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE..)' " when: 'datasource.stdout | outcome => failed'
المشكلات:
- غير تصريحي
- يقوم JBoss-CLI بإظهار نص عادي كخرج بصيغة مشابهة لصيغة JSON وهو ما يضعف هذا النهج لأننا بحاجة إلى محلل لغوي لهذه الصياغة، حتى المحلل اللغوي الذي يبدو بسيطًا يمكن أن يكون معقدًا للغاية لمعالجة العديد من الاستثناءات.
- يعتبر JBoss-CLI واجهة فقط لإرسال الطلبات إلى واجهة برمجة تطبيقات الإدارة عبر المنفذ 9990.
- يعد إرسال طلب HTTP أكثر فاعلية من فتح جلسة JBoss-CLI جديدة والاتصال وإرسال أمر
- لا تتقارب مع الحالة المرغوبة بل يقوم فقط بإنشاء المورد عندما لا يكون موجودًا
لجعل الدليل تصريحيًا ومستقرًا وسهل القراءة ويتقارب مع الحالة المرغوبة بغض النظر عن الحالة الحالية، قد تبدو الوحدة المخصصة لذلك كما يلي:
- name: Configure datasource jboss_resource: name: "/subsystem=datasources/data-source=DemoDS" state: present attributes: driver-name: h2 connection-url: "jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" jndi-name: "java:jboss/datasources/DemoDS" user-name: sa password: sa min-pool-size: 20 max-pool-size: 40
أهمية تعلم بناء وحدات مخصصة
يوجد العديد من الأهداف التي يستلزم تحقيقها إنشاء وحدات مخصصة وهي:
- تحسين الوحدات الموجودة
- تحسين أدلة التشغيل السيئة
- تجنب وجود أدلة تشغيل سيئة
- زيادة الإنتاجية بسبب تحسين قدرة المستخدم على تصحيح المشكلات في أدلة التشغيل
وحدات Ansible المخصصة
تتصف بما يلي:
- يمكن كتابتها بأي لغة لكن عادةً ما تكون لغة بايثون Python هي الخيار الأفضل أو ثاني أفضل خيار
- معظم الوحدات التي يتم تسليمها باستخدام Ansible (lib/ansible/modules) مكتوبة بلغة بايثون Python ويجب أن تدعم الإصدارات المتوافقة
- يكون الخرج المعيار بصيغة JSON
طريقة Ansible
يجب كتابة الأمر التالي:
git clone https://github.com/ansible/ansible.git
ثم اتباع المسار /lib/ansible/modules ثم قراءة شيفرة الوحدة الحالية.
تكون الأدوات المتاحة هي: Git و Python و virtualenv و pdb وهو مصحح الأخطاء في بايثون، ويمكن الحصول على تعليمات شاملة من خلال الاستعانة بالتوثيق الرسمي.
يوجد بديل وهو وضع الوحدة في مجلد library، ويمكن استخدام تصميم المجلد هذا لتعديل أو تصحيح الوحدات الموجودة وهو مناسب للوحدات التي ستُستخدم داخليًا وأسهل في البداية ولا يتطلب أي شيء سوى Ansible وبيئة التطوير المتكاملة IDE ومحرر نصوص، وذلك بالشكل التالي:
library/ # تُوضع الوحدات المخصصة هنا في حال وجودها (الأمر اختياري) module_utils/ # تُوضع الأدوات المُساعدة للوحدات المخصصة في حال وجودها هنا filter_plugins/ # تُوضع ملحقات المرشحات المخصصة في حال وجودها هنا، الأمر اختياري site.yml # دليل التشغيل الرئيسي webservers.yml # دليل التشغيل الخاص بطبقة خادم الويب dbservers.yml # دليل التشغيل الخاص بطبقة خادم قاعدة البيانات roles/ common/ # تمثل هذه الهرمية دورًا library/ # يمكن أن تتضمن الأدوار أيضًا وحدات مخصصة module_utils/ # يمكن أن تتضمن الأدوار أيضًا أدوات وحدات مخصصة lookup_plugins/ # أو تتضمن أنواع أخرى من الملحقات، مثل ملحقات البحث في هذا المثال
الحل البديل لطريقة Ansible
يمكن القيام بذلك بأي أسلوب وحتى باستخدام لغة أخرى، أو يمكن استخدام الصنف AnsibleModule لأنه من الأسهل تمرير صيغة JSON إلى الخرج المعياري (()exit_json(), fail_json) بالطريقة التي يتوقعها Ansible وهي (msg, meta, has_changed, result)، ومن الأسهل أيضًا معالجة المدخلات ([]params) وتسجيل تنفيذها (()log(), debug).
def main(): arguments = dict(name=dict(required=True, type='str'), state=dict(choices=['present', 'absent'], default='present'), config=dict(required=False, type='dict')) module = AnsibleModule(argument_spec=arguments, supports_check_mode=True) try: if module.check_mode: # لا ينفذ أي شيء، بل يتحقق فقط من الحالة الحالية ويبلغ عنها module.exit_json(changed=has_changed, meta=result, msg='Fez alguma coisa ou não...') if module.params['state'] == 'present': # يتحقق من وجود مورد # يتحقق من مطابقة الحالة المطلوبة module.params['param_name'] مع الحالة الحالية module.exit_json(changed=has_changed, meta=result) if module.params['state'] == 'absent': # يزيل المصدر في حال وجوده module.exit_json(changed=has_changed, meta=result) except Error as err: module.fail_json(msg=str(err))
يسمح وضع التحقق "dry run" بتنفيذ دليل التشغيل أو التحقق فقط مما إذا كانت التغييرات مطلوبة ولا يتم تنفيذها، ويمكن استخدام مجلد module_utils للشيفرة البرمجية المشتركة بين الوحدات المختلفة.
للحصول على مثال Wildfly الكامل، يمكن الاطلاع على طلب السحب هذا.
إجراء الاختبارات
هنالك طريقتان لإجراء الاختبارات، الأولى وفق طريقة Ansible والثانية طريقة بديلة.
في طريقة Ansible، يتم اختبار شيفرة Ansible الأساسية بصعوبة حيث تؤدي كل عملية تنفيذ إلى إنشاء بناء في خادم التكامل المستمر CI، و Shippable والذي يتضمن الفحص واختبارات الوحدة واختبارات التكامل، أما بالنسبة لاختبارات التكامل فإنه يستخدم الحاويات و Ansible نفسه لأداء مرحلة الإعداد والتحقق.
فيما يلي حالة اختبار مكتوبة في Ansible لنموذج شيفرة الوحدة المخصصة لدينا:
- name: Configure datasource jboss_resource: name: "/subsystem=datasources/data-source=DemoDS" state: present attributes: connection-url: "jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" ... register: result - name: assert output message that datasource was created assert: that: - "result.changed == true" - "'Added /subsystem=datasources/data-source=DemoDS' in result.msg"
يوجد بديل وهي الطريقة الثانية وذلك عبر تجميع وحدة اعتمادًا على صلاحيات ودور المستخدم، وتوفر قدرًا أكبر من المرونة لاختيار ما يلي:
- إعداد مبسط
- كيفية تطوير البنية التحتية الخاصة بالمستخدم: Vagrant و Docker و OpenStack و EC2
- كيفية التحقق من اختبارات البنية التحتية: Testinfra و Goss
ولكن تتطلب هذه الطريقة كتابة الاختبارات باستخدام إطار العمل pytest بواسطة Testinfra أو Goss بدلًا من Ansible العادي، وفيما يلي مثال كامل داخل دور بسيط: Molecule + Vagrant + pytest: molecule init
(داخل roles/).
اقتباس"الأفكار المجردة توفر علينا وقت العمل لكنها لا توفر علينا وقت التعلم" —جويل سبولسكي Joel Spolsky، قانون التجريد المتناقص
ترجمة -وبتصرف- للمقال What you need to know about Ansible modules لصاحبه Jairo da Silva Junior.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.