Поговорим про Templates для Ansible Roles
Сейчас пришлось окунуться в изучение Roles для Ansible. Поэтому пока знания свежие спешу поделиться ими и легко объяснить то, с чем у меня возникли трудности.
Ликбез
Статья расчитана на базовые знания Ansible, поэтому слишком глубоко уходить в теорию мы не будем. Лишь поверхностно пробежимся по основным терминам.
Ansible — инструмент, который помогает конфигурировать удаленные хосты, разворачивать на них приложения и т. д. и т. п. Для этого используются либо обычные команды:
ansible localhost -m ansible.builtin.apt -a "name=apache2 state=present" -b -K
Данная команда запускает ansible localhost- на вашей локальной системе. — name=apache2 state=present — устанавливает пакет apache2 на систему на базе Debian. -b — использует become для выполнения с повышенными привилегиями. -m — указывает имя модуля. -K — запрашивает пароль для повышения привилегий.
Либо же можно использовать Playbook (далее просто «Плейбук"/"Плейбуки»).
Ansible Playbooks — основная конфигурация, которой будет следовать Ansible при настройке удаленных хостов. Плейбуки хороши своей легкостью, повторяемостью и изменяемостью.
Ansible Roles — вот переведенное определение из документации: «Роли позволяют автоматически загружать связанные с ними vars, файлы, задачи, обработчики и другие артефакты Ansible на основе известной структуры файлов. Сгруппировав содержимое по ролям, вы сможете легко использовать их повторно и делиться ими с другими пользователями.»
Если говорить проще, то Roles (в дальнейшем просто «Роли») помогают разделять задачи на контексты.
Также я упоминал «…указывает имя модуля…».
Ansible Module (далее просто «Модуль») — основной параметр, с помощью которого Ansible будет знать, что делать в конкретной задаче. Для примера:
- ansible.builtin.copy — может копировать файлы
- community.docker.docker_compose — работает с файлами докера, тем самым может запускать контейнеры, проверять запущены ли они уже и многое другое.
На этом ликбез окончен и мы переходим к нашему сегодняшнему гостю — ролям для Ansible.
Роли
Вообще сами по себе роли не сложны. По сути, если мы используем роли, то наш каталог, где находится плейбук, дополняется директорией roles:
roles/ └── role_name/ ├── defaults/ # Переменные по умолчанию (низкий приоритет) ├── vars/ # Переменные роли (высокий приоритет) ├── tasks/ # Основные задачи ├── handlers/ # Обработчики ├── templates/ # Jinja2-шаблоны ├── files/ # Статические файлы ├── meta/ # Информация о роли (зависимости и т. д.) └── tests/ # Тесты
Роль может иметь любое название, чтобы запустить роль в главном .yml файле плейбука нужно указать:
--- - hosts: all roles: - <имя_вашей_роли>
Потом в директории tasks создаете файл, например main.yml, в котором пишете:
--- - name: Creates docker directory become: true ansible.builtin.file: path: /home/ubuntu/docker state: directory
Это пример задачи, которая создаст директорию docker.
Можно еще использовать файлы, с которыми нужно будет взаимодействовать. Их требуется расположить в каталоге files внутри каталога с ролью.
--- - name: Copying backup_db become: true ansible.builtin.copy: dest: /home/ubuntu/docker/scripts/ src: files/backup.sh
Данная задача копирует файл на удаленный хост.
Казалось бы, обычные роли, но в файлах, которые используются, есть чувствительная информация, например пароли для базы данных или для админа сервиса, которые разворачивается в контейнере. Тут уже могут возникнуть трудности.
Роли с чувствительной информацией: как прятать пароли с помощью templates
Для нашего случая возьмем простейший docker compose файл:
version: '3.8' services: postgres: image: postgres:latest environment: POSTGRES_PASSWORD: "mysecretpassword" # Пароль для PostgreSQL ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:
А теперь легенда:
Мы разработчики, которые разворачивают контейнер с базой данных PostgreSQL у себя на удаленном хосте. Весь свой код мы храним в каком-нибудь хранилище кода, например GitLab. Также у нас настроен CI, который сам будет выполнять плейбуки для Ansible.
Получается такой алгоритм:
Но все не так просто. Для CI нужны переменные паролей, а в открытом виде их держать нельзя. Вот тут и выходят на сцену Templates. И вот их я постараюсь объяснить максимально легко.
Итак, Template — это файл с расширением .j2, ссылаясь на который Ansible будет генерировать другой файл. Звучит сложно, понимаю, поэтому вот пример:
Наш docker compose файл называется docker-compose.yml. Его template будет называться docker-compose.yml.j2. Выглядеть template будет так:
version: '3.8' services: postgres: image: postgres:latest environment: POSTGRES_PASSWORD: {{ postgres_password }} # Пароль для PostgreSQL ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:
Мне кажется, что теперь все должно проясниться. Наш CI будет иметь переменную, которую передаст Ansible. Ansible возьмет template и сгенерирует на его основе исходный файл, заменяя переменные в {{ … }} переменными, которые получили из CI.
А теперь как это все провернуть.
Во первых, создайте переменные в своем хранилище кода. Например в GitLab это делается так:
Settings → CI/CD → Variables → Add Variable
Отталкиваясь от нашего примера в переменной мы укажем следующее:
KEY: POSTGRES_PASSWORD
VALUE: mysecretpassword
Во вторых, нам нужно изменить (не скопировать, не добавить, а именно изменить) файл, где находится чувствительная информация, а затем переместить его по пути /roles/<имя_вашей_роли>/templates. Изменяем мы его следующим образом:
- Чувствительная информация заменяется на {{ <имя_переменной> }}. Тут смотрите внимательно: имя переменной, которая НЕ В ХРАНИЛИЩЕ. Это важно.
- К имени файла добавляется .j2
В третьих, нужно создать задачу, которая сгенерирует нам исходный файл.
- name: Rendering docker-compose file become: true ansible.builtin.template: src: templates/docker-compose.yml.j2 dest: /home/ubuntu/docker/docker-compose.yml owner: ubuntu group: ubuntu mode: '0644'
И наконец построим «мост», по которому наши переменные будут передаваться от хранилища кода и сопоставляться с переменными Ansible. Для этого создаем директорию, которая находится на одном уровне с roles/. Называем ее group_vars, внутри добавляем файл, например all.yml с содержимым:
<имя_вашей_переменной_которую_вы_указали_в_файле_.j2>: "{{ lookup('ansible.builtin.env', '<имя_вашей_переменной_в_хранилище_кода>' }}"
И на этом все. Ansible будет работать. И для полноты картины покажу полный пример с нашим Docker Compose.
Пример
version: '3.8' services: postgres: image: postgres:latest environment: POSTGRES_PASSWORD: "mysecretpassword" # Пароль для PostgreSQL ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:
Измененный ./roles/postgre_install/templates/docker-compose.yml.j2:
version: '3.8' services: postgres: image: postgres:latest environment: POSTGRES_PASSWORD: {{ postgres_password }} # Пароль для PostgreSQL ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:
Переменная внутри хранилища кода:
KEY: POSTGRES_PASSWORD
VALUE: mysecretpassword
Файл с переменными ./group_vars/all.yml:
postgres_password: "{{ lookup('ansible.builtin.env', 'POSTGRES_PASSWORD' }}"
Основной файл ./roles/postgre_install/tasks/main.yml:
--- - name: Rendering docker-compose file become: true ansible.builtin.template: src: templates/docker-compose.yml.j2 dest: /home/ubuntu/docker/docker-compose.yml owner: ubuntu group: ubuntu mode: '0644' - name: starting docker-compose become: true community.docker.docker_compose: project_src: "/home/ubuntu/docker" state: present
Самый главный файл ./playbook.yml:
--- - hosts: all roles: - postgre_instal
После этого ваша роль заработает на нужных хостах.
Спасибо большое за прочтение статьи, я надеюсь, что хоть кому-то помог и сэкономил его время.
Всем добра!