Ansible. Шаблоны Jinja2

В предыдущей статье по принятию решений в Ansible вы узнали, как вносить простые изменения в файлы с помощью блоков blockinfile или встроенных модулей Ansible.
В этой статье вы узнаете, как использовать механизм шаблонов Jinja2 для выполнения более сложных и динамических изменений файлов.
Вы узнаете, как получить доступ к переменным и фактам в шаблонах Jinja2. Кроме того, вы узнаете, как использовать условные операторы и структуры циклов в Jinja2.
Доступ к переменным в Jinja2
Ansible будет искать файлы шаблонов jinja2 в каталоге вашего проекта или в каталоге с именем templates в каталоге вашего проекта.
Давайте создадим каталог шаблонов, чтобы все было чище и организовано:
[andreyex@control plays]$ mkdir templates [andreyex@control plays]$ cd templates/
Теперь создайте свой первый шаблон Jinja2 с именем index.j2 :
[andreyex@control templates]$ cat index.j2
A message from {{ inventory_hostname }}
{{ webserver_message }}
Обратите внимание, что имена файлов шаблонов Jinja2 должны заканчиваться расширением .j2.
Inventory_hostname еще один встроенный Ansible ( так называемый специальной или волшебный). Webserver_message является переменной, что вы будете определять в вашем PlayBook.
Теперь вернитесь на шаг назад в каталог вашего проекта и создайте следующий файл check-apache.yml :
[andreyex@control plays]$ cat check-apache.yml
---
- name: Check if Apache is Working
hosts: webservers
vars:
webserver_message: "I am running to the finish line."
tasks:
- name: Start httpd
service:
name: httpd
state: started
- name: Create index.html using Jinja2
template:
src: index.j2
dest: /var/www/html/index.html
Обратите внимание, что пакет httpd уже был установлен в предыдущем руководстве.
В этом руководстве вы сначала убедитесь, что Apache запущен в первой задаче Start httpd. Затем используйте модуль шаблона во второй задаче Create index.html с помощью процесса Jinja2to и перенесите созданный вами файл шаблона index.j2 Jinja2 в место назначения /var/www/html/index.html.
Идите вперед и запустите playbook:
[andreyex@control plays]$ ansible-playbook check-apache.yml PLAY [Check if Apache is Working] ********************************************** TASK [Gathering Facts] ********************************************************* ok: [node3] ok: [node2] TASK [Start httpd] ************************************************************* ok: [node2] ok: [node3] TASK [Create index.html using Jinja2] ****************************************** changed: [node3] changed: [node2] PLAY RECAP ********************************************************************* node2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 node3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
Пока все выглядит хорошо; давайте запустим быструю специальную команду Ansible, чтобы проверить содержимое index.html на узлах веб-серверов:
[andreyex@control plays]$ ansible webservers -m command -a "cat /var/www/html/index.html" node3 | CHANGED | rc=0 >> A message from node3 I am running to the finish line. node2 | CHANGED | rc=0 >> A message from node2 I am running to the finish line.
Удивительно! Обратите внимание, как Jinja2 смог получить значения встроенной переменной inventory_hostname и переменной webserver_message в вашем playbook.
Вы также можете использовать команду curl, чтобы узнать , получили ли вы ответ от обоих веб-серверов:
[andreyex@control plays]$ curl node2.andreyexru.local A message from node2 I am running to the finish line. [andreyex@control plays]$ curl node3.andreyexru.local A message from node3 I am running to the finish line.
Доступ к фактам в Jinja2
Вы можете получить доступ к фактам в шаблонах Jinja2 так же, как вы получаете доступ к фактам из своей книги.
Для демонстрации перейдите в каталог шаблонов и создайте файл info.j2 Jinja2 со следующим содержимым:
[andreyex@control templates]$ cat info.j2
Server Information Summary
--------------------------
hostname={{ ansible_facts['hostname'] }}
fqdn={{ ansible_facts['fqdn'] }}
ipaddr={{ ansible_facts['default_ipv4']['address'] }}
distro={{ ansible_facts['distribution'] }}
distro_version={{ ansible_facts['distribution_version'] }}
nameservers={{ ansible_facts['dns']['nameservers'] }}
totalmem={{ ansible_facts['memtotal_mb'] }}
freemem={{ ansible_facts['memfree_mb'] }}
Обратите внимание, что info.j2 имеет доступ к восьми различным фактам. Теперь вернитесь в каталог своего проекта и создайте следующий playbook server-info.yml :
[andreyex@control plays]$ cat server-info.yml
---
- name: Server Information Summary
hosts: all
tasks:
- name: Create server-info.txt using Jinja2
template:
src: info.j2
dest: /tmp/server-info.txt
Обратите внимание, что вы создаете /tmp/server-info.txt на всех хостах на основе файла шаблона info.j2. Идите вперед и запустите playbook:
[andreyex@control plays]$ ansible-playbook server-info.yml PLAY [Server Information Summary] ******************************************* TASK [Gathering Facts] ********************************** ok: [node4] ok: [node1] ok: [node3] ok: [node2] TASK [Create server-info.txt using Jinja2] ******** changed: [node4] changed: [node1] changed: [node3] changed: [node2] PLAY RECAP ************************* node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node3 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node4 : ok=2 changed=1 unreachable=0 failed=0 skipped=0
Все хорошо! Теперь давайте запустим быструю специальную команду, чтобы проверить содержимое файла /tmp/server-info.txt на одном из узлов:
[andreyex@control plays]$ ansible node1 -m command -a "cat /tmp/server-info.txt" node1 | CHANGED | rc=0 >> Server Information Summary -------------------------- hostname=node1 fqdn=node1.andreyexru.local ipaddr=10.0.0.5 distro=CentOS distro_version=8.2 nameservers=['168.63.129.16'] totalmem=1896 freemem=1087
Как видите, Jinja2 смог получить доступ и обработать все факты.
Условные операторы в Jinja2
Вы можете использовать условный оператор if в Jinja2 для тестирования различных условий и сравнения переменных. Это позволяет определить последовательность выполнения шаблона файла в соответствии с условиями тестирования.
Для демонстрации перейдите в каталог шаблонов и создайте следующий шаблон selinux.j2 :
[andreyex@control templates]$ cat selinux.j2
{% set selinux_status = ansible_facts['selinux']['status'] %}
{% if selinux_status == "enabled" %}
"SELINUX IS ENABLED"
{% elif selinux_status == "disabled" %}
"SELINUX IS DISABLED"
{% else %}
"SELINUX IS NOT AVAILABLE"
{% endif %}
Первый оператор в шаблоне создает новую переменную, для которой selinux_status установлено значение ansible_facts[‘selinux’][‘status’].
Затем вы можете использовать selinux_status в вашем, если условия испытания, чтобы определить, является ли SELinux включен, отключен или не установлен. В каждом из трех разных случаев вы отображаете сообщение, отражающее статус Selinux.
Обратите внимание, как если утверждение в jinja2 мимике Python, заявление if; только не забывайте использовать {% endif %}.
Теперь вернитесь в каталог своего проекта и создайте следующую playbook в selinux-status.yml :
[andreyex@control plays]$ cat selinux-status.yml
---
- name: Check SELinux Status
hosts: all
tasks:
- name: Display SELinux Status
debug:
msg: "{{ ansible_facts['selinux']['status'] }}"
- name: Create selinux.out using Jinja2
template:
src: selinux.j2
dest: /tmp/selinux.out
Идите вперед и запустите playbook:
[andreyex@control plays]$ ansible-playbook selinux-status.yml
PLAY [Check SELinux Status] ****************************************************
TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node2]
ok: [node3]
ok: [node1]
TASK [Display SELinux Status] **************************************************
ok: [node1] => {
"msg": "enabled"
}
ok: [node2] => {
"msg": "disabled"
}
ok: [node3] => {
"msg": "enabled"
}
ok: [node4] => {
"msg": "Missing selinux Python library"
}
TASK [Create selinux.out using Jinja2] *****************************************
changed: [node4]
changed: [node1]
changed: [node3]
changed: [node2]
PLAY RECAP *********************************************************************
node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
node2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
node3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 node4 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
Из вывода playbook; вы можете видеть, что SELinux включен как на node1, так и на node3. Мы отключили SELinux на node2 перед запуском playbook, а на node4 не установлен SELinux, потому что Ubuntu использует AppArmor вместо SELinux.
Наконец, вы можете запустить следующую специальную команду для проверки содержимого selinux.out на всех управляемых хостах:
[andreyex@control plays]$ ansible all -m command -a "cat /tmp/selinux.out" node4 | CHANGED | rc=0 >> "SELINUX IS NOT AVAILABLE" node2 | CHANGED | rc=0 >> "SELINUX IS DISABLED" node3 | CHANGED | rc=0 >> "SELINUX IS ENABLED" node1 | CHANGED | rc=0 >> "SELINUX IS ENABLED"
Цикл в Jinja2
Вы можете использовать оператор for в Jinja2 для перебора элементов в списке, диапазоне и т. д. Например, следующий цикл for будет перебирать числа в диапазоне (1,11) и, следовательно, будет отображать числа от 1 до 10:
{% for i in range(1,11) %}
Number {{ i }}
{% endfor %}
Обратите внимание, как цикл for в Jinja2 имитирует синтаксис цикла for Python; снова не забудьте завершить цикл с помощью {% endfor %}.
Теперь давайте создадим полный пример, демонстрирующий возможности циклов for в Jinja2. Перейдите в каталог шаблонов и создайте следующий файл шаблона hosts.j2:
[andreyex@control templates]$ cat hosts.j2
{% for host in groups['all'] %}
{{ hostvars[host].ansible_facts.default_ipv4.address }} {{ hostvars[host].ansible_facts.fqdn }} {{ hostvars[host].ansible_facts.hostname }}
{% endfor %}
Обратите внимание, что здесь вы использовали новую встроенную специальную (магическую) переменную hostvars, которая в основном представляет собой словарь, содержащий все хосты в инвентаре и назначенные им переменные.
Вы перебрали все хосты в своем инвентаре, а затем для каждого хоста; вы отобразили значение трех переменных:
- {{hostvars [host] .ansible_facts.default_ipv4.address}}
- {{hostvars [host] .ansible_facts.fqdn}}
- {{hostvars [host] .ansible_facts.hostname}}
Также обратите внимание, что вы должны включить эти три переменные в одну строку рядом, чтобы соответствовать формату файла /etc/hosts.
Теперь вернитесь в каталог ваших проектов и создайте следующий playbook local-dns.yml:
[andreyex@control plays]$ cat local-dns.yml
---
- name: Dynamically Update /etc/hosts File
hosts: all
tasks:
- name: Update /etc/hosts using Jinja2
template:
src: hosts.j2
dest: /etc/hosts
Тогда продолжайте и запустите playbook:
[andreyex@control plays]$ ansible-playbook local-dns.yml PLAY [Dynamically Update /etc/hosts File] ********************************************* TASK [Gathering Facts] *************************** ok: [node4] ok: [node2] ok: [node1] ok: [node3] TASK [Update /etc/hosts using Jinja2] *********************************************** changed: [node4] changed: [node3] changed: [node1] changed: [node2] PLAY RECAP ********************** node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node3 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 node4 : ok=2 changed=1 unreachable=0 failed=0 skipped=0
Пока все выглядит хорошо; теперь выполните следующую специальную команду, чтобы убедиться, что файл /etc/hosts правильно обновлен на узле node1:
[andreyex@control plays]$ ansible node1 -m command -a "cat /etc/hosts" node1 | CHANGED | rc=0 >> 10.0.0.5 node1.andreyexru.local node1 10.0.0.6 node2.andreyexru.local node2 10.0.0.7 node3.andreyexru.local node3 10.0.0.8 node4.andreyexru.local node4
Отлично! Выглядит правильно отформатированным, как вы и ожидали.
Надеюсь, теперь вы осознали всю мощь шаблонов Jinja2 в Ansible. Следите за обновлениями, и вы увидите следующее руководство, в котором вы научитесь защищать конфиденциальную информацию и файлы с помощью Ansible Vault.
Редактор: AndreyEx