Знакомство с Ansible. Часть 4. Шаблоны и переменные Ansible

Знакомство с Ansible. Часть 4. Шаблоны и переменные Ansible

Мы с вами уже немного познакомились с 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 предоставляет довольно гибкий подход в работе с переменными. Вы можете объявлять переменные в трех местах:

  1. На уровне действия.
  2. На уровне playbook.
  3. Во внешних файлах.

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

Определение переменных на уровни действия

Давайте начнем с примера того, как можно определить переменные на уровне конкретного действия (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

Результат запуска отображен ниже:

roman@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/itproblog.crt, dest: "{{ cert_dest }}" }
  - name: Copy configuration files
    copy:
      src: "{{ item.src }}"
      dest: "{{ item.dest }}"
    with_items:
    - {src: files/site1.itoservice.ru, dest: "{{ conf_dest }}" }
    - {src: files/site2.itoservice.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.itoservice.ru
│   └── site2.itoservice.ru
├── inventory
│   └── inv.ini
└── nginx.yml

2 directories, 6 files
roman@ansible:~/nginx$ 

Теперь попробуем запустить наш результирующий 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 не перезапускался, если в синтаксисе обнаружены какие-то ошибки.