Введение
Что такое Ansible
Если супер коротко, то Ansible – это мощный инструмент автоматизации настройки и развёртывания. И я предполагаю, что если ты читаешь это, то скорее всего уже слышал о нём и представляешь, как применишь Ansible для своих целей.
Ansible – это не единственный инструмент в своём роде. Есть Fabric, pyinfra, Nix и, наверное, много других.
Зачем еще одно обучение
Чтобы начать использовать Ansible, достаточно одного файла. Тебе не нужно всё хитроумно раскладывать по папочкам и пытаться сделать красиво с первого раза. Позволь Ansible развиваться вместе с тобой. Если это звучит не убедительно, то просто взгляни на 22 уровня приоритетов переменных. Какой смысл запоминать и использовать их все сразу?
Когда я погружался в официальный User Guide на сайте документации Ansible, я был удивлён, как всё запутано и нагромождено.
Для меня классическое обучение – это когда:
- определена цель
- можно следовать каким-то шагам
- и затем получить какой-то результат
Ничего этого в официальной документации я не нашёл.
Раздел Getting Started подразумевает какую-то подготовку:
- откуда мне взять IP-адреса тачек?
- что такое инвентарь?
- причем тут python?
- из какого места мне нужно запускать эти команды?
Но даже если бы я всё это каким-то образом узнал, описание внезапно прерывается после нескольких определений.
Поэтому я решил задокументировать свой процесс погружения в Ansible и параллельно описать понятные последовательные этапы. Как обычно, хороший способ научиться чему-то это рассказать так, чтобы другие поняли.
Как пользоваться этим обучением
Цель этого обучения – познакомить тебя с основами Ansible, чтобы ты мог спокойно самостоятельно перемещаться по документации и эволюционировать структуру конфигов.
Первый, второй и третий разделы максимально последовательны, поэтому пропускать какой-либо из них я не советую.
В процессе мы поднимем несколько Docker-контейнеров и позапускаем на них команды.
А в результате у тебя будет справочная папка со скриптами, которые можно адаптировать под свои нужды.
Если застрял
Все файлы, упомянутые в обучении, лежат в репозитории этой книги.
В начале каждого раздела есть список материалов, использованых для его написания. К нему можно обращаться за подробностями по той или иной теме.
Подготовка
Для этого раздела были использованы следующие материалы:
- Ansible Basic Concepts
- Ansible Getting Started
- Ansible Installation Guide
- Alpine SSH Container by Praqma/alpine-sshd
- Ansible Inventory Basics
- Ansible Configuration Settings
Определения
Control node - любой компьютер, на котором установлен Ansible. С него можно запускать команды и плейбуки.
Managed nodes - устройства/серверы в сети, которыми можно управлять с помощью Ansible. Иногда называют "хост". На них Ansible не установлен.
Inventory - список, управляемых хостов/нод. Иногда инвентарь называют хостфайлом. Там указывают IP-адреса или hostname cерверов. В инвентаре можно объединять хосты в группы для удобства выкатывания. Инвентарь можно описывать в YAML или INI-формате. Здесь я буду использовать YAML-формат.
Modules - единица кода, которую выполняет Ansible. Может настраивать систему, можно объединять в задачи "tasks", можно выполнять в рамках плейбука.
Tasks - единица действия, которое может выполнить Ansible. Можно выполнять таски разово с помощью ad-hoc команд.
Playbooks - упорядоченный список Plays.
Plays – набор Tasks. Для старта рекомендую ограничиться одним Play и воспринимать Playbook как набор Tasks.
Tasks, Plays и Playbooks пишутся на YAML и могут включать в себя переменные.
Установка и папки
Необходимое
Нам потребуется несколько утилит, чтобы следовать описанию:
- Docker - автоматизировать мы будем контейнеры, так что нужен инструмент для контейниризации. Конкретно Docker не обязателен, можно использовать любой доступный инструмент, который понимает формат
Dockerfile
- Python - основной способ установки Ansible это pip-пакет
Папки
- создать папку
ansible
, в ней будут находиться все конфиги и отправная точка для команд - создать папку
containers
в папкеansible
Примечание: Название папки не принципиально, но нужно будет использовать выбранное на протяжении всего обучения
Ansible
Официальный гайд будто намекает, что можно использовать виртуальную среду. Давайте создадим её и активируем.
Use pip in your selected Python environment to install the Ansible package
python -m venv .venv
source .venv/bin/activate
А дальше сам ansible
:
pip install ansible
Подготовка хостов
У нас будет два Debian хоста в виде Docker-контейнеров. Так как Ansible будет ругаться на пароли при подключении по SSH, мы сразу настраиваем авторизацию по ключу. Файлы конфигурации контейнеров взяты практически один-в-один из репозитория Praqma/alpine-sshd
.
Создать Dockerfile
внутри containers
:
FROM debian:12
COPY entrypoint.sh /
RUN apt-get update \
&& apt-get install -y openssh-server python3.11 \
&& mkdir /var/run/sshd\
&& mkdir -p /root/.ssh \
&& chmod 0700 /root/.ssh \
&& ssh-keygen -A \
&& sed -i s/^#PasswordAuthentication\ yes/PasswordAuthentication\ no/ /etc/ssh/sshd_config \
&& chmod +x entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/usr/sbin/sshd", "-D"]
Создать entrypoint.sh
рядом с Dockerfile
:
#!/bin/sh
if [ -z "${AUTHORIZED_KEYS}" ]; then
echo "AUTHORIZED_KEYS env variable is not set. It is required to setup ssh access to the containers."
exit 1
fi
echo "Populating /root/.ssh/authorized_keys with the value from AUTHORIZED_KEYS"
echo "${AUTHORIZED_KEYS}" > /root/.ssh/authorized_keys
# Execute the CMD from the Dockerfile:
exec "$@"
Создать docker-compose.yml
внутри containers
:
version: "3"
services:
deb1:
build: .
environment:
AUTHORIZED_KEYS: ${AUTHORIZED_KEYS}
ports:
- "2222:22"
deb2:
build: .
environment:
AUTHORIZED_KEYS: ${AUTHORIZED_KEYS}
ports:
- "2223:22"
К этому моменту наша рабочая папка выглядит так:
├── .venv
│ └── ...
└── containers
├── Dockerfile
├── docker-compose.yml
└── entrypoint.sh
Установить переменную окружения для ssh-ключей, собрать образы и запустить:
export AUTHORIZED_KEYS=$(cat ~/.ssh/id_rsa.pub)
docker compose up --build
Если всё ок, то в конце вывода будет так:
[+] Running 3/1
⠿ Network containers_default Created
⠿ Container containers-deb2-1 Created
⠿ Container containers-deb1-1 Created
Attaching to containers-deb1-1, containers-deb2-1
containers-deb2-1 | Populating /root/.ssh/authorized_keys with the value from AUTHORIZED_KEYS env variable ...
containers-deb1-1 | Populating /root/.ssh/authorized_keys with the value from AUTHORIZED_KEYS env variable ...
Теперь можно перейти в сосдений терминал и проверить подключение по ssh:
> ssh root@localhost -p 2222
> ssh root@localhost -p 2223
На всякий случай на время обучения можно в ~/.ssh/config
добавить настройку для localhost
, чтобы не было проблем с добавлением ключей в known_hosts
:
Host localhost
StrictHostKeyChecking no
Конфиги и инвентарь
Теперь можно начать настраивать скрипты. Нужно перейти из containers/
на уровнеь выше и создать inventory.yml
внутри ansible/
:
---
debs:
hosts:
deb1:
ansible_port: 2222
ansible_host: localhost
ansible_user: root
ansible_python_interpreter: /usr/bin/python3.7
deb2:
ansible_port: 2223
ansible_host: localhost
ansible_user: root
ansible_python_interpreter: /usr/bin/python3.7
Конфигурация Ansible
Создать ansible.cfg
:
[defaults]
inventory = inventory.yml
Помимо ссылки на инвентарь я обычно устанавливаю эти переменные:
deprecation_warnings=False
nocows=True
vault_password_file=/path/to/vault/password/file
Подробно про каждую настройку и другие можно почитать здесь Ansible Configuration Settings.
Что должно получиться
├── .venv
│ └── ...
├── containers
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── entrypoint.sh
├── ansible.cfg
└── inventory.yml
Теперь мы готовы запускать первые команды. Можно переходить к следующей части.
Первые команды
Для этого раздела были использованы следующие материалы:
- Ansible Getting Started
- Ansible Intro to ad-hoc commands
- Ansible Module
apt
- Ansible Module
user
- How do I generate encrypted passwords for the user module?
Hello World по-ансибловски
Для проверки подключения к хостам выполнить:
ansible all -m ping
Если всё ок, то вывод будет примерно такой:
deb2 | SUCCESS => {
"changed": false,
"ping": "pong"
}
deb1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Возможно потребуется написать yes
для того чтобы добавить отпечатки подключений, если в прошлой части не добавляли StrictHostKeyChecking
для localhost
.
Пользователи
В нашей конфигурации мы используем root
, но это не всегда так, поэтому в доках описан такой сценарий:
# as bruce
> ansible all -m ping -u bruce
# as bruce, sudoing to root (sudo is default method)
> ansible all -m ping -u bruce --become
# as bruce, sudoing to batman
> ansible all -m ping -u bruce --become --become-user batman
Разовые команды - 1
Также известны как ad-hoc команды. Нужны для автоматизации одной задачи на одном или нескольких хостах. Разовые команды легко быстро запустить, но их нельзя переиспользовать. Ansible пишет, что разовые команды нужны скорее для того чтобы показать насколько сильным инструментом он может быть. Хотя концепции разовых команд хорошо транслируются в тему плейбуков.
Самые распространённые примеры использования разовых команд: перезагрузить сервер, скопировать файлы, устанавливать и удалять пакеты, управлять пользователями и т.п.
Разовые команды используют те же принципы, что и плейбуки в том смысле, что они оценивают состояние состояние хоста до выполнения и выполняют действия только если состояние отличается от желаемого.
Самая простая команда:
> ansible deb1 -a "echo hello"
deb1 | CHANGED | rc=0 >>
hello
Тоже самое можно выполнить для группы хостов:
> ansible debs -m shell -a 'echo $TERM'
deb2 | CHANGED | rc=0 >>
screen-256color
deb1 | CHANGED | rc=0 >>
screen-256color
Попробуем скопировать файл инвентаря с нашей машины на удалённые хосты:
> ansible debs -m copy -a "src=inventory.yml dest=/tmp/foo"
deb2 | CHANGED => {
"changed": true,
"checksum": "3b27d49ab53f170b384d5608fb9e3738f69d44fa",
"dest": "/tmp/foo",
"gid": 0,
"group": "root",
"md5sum": "349210dadef1c2366d62b0c4314c2c5b",
"mode": "0644",
"owner": "root",
"size": 305,
"src": "/root/.ansible/tmp/ansible-tmp-1590317158.994046-72047-272241656453308/source",
"state": "file",
"uid": 0
}
deb1 | CHANGED => {
"changed": true,
"checksum": "3b27d49ab53f170b384d5608fb9e3738f69d44fa",
"dest": "/tmp/foo",
"gid": 0,
"group": "root",
"md5sum": "349210dadef1c2366d62b0c4314c2c5b",
"mode": "0644",
"owner": "root",
"size": 305,
"src": "/root/.ansible/tmp/ansible-tmp-1590317159.002979-72045-90011462229862/source",
"state": "file",
"uid": 0
}
Можно убедиться, что файл скопировался:
> docker exec -it containers-deb1-1 cat /tmp/foo
---
debs:
hosts:
deb1:
ansible_port: 2222
ansible_host: localhost
ansible_user: root
ansible_python_interpreter: /usr/bin/python3.7
deb2:
ansible_port: 2223
ansible_host: localhost
ansible_user: root
ansible_python_interpreter: /usr/bin/python3.7
Попробуем установить пакет:
> ansible deb1 -m apt -a "name=acme state=present"
deb1 | CHANGED => {
"cache_update_time": 1590317476,
"cache_updated": false,
"changed": true,
"stderr": "debconf: delaying package configuration, since apt-utils is not installed\n",
"stderr_lines": [
"debconf: delaying package configuration, since apt-utils is not installed"
],
"stdout": "...",
"stdout_lines": [
"..."
]
}
В документации модуля apt есть описание параметра state. Из описания понятно, что для удаления нужно изменить его на absent. Попробуем "удалить" пакет с хоста, на который мы его не устанавливали:
> ansible deb2 -m apt -a "name=acme state=absent"
deb2 | SUCCESS => {
"changed": false
}
А теперь тоже самое для всей группы:
> ansible debs -m apt -a "name=acme state=absent"
deb2 | SUCCESS => {
"changed": false
}
deb1 | CHANGED => {
"changed": true,
"stderr": "",
"stderr_lines": [],
"stdout": "...",
"stdout_lines": [
"..."
]
}
Мои первые команды на любом новом сервере обычно такие (ssh под root
-ом в разных вариациях):
> apt-get update
> apt-get upgrade
Так это будет выглядеть в виде ad-hoc команды:
> ansible debs -m apt -a "update_cache=yes upgrade=yes"
deb1 | SUCCESS => {
"changed": false,
"msg": "...",
"stderr": "",
"stderr_lines": [],
"stdout": "...",
"stdout_lines": [
"..."
]
}
deb2 | SUCCESS => {
"changed": false,
"msg": "...",
"stderr": "",
"stderr_lines": [],
"stdout": "...",
"stdout_lines": [
"..."
]
}
Разовые команды - 2
Следующие часто выполняемые команды обычно про настройку пользователя и SSH доступа. Я эти команды не могу запомнить, поэтому обычно нахожу статью вроде Digital Ocean "Initial Server Setup with Debian 10" и следую шагам/командам, не читая саму статью. Получается так:
> adduser biozz
> usermod -aG sudo biozz
В наших контейнерах сейчас нет утилиты sudo
, можно её установить:
> ansible debs -m apt -a "name=sudo state=present"
Когда создаёшь пользователя есть возможность интерактивно создать пароль. Модуль user
в Ansible предлагает сделать это с помощью зашифрованной строки:
ansible debs -m user -a "name=biozz groups=sudo password={{ 'test' | password_hash('sha512') }}"
Остался ещё один пример - управление сервисами. Будем испытвать на nginx
.
> ansible debs -m apt -a "name=nginx state=present"
Запустим сервис
> ansible debs -m service -a "name=nginx state=started"
deb1 | CHANGED => {
"changed": true,
"name": "nginx",
"state": "started"
}
deb2 | CHANGED => {
"changed": true,
"name": "nginx",
"state": "started"
}
Повторное выполнение той же команды, просто чтобы посмотреть на вывод:
> ansible ansible debs -m service -a "name=nginx state=started"
deb2 | SUCCESS => {
"changed": false,
"name": "nginx",
"state": "started"
}
deb1 | SUCCESS => {
"changed": false,
"name": "nginx",
"state": "started"
}
В статье ещё предлагает посмотреть на вывод модуля setup
, но я считаю, что на этом этапе это не нужно. Там слишком много информации. Чисто ради интереса можно сделать:
> ansible all -m setup
Плейбуки
Плейбуки совсем не похожи на ad-hoc команды из прошлой части. Если модули Ansible это инструменты, то плейбуки - это инструкции по их использованию. Они подходят для выкатывания сложных конфигураций и оркестрации хостов. В отличие от разовых команд, плейбуки принято хранить в системе контроля версий.
Синтаксис
Плейбуки пишут на YAML. В мануале предлагается установить и использовать утилиты для валидации, типа ansible-lint, но для начала хватит простой подстветки. Отличный источник для вдохновения и примеров - ansible-examples.
Плейбук состоит из набора шагов, которые должны привести хост к состоянию его конечному состоянию, то есть к его роли. В мануале пишут про отсылки к спортивным играм, отсюда и термины. В каждом шаге может быть несколько задач.
Если взять часть команд из 2-ой статьи и собрать их, то получится:
---
- hosts: debs
tasks:
- name: check terminal setup
shell:
cmd: echo $TERM
- name: install acme package
apt:
name: acme
state: present
update_cache: true
- name: install sudo package
apt:
name: sudo
state: present
- name: setup biozz user
user:
name: biozz
groups: sudo
password: "{{ 'test' | password_hash('sha512') }}"
Сохраним это в playbook.yml
и запустим (убрал повторяющийся вывод):
ansible-playbook playbook.yml
PLAY [debs] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [deb1]
ok: [deb2]
TASK [xxx] ****************************************************
changed: [deb1]
changed: [deb2]
PLAY RECAP *********************************************************************
deb1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
deb2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Если в последней команде оставить без двойных кавычек, то получим ошибку. Что сразу же говорит о том, что есть шаблонные выражения и надо не забывать их экранировать кавычками.
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)
Syntax Error while loading YAML.
found character that cannot start any token
The error appears to be in '/Users/biozz/other/ansible/playbook.yml': line 20, column 29, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
groups: sudo
password: {{ 'test' | password_hash('sha512') }}
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
Мы уже довольно много команд выполнили под root-ом, что считается небезопасным подходом. А у нас уже есть пользователь, поэтому можно переключиться на него для выполнения команд:
---
- hosts: debs
remote_user: biozz
become: yes
become_method: sudo
https://docs.ansible.com/ansible/latest/user_guide/playbooks.html#working-with-playbooks
https://github.com/ansible/ansible-examples/tree/master/
Материалы из этой части
Разные конфиги для разных хостов
TL;DR: в названии файлов конфигов можно указывать
inventory_hostname_short
, напримерtelegraf.myhost.conf
,_short
чтобы можно было в инвентаре делатьmyhost.this
иmyhost.that
, так как это удобно для разделения ssh-конфигов
Иногда бывает так, что нужно запускать один и тот же плейбук или одну и ту же роль для разных хостов, но чтобы у них были разные конфиги. Кастомизация содержимого конфига будет сильно зависеть от целей. Иногда достаточно прокинуть переменную из инвентаря или указать какой-нибудь флаг в defaults
и вручную менять его перед запуском.
Нас интересует полностью автоматический метод, поэтому остановимся на переменных инвентаря. Примеры написаны для Telegraf, этакий комбайн для сбора, обработки и отправки метрик.
Полностью рабочие примеры проектов можно найти тут: src/00_sources/06_tricks/01_host_specific_configs
. Запускается из папки с инвентарём с помощью ansible-playbook main.yml
.
Переменные инвентаря
Вот список файлов для этого подхода:
├── inventory
├── main.yml
└── roles
└── telegraf
├── defaults
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
└── telegraf.conf.j2
Сначала рассмотрим инвентарь:
deb1 is_net_enabled=true
deb2
is_net_enabled=true
- это тот самый флаг, который поможет внутри шаблона конфига. Условно, будем считать что с одного хоста мы хотим собирать метрики сети (net
), а с другого нет.
Примечание: переменные инвентаря в таком виде будут в первой половине иерархии переменных и велика вероятность, что они перезапишутся из других мест, если указать где-то еще. Будьте внимательны.
И вот он в telegraf.conf.j2
, шаблоне, который рендерится в tasks/main.yml
:
{{ if is_net_enabled }}
# Read metrics about network interface usage
[[inputs.net]]
# no configuration
{{ endif }}
Проблема в том, что со временем конфиги могут сильно отличаться и if-чики в коде начнут сбивать с толку.
inventory_hostname_short
То есть нам по сути нужно как-то завязаться на переменную названия хоста.
Для этого сценария дерево файлов изменится лишь в templates/
:
└── templates
├── telegraf.deb1.conf.j2
└── telegraf.deb2.conf.j2
Возможно появился вопрос, почему именно _short
, а не обычный inventory_hostname
? Это небольшой хак, который позволяет указывать один и тот же хост, только с разными суффиксами. Например, это полезно, когда подключаешься к домашнему серваку дома через локальный IP, а когда на выезде - через какой-нибудь VPN. Так вот, _short
, как уже догадались, возьмёт часть до точки.
deb1.local ; -> deb1
deb1.vpn ; -> deb1
deb2 ; -> deb2
Интересный факт, в разделе Special variables в официальной доке Ansible про это не написано. Зато наглядно показано в этой статье.
И тогда в tasks/main.yml
можно написать так:
- name: Create telegraf configuration from template
template:
src: "templates/telegraf.{{ inventory_hostname_short }}.conf.j2"
dest: "{{ telegraf_directory }}/telegraf.conf"
На выходе получилось два независимых конфига на двух разных хостах.
Это не супер-масштабируемо, так как при добавлении нового хоста для него нужно будет добавлять ещё один конфиг. Зато явно и наглядно.
Переиспользование плейбука
Иногда приходишь к тому, что плейбук становится универсальным или однотипным для каких-то нужд и при этом ты постоянно его копируешь. Копировать в целом неплохо, но хочется сократить количество открытых файлов и вносимых изменений.
Суть этого трюка в двух моментах:
- вынести общие таски деплоя в отдельный файл, чтобы импортировать
- кастомизировать отдельные таски деплоя в переменных роли
Вот древо файлов для этого трюка:
├── app
│ ├── defaults
│ │ └── main.yml
│ └── tasks
│ └── main.yml
└── common
└── app.yml
Как обычно, исходники можно найти тут - src/00_sources/06_tricks/02_playbook_reuse
.
Предположим есть такой плейбук:
# 01_example/common/app.yml
---
- name: Create the domain in Cloudflare
community.general.cloudflare_dns:
zone: "{{ base_domain }}"
record: "{{ app_name }}"
type: A
value: "{{ server_ipv4 }}"
account_email: "{{ cloudflare_email }}"
account_api_key: "{{ cloudflare_api_key }}"
when: app_init
- name: Create directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
with_items:
- "{{ app_directory }}"
- "{{ app_source_directory }}"
when: app_init
- name: Download files via git
ansible.builtin.git:
repo: "{{ app_repository }}"
dest: "{{ app_source_directory }}"
force: true
accept_hostkey: true
key_file: "{{ git_ssh_key_path }}"
- name: Build docker image
community.general.docker_image:
build:
path: "{{ app_source_directory }}"
name: "{{ app_name }}"
tag: latest
source: build
state: present
- name: Start docker container
community.docker.docker_container:
name: "{{ app_name }}"
image: "{{ app_name }}:latest"
state: started
pull: false
recreate: true
restart_policy: "unless-stopped"
command: "{{ app_command }}"
labels: "{{ app_labels | items2dict }}"
- name: Wait for the app to be online
uri:
url: "https://{{ app_host }}"
status_code: 200
register: result
until: result.status == 200
retries: 60
delay: 1
- name: "Done"
debug:
msg: "https://{{ app_host }}"
Который можно кастомизировать с помощью переменных вот так:
---
app_init: no
app_directory: "/path/to/myapp"
app_source_directory: "{{ app_directory }}/src"
app_repository: "ssh://[email protected]/myapp.git"
app_name: "myapp"
app_domain: "{{ app_name }}"
app_host: "{{ app_domain }}.{{ base_domain }}"
app_internal_port: "8080"
app_command:
- "--db-uri=mongodb://mongodb:27017"
- "server"
- "--bind=:{{ app_internal_port }}"
app_labels:
- key: "traefik.enable"
value: "true"
- key: "traefik.http.routers.{{ app_name }}.entrypoints"
value: "https"
- key: "traefik.http.routers.{{ app_name }}.rule"
value: "Host(`{{ app_host }}`)"
- key: "traefik.http.routers.{{ app_name }}.tls"
value: "true"
- key: "traefik.http.routers.{{ app_name }}.tls.certresolver"
value: "letsencrypt"
- key: "traefik.http.services.{{ app_name }}.loadbalancer.server.port"
value: "{{ app_internal_port }}"
Из интересных вещей тут следующие:
- команды для запуска внутри контейнера в одной переменной в виде списка
app_command:
- "--db-uri=mongodb://mongodb:27017"
- "server"
- "--bind=:{{ app_internal_port }}"
- лейблы для контейнера разложены на
key
/value
, которые дальше в плейбуке раскладываются с помощьюitems2dict
# 01_example/app/defaults/main.yml
app_labels:
- key: "traefik.enable"
value: "true"
- key: "traefik.http.routers.{{ app_name }}.entrypoints"
value: "https"
- key: "traefik.http.routers.{{ app_name }}.rule"
value: "Host(`{{ app_host }}`)"
- key: "traefik.http.routers.{{ app_name }}.tls"
value: "true"
- key: "traefik.http.routers.{{ app_name }}.tls.certresolver"
value: "letsencrypt"
- key: "traefik.http.services.{{ app_name }}.loadbalancer.server.port"
value: "{{ app_internal_port }}"
# 01_example/common/app.yml
labels: "{{ app_labels | items2dict }}"
И теперь всё, что остаётся для каждого нового приложения - это заимпортировать из common/app.yml
и изменить переменные по вкусу:
# 01_example/app/tasks/main.yml
---
- name: Generic app tasks
ansible.builtin.include_tasks: "../../common/tasks/app.yml"
Использование Taskfile
Примечание: для всех, кто тригернулся на Taskfile, представьте вместо него любой запускатор скриптов или сборщик на ваш вкус
Я фанат Taskfile. Я даже написал плагин для Sublime Text. Я использую Taskfile практическо во всех своих проектах. И Ansible-скрипты не исключение.
Вот небольшой кусочек Taskfile в моем проекте с Ansible-скриптами:
version: '3'
tasks:
requirements:
cmds:
- ansible-galaxy install -r requirements.yml --force
silent: true
run:
cmds:
- ansible-playbook main.yml -i deb1, --tags {{ .CLI_ARGS }}
run-deb2:
cmds:
- ansible-playbook main.yml -i deb2, --tags {{ .CLI_ARGS }}
Хочется остановиться на run
и run-deb2
.
Чаще всего мы хотим выкатывать какие-то вещи на определенный хост, а запоминать и вводить эту длинную команду ансибла лениво. Поэтому можно написать её один раз и оставить только то, что нужно для кастомизации.
Используются эти команды вот так: task run -- tag1
, где tag1
- это тег роли или нескольких ролей. Подробнее про теги можно почитать в следующей галаве.
Теги
В трюке про Taskfile была упомянута команда task run -- tag1
. В этой главе я покажу, что нужно, чтобы эта команда заработала.
На самом деле всё довольно просто. Рассмотрим пример проекта:
.
├── inventory
├── main.yml
└── roles
├── myrole1
│ └── tasks
│ └── main.yml
├── myrole2
│ └── tasks
│ └── main.yml
└── myrole3
└── tasks
└── main.yml
В главном плейбуке main.yml
перечисляются роли и к каждой роли подписывается набор тегов:
---
- name: Deploy
hosts: all
become: true
roles:
- { role: myrole1, tags: [foo, init] }
- { role: myrole2, tags: [bar, init] }
- { role: myrole3, tags: [buz] }
Я использую теги двумя способами и в этом суть этого трюка:
- когда нужно запустить конкретную роль
task run -- foo
илиtask run -- bar
- когда нужно запустить серию плейбуков на какой-то группе хостов
tas run -- init