Slide 1

Slide 1 text

Ansible playbooks — это код: проверяем, тестируем, непрерывно интегрируем Иван Пономарёв, МФТИ / КУРС [email protected] @inponomarev 1

Slide 2

Slide 2 text

Сейчас в других залах: • Зал №1 Виктор Гамов. Kafka на Kubernetes • Зал №3 Андрей Ермаков. AntiYAML: DSL is the new black

Slide 3

Slide 3 text

Ansible playbooks — это код: проверяем, тестируем, непрерывно интегрируем Иван Пономарёв, МФТИ / КУРС [email protected] @inponomarev 3

Slide 4

Slide 4 text

С чем приходится иметь дело: • Самые простые проекты — классическая «трёхзвенка» (1–2 сервера) • Самый сложный проект — около 40 серверов (DigitalOcean) • Terraform + Ansible 4

Slide 5

Slide 5 text

Десятки ролей… 5 …бэк, фронт, прокси, базы данных, мониторинг, сбор логов, etc, etc…

Slide 6

Slide 6 text

Как всё было: Проект 1 group_vars/ inventory/ roles/ roleA/ roleB/ roleC/ webservers.yml database.yml logs.yml Проект 2 group_vars/ inventory/ roles/ roleA/ roleB/ roleD/ webservers.yml database.yml logs.yml 6 copy-paste-modify

Slide 7

Slide 7 text

Много кода — знакомые проблемы: • Страх поломать – Код не переиспользуется, а копируется в проекты – Нет рефакторинга • Нет уверенности, что эта куча кода вообще сработает • Отладка в процессе деплоя 7

Slide 8

Slide 8 text

Знакомые проблемы — знакомое решение 8 — Но как?! — Автоматическое тестирование и CI!

Slide 9

Slide 9 text

Что мы можем проверить сразу? 9 Код в гите вообще синтаксически валидный?? 1. YAMLLint 2. AnsibleLint 3. ansible-playbook \ --syntax-check

Slide 10

Slide 10 text

1. синтаксис YAML 2. лишние пробелы 3. переносы строк UNIX-style 4. одинаковость отступов, три дефиса в начале файла… строже, чем сам Ansible! 10 adrienverge/yamllint

Slide 11

Slide 11 text

yamllint -c yamllint.yml . 11 настройка правил

Slide 12

Slide 12 text

1. command vs shell module 2. command vs standard modules (wget, curl, git etc) 3. command/shell idempotence… строже, чем сам Ansible! 4. легкий фреймворк для создания своих правил на Python 12 willthames/ansible-lint

Slide 13

Slide 13 text

ansible-lint \ --exclude=/var/lib/jenkins/.ansible/roles \ -v *.yml 13 Ansible-lint обходит роли, но стандартные роли содержат критические ворнинги willthames/ansible-lint

Slide 14

Slide 14 text

ansible-lint –v *.yml 14

Slide 15

Slide 15 text

Syntax check ansible-galaxy install -r requirements.yml ansible-playbook \ playbook.yml --syntax-check 15 Установите стандартные роли — иначе проверка синтаксиса свалится при упоминании неизвестной роли

Slide 16

Slide 16 text

Все три инструмента — в CI- скрипт node { stage ('Clone') { checkout scm } stage('YAML lint') { sh 'yamllint -c yamllint.yml .' } 16

Slide 17

Slide 17 text

17 — Возможности статического анализа ограничены. Что насчёт реального тестирования?

Slide 18

Slide 18 text

История вопроса 18

Slide 19

Slide 19 text

Два года спустя… 19

Slide 20

Slide 20 text

Meet Molecule 20

Slide 21

Slide 21 text

Зависимости Molecule от других проектов 21 . . . adrienverge/yamllint willthames/ansible-lint pycqa/flake8 philpep/testinfra

Slide 22

Slide 22 text

Установка Prerequisites (Ubuntu): gcc, python-dev • pip install ansible • pip install molecule • pip install docker-py 22

Slide 23

Slide 23 text

Role Структура проекта molecule 23 Scenario 1 Scenario 1/ Instance 1 Scenario 1/ Instance 2 playbook.yml tests Scenario 2 Scenario 2/ Instance 1 Scenario 2/ Instance 2 playbook.yml tests

Slide 24

Slide 24 text

Инициализация Новая роль: molecule init role -r newrole 24

Slide 25

Slide 25 text

Инициализация Существующая роль: molecule init scenario -r 25 (Ключом --scenario-name можно задать имя сценария, по умолчанию — default)

Slide 26

Slide 26 text

Запуск molecule test 26

Slide 27

Slide 27 text

Запуск molecule --debug test 27

Slide 28

Slide 28 text

“Test matrix” 28

Slide 29

Slide 29 text

Настройка instances 29 platforms: - name: ubuntuinstance image: solita/ubuntu-systemd:latest command: /sbin/init privileged: True volumes: - "/sys/fs/cgroup:/sys/fs/cgroup:rw" - name: centosinstance image: solita/centos-systemd:latest command: /sbin/init privileged: True volumes: - "/sys/fs/cgroup:/sys/fs/cgroup:rw" . driver: name: docker molecule/default/molecule.ym l

Slide 30

Slide 30 text

Кроме docker, есть драйверы • Azure (via Ansible’s azure_module) • EC2 (via Ansible’s ec2_module) • GCE (via Ansible’s gce_module) • Delegated (самостоятельное определение create/destroy/ssh params) • Vagrant • …and more 30

Slide 31

Slide 31 text

Подключение зависимостей 31 molecule/default/molecule.yml dependency: name: galaxy options: role-file: requirements.yml --- - src: ansiblebit.oracle-java version: "5.14.14" requirements.ym l

Slide 32

Slide 32 text

Этап статического анализа (lint) • yamllint • ansible-lint • ansible-playbook --syntax-check 32

Slide 33

Slide 33 text

Converge 33 --- - name: Converge hosts: all roles: - role: ansiblebit.oracle-java - role: fluteansible tasks: … molecule/default/playbook.yml

Slide 34

Slide 34 text

Подключение к тестовой ноде для отладки результатов выполнения molecule test --destroy=never docker exec -it instance /bin/bash 34

Slide 35

Slide 35 text

Проверка идемпотентности Раньше (из статьи Jeff Geerling): ansible-playbook -i tests/inventory tests/test.yml --connection=local --sudo | grep -q 'changed=0.*failed=0' && (echo 'Idempotence test: pass' && exit 0) || (echo 'Idempotence test: fail' && exit 1) 35

Slide 36

Slide 36 text

Проверка идемпотентности Теперь (в Molecule): ansible-playbook --diff playbook.yml 36

Slide 37

Slide 37 text

Инфраструктурные тесты Molecule поддерживает: • Testinfra (Python, default) • Serverspec (Ruby) • Goss (written in Go, tests in YAML) 37

Slide 38

Slide 38 text

Testinfra bootstrap # default/test/test_default.py import os import testinfra.utils.ansible_runner testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') 38

Slide 39

Slide 39 text

Результат запуска команды def test_jython_installed(host): cmd = host.run('jython --version') assert cmd.rc == 0 assert cmd.stderr.find(u'Jython 2') > -1 39

Slide 40

Slide 40 text

«Умный» assert 40

Slide 41

Slide 41 text

Быстрый перезапуск тестов 41 molecule test --destroy=never <дописываем тест…> molecule verify <дописываем тест…> molecule verify

Slide 42

Slide 42 text

‘Keep the bar green to keep your infrastructure clean’ 42

Slide 43

Slide 43 text

Веб-сервисы def test_service_greeting(host): cmd = 'curl -o -I -L -s -w "%{http_code}\n" http://localhost:8080/service' assert host.check_output(cmd) == '200' cmd = "curl -L 'http://localhost:8080/service'" assert host.check_output(cmd).find(u'Hello!') > -1 43 (Предварительно установив curl в playbook.yml)

Slide 44

Slide 44 text

Процессы def test_jsvc_process(host): procs = host.process.filter(comm="jsvc") assert len(procs) > 0 for proc in procs: assert proc.user == 'flute3' assert proc.args.find('-Xmx1024M') > -1 44

Slide 45

Slide 45 text

Сервисы def test_service_is_running(host): assert host.service('flute').is_running 45

Slide 46

Slide 46 text

Файлы и их содержимое def test_log_files(host): stdout = host.file('/var/log/flute/std.out') stderr = host.file('/var/log/flute/std.err') assert stdout.exists assert stderr.exists assert stderr.contains('Flute started') assert stdout.contains('Flute started. 0 taskSources are being processed') 46

Slide 47

Slide 47 text

Test fails Test pass Refactor 47 — TDD for Ansible? — Ну, если хотите — да!

Slide 48

Slide 48 text

48 - name: call for jython version command: jython --version register: cmd_result - name: check Jython assert: that: "'Jython 2' in cmd_result.stderr" — Но и в самом Ansible есть модуль assert! ERROR: Idempotence test failed

Slide 49

Slide 49 text

49

Slide 50

Slide 50 text

Проверки в хэндлерах 50 - name: call for jython version command: jython --version register: cmd_result listen: "validate Jython" - name: check Jython assert: that: "'Jython 2' in cmd_result.stderr" listen: "validate Jython" handlers/main.ym l - name: download jython ... notify: "validate Jython" - name: install jython ... notify: "validate Jython" tasks/main.ym l

Slide 51

Slide 51 text

Ещё можно проверить в хэндлерах: 51 - stat: path: /path/to/something register: p - assert: that: - "p.stat.exists and p.stat.isdir" - uri: url: http://www.example.com return_content: yes register: webpage - assert: that: - "'AWESOME' in webpage.content" файлы: веб- сервисы:

Slide 52

Slide 52 text

Role development process 52 master feature branch CI system check

Slide 53

Slide 53 text

CI: Jenkins Multibranch Pipeline node { stage ("Get Latest Code") { checkout scm } stage ("Molecule test") { sh 'mkdir -p molecule/default/roles' sh 'ln -sf `pwd` molecule/default/roles/fluteansible' sh 'molecule test' } } 53

Slide 54

Slide 54 text

Разделение по стадиям 54 stage ("Executing Molecule lint") { sh 'molecule lint' } stage ("Executing Molecule create") { sh 'molecule create' (код из статьи Werner Dijkerman, “Continuous deployment of Ansible Roles”)

Slide 55

Slide 55 text

Разделение по стадиям 55 (Image from: https://werner-dijkerman.nl/2017/09/17/continuous-deployment-of-ansible-roles/)

Slide 56

Slide 56 text

Travis-CI --- language: python python: "2.7" sudo: required services: - docker before_install: - sudo apt-get update -qq 56 install: - pip install ansible==2.5.0 - pip install molecule - pip install docker-py script: - molecule test notifications: webhooks: https:// galaxy.ansible.com/api/v1/notifications/

Slide 57

Slide 57 text

57

Slide 58

Slide 58 text

Было… Проект 1 group_vars/ inventory/ roles/ roleA/ roleB/ roleC/ webservers.yml database.yml logs.yml Проект 2 group_vars/ inventory/ roles/ roleA/ roleB/ roleD/ webservers.yml database.yml logs.yml 58 copy-paste-modify

Slide 59

Slide 59 text

…стало: Проект 1 group_vars/ inventory/ roles/ roleC/ webservers.yml database.yml logs.yml Проект 2 group_vars/ inventory/ roles/ roleD/ webservers.yml database.yml logs.yml 59 Ansible Galaxy roleA/ roleB/ Molecule Linting

Slide 60

Slide 60 text

Можно ещё что-то улучшить? Проект 1 group_vars/ inventory/ roles/ roleC/ webservers.yml database.yml logs.yml Проект 2 group_vars/ inventory/ roles/ roleD/ webservers.yml database.yml logs.yml 60 Ansible Galaxy roleA/ roleB/

Slide 61

Slide 61 text

Как быть с конфигурацией? • Плохие новости: – Molecule — только для ролей. – Проверить развёртывание на прод можно только развернув на прод. )) • Хорошая новость: – Мы можем проверить проект, не запуская его! 61

Slide 62

Slide 62 text

62 Андрей Сатарин «Как проверить систему, не запуская её» Heisenbug 2017, Москва Руслан Черемин «Тестирование конфигурации для Java-разработчиков: практический опыт» Heisenbug 2018, Санкт-Петербург JUG.MSK, июнь 2018, Москва

Slide 63

Slide 63 text

Тесты конфигурации • Формат значений переменных: – порты — числа в допустимом диапазоне, – хосты — доменные имена или IP, – URL’ы — валидные и т. п. • Паролей нет в явном виде. • Порты сервисов, поднимаемых в пределах хоста, уникальны. • И более специфические вещи. 63

Slide 64

Slide 64 text

Проверяем валидность портов 64 @pytest.mark.parametrize("k,v", port_var_values(BASEDIR)) def test_port(k, v): port = int(v) assert port > 0 assert port < 32000

Slide 65

Slide 65 text

Обходим нужные нам переменные… 65 def port_var_values(path): key_pattern = re.compile('port$') for (k, v) in var_values(path): if key_pattern.search(k): yield (k, v)

Slide 66

Slide 66 text

Проверяем уникальность портов 66 @pytest.mark.parametrize("path", [BASEDIR]) def test_port_uniqueness(path): ports = set() for (k, v) in port_var_values(path): port = int(v) assert not(port in ports) ports.add(port)

Slide 67

Slide 67 text

‘Keep the bar green to keep the configuration clean’ 67

Slide 68

Slide 68 text

Ловим «утекающие» пароли def password_var_values(path): key_pattern = re.compile('p(ass(word)?|wd)$') for (k, v) in var_values(path): if key_pattern.search(k): yield (k, v) @pytest.mark.parametrize("k,v", password_var_values(BASEDIR)) def test_password(k, v): var_pattern = re.compile('\{\{([^}]|\}[^}])+\}\}') print '%s: %s' % (k, v) assert var_pattern.search(v) 68

Slide 69

Slide 69 text

Ай-яй-яй! 69

Slide 70

Slide 70 text

Выводы 70

Slide 71

Slide 71 text

Тестируйте ваш Ansible! • YAMLLint + AnsibleLint + syntax check прямо сегодня! • Проверяйте роли на Molecule • Вставляйте проверки в хэндлеры • Тестируйте конфигурацию 71

Slide 72

Slide 72 text

Molecule is ‘must have’ при разработке ролей • Создайте тесты на ваши Ansible-роли прямо сегодня — это просто • Лень разбираться? Без тестов, проверка converge и idempotence • Совсем лень разбираться? lint /syntax 72

Slide 73

Slide 73 text

Назвался кодом — полезай в CI! 73

Slide 74

Slide 74 text

CI/CD для Ansible-ролей • «Штатный» набор инструментов — GitHub+Travis+Galaxy, это если роли в OpenSource • Jenkins Multibranch тоже работает отлично 74

Slide 75

Slide 75 text

Ссылки ● Иван Пономарёв Тестирование и непрерывная интеграция для Ansible-ролей при помощи Molecule и Jenkins https://habr.com/post/351974/ ● Jeff Geerling Testing Ansible Roles with Travis CI on GitHub https://www.jeffgeerling.com/blog/testing-ansible-roles-travis-ci- github ● Werner Dijkerman Continuous deployment of Ansible Roles https://werner-dijkerman.nl/2017/09/17/continuous-deployment-of-a nsible-roles/ ● Андрей Сатарин: Как проверить систему, не запуская её https://asatarin.github.io/talks/how-to-check-a-system-with75

Slide 76

Slide 76 text

Задавайте вопросы :-) [email protected] @inponomarev 76 Тестировать Ansible-код — просто и приятно!