Ansible
- Установка и настройка
- Запуск и элементы управления ansible скриптами
- Модули
- Роли
- Тестирование
- Файл inventory и переменные
- Последовательность выполнения
Установка и настройка
Установка
Было написано, что необходим только python. Где-то предлагают устанавливать через pip, brew,
На Debian-подобных системах:
apt-get install ansible
Настройка управляемых хостов
На каждом хосте:
- Если sudo нет, то
apt install sudo - Если создается новый пользователь, то
sudo adduser ansibleuser sudo usermod -aG sudo ansibleuser - Если существующий пользователь, то добавить в группу sudo:
usermod -aG sudo sergey - Разрешить пользователю повышать привилегии без ввода пароля. Создать файл
sudo nano /etc/sudoers.d/ansibleuser - И добавить текст
ansibleuser ALL=(ALL) NOPASSWD:ALL - Установить пароль на пользователя
passwd ansibleuser - Еще создать директорию /home/ansibleuser
Теперь можно использовать в скриптах become: true
Настройка сервера
ssh-keygen
ssh-copy-id username@remote_host
- Создать ключ доступа
- Скопировать на каждый управляемый хост, используя логин на хост. Удобнее, если для задач управления на всех хостах сделать одинаковый логин.
В настройке каждого хоста нужно использовать соответствующий логин, для которого настроен ключ доступа.
Настройка ansible
- ANSIBLE_CONFIG (environment variable if set)
- ansible.cfg (in the current directory)
- ~/.ansible.cfg (in the home directory)
- /etc/ansible/ansible.cfg (default)
ansible-config init --disabled > ansible.cfg
ansible-config init --disabled -t all > ansible.cfg
# Обычная группа
[example]
www.example.com
1.2.3.4:2222
# Группа 'multi', состоящая из других групп
[multi:children]
example
#5 servers in one line [a:z] or [A:Z], or numbers with specific digits, such as [001:250].
[dyngroup]
Node[0:4].lab.edu
# Variables that will be applied to all servers
[multi:vars]
ansible_ssh_user=vagrant
Вывод дерева hosts:
ansible-inventory --graph
--vars дополнительно выведет переменные
---
ansible_user=setup
ansible_private_ssh_key=/home/user/ansible.key
Может применяться и плоское определение, и вложенное, тогда в скрипте {{ db.user }}:
db: lll
user: ppp
Динамический реестр:
Должен поддерживать следующий интерфейс:
--host=hostname - реестр выдает список в JSON { "ansible_ssh_host": "127.0.0.1", "ansible_ssh_port": 2200, "ansible_ssh_user": "vagrant"}
--list реестр выдает список групп {"staging": [ "ontaro.ехамрlе.сом", "quebec.ехаmple.сом"], "vagrant": [ "vagrant1", "vagrant2", "vagrantЗ"]}
"_meta" : {"hostvars" : {"vagrant1" : {"ansible_ssh_host": "127.0.0.1", "ansible_ssh_port": 2222, "ansible_ssh_user": "vagrant"}, "vagrant2": {"ansible_ssh_host": "127.0.0.1", "ansible_ssh_port": 2200, "ansible_ssh_user": "vagrant"}}}
Если создать папку inventory и добавить в ansible.cfg параметр hostfile = inventory то статический hosts и динамический будут объединены
Если включить сбор фактов gather_facts: True то можно во время выполнения задач группировать хосты (например debian/centos)
Использование динамического реестра (в примере my_dynamic_inventory.py должен быть исполняемым скриптом)
ansible-playbook -i my_dynamic_inventory.py my_playbook.yml
В Ссылках есть материал по динамическим реестрам
| Переменная | Использование |
| ansible_user | пользователь, от имени которого выполняются задачи на хостах данной группы. Значение в файле задач игнорируется. |
| ansible_host | Сопоставление ip-имени |
| ansible_port | Порт доступа по ssh |
| ansible_connection | Может быть ssh, local, docker (запуск команд непосредственно на контейнере) |
| ansible_become | + sudo |
| ansible_become_user | sudo -> another user |
| ansible_ssh_private_key_file | адрес ключа |
Запуск и элементы управления ansible скриптами
ansible multi -a "hostname"
Запуск через ansible playbook
ansible-playbook filename.yml
---
- hosts: [host ip | host group | all]
remote_user: []
become: true
tasks:
- [task 1]
- [task n]
---
- hosts: [host ip | host group | all]
remote_user: []
become: true
tasks:
- [task 1]
- [task n]
Параметры задачи
gather_facts: False Проводить сбор данных по умолчанию
ignore_errors: True Прекращать или нет работу при ошибке
Переменные из модулей
---
- name: Check if host is up and running
hosts: all
become: false
gather_facts: false
tasks:
- name: Ping the remote server
ping:
register: result
until: result is succeeded
retries: 3
delay: 10
- name: Get system uptime
command: uptime
when: result is succeeded
register: uptime_result
- name: Show system uptime
debug:
var: uptime_result.stdout
when: result is succeeded
10: полученное значение из модуля ping сохраняется в переменной result
11: данное задание выполняется до тех пор, пока result не будет succeeded, но с ограничением кол-ва попыток (12) и задержкой между попытками (13)
17: задание 15 выполняется если result succeeded
18: полученное значение заносится в переменную uptime_result
21: вывод значения uptime_result
Условия (handlers)
when: ansible_os_family == "Debian"
Получить список фактов:
- name: Show facts available on the system
ansible.builtin.debug:
var: ansible_facts
В папке /etc/ansible/facts.d/*.fact файлы с локальными фактами о машине. Обращение к фактам в yaml:
ansible_local.<fact file name>.<fact group inside file>.<varname>
Создание новой переменной:
set_fact:<valname>=<...>
Либо команда
ansible -i inventory.ini docker_hosts -m setup
inventory.ini - файл hosts, docker_hosts - имя группы
Циклы (loops)
Цикл с объявленной переменной:
- name: use apt to install multiple apps
apt:
name: '{{ app }}'
state: latest
update_cache: yes
vars:
app:
- htop
- mc
- nload
become: yes
Цикл с необъявленной переменной:
- name: use apt to install multiple apps
apt:
name: '{{ item }}'
state: latest
update_cache: yes
loop:
- htop
- mc
- nload
become: yes
Цикл с переменной-словарем
- name: install python packages
pip: name={{item.name}} version={{item.version}}
become: True
with_items:
- {name=mezzanine, version=1.2.1}
- {name=guincorn, version=3.2.9}
Проверка ошибок выполнения
Проверяет условия и останавливает выполнение при нарушении
- name: Проверка значения переменной
assert:
that:
- my_variable == "expected_value"
Примеры из документации:
- name: A single condition can be supplied as string instead of list
ansible.builtin.assert:
that: "ansible_os_family != 'RedHat'"
- name: Use yaml multiline strings to ease escaping
ansible.builtin.assert:
that:
- "'foo' in some_command_result.stdout"
- number_of_the_counting == 3
- >
"reject" not in some_command_result.stderr
- name: After version 2.7 both 'msg' and 'fail_msg' can customize failing assertion message
ansible.builtin.assert:
that:
- my_param <= 100
- my_param >= 0
fail_msg: "'my_param' must be between 0 and 100"
success_msg: "'my_param' is between 0 and 100"
- name: Please use 'msg' when ansible version is smaller than 2.7
ansible.builtin.assert:
that:
- my_param <= 100
- my_param >= 0
msg: "'my_param' must be between 0 and 100"
- name: Use quiet to avoid verbose output
ansible.builtin.assert:
that:
- my_param <= 100
- my_param >= 0
quiet: true
Модули
Общая информация
Модули - созданный на python скрипт, упрощающий конкретную задачу.
Модули возвращают значения, название модуля должно быть под name. Есть общие переменные и специфичные для модулей
Справка по модулю
ansible-doc apt
Список модулей
ansible-doc -l
Пример проверки необходимости перезагрузки
- name: check if reboot is required
become: yes
become_method: sudo
shell: "[ -f /var/run/reboot-required ]"
failed_when: False
register: reboot_required
changed_when: reboot_required.rc == 0
notify: reboot
handlers:
- name: reboot
command: shutdown -r now "Ansible triggered reboot after system updated"
async: 0
poll: 0
ignore_errors: true
Ссылки
Информация на русском языке о модулях
Системные модули и скрипты
Установка и/или проверка установки apt пакета
Название модуля: apt
Переменные:
- name: ntp #имя проверяемого и устанавливаемого модуля
- state: present #состояние после завершения
- update_cache: yes # обновлять ли кэш
- name: Install module
apt:
name: ntp
state: present
update_cache: yes
Добавить ключ стороннего репозитория
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
Добавить сторонний репозиторий
- name: Add Docker repository
apt_repository:
repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable
Обновить apt кэш
- name: Update apt cache
apt: update_cache=yes
Копирование файлов
Копирование с локального на удаленный
- name: Copy from server to client
copy:
src: /home/user/file.txt
dest: /home/setup/file.txt
owner: foo
group: foo
mode: '0644'
Копирование с удаленного на локальный
- name: Copy from client to server
fetch:
src: /var/log/access.log
dest: /var/log/fetched
flat: true удалить структуру родительских папок для файла
Создание пользователя и группы
Создание пользователя
- name: Create and/or check presence user
user:
name: install
state: present
shell: /bin/bash
group: sudo
system: yes
hidden: yes
ssh_key_file: .ssh/id_rsa
expires: -1
Создание группы
- name: Create check group
group:
name: clustergroup
state: present
gid: 1040
Управление сервисами (daemon)
- name: Update sysctl
sysctl:
name: net.ipv4.ip_forward
value: 1
sysctrl_set: yes
state: present
reload: yes
- name: Set or check service
service:
name: ntp
state: started
enabled: yes
- name: Set check daemon starting
systemd:
name: ntp
state: started
enabled: yes
masked: no
daemon_reload: yes
register: systemd
cron:
Скрипты и консольные команды
- name: Raw command
raw: echo "this was written by a raw Ansible module!!" >> ~/raw.txt
- name: Executing script
shell: ./shell_script.sh >> ~/shell.txt
args:
chdir: /usr/local/
creates: ~/shell.txt
executable: /bin/csh
- name: Executing python script
script: ./shell_script.py –some-argumets "42"
args:
creates: ~/shell.txt
executable: python
Для исполнения expect скриптов нужно сначала проверить и установить пакет expect
- name: Expect module
expect:
command: passwd user1
responses:
(?i)password: "Ju5tAn07herP@55w0rd":
Git и pip
Git
- name: Clone update repo
git:
repo: https://github.com/ansible/ansible.git
dest: /usr/local/ansible
clone: yes
update: yes
Pip
Из официального репозитория:
- name: Install python package
pip:
name: numpy
version: 0.3
Из внешнего источника
- name: install a python library from a github
pip:
name: https://github.com/jakubroztocil/httpie
Docker
Необходимые модули:
pip install 'docker-py>=1.7.0'
pip install 'docker-compose>=1.7.0'
ansible-container позволяет работать с docker без dockerfile
Создание контейнера
- name: create a container
docker_container:
name: debianlinux
image: debian:9
pull: yes
state: present
Запуск контейнера
- name: start a container
docker_container:
name: debianlinux
state: started
devices:
- "/dev/sda:/dev/xvda:rwm"
Остановка контейнера
- name: stop a container
docker_container:
name: debianlinux
state: stopped
Удаление образа из локального хранилища
- name: remove a container image
docker_image:
name: labimages/ubuntu
state: absent
tag: lab16
Авторизация на docker hub
- name: login to DockerHub
docker_login:
username: labuser1
password: "L@bp@55w0rd"
email: user1@lab.edu
Скачать образ из docker hub
- name: pull a container image
docker_image:
name: ubuntu:18.04
pull: yes
Сохранить образ в docker hub
- name: push a container image to docker hub
docker_image:
name: labimages/ubuntu
repository: labimages/ubuntu
tag: lab18
push: yes
Консольные команды
При помощи модуля command можно выполнить команду на всех серверах из inventory файла.
ansible -i inventory.ini docker_hosts -m command -a "ip a" -b
-b выполнение от sudo
Роли
Роль — способ организовать набор tasks, переменных, шаблонов, файлов и обработчиков в переиспользуемый модуль. Роли используются для конфигурации конкретных приложений. По умолчанию директория /etc/ansible/roles
Настраивается в cfg файле:
[defaults]
roles_path = ./roles
Также в переменной ANSIBLE_ROLES_PATH
Структура роли и добавление в playbook
- Создается папка roles, внутри папки с названиями ролей, внутри каждой папки - defaults files handlers meta templates tasks vars
- в playbook добавляется
roles: - имя
Назначения папок
| Папка | Назначение |
| defaults | Устанавливает переменные по умолчанию для включенных ролей. Обычно defaults/main.yml
|
| files |
содержит статические файлы и файлы сценариев, которые могут быть скопированы на удалённый сервер или выполнены на нём. Пример копирования внутри роли и выполнения: roles/myrole/
|
| handlers | Обработчики, выполняемые в самом конце |
| meta | для метаданных роли, которые используются для управления зависимостями. Например, вы можете определить список ролей, которые должны быть применены до вызова текущей роли. |
| templates |
Шаблоны генерации файлов на удалённых хостах. Шаблоны jinja2. Например нужно создать файл /etc/docker/daemon.json Создаем шаблон в roles/docker/templates/daemon.json.j2
Добавляем переменные в roles/docker/defaults/main.yml
Создаем задачу
Создаем handlers roles/docker/handlers/main.yml
В результате на сервере появится json файл. |
| tasks |
Содержит файлы с задачами, которые определяются в разделе tasks обычного плейбука Ansible. Сначала просматривается файл tasks/main.yml В нем указывается условия импорта других файлов
Пути указываются относительно директории tasks Эти задачи могут напрямую ссылаться на файлы и шаблоны, содержащиеся в соответствующих каталогах внутри роли, без необходимости указывать полный путь к файлу. |
| vars | переменные для роли могут быть указаны в файлах внутри каталога, а затем ссылаться на них в другом месте роли. |
Ссылки:
Пример преобразования одного файла в роль
Хранилище ролей
Есть сайт с ролями почти для всего. Сайт с ролями ansible Можно найти нужную роль на сайте, затем установить при помощи
ansible-galaxy install current_role_name -p .
Создание роли:
ansible-galaxy init new_role_name
Пример создания новой роли
Пусть будет задача в установке docker. Реализуем из инструкции по Docker: общая информация и установка
Инициализируем новую роль
ansible-galaxy init just_new_role
Структура директорий создана. Проанализируем инструкцию.
- Написана для 3 вариаций ОС: Debian, Ubuntu, Alt.
- Последовательность установки:
- Создание пользователя
- Добавление сертификатов
- Непосредственно установка пакетов
- Дополнительные удобства
В константы будем постепенно добавлять нужные. Итоговый defaults/main.yml :
---
# defaults file for just_new_role
docker_user: webadmin
packages_certificates:
- ca-certificates
- curl
- gnupg
- lsb-release
packages_docker_debian:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
packages_docker_alt:
- docker-engine
- docker-compose-v2
Начнем с установки sudo и добавления пользователя в группы, то есть реализуем следующие команды:
apt-get install sudo
sudo apt-get update
usermod -aG sudo docker_user
Файл задач tasks/01_create_user.yml :
- name: Install sudo
ansible.builtin.apt:
name: sudo
state: present
update_cache: true
- name: Ensure user exists and is in sudo group
ansible.builtin.user:
name: "{{ docker_user }}"
groups: sudo
append: true
state: present
Да, если сопоставить количество строк команд и того, что необходимо написать для их исполнения, возникает ощущение перебора (в 3 раза). Но это плата за индепотентность.
Добавим в файл tasks/main.yml задачу старта 01_create_user.yml
---
# tasks file for just_new_role
- include_tasks: 01_create_user.yml
Запуск роли
Когда роль написана, нужно создать следующую структуру директорий для playbook
project/
├── inventory.ini
├── playbook.yml
└── roles/
└── just_new_role/
├── defaults/
├── tasks/
├── handlers/
└── ...
Пример файла inventory.ini
[docker_hosts]
192.168.1.100
[docker_hosts:vars]
ansible_user=webadmin
Пример playbook.yml
---
- name: Install Docker
hosts: all
become: true
roles:
- just_new_role
Итоговые файлы:
main.yml
---
# tasks file for just_new_role
- include_tasks: 01_create_user.yml
- include_tasks: 02_append_certs.yml
01_create_user.yml
- name: Install sudo
ansible.builtin.apt:
name: sudo
state: present
update_cache: true
when: ansible_distribution == "Debian"
- name: Ensure user exists and is in sudo group
ansible.builtin.user:
name: "{{ docker_user }}"
groups: sudo
append: true
state: present
- name: Install required packages
ansible.builtin.apt:
name: "{{ packages_certificates }}"
state: present
update_cache: true
when: ansible_distribution == "Debian"
02_append_certs.yml
- name: Create apt keyrings directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
- name: Download Docker GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/debian/gpg
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
- name: Create binary keyring
ansible.builtin.command:
cmd: >
gpg --dearmor
--output /etc/apt/keyrings/docker.gpg
/etc/apt/keyrings/docker.asc
creates: /etc/apt/keyrings/docker.gpg
- name: Add Docker repository
ansible.builtin.apt_repository:
repo: >-
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg]
https://download.docker.com/linux/debian
{{ ansible_distribution_release }} stable
filename: docker
state: present
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
- name: Install required packages
ansible.builtin.apt:
name: "{{ packages_docker_debian }}"
state: present
update_cache: true
when: ansible_distribution == "Debian"
- name: Ensure user exists and is in sudo group
ansible.builtin.user:
name: "{{ docker_user }}"
groups: docker
append: true
state: present
- name: Enable and start Docker service
ansible.builtin.systemd:
name: docker
enabled: true
state: started
Тестирование
Лучше тестовая среда - предпрод среда - прод среда
Виды тестирования
| E2E | Тестирование основного бизнес функционала. Объединенная работа нескольких сервисов |
| Системное тестирование |
Тестирование одного сервиса. Обычно ручное. Варианты:
|
| Интеграционное тестирование | Тестирование взаимодействий сервисов |
| Unit тестирование | Проверка корректности отдельных модулей |
| Mock тестирование | Фиктивная реализация интерфейса для тестирования |
Quality gate - модуль тестирования
Выполнение скрипта с кодом возврата:
tasks:
- script: test_script_1
- script: test_script_2 --parameter1 value1 --parameter2 value2
Проверка наличия файлов модулем stat
tasks:
- stat:
path: /path/to/something
register: p
- assert:
that:
- p.stat.exist and p.stat.isdir
Использование модуля assert
tasks:
- shell: /usr/bin/some_command --some_parameter value
register: cmd_result
- assert:
that:
- "'not ready' not in cmd_result.stderr"
- "'gizmo enabled' in cmd_result.stdout"
Жизненный цикл тестирования:
- Использовать один и тот же playbook при тестировании всех сред, включая production
- Запускайте тестирование каждый раз
- Использовайте тесты, написанные командой обеспечения качества
- Используйте те же самые тесты при деплое в production
Достижение непрерывного развертывания
- Использовать автоматизацию при развертывании виртуальных машин (развертывание по кнопке)
- При помощи CI системы развертывайте на тестовой среде
- Одна из задач при деплое вызывает тестирующий скрипт со статусом Прошел/ не прошел перед каждым деплоем
- Если деплой прошел успешно, то сборка повторяется на production контуре
Пример схемы CI/CD
Molecule
Поддерживает текущую версию и -1 промежуточной (второй цифры). Установка:
pipx install molecule
Инициализация molecule
В директории с папкой roles выполнить
molecule init scenario name_of_role
При инициализации создается несколько файлов в molecule/name_of_role/
Назначения файлов
| molecule.yml |
Конфигурация запуска фреймворка. Основной файл.
Блоки настройки: dependency: управление зависимостями. driver: управление сервером тестирования. По умолчанию docker. Устанавливаются отдельно. platforms: конкретный элемент для запуска provisioner: инструмент запуска converge.yml verifier: инструмент запуска verify.yml для проверки перехода в нужное состояние. lint: инструмент поиска синтаксических ошибок. scenario: жизненный цикл тестов. Может быть несколько. |
| converge.yml | Playbook для накатывания роли |
| verify.yml | Тестирование того, что роль успешно применилась |
В сценариях используются еще блоки, их называют программами.
molecule test запускает test_sequence
Karate
OpenSource инструмент для тестирования, удобно тестировать API
Формирует отчеты.
Файл inventory и переменные
Список хостов с тэгами (группами), на которые впоследствии ссылаются в задачах.
Последовательность поиска файла
По умолчанию: /etc/ansible/hosts
Однако проще указывать файл при запуске playbook
ansible-playbook -i my_inventory.ini playbook.yml
Наследование переменных
- переменные группы all
- переменные родительской группы
- переменные дочерней группы
- переменные хоста
Варианты запуска inventory файла
ansible-playbook -i prod.ini
ansible.cfg:
[defaults]
inventory = /root/ansible_playbooks/inventory/cloud_inv.py
Проверка интерпретации файла:
# Покажет структуру inventory в JSON
ansible-inventory -i ваш_файл --list
# Покажет в удобном для человека виде
ansible-inventory -i ваш_файл --graph
Можно в директории хранить несколько файлов и использовать их по необходимости.
# Посмотреть все хосты из inventory
ansible all -i inventory.ini --list-hosts
# Посмотреть переменные конкретного хоста
ansible -i inventory.ini web1 -m debug -a "var=hostvars[inventory_hostname]"
# Выполнить команду на группе серверов
ansible web_servers -i inventory.ini -m command -a "df -h"
Форматы файлов:
Есть динамический, ini и yaml формат. Определяет формат по содержанию файла, расширение файла (.ini, .yaml, .yml) не играет роли. Порядок анализа:
- Является ли файл исполняемым — если да, то запускает его как динамический inventory
- Пытается распарсить как INI — если получается, использует INI-формат
- Пытается распарсить как YAML — если получается, использует YAML-формат
- Если ничего не подошло — выдает ошибку
Важно:
- Нельзя смешивать форматы в одном файле
- Способ отличия форматов:
INI-формат: начинается с секций в квадратных скобках [group]
YAML-формат: начинается с --- (опционально) или со слова all: / children: / hosts: - Автодетект может ошибаться. Если будет файл, валидный как INI и как YAML, то Ansible выберет INI.
ini формат:
Ожидается, что hosts в формате ini.
[example]
www.example.com
192.168.0.1
192.168.0.2:2222
Внутри [] название группы, далее состав этой группы (доменное имя или IP). Указание порта определяет порт ssh сервера.
Группы по умолчанию
- Группа all - все хосты
- Группа ungrouped - хосты без группы
Вложенные группы
При указании :children создается метагруппа. То есть
[staging:children]
web_servers
db_servers
создаст группу staging, которая будет состоять из хостов группы web_servers и db_servers. Это именно группы, хосты указывать в метагруппе нельзя. Можно городить любую иерархию, одна метагруппа внутри другой.
Диапазоны
web[01:05].example.com
Переменные
Для хоста переменные через пробел в одной строке
[databases]
192.168.1.20 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ubuntu-key.pem
При помощи :vars задаются переменные для группы
[staging:vars]
ansible_user=staging_user
env=staging
app_version=latest
yaml формат
| Когда использовать YAML | Когда проще INI |
|
Сложная иерархия групп (3+ уровня) Много переменных со сложными типами (списки, словари) Храните инвентарь в Git (лучше для code review) Команда привыкла к YAML |
Мало хостов (<20) Простая структура (1-2 уровня групп) Быстрые временные инвентари |
Корневая группа одна — all. Группа all является специальной встроенной группой Ansible:
- в неё входят все хосты inventory
- все группы являются её потомками (напрямую или косвенно)
Поддерживает диапазоны аналогично ini
Если будет несколько корневых групп, то будет ошибка. Структура файла настроек:
all:
hosts:
vars:
children:
Хосты:
Внутри hosts указываются переменные для хоста.
all:
hosts:
server1:
ansible_host: 192.168.1.10
server2:
ansible_host: 192.168.1.11
Переменные:
vars:
http_port: 80
app_user: nginx
Переменные из all.vars наследуются всеми хостами
Теперь переменные доступны в playbook
- name: Show port
debug:
msg: "{{ http_port }}"
Переменные переопределяются по иерархии.
Создание дальнейшей вложенной структуры создает вложенную структуру переменной:
all:
hosts:
db-master:
replication:
role: master
sync_mode: synchronous
То есть у хоста db-master переменные replication.role и replication.sync_mode. Можно также использовать списки:
vars:
allowed_ips:
- 192.168.1.0/24
- 10.0.0.0/8
Группы:
Внутри children указываются имена дочерних групп и их настройки. Структура дочерних групп аналогичная. Допускается множественное вложение.
all:
children:
webservers:
hosts:
web1:
ansible_host: 10.0.0.10
web2:
ansible_host: 10.0.0.11
dbservers:
hosts:
db1:
ansible_host: 10.0.0.20
Множественное вложение:
all:
children:
msk_web:
hosts:
msk-web-01:
msk-web-02:
msk_db:
hosts:
msk-db-01:
spb_web:
hosts:
spb-web-01:
spb_db:
hosts:
spb-db-01:
msk:
children:
msk_web
msk_db
vars:
datacenter: moscow
region: central
spb:
children:
spb_web
spb_db
vars:
datacenter: st_petersburg
region: north-west
russia:
children:
msk
spb
production:
children:
russia
vars:
env: production
monitoring_enabled: true
Однако при усложнении правил (субъективно) теряется сама идея простоты настройки. То есть приходится объяснять правила объединения, рисовать зависимости, ... А когда переменные еще и отличаются для разных групп, и этих групп много, то все становится совсем плохо.
Выполнение playbook только для группы:
ansible-playbook -i inventory.yml site.yml --limit webservers
Можно использовать якоря
defaults: &defaults
ansible_user: admin
ansible_port: 22
webservers:
hosts:
web1:
<<: *defaults
ansible_host: 192.168.1.10
web2:
<<: *defaults
ansible_host: 192.168.1.11
Разделение данных на файлы
Предыдущий текст относится к ситуации, когда хосты и переменные расположены в одном файле. Есть возможность разбить проект на специально названные файлы. Например:
inventory/
└── testing/
├── hosts.yml
├── group_vars/
│ ├── all.yml
└── production/
├── hosts.yml
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── dbservers.yml
└── host_vars/
├── web1.yml
└── db1.yml
Создана директория inventory, внутри окружения testing и production, в каждом окружении файл hosts.yml и директории group_vars и host_vars. Запуск playbook будет выглядеть так:
ansible-playbook -i inventory/production/hosts.yml site.yml
hosts.yml: только хосты/группы, стандартная структура без переменных.
all:
children:
webservers:
hosts:
web1:
web2:
dbservers:
hosts:
db1:
group_vars/all.yml: Переменные для всех групп, все пишется на одном уровне
ansible_user: ubuntu
timezone: Europe/Amsterdam
group_vars/<group_name>.yml: Переменные для группы group_name, все пишется на одном уровне
host_vars/<host_name>.yml: Переменные только для хоста <host_name>
Динамический файл
Это обычный исполняемый файл, который:
- Может быть написан на любом языке (Python, Bash, Go, Ruby и т.д.)
- При запуске возвращает JSON с описанием хостов и групп
- Ansible запускает его вместо чтения статического файла
ansible-playbook -i dynamic_inventory.py playbook.yml
Пример структуры JSON:
Например API отдает следующий JSON
{
"servers": [
{
"id": "i-12345abc",
"ip_address": "192.168.1.10",
"type": "web",
"environment": "production",
"name": "web-server-01",
"status": "running",
"region": "us-east-1",
"instance_type": "t2.medium"
},
{
"id": "i-12345def",
"ip_address": "192.168.1.11",
"type": "web",
"environment": "production",
"name": "web-server-02",
"status": "running",
"region": "us-east-1",
"instance_type": "t2.medium"
},
{
"id": "i-67890ghi",
"ip_address": "192.168.1.20",
"type": "db",
"environment": "production",
"name": "db-server-01",
"status": "running",
"region": "us-east-1",
"instance_type": "r5.large"
},
{
"id": "i-67890jkl",
"ip_address": "192.168.1.21",
"type": "db",
"environment": "staging",
"name": "db-server-02",
"status": "running",
"region": "us-west-2",
"instance_type": "r5.large"
},
{
"id": "i-99999mno",
"ip_address": "192.168.1.30",
"type": "web",
"environment": "staging",
"name": "web-server-03",
"status": "stopped",
"region": "us-west-2",
"instance_type": "t2.micro"
}
]
}
Тогда скрипт, разбирающий этот JSON в формат Ansible
#!/usr/bin/env python3
import json
import requests
def get_servers():
# Получаем список серверов из API
response = requests.get('https://api.mycloud.com/v1/servers',
headers={'Authorization': 'Bearer token123'})
servers = response.json()
inventory = {
'all': {
'hosts': [],
'vars': {}
},
'web': {
'hosts': [],
'vars': {'role': 'web'}
},
'db': {
'hosts': [],
'vars': {'role': 'database'}
},
'_meta': {
'hostvars': {}
}
}
for server in servers:
ip = server['ip_address']
inventory['all']['hosts'].append(ip)
if server['type'] == 'web':
inventory['web']['hosts'].append(ip)
elif server['type'] == 'db':
inventory['db']['hosts'].append(ip)
# Переменные для конкретного хоста
inventory['_meta']['hostvars'][ip] = {
'ansible_host': ip,
'environment': server['environment'],
'instance_id': server['id']
}
print(json.dumps(inventory, indent=2))
if __name__ == '__main__':
get_servers()
Использование скрипта
# Сделать скрипт исполняемым
chmod +x inventory.py
# Проверить, что выдает скрипт
./inventory.py
# Использовать с Ansible
ansible-inventory -i inventory.py --list
ansible-inventory -i inventory.py --graph
# Выполнить команду на всех хостах
ansible all -i inventory.py -m ping
# Выполнить команду только на web-серверах
ansible web -i inventory.py -m shell -a "nginx -v"
# Выполнить команду только на db-серверах
ansible db -i inventory.py -m shell -a "psql --version"
Важные правила
- Хост может быть в нескольких группах
- Переменные переопределяются. Приоритет (от низшего к высшему):
- all vars
- Групповые vars
- _meta hostvars (самый высокий приоритет)
- Имена хостов и групп
- Могут содержать буквы, цифры, _, -
- Чувствительны к регистру
- Не должны начинаться с _ (зарезервировано для _meta)
Элементы JSON
Группы ("group_name")
Каждая группа может содержать три ключа:
{
"group_name": {
"hosts": ["host1", "host2"], // список хостов в группе
"vars": { // переменные для всей группы
"var1": "value1",
"var2": "value2"
},
"children": ["child_group1", "child_group2"] // вложенные группы
}
}
Специальная группа "all"
Обязательная группа, которая содержит все хосты:
{
"all": {
"hosts": ["web1", "web2", "db1"],
"vars": {
"ansible_python_interpreter": "/usr/bin/python3",
"ntp_server": "pool.ntp.org"
}
}
}
Специальная группа "_meta"
Содержит переменные для конкретных хостов:
{
"_meta": {
"hostvars": {
"web1": {
"ansible_host": "10.0.0.1",
"ansible_user": "deploy"
},
"web2": {
"ansible_host": "10.0.0.2"
}
}
}
}
При использовании _meta, в группах можно просто перечислять имена хостов без переменных.
Последовательность выполнения
В playbook может присутствовать несколько play, внутри каждого может быть 1+ tasks. Play отличается наличием списка хостов. То есть задачи в пределах одного play может быть только для одного и того же списка хостов. Для name критично именование для отличия одного от другого.
Порядок выполнения внутри playbook
Есть несколько элементов