В предыдущей статье по принятию решений в 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.