مرحبا بالجميع!
أنا أعمل كمهندس DevOps في خدمة حجز الفنادق Ostrovok.ru . في هذه المقالة أريد أن أتحدث عن تجربتنا في اختبار الأدوار الغريبة.
في Ostrovok.ru ، نستخدم ansible كمدير التكوين. توصلنا مؤخرًا إلى الحاجة إلى اختبار الأدوار ، ولكن كما تبين لاحقًا ، لا توجد أدوات كثيرة لذلك - ربما يكون إطار Molecule هو الأكثر شعبية ، لذلك قررنا استخدامه. ولكن تبين أن وثائقه كانت صامتة حول العديد من المزالق. لم نتمكن من العثور على دليل مفصل باللغة الروسية ، لذلك قررنا كتابة هذا المقال.

جزيء
جزيء - إطار للمساعدة في اختبار الأدوار ansible.
وصف مبسط: ينشئ الجزيء مثيلًا على النظام الأساسي الذي تحدده (سحابة ، جهاز ظاهري ، حاوية ؛ لمزيد من التفاصيل ، راجع قسم برنامج التشغيل ) ، ويؤدي دورك فيه ، ثم يدير الاختبارات ويحذف المثيل. في حالة الفشل في إحدى الخطوات ، سيعلمك الجزيء بهذا.
الآن بمزيد من التفاصيل.
قليلا من الناحية النظرية
النظر في الكيانين الرئيسيين للجزيء: السيناريو والسائق.
سيناريو
يحتوي البرنامج النصي على وصف لما وأين وكيف وكيف سيتم تنفيذ التسلسل. يمكن أن يحتوي أحد الأدوار على العديد من البرامج النصية ، وكل منها عبارة عن دليل على طول المسار <role>/molecule/<scenario>
، والذي يحتوي على أوصاف للإجراءات اللازمة للاختبار. يجب أن يكون البرنامج النصي default
موجودًا ، والذي سيتم إنشاؤه تلقائيًا في حالة تهيئة الدور باستخدام Molecule. يتم اختيار أسماء السيناريوهات التالية وفقًا لتقديرك.
يُطلق على تسلسل الاختبار في البرنامج النصي المصفوفة ، وهي افتراضيًا كما يلي:
(الخطوات المميزة بـ ?
تخطيها افتراضيًا إذا لم يصفها المستخدم)
lint
- تشغيل لينتر. افتراضيًا ، flake8
yamllint
و flake8
،destroy
- إزالة الحالات من إطلاق Molecule الأخير (في حالة اليسار) ،dependency
؟ - تثبيت التبعية الواضحة للدور المختبر ،syntax
- تحقق من بناء دور باستخدام ansible-playbook --syntax-check
create
- إنشاء مثيل ،prepare
؟ - إعداد المثيل ؛ على سبيل المثال فحص / تثبيت python2converge
- إطلاق قواعد اللعبة التي تم اختبارها ،idempotence
- إعادة تشغيل playbook لاختبار idempotency ،side_effect
؟ - الإجراءات التي لا تتعلق مباشرة بالدور ، ولكنها ضرورية للاختبارات ،verify
- تشغيل اختبارات التكوين الناتج باستخدام testinfra
(افتراضي) / goss
/ inspec
،cleanup
؟ - (في الإصدارات الجديدة) - "تنظيف" البنية التحتية الخارجية المتأثرة بالجزيء ،destroy
- حذف مثيل.
يغطي هذا التسلسل معظم الحالات ، ولكن يمكنك تغييره إذا لزم الأمر.
يمكن تشغيل كل خطوة من الخطوات أعلاه بشكل منفصل باستخدام molecule <command>
. ولكن من المفيد أن نفهم أنه لكل أمر من هذه الأوامر قد يكون هناك تسلسل من الإجراءات الخاصة به ، والتي يمكن التعرف عليها من خلال تنفيذ molecule matrix <command>
. على سبيل المثال ، عند تشغيل الأمر converge
(تشغيل playbook الذي تم اختباره) ، سيتم تنفيذ الإجراءات التالية:
$ molecule matrix converge ... └── default
يمكن تحرير تسلسل هذه الإجراءات. إذا تم بالفعل إكمال شيء من القائمة ، فسيتم تخطيه. يتم تخزين الحالة الحالية ، بالإضافة إلى تهيئة المثيل ، في دليل $TMPDIR/molecule/<role>/<scenario>
.
إضافة خطوات مع ?
يمكنك ، بعد وصف الإجراءات المرغوبة بتنسيق ansible-playbook ، وجعل اسم الملف وفقًا للخطوة: side_effect.yml
/ side_effect.yml
. سينتظر الجزيء لهذه الملفات في مجلد البرنامج النصي.
سائق
برنامج التشغيل هو كيان حيث يتم إنشاء مثيلات الاختبار.
قائمة برامج التشغيل القياسية التي تحتوي على قوالب جاهزة للجزيء هي كما يلي: Azure ، Docker ، EC2 ، GCE ، LXC ، LXD ، OpenStack ، Vagrant ، Delegated.
في معظم الحالات ، تكون القوالب هي destroy.yml
create.yml
و create.yml
في مجلد البرنامج النصي الذي يصف إنشاء المثيل create.yml
، على التوالي.
الاستثناءات هي Docker و Vagrant ، نظرًا لأن التفاعلات مع الوحدات النمطية يمكن أن تحدث بدون الملفات المذكورة أعلاه.
يجدر تمييز برنامج التشغيل المفوض ، لأنه إذا تم استخدامه في الملفات لإنشاء مثيل وحذفه ، فسيتم وصف العمل فقط مع تكوين المثيلات ، وينبغي أن يصف المهندس الباقي.
برنامج التشغيل الافتراضي هو Docker.
ننتقل الآن إلى الممارسة وننظر في المزيد من الميزات هناك.
الابتداء
ك "عالم الترحيب" ، نحن نختبر الدور البسيط لتثبيت nginx. سنختار عامل التشغيل كبرنامج التشغيل - أعتقد أنه مثبت على معظمكم (وتذكر أن عامل التشغيل هو برنامج التشغيل الافتراضي).
إعداد virtualenv
وتثبيت molecule
فيه:
> pip install virtualenv > virtualenv -p `which python2` venv > source venv/bin/activate > pip install molecule docker
الخطوة التالية هي تهيئة دور جديد.
تتم تهيئة دور جديد ، وكذلك سيناريو جديد ، باستخدام الأمر molecule init <params>
:
> molecule init role -r nginx --> Initializing new role nginx... Initialized role in <path>/nginx successfully. > cd nginx > tree -L 1 . ├── README.md ├── defaults ├── handlers ├── meta ├── molecule ├── tasks └── vars 6 directories, 1 file
وكانت النتيجة دور ansible نموذجي. علاوة على ذلك ، يتم إجراء جميع التفاعلات مع جزيئات CLI من جذر الدور.
دعونا نرى ما هو في دليل الدور:
> tree molecule/default/ molecule/default/ ├── Dockerfile.j2
دعنا نحلل molecule/default/molecule.yml
config (سنستبدل فقط صورة عامل الميناء):
--- dependency: name: galaxy driver: name: docker lint: name: yamllint platforms: - name: instance image: centos:7 provisioner: name: ansible lint: name: ansible-lint scenario: name: default verifier: name: testinfra lint: name: flake8
التبعية
يصف هذا القسم مصدر التبعيات.
الخيارات الممكنة: المجرة ، المذهبة ، قذيفة.
Shell هو مجرد أمر قيادة يتم استخدامه إذا كانت المجرة والمذهبة لا تغطي احتياجاتك.
لن أتوقف هنا لفترة طويلة ، فهو موصوف بما فيه الكفاية في الوثائق .
سائق
اسم السائق. لدينا هذا عامل ميناء.
الوبر
كما linter ، يستخدم yamllint.
الخيارات المفيدة في هذا الجزء من التكوين هي القدرة على تحديد ملف تكوين لل yamllint ، أو متغيرات البيئة إلى الأمام أو تعطيل linter:
lint: name: yamllint options: config-file: foo/bar env: FOO: bar enabled: False
يصف تكوين الحالات.
في حالة عامل الإرساء كسائق ، يتم تكرار الجزيء فوق هذا القسم ، ويتوفر كل عنصر قائمة في Dockerfile.j2
كمتغير item
.
في حالة برنامج التشغيل ، حيث تكون create.yml
و create.yml
destroy.yml
، يكون القسم متاحًا بها كـ molecule_yml.platforms
، والتكرارات الموجودة عليه موصوفة بالفعل في هذه الملفات.
نظرًا لأن الجزيء يوفر التحكم في مثيل الوحدات النمطية غير المرئية ، يجب البحث عن قائمة بالإعدادات الممكنة هناك. بالنسبة إلى عامل الإرساء ، على سبيل المثال ، يتم استخدام وحدة docker_container_module . يمكن العثور على الوحدات النمطية المستخدمة في برامج التشغيل الأخرى في الوثائق .
ويمكن أيضًا العثور على أمثلة لاستخدام محركات مختلفة في اختبارات الجزيء نفسه .
استبدال centos: 7 على أوبونتو هنا .
الممون
"المزود" هو الكيان الذي يتحكم في المثيلات. في حالة الجزيء ، يكون هذا أمرًا غير ممكن ، ولا يتم التخطيط لدعم الآخرين ، لذلك يمكن استدعاء هذا القسم مع التحفظ بالتكوين الموسع لـ ansible.
هنا يمكنك تحديد الكثير من الأشياء ، وسأبرز أهم لحظات ، في رأيي:
- playbooks : يمكنك تحديد playbooks التي يجب استخدامها في مراحل معينة.
provisioner: name: ansible playbooks: create: create.yml destroy: ../default/destroy.yml converge: playbook.yml side_effect: side_effect.yml cleanup: cleanup.yml
provisioner: name: ansible config_options: defaults: fact_caching: jsonfile ssh_connection: scp_if_ssh: True
- connection_options : معلمات الاتصال
provisioner: name: ansible connection_options: ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"
- خيارات : خيارات Ansible ومتغيرات البيئة
provisioner: name: ansible options: vvv: true diff: true env: FOO: BAR
سيناريو
اسم ووصف تسلسل البرنامج النصي.
يمكنك تغيير مصفوفة الإجراء الافتراضية لأحد <command>_sequence
إضافة <command>_sequence
وتحديد قائمة الخطوات التي نحتاجها كقيمة لها.
لنفترض أننا نريد تغيير تسلسل الإجراءات عند تشغيل الأمر playbook run: molecule converge
# : # - dependency # - create # - prepare # - converge scenario: name: default converge_sequence: - create - converge
المدقق
وضع إطار للاختبارات ولينتر إليها. بشكل افتراضي ، يتم استخدام testinfra
و flake8
testinfra
. الخيارات الممكنة تشبه ما ورد أعلاه:
verifier: name: testinfra additional_files_or_dirs: - ../path/to/test_1.py - ../path/to/test_2.py - ../path/to/directory/* options: n: 1 enabled: False env: FOO: bar lint: name: flake8 options: benchmark: True enabled: False env: FOO: bar
دعنا نعود إلى دورنا. قم بتحرير tasks/main.yml
:
--- - name: Install nginx apt: name: nginx state: present - name: Start nginx service: name: nginx state: started
وأضف الاختبارات إلى molecule/default/tests/test_default.py
def test_nginx_is_installed(host): nginx = host.package("nginx") assert nginx.is_installed def test_nginx_running_and_enabled(host): nginx = host.service("nginx") assert nginx.is_running assert nginx.is_enabled def test_nginx_config(host): host.run("nginx -t")
انتهى ، يبقى فقط للتشغيل (من جذر الدور ، أذكرك):
> molecule test
العادم الطويل تحت المفسد: --> Validating schema <path>/nginx/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy --> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in <path>/nginx/... Lint completed successfully. --> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/... Lint completed successfully. --> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml... Lint completed successfully. --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* ok: [localhost] => (item=None) ok: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file. --> Scenario: 'default' --> Action: 'syntax' playbook: <path>/nginx/molecule/default/playbook.yml --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) TASK [Create Dockerfiles from image names] ************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Discover local Docker images] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Build an Ansible compatible image] *************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Create docker network(s)] ************************************************ TASK [Create molecule instance(s)] ********************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) creation to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured. --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [instance] TASK [nginx : Install nginx] *************************************************** changed: [instance] TASK [nginx : Start nginx] ***************************************************** changed: [instance] PLAY RECAP ********************************************************************* instance : ok=3 changed=2 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully. --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured. --> Scenario: 'default' --> Action: 'verify' --> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/... ============================= test session starts ============================== platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0 rootdir: <path>/nginx/molecule/default, inifile: plugins: testinfra-1.16.0 collected 4 items tests/test_default.py .... [100%] ========================== 4 passed in 27.23 seconds =========================== Verifier completed successfully. --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Delete docker network(s)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0
تم اختبار دورنا البسيط دون مشاكل.
تجدر الإشارة إلى أنه إذا كانت هناك مشاكل أثناء تشغيل molecule test
، فإذا لم تقم بتغيير التسلسل القياسي ، فسيحذف الجزيء المثيل.
الأوامر التالية مفيدة لتصحيح الأخطاء:
> molecule --debug <command>
الدور الحالي
تتم إضافة برنامج نصي جديد إلى دور موجود من دليل الدور باستخدام الأوامر التالية:
في حال كان هذا هو البرنامج النصي الأول في الدور ، يمكن حذف -s
حيث سيتم إنشاء البرنامج النصي default
.
استنتاج
كما ترى ، فإن الجزيء ليس معقدًا للغاية ، وعند استخدام القوالب الخاصة بك ، يمكنك تقليل نشر برنامج نصي جديد لتحرير المتغيرات في قواعد التشغيل لإنشاء مثيلات وحذفها. يتكامل الجزيء بسلاسة مع أنظمة CI ، مما يتيح لك زيادة سرعة التطوير من خلال تقليل الوقت الذي يستغرقه اختبار قواعد اللعب يدويًا.
شكرا لاهتمامكم إذا كانت لديك خبرة في اختبار الأدوار غير المرئية ، ولا يتعلق الأمر بالجزيء - فأخبرنا بها في التعليقات!