Переменные Ansible — это динамические значения, которые используются в плейбуках и ролях Ansible для настройки и возможности повторного использования конфигураций. Они очень похожи на переменные в языках программирования и помогают эффективно управлять сложными задачами. Они позволяют применять один и тот же сценарий или роль в разных средах, системах или контекстах без необходимости хардкодинга конкретной информации.

Представляем перевод статьи, где подробно рассматриваются переменные Ansible.

Зачем в Ansible переменные

С переменными проще управлять динамическими значениями во всем проекте Ansible. Потенциально это уменьшает количество ошибок связанных с человеческим фактором. С помощью переменных мы получаем удобный способ обработки вариаций и различий между разными средами и системами.

Мы можем определять переменные в нескольких местах с разным приоритетом в зависимости от варианта использования. Мы также можем регистрировать новые переменные в наших плейбуках, используя возвращаемое значение задачи.

Ansible facts — это особый тип переменных, которые Ansible получат из любого удаленного хоста, чтобы использовать их в проектах Ansible. Например, мы можем получить информацию о дистрибутиве операционной системы с помощью ansible_distribution, информацию об устройствах на хосте, версию Python, которую Ansible использует с помощью ansible_python_version, и архитектуру системы, среди прочего. Чтобы получить доступ к этим данным, мы должны сослаться на переменную ansible_facts

Правила имен переменных

Ansible имеет строгий набор правил для создания допустимых имен переменных. Имена переменных могут содержать только буквы, цифры и знаки подчеркивания и должны начинаться с буквы или знака подчеркивания. Некоторые строковые значения зарезервированы для других целей и их нельзя использовать как имена переменных, например это касается Python Keywords или Playbook Keywords.

Определение простых переменных и ссылки на них

Самый простой вариант использования переменных — определить имя переменной с одним значением, используя стандартный синтаксис YAML. Хотя этот шаблон можно использовать во многих местах, для простоты мы покажем пример в плейбуке.

- name: Example Simple Variable
  hosts: all
  become: yes
  vars:
    username: bob

  tasks:
  - name: Add the user {{ username }}
    ansible.builtin.user:
      name: "{{ username }}"
      state: present

В приведенном выше примере после блока vars мы определяем переменную username и присваиваем значение bob. Позже, чтобы сослаться на значение в задаче, мы используем синтаксис Jinja2, например, «{{ имя пользователя }}». 

Если значение переменной начинается с фигурных скобок, мы должны заключить все выражение в кавычки, чтобы YAML правильно интерпретировал синтаксис.

Список, словарь и вложенные переменные

Есть много других вариантов для определения более сложных переменных, таких как списки, словари и вложенные структуры. Чтобы создать переменную с несколькими значениями, мы можем использовать синтаксис списков YAML:

vars:
  version:
    - v1
    - v2
    - v3

Чтобы сослаться на конкретное значение из списка, мы должны выбрать правильное поле. Например, чтобы получить доступ к третьему значению v3 :

version: "{{ version[2] }}"

Другой полезный вариант — хранить пары ключ-значение в переменных как словари. Например:

vars:
  users: 
    - user_1: maria
    - user_2: peter
    - user_3: sophie

Точно так же, чтобы сослаться на третье поле из словаря, используйте скобки или запись через точку:

users['user_3']
users.user_3

Обратите внимание, что предпочтительнее использовать запись в квадратных скобках, так как в особых случаях могут возникнуть проблемы с записью через точку.

Иногда нам приходится создавать или использовать структуры вложенных переменных. Например, факты представляют собой вложенные структуры данных. Мы должны использовать скобки или точечную нотацию для ссылки на вложенные переменные.

vars:
  cidr_blocks:
      production:
        vpc_cidr: "172.31.0.0/16"
      staging:
        vpc_cidr: "10.0.0.0/24"

tasks:
- name: Print production vpc_cidr
  ansible.builtin.debug:
    var: cidr_blocks['production']['vpc_cidr']

Специальные переменные

Специальные переменные Ansible — это набор предопределенных переменных, которые содержат информацию о системных данных, инвентаре или контексте выполнения внутри плейбука или роли Ansible. К ним относятся магические переменные, переменные соединения и факты. Имена этих переменных зарезервированы. 

Магические переменные

Волшебные переменные автоматически создаются Ansible и не могут быть изменены пользователем. Эти переменные всегда будут отражать внутреннее состояние Ansible, поэтому их можно использовать только такими, какие они есть.

---
- name: Echo playbook
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Echo inventory_hostname
      ansible.builtin.debug:
        msg:
          - "Hello from Ansible playbook!"
          - "This is running on {{ inventory_hostname }}"

В приведенном выше плейбуке мы определяем плейбук, который использует магическую переменную inventory_hostname . Мы используем эту переменную, чтобы получить имя хоста, на котором работает Ansible, и распечатать с ним сообщение, как показано ниже.

PLAY [Echo playbook] ********************************************************************************************************************************

TASK [Echo inventory_hostname] **********************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "Hello from Ansible playbook!",
        "This is running on localhost"
    ]
}

PLAY RECAP ******************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Помимо inventory_hostname , есть и другие важные магические переменные:

hostvars → используется для получения информации о других хостах в инвентаре, включая любые связанные с ними переменные.

play_hosts → перечисляет все хосты, на которые нацелена текущий запуск.

group_names → содержит список имен групп, к которым принадлежит текущий хост в инвентаре.

groups → пара ключ/значение всех групп в инвентаре со всеми хостами, принадлежащими каждой группе.

Ansible Facts

Факты используются для получения сведений о системе и оборудовании, собранных о текущем хосте во время выполнения плейбука. Эти данные часто используются для создания динамических инвентаризаций, шаблонов или принятия решений на основе атрибутов хоста. Доступ к собранным фактам можно получить с помощью переменной ansible_facts, что позволяет ссылаться на конкретную информацию, такую как операционная система, IP-адрес или архитектура процессора.

Чтобы собрать факты о конкретном хосте, вы можете запустить следующую команду:

ansible -m setup <hostname>

Кроме того, вы можете добавить в свой плейбук опцию gather_facts: yes, чтобы обеспечить сбор фактов после выполнения задач.

В Ansible есть возможность фильтровать определенные факты, такие как os или даже IP-адрес. Это можно сделать с помощью:

ansible -m setup <hostname> -a "filter=<fact_name>

Переменные соединения

Переменные соединения используются для определения того, как машина, на которой работает Ansible, подключается к удаленным хостам во время выполнения задач и плейбуков. Эти переменные обеспечивают гибкость в управлении различными типами соединений, методами аутентификации и специфичными для хоста конфигурациями.

---
- name: Echo message on localhost
  hosts: localhost
  connection: local
  gather_facts: no
  vars:
    message: "Hello from Ansible playbook on localhost!"
  tasks:
    - name: Echo message and connection type
      ansible.builtin.shell: "echo '{{ message }}' ; echo 'Connection type: {{ ansible_connection }}'"
      register: echo_output

    - name: Display output
      ansible.builtin.debug:
        msg: "{{ echo_output.stdout_lines }}"

В приведенном выше примере мы используем переменную подключения, чтобы показать тип подключения при запуске, как показано ниже:

PLAY [Echo message on local host] *******************************************************************************************************************

TASK [Echo message and connection type] *************************************************************************************************************
changed: [localhost]

TASK [Display output] *******************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "Hello from Ansible playbook on localhost!",
        "Connection type: local"
    ]
}

PLAY RECAP ******************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Регистрация переменных

Во время наших запусков нам может понадобиться использовать выходные данные задачи в качестве переменной, которую мы можем использовать в следующих задачах. Мы можем использовать регистр ключевых слов для создания собственных пользовательских переменных из выходных данных задачи.

- name: Example Register Variable Playbook
  hosts: all
  
  tasks:
  - name: Run a script and register the output as a variable
    shell: "find hosts"
    args:
      chdir: "/etc"
    register: find_hosts_output
  - name: Use the output variable of the previous task
    debug:
      var: find_hosts_output

В приведенном выше примере мы регистрируем вывод команды find /etc/hosts и демонстрируем, как мы можем использовать переменную в следующей задаче, выводя ее значение.

Мощным шаблоном является объединение зарегистрированных переменных с условными выражениями для создания задач, которые будут выполняться только при соблюдении определенных пользовательских условий.

- name: Example Registered Variables Conditionals
  hosts: all
  
  tasks:
  - name: Register an example variable
    shell: cat /etc/hosts
    register: hosts_contents

  - name: Check if hosts file contains the word "localhost"
    debug:
      msg: "/etc/hosts file contains the word localhost"
    when: hosts_contents.stdout.find("localhost") != -1
      var: find_hosts_output

Здесь мы прописали в переменной hosts_contents содержимое файла /etc/hosts, а вторую задачу выполняем только в том случае, если в файле есть слово localhost .

Поскольку зарегистрированные переменные хранятся в памяти, их невозможно использовать в будущих воспроизведениях, и они доступны только для текущего запуска плейбука.

Совместное использование переменных YAML Anchors и Aliases

Когда мы хотим повторно использовать и совместно использовать переменные, мы можем использовать YAML Anchors и Aliases. Они обеспечивают нам большую гибкость в работе с общими переменными и помогают сократить повторение данных. Узнайте больше в руководстве по YAML.

Anchors определяются с помощью &, а затем ссылаются на alias, обозначенный *. Давайте пойдем и проверим практический пример в плейбуке.

- name: Example Anchors and Aliases
  hosts: all
  become: yes
  vars:
    user_groups: &user_groups
     - devs
     - support
    user_1:
        user_info: &user_info
            name: bob
            groups: *user_groups
            state: present
            create_home: yes
    user_2:
        user_info:
            <<: *user_info
            name: christina
    user_3:
        user_info:
            <<: *user_info
            name: jessica
            groups: support

  tasks:
  - name: Add several groups
    ansible.builtin.group:
      name: "{{ item }}"
      state: present
    loop: "{{ user_groups }}"

  - name: Add several users
    ansible.builtin.user:
      <<: *user_info
      name: "{{ item.user_info.name }}"
      groups: "{{ item.user_info.groups }}"
    loop:
      - "{{ user_1 }}"
      - "{{ user_2 }}"
      - "{{ user_3 }}"

Поскольку некоторые параметры являются общими для пользователей, здесь вместо того, чтобы переписывать одни и те же значения, мы делимся общими с anchor &user_info. Для каждого последующего объявления пользователя мы используем alias *user_info, чтобы максимально избежать повторения.

Значения для state и create_home одинаковы для всех пользователей, а name и groups заменяются с помощью оператора слияния << .

Точно так же мы повторно используем объявление user_groups в определении привязки user_info. Таким образом, нам не нужно снова вводить одни и те же группы для user_2, в то время как у нас все еще есть возможность переопределить группы, как мы это делаем для user_3.

В результате user_1 и user_2 добавляются в группы devs и support, а user_3 добавляется только в группу support.

Область применения переменной

Ansible предоставляет множество вариантов установки переменных, и окончательное решение о том, где их установить, лежит на нас в зависимости от области действия, которую мы хотели бы, чтобы они имели. Концептуально существует три основных варианта определения области видимости переменных. 

Во-первых, у нас есть глобальная область, в которой значения устанавливаются для всех хостов. Это может быть определено конфигурацией Ansible, переменными среды и командной строкой. 

Мы устанавливаем значения для конкретного хоста или группы хостов, используя область хоста. Например, есть возможность определить некоторые переменные для каждого хоста в файле инвентаризации.

Наконец, у нас есть область воспроизведения, где значения устанавливаются для всех хостов в контексте воспроизведения. Примером может служить раздел vars, который мы видели в предыдущих примерах в каждом плейбуке.

Параметры настройки переменных и приоритет

В Ansible переменные могут быть определены во многих разных местах. Существуют параметры для установки переменных в плейбуках, ролях, инвентаре, файлах var и командной строке. Давайте пойдем и рассмотрим некоторые из этих вариантов. 

Как мы видели ранее, самый простой способ — определить переменные в разделе vars.

- name: Set variables in a play
  hosts: all
  vars:
    version: 12.7.1

Другой вариант — определить переменные в файле инвентаризации . Мы можем установить переменные для каждого хоста или установить общие переменные для групп. В этом примере определяется отдельный доступный пользователь для подключения для каждого хоста в качестве переменной хоста и один и тот же порт HTTP для всех веб-серверов в качестве групповой переменной .

[webservers]
webserver1 ansible_host=10.0.0.1 ansible_user=user1
webserver2 ansible_host=10.0.0.2 ansible_user=user2

[webservers:vars]
http_port=80

Чтобы лучше организовать наши переменные, мы могли бы собрать их в отдельные файлы переменных хоста и группы. В том же каталоге, где мы храним наши файлы инвентаризации Ansible или плейбуки, мы можем создать две папки с именами group_vars и host_vars, которые будут содержать наши файлы переменных. Например:

group_vars/databases 
group_vars/webservers
host_vars/host1
host_vars/host2

Переменные также могут быть установлены в пользовательских файлах var. Давайте рассмотрим пример, в котором используются переменные из внешнего файла и каталоги group_vars и host_vars.

- name: Example External Variables file
  hosts: all
  vars_files:
    - ./vars/variables.yml

  tasks:
  - name: Print the value of variable docker_version
    debug: 
      msg: "{{ docker_version}} "
  
  - name: Print the value of group variable http_port
    debug: 
      msg: "{{ http_port}} "
  
  - name: Print the value of host variable app_version
    debug: 
      msg: "{{ app_version}} "

Файл vars /variables.yml:

docker_version: 20.10.12

Файл group_vars /websservers :

http_port: 80
ansible_host: 127.0.0.1
ansible_user: vagrant

Файл host_vars /host1 :

app_version: 1.0.1
ansible_port: 2222
ansible_ssh_private_key_file: ./.vagrant/machines/host1/virtualbox/private_key

Файл host_vars /host2 :

app_version: 1.0.2
ansible_port: 2200
ansible_ssh_private_key_file: ./.vagrant/machines/host2/virtualbox/private_key

Файл инвентаризации содержит группу с именем веб-серверы, в которую входят два наших хоста, host1 и host2:

[webservers]
host1 
host2

Если мы запустим этот плейбук, мы заметим, что на обоих хостах используется одно и то же значение для групповой переменной  http_port, но другое значение для переменной хоста app_version.

Хорошим примером использования отдельных файлов переменных является то, что вы можете хранить в них конфиденциальные значения, не сохраняя их в плейбуках или системах контроля версий.

Иногда может оказаться полезным определить или переопределить переменные во время выполнения, передав их в командной строке с аргументом –extra-vars или –e . Например:

ansible-playbook example-external-vars.yml --extra-vars "app_version=1.0.3"

Поскольку переменные могут быть установлены в нескольких местах, Ansible применяет приоритет переменных для выбора значения переменной в соответствии с некоторой иерархией. Общее правило заключается в том, что переменные, определенные с более явной областью действия, имеют более высокий приоритет.

Например, значения по умолчанию для роли переопределяются в основном любым другим параметром. Переменные также выравниваются для каждого хоста перед каждым воспроизведением, поэтому все переменные группы и хоста объединяются. Переменные хоста имеют более высокий приоритет, чем переменные группы. 

Явные определения переменных, такие как каталог vars или задача include_vars, переопределяют переменные из инвентаря. Наконец, extra переменные, определенные во время выполнения, всегда имеют приоритет. Полный список опций и их иерархию смотрите в официальной документации Understanding variable priority.

Где устанавливать переменные и лучшие практики

Поскольку Ansible предоставляет множество опций для определения переменных, может быть сложно найти лучший способ и место для их установки. Давайте проверим некоторые распространенные и лучшие практики установки переменных, которые могут помочь нам лучше организовать наши проекты Ansible.

  • Всегда давайте описательные и понятные имена своим переменным. Время, потраченное на то, чтобы правильно подумать о том, как назвать переменные, всегда окупается в долгосрочной перспективе.

  • Если есть значения по умолчанию для общих переменных, установите их в group_vars/all

  • Предпочтительно устанавливать групповые и хостовые переменные в каталогах group_vars и host_vars, а не в файле инвентаризации.

  • Если переменные, связанные с географией или поведением, привязаны к определенной группе, предпочтительнее задавать их как групповые переменные.

  • Если вы используете роли, всегда устанавливайте переменные ролей по умолчанию в roles/your_role/defaults/main.yml.

  • Когда вы вызываете роли, передайте переменные, которые вы хотите переопределить, в качестве параметров, чтобы ваши плейбуки было легче читать.

roles:
       - role: example_role
         vars:
            example_var: 'example_string'
  • Вы всегда можете использовать –extra-vars или –e, чтобы переопределить любую другую опцию.

  • Не храните конфиденциальные переменные в репозитории исходного кода в виде простого текста. В этих случаях вы можете использовать Ansible Vault.

В общем, старайтесь максимально упростить использование переменных. Вам не нужно использовать все существующие параметры и разбрасывать определение переменных повсюду, потому что это затрудняет отладку ваших проектов Ansible. Попробуйте найти структуру, которая лучше всего соответствует вашим потребностям, и придерживайтесь ее! 

Чтобы узнать больше, ознакомьтесь с 44 рекомендациями по использованию Ansible.

Ключевые моменты

В этой статье мы подробно рассмотрели переменные Ansible и увидели, как мы можем определить и использовать их в плейбуках. Более того, мы изучили различные варианты их совместного использования, настройки и ссылки на них, а также некоторые рекомендации и передовые методы, чтобы упростить наше путешествие по Ansible.


Если вы узучаете Ansible, приходите на курс «Ansible: Infrastructure as Code». Вы сможете систематизировать знания и получить практику на наших стендах.

Новый поток стартует 21 августа. Посмотреть программу и записаться на курс можно на нашем сайте. Ждём на курсе!

Комментарии (1)


  1. rasperepodvipodvert
    20.08.2023 13:28

    Повторение мать учения! Спасибо за статью!