Знакомство с 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

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

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

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

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