devops
June 5

Поговорим про 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.
Получается такой алгоритм:

  1. Написали код.
  2. Запушили в хранилище.
  3. CI начинает выполняться.
  4. Наша БД готова.

Но все не так просто. Для 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.

Пример

Исходный docker-compose.yml:

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

После этого ваша роль заработает на нужных хостах.


Спасибо большое за прочтение статьи, я надеюсь, что хоть кому-то помог и сэкономил его время.

Всем добра!