Мой базовый паттерн использования ansible

от автора

Всем привет! Сегодня я хочу поделиться с вами простым паттерном использования ansible к которому я пришёл за годы работы с этим инструментом. Несмотря на простоту, в типовой инфраструктуре он покрывает процентов 80 кейсов IaC, т.е. является довольно универсальным.

Раз за разом описывая инфраструктуру в своих плейбуках, я заметил, что в большинстве случаев повторяется следующая последовательность:

  1. Мы устанавливаем на сервер какие-то пакеты.

  2. Копируем на сервер необходимые файлы (конфиги, скрипты, сертификаты etc.).

  3. Запускаем нужные сервисы и добавляем их в автозагрузку.

Плэйбуки распухают в основном на п.2, т.к. общепринято писать по таску template/copy на каждый файл. В какой-то момент я понял, что любое количество файлов можно доставить на серверы всего двумя тасками или тремя, если требуются специфичные права доступа. Собственно так и родился паттерн, о котором идёт речь. Он прошёл проверку временем и сейчас я использую его повсеместно. Он идеально подходит в случае если инфраструктуру нужно покрыть кодом максимально быстро. Конечно же за любое упрощение приходится чем-то платить и паттерн имеет свои недостатки и ограничения, но об этом ниже.

Начну пожалуй со структуры репозитория.

├── playbooks/ │   ├── files/ │   └── site.yml └── site.yml
  1. site.yml в корне проекта является своеобразной точкой входа, где при помощи import_playbook мы описываем верхнеуровневую структуру проекта, какие плэйбуки из каталога playbooks/ подключаются, в какой последовательности, тут же удобно навесить нужные теги и т.п.

  2. Все файлы, которые требуется выложить на серверы, размещаем в playbooks/files/ с теми же путями, по которым эти файлы должны располагаться на серверах. Например, если на сервер надо выложить сертификат /etc/ssl/certs/nginx-selfsigned.crt, то в репозитории мы помещаем его в playbooks/files/etc/pki/tls/certs/nginx-selfsigned.crt.

Далее приведу пример плэйбука с комментариями.

--- - name: Setup nginx   hosts: all    # Параметр для того чтобы ansible-playbook в check mode не завершался после первой ошибки.   # Так мы можем собрать больше ошибок за каждый прогон и сэкономить время.   ignore_errors: '{{ ansible_check_mode }}'    # Необходимые переменные, описываю здесь для наглядности.   vars:     rpm_packages:       - nginx     reload_services:       - nginx     restart_services: []    tasks:     - name: Install packages       ansible.builtin.dnf:         name: '{{ rpm_packages }}'         state: present         update_cache: true       when: rpm_packages is defined and rpm_packages      # Таск пробежит по дереву каталогов в files/ и создаст недостающие на серверах.     # noqa - подсказка для ansible-lint о том, что права доступа опущены целенаправленно.     - name: Create directories tree       # noqa: risky-file-permissions       ansible.builtin.file:         path: '/{{ item.path }}'         state: directory       with_community.general.filetree: files/       when: item.state == 'directory'      # Таск пробежит по дереву каталогов в files/ и скопирует на серверы все файлы в этих каталогах.     # Т.к. мы используем ansible.builtin.template, то файлы расцениваются как шаблоны jinja2.     - name: Copy config files       # noqa: risky-file-permissions       ansible.builtin.template:         src: '{{ item.src }}'         dest: '/{{ item.path }}'       with_community.general.filetree: files/       when: item.state == 'file'       notify:         - Reload services         - Restart services      # Таск нужен для финальной корректировки прав доступа к файлам и каталогам,     # т.к. при "пакетном" создании тасками выше установить специфичные права доступа не получится.     - name: Set files permissions       ansible.builtin.file:         path: '{{ item.path }}'         owner: '{{ item.owner | d("root") }}'         group: '{{ item.group | d("root") }}'         mode: '{{ item.mode | d("0600") }}'       loop:         - path: /etc/pki/tls/certs/nginx-selfsigned.crt         - path: /etc/pki/tls/private/nginx-selfsigned.key         - path: /usr/local/bin/example_script           mode: '0755'      - name: Start services       ansible.builtin.systemd:         name: '{{ item }}'         state: started         enabled: true         daemon_reload: true       loop: '{{ reload_services + restart_services }}'    handlers:     - name: Reload services       ansible.builtin.systemd:         name: '{{ item }}'         state: reloaded       loop: '{{ reload_services }}'       when: reload_services is defined and reload_services      - name: Restart services       ansible.builtin.systemd:         name: '{{ item }}'         state: restarted       loop: '{{ restart_services }}'       when: restart_services is defined and restart_services

И это всё. Пять тасков + два хендлера = 80% IaC для bare metal/VM. Дополнительным преимуществом паттерна является структура каталогов в files/ аналогичная тому как файлы/каталоги располагаются на серверах. Обычно если в templates/ просто свалены шаблоны, то далеко не всегда из названия файла можно понять к какому сервису он относится. Надо открыть файл и/или заглянуть в плэйбук. Это лишние действия. Но если дерево каталогов в репозитарии такое же, как на серверах, то мы сразу же можем понять что к чему относится и можем просто добавлять новые каталоги/файлы не заглядывая в плэйбук. Процесс создания IaC становится максимально простым и быстрым: копипастим с минимальными правками плейбук из пяти тасков, добавляем в переменные три списка (rpm_packages, reload_services, restart_services), а дальше можем полностью сконцентрироваться на самой конфигурацуии в files/.

Теперь стоит поговорить о недостатках и подхода и способах их устранения или смягчения.

  1. Т.к. все файлы мы копируем на серверы одним таском, то нет простого способа перезапустить только сервисы в конфигурации которых были изменения. Это несущественно для сервисов которые умеют перечитывать конфигурацию по сигналу, но для тех немногих, которые не умеют, это может являться серьёзной проблемой. И действительно, идея перезагружать, например, боевую СУБД при любом изменении любых файлов является сомнительной, хотя приемлемой там, где это допускает SLA.

  2. Второй существенный недостаток проистекает опять же из копирования файлов одним таском. Если вы используете CI, а я надеюсь, что вы используете, то в журналы пайплайнов могут попасть чувствительные данные (пароли, ключи, сертификаты etc.). Эту проблему можно решить «в лоб» добавив в таск параметр «no_log: true«, но тогда мы потеряем возможность видеть изменения в файлах при выполнении ansible-playbook. Вариант конечно рабочий, но мне не нравится.

К счастью, обе указанные проблемы возникают не всегда, а когда возникают, их можно решить умеренным усложнением плэйбка. Приведу пример из рабочего плэйбука для установки Sonatype Nexus Repository Manager.

    # Отдельным таском выкладываем файлы при изменении которых нужно перезапустить сервис.     # Т.к. в файлах есть секреты, то отключаем вывод change.     - name: Copy nexus files       ansible.builtin.template:         src: 'files{{ item }}'         dest: '{{ item }}'         owner: root         group: nexus         mode: '0640'       loop:         - /opt/sonatype-work/nexus3/etc/nexus.lic         - /opt/sonatype-work/nexus3/etc/nexus.properties         - /opt/sonatype-work/nexus3/etc/nexus-secret.json         - /opt/sonatype-work/nexus3/etc/fabric/nexus-store.properties         - /opt/nexus/bin/nexus.vmoptions       notify:         - Restart nexus.service       no_log: true      # Так же отдельным таском скопируем ключи и сертификаты nginx     - name: Copy nginx TLS certificates       ansible.builtin.template:         src: '{{ item.src }}'         dest: '/{{ item.path }}'         owner: root         group: root         mode: '0600'       with_community.general.filetree: files/       when: item.state == 'file'         and item.path is search('etc/nginx/ssl')       notify:         - Reload services       no_log: true      # Далее как обычно копируем все остальные файлы, не забывая при этом во when     # пропустить копирование тех, содержимое которых не должно попасть в change.     - name: Copy config files       # noqa: risky-file-permissions       ansible.builtin.template:         src: '{{ item.src }}'         dest: '/{{ item.path }}'       with_community.general.filetree: files/       when: item.state == 'file'         and item.path is not search('etc/nginx/ssl')         and item.path is not search('nexus-store.properties')         and item.path is not search('nexus-secret.json')       notify:         - Reload services

И на этом всё. Надеюсь материал будет для вас полезным. Если обнаружите ошибки/опечатки, то сообщите, пожалуйста, в лс. Всем IaC и passed-пайплайнов!


ссылка на оригинал статьи https://habr.com/ru/articles/856762/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *