Мы с вами уже немного познакомились с Ansible в предыдущих публикациях. В прошлый раз мы с вами говорили о playbook. В этой публикации мы продолжим экспресс знакомство с Ansible, но расширим материал из предыдущей статьи и на примере посмотрим, как шаблоны и переменные Ansible могут упростить структуру playbook, а также добавить гибкости в его логику.
Зачем нужны шаблоны и переменные Ansible
Переменные Ansible действуют ровно также, как и переменные в языках программирования или скриптовых языках. Они хранят какую-то информацию, на которую вы можете ссылать в последующем. Переменную достаточно определить один раз и затем уже обращаться к этой переменной по имени.
Для того, чтобы обратиться к переменной по имени вы используете шаблоны Ansible – {{ var_name }}. Такая конструкция с двумя фигурными скобками воспринимается, как обращение к значению переменной. Вы можете один раз определить переменную, например, с рабочим каталогом разворачиваемого приложения, а затем несколько раз обращаться к этой переменной. В случае изменения рабочего каталога приложения вам достаточно изменить переменную.
Рассмотрим базовый пример. Определим переменную и выведем на экран её значение. Для вывода значения переменной я буду использовать модуль debug.
Вот самый простой пример. Содержимое файла vars.yml:
---
- name: Vars example
hosts: front
vars:
var1: "/etc/hosts"
tasks:
- name: Print var value
debug:
msg: "{{ var1 }}"
Содержимое файла инвентаризации inv.ini:
[front]
tst1
[front:vars]
ansible_host=10.10.10.106
[back]
tst2
[back:vars]
ansible_host=10.10.10.111
[linux:children]
front
back
[all:vars]
ansible_user=roman
Запустим наш тестовый runbook:
ansible-playbook -i inv.ini vars.yml
Результат работы runbook:
roman@ansible:~$ ansible-playbook -i inv.ini vars.yml
PLAY [Vars example] *********************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************
ok: [tst1]
TASK [Print var value] ******************************************************************************************************************
ok: [tst1] => {
"msg": "/etc/hosts"
}
PLAY RECAP ******************************************************************************************************************************
tst1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Как видно из структуры runbook и результатов его работы – при обращении к переменной var1 в модуле debug мы с вами получили значение этой переменной.
Способы определения переменных
Ansible предоставляет довольно гибкий подход в работе с переменными. Вы можете объявлять переменные в трех местах:
- На уровне действия.
- На уровне playbook.
- Во внешних файлах.
Ниже я покажу по одному краткому примеру для каждого из возможных вариантов объявления переменных.
Определение переменных на уровни действия
Давайте начнем с примера того, как можно определить переменные на уровне конкретного действия (play). Рассмотрим вот такой пример:
---
- name: Define variable in play
hosts: front
tasks:
- name: Print var value
vars:
ex1: "examle 1"
debug:
msg: "{{ ex1 }}"
Я сохраню этот код в файле ex1.yml. попробую запустить его:
ansible-playbook -i inv.ini ex1.yml
roman@ansible:~$ ansible-playbook -i inv.ini ex1.yml
PLAY [Define variable in play] **************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************
ok: [tst1]
TASK [Print var value] **********************************************************************************************************
ok: [tst1] => {
"msg": "examle 1"
}
PLAY RECAP **********************************************************************************************************************
tst1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
roman@ansible:~$
Как видно из вывода выше – переменная объявленная на уровне конкретного действия успешно отображается в модуле debug этого действия.
Определение переменных на уровне playbook
С вариантом определения переменных на уровне playbook мы с вами уже познакомились, хотя явно не акцентировали на этом внимания. Вместо определения переменных на уровне действия вы можете вынести блок определения переменных на уровень выше – на уровень laybook. Рассмотрим пример:
---
- name: Define variable in playbook
hosts: front
vars:
ex2: "examle 2"
tasks:
- name: Print var value
debug:
msg: "{{ ex2 }}"
Сохраним текст в файле ex2.yml и запустим этот playbook:
ansible-playbook -i inv.ini ex2.yml
Результат запуска отображен ниже:
oman@ansible:~$ ansible-playbook -i inv.ini ex2.yml
PLAY [Define variable in playbook] **********************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************
ok: [tst1]
TASK [Print var value] **********************************************************************************************************
ok: [tst1] => {
"msg": "examle 2"
}
PLAY RECAP **********************************************************************************************************************
tst1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Модуль debug также корректно отображает значение переменной ex2. Если в вашем playbook будет несколько действий, то все они смогут обращаться к переменной ex2, т.к. она определена на уровне всего playbook.
Определение переменных во внешних файлах
Пример с определением переменной во внешнем файле будет немного сложнее. Начиная с этого примера я бы уже начал структурировать и раскладывать по отдельным директориям логически связанные файлы. В отдельную директорию я бы положил файл (файлы) инвентаризации. В другую директорию – файлы с объявлением переменных. Структура каталога для этого примера будет следующая:
roman@ansible:~/ex$ tree
.
├── ex3.yml
├── inventory
│ └── inv.ini
└── vars
└── var.yml
2 directories, 3 files
Файл ex3.yml – это основной файл с определнием playbook.
inventory/inv.ini – файл с инвентаризацией.
Файл vars/var.yml – файл, в котором будут определены наши с вами переменные.
Приведу содержимое каждого из файлов. Файл ex3.yml:
---
- name: Define variable in external file
hosts: front
vars_files:
- vars/var.yml
tasks:
- name: Print var value
debug:
msg: "{{ ex3 }}"
Именно в этом файле в блоке vars_files мы с вами указываем – в каком именно файле хранятся определения переменных.
Содержимое файла vars/var.yml довольно тривиально:
ex3: "examle 3"
Теперь перейдем к содержимому файла inventory/inv.ini:
[front]
tst1
[front:vars]
ansible_host=10.10.10.106
Соберем всю эту мозаику вместе и попробуем запустить playbook:
ansible-playbook -i inventory/inv.ini ex3.yml
Результат запуска playbook:
roman@ansible:~/ex$ ansible-playbook -i inventory/inv.ini ex3.yml
PLAY [Define variable in external file] *****************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************
ok: [tst1]
TASK [Print var value] **********************************************************************************************************
ok: [tst1] => {
"msg": "examle 3"
}
PLAY RECAP **********************************************************************************************************************
tst1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Переменная ex3, которая объявлена во внешнем файле vars/var.yml также отображается успешно.
Какой из вариантов объявления переменных более правильный? Здесь нет понятия правильно/неправильно. Все зависит от ваших предпочтений и объемов и количества ваших playbook.
Пример использования шаблонов и переменных
Предлагаю перейти к финальному варианту для закрепления полученных данных. Давайте подготовим playbook, который подготовит веб сервер. Необходимо выполнить следующие задачи:
- Установит пакет nginx.
- Скопирует файлы с открытым и закрытым ключом в соответствующую директорию. Да, в боевой среде так делать не стоит, но мы акцентируем внимание на действие, а не на лучших практиках.
- Скопирует два конфигурационных файла для публикации сайтов в директорию sites-available.
- Активирует конфигурации для публикации двух сайтов.
- Перезапустить сервис nginx.
Как раз здесь я кратко расскажу про шаблоны.
Приступим к написанию нашего самого последнего runbook в этой статье. Для наглядности я разделю весь laybook на несколько логичесих действий(play). Сначала определим действие для установки nginx:
---
- name: Define variable in playbook
hosts: front
become: yes
tasks:
- name: Update apt repo metainfo
apt:
update_cache: yes
force_apt_get: yes
- name: Install nginx
apt:
name: nginx
state: present
- name: Start and enable autostart for nginx
service:
name: nginx
state: started
enabled: yes
Затем нам нужно скопировать файлы с сертификатами и определением публикуемых сайтов. Определим для этого отдельное действие:
- name: Copy certificates and configuration files
hosts: front
become: yes
vars:
cert_dest: /etc/nginx/ssl/
conf_dest: /etc/nginx/sites-available/
tasks:
- name: Copy certificates
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
with_items:
- {src: files/itproblog.key, dest: "{{ cert_dest }}" }
- {src: files/iiproblog.crt, dest: "{{ cert_dest }}" }
- name: Copy configuration files
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
with_items:
- {src: files/site1.itproblog.ru, dest: "{{ conf_dest }}" }
- {src: files/site2.itproblog.ru, dest: "{{ conf_dest }}" }
И вот здесь мы с вами видим новую конструкцию – шаблоны. В данном примере я использовал шаблон with_items. Этот шаблон объявляет список объектов с заданными полями. В примере выше для каждого объекта объявляется два поля – src и dest. Затем мы можем ссылаться в переменных на каждый объект этого списка через переменную item. Если нам нужен доступ до какого-то конкретного поля переменной, то мы используем операцию точка и указываем имя поля. Запись вида item.src ссылается на поле src текущего объекта item. Шаблон with_items поочередно проходит по каждому из объектов списка.
Теперь нам нужно активировать конфигурацию nginx. Для этого нужно создать символическую ссылку из файла в директории /etc/nginx/sites-available/ для файла в директории /etc/nginx/sites-enabled. Подготовим для этого отдельное действие.
- name: Enable nginx config by symlink
hosts: front
become: yes
vars:
conf_src: /etc/nginx/sites-available/
conf_dest: /etc/nginx/sites-enabled/
tasks:
- name: Copy certificates
file:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
state: link
with_items:
- {src: "{{ conf_src }}/site1.itproblog.ru", dest: "{{ conf_dest }}/site1.itproblog.ru" }
- {src: "{{ conf_src }}/site2.itproblog.ru", dest: "{{ conf_dest }}/site2.itproblog.ru" }
В заключении нам необходимо проверить конфигурацию nginx и перезагрузить сервис nginx. Это довольно простая задача, но мы в дополнении через модуль debug выведем результат проверки конфигурации nginx:
- name: Check nginx config and reload service
hosts: front
become: yes
tasks:
- name: Check nginx config
command: nginx -t
register: nginx_ckech_status
- name: Print nginx configuration check
debug:
msg: "{{ nginx_ckech_status }}"
- name: Reload nginx
command: systemctl reload nginx
Общая структура директории у меня получилась вот такая:
tree
roman@ansible:~/nginx$ tree
.
├── files
│ ├── itproblog.crt
│ ├── itproblog.key
│ ├── site1.itproblog.ru
│ └── site2.itproblog.ru
├── inventory
│ └── inv.ini
└── nginx.yml
2 directories, 6 files
roman@ansible:~/nginx$
Это последнее действие в нашем playbook. Для удобства я приложу итоговый архив со вмести файлами:
Теперь попроуем запустить наш результирующий playbook:
ansible-playbook nginx.yml -i inventory/inv.ini -K
roman@ansible:~/nginx$ ansible-playbook nginx.yml -i inventory/inv.ini -K
BECOME password:
PLAY [Define variable in playbook] ************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [tst1]
TASK [Update apt repo metainfo] ***************************************************************************************************
changed: [tst1]
TASK [Install nginx] **************************************************************************************************************
changed: [tst1]
TASK [Start and enable autostart for nginx] ***************************************************************************************
ok: [tst1]
PLAY [Copy certificates and configuration files] **********************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [tst1]
TASK [Copy certificates] **********************************************************************************************************
changed: [tst1] => (item={'src': 'files/itproblog.key', 'dest': '/etc/nginx/ssl/'})
changed: [tst1] => (item={'src': 'files/itproblog.crt', 'dest': '/etc/nginx/ssl/'})
TASK [Copy configuration files] ***************************************************************************************************
changed: [tst1] => (item={'src': 'files/site1.itproblog.ru', 'dest': '/etc/nginx/sites-available/'})
changed: [tst1] => (item={'src': 'files/site2.itproblog.ru', 'dest': '/etc/nginx/sites-available/'})
PLAY [Enable nginx config by symlink] *********************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [tst1]
TASK [Copy certificates] **********************************************************************************************************
changed: [tst1] => (item={'src': '/etc/nginx/sites-available//site1.itproblog.ru', 'dest': '/etc/nginx/sites-enabled//site1.itproblog.ru'})
changed: [tst1] => (item={'src': '/etc/nginx/sites-available//site2.itproblog.ru', 'dest': '/etc/nginx/sites-enabled//site2.itproblog.ru'})
PLAY [Check nginx config and reload service] **************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************
ok: [tst1]
TASK [Check nginx config] *********************************************************************************************************
changed: [tst1]
TASK [Print nginx configuration check] ********************************************************************************************
ok: [tst1] => {
"msg": {
"changed": true,
"cmd": [
"nginx",
"-t"
],
"delta": "0:00:00.024399",
"end": "2023-07-07 15:21:43.281731",
"failed": false,
"msg": "",
"rc": 0,
"start": "2023-07-07 15:21:43.257332",
"stderr": "nginx: the configuration file /etc/nginx/nginx.conf syntax is ok\nnginx: configuration file /etc/nginx/nginx.conf test is successful",
"stderr_lines": [
"nginx: the configuration file /etc/nginx/nginx.conf syntax is ok",
"nginx: configuration file /etc/nginx/nginx.conf test is successful"
],
"stdout": "",
"stdout_lines": []
}
}
TASK [Reload nginx] ***************************************************************************************************************
changed: [tst1]
PLAY RECAP ************************************************************************************************************************
tst1 : ok=13 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
roman@ansible:~/nginx$
Обратите внимание не заключительную часть вывода:
Здесь мы с вами сидим, что в результате проверки синтаксиса конфигурации nginx проблем не обнаружено. В реальной жизни мы бы, конечно, добавили дополнительную проверку для того, чтобы сервис nginx не перезапускался, если в синтаксисе обнаружены какие-то ошибки.
Надеюсь, что я не очень запутал вам последним примером, но я хотел показать насколько playbook может быть далеко не самым простым, но при это выполнять очень полезную работу и автоматизировать ручные действия.