Управление многосупенчатой средой с помощью Ansible

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

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

Неполноценные стратегии управления многоступенчатой средой с помощью Ansible

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

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

Если вы хотите перейти к рекомендуемой стратегии Ansible, пропустите этот раздел.

Стратегии на основе групповых переменных

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

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

  1. среды развертывания (локальная и промежуточная, среда разработки, производства и т. д.)
  2. функциональность хоста (веб-серверы, серверы баз данных и т. д.)
  3. регион центра обработки данных (NYC, SFO и т. д.)

В этих случаях хосты обычно находятся в одной группе на категорию. То есть хост может быть веб-сервером (это функциональность) в промежуточной среде (это среда развертывания) в Нью-Йорке (регион центра обработки данных).

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

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

Использование дочерних групп для установления иерархии

Ansible позволяет назначать группы другим группам, используя синтаксис [groupname:children] в инвентаре. Это дает вам возможность назначить определенные группы членами других групп. Дочерние группы имеют возможность переопределять переменные, установленные родительскими группами.

Как правило, это используется при естественной классификации. Например, у вас может быть группа под названием environment, которая включает группы dev, stage, prod. Это означает, что вы можете установить переменные в группе environment и переопределить их в группе dev . Вы также можете создать родительскую группу под названием functions, которая содержит группы web, database и loadbalancer.

Это не решит проблему пересечения групп, так как дочерние группы переопределяют только параметры своих родителей. Дочерние группы могут переопределять переменные внутри родительского элемента, но организация выше не установила каких-либо отношений между категориями групп, такими как environments и functions. Приоритет переменной между двумя категориями все еще не определен.

Эту систему можно использовать, устанавливая неестественное членство в группе. Например, если вы хотите установить следующий приоритет (от наивысшего приоритета к низшему):

  • среда разработки
  • регион
  • функционал

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

. . .
[function:children]
web
database
loadbalancer
region
[region:children]
nyc
sfo
environments
[environments:children]
dev
stage
prod

Здесь мы установили иерархию, которая позволяет переменным региона переопределять переменные функционала, поскольку группа региона является дочерней по отношению к группе функции. Аналогично, переменные, установленные в группах среды, могут переопределять любые другие переменные. Это означает, что если мы установим одну и ту же переменную с разными значениями в группах dev, nyc и web, хост, принадлежащий каждой из них, будет использовать переменную из dev.

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

Конструкты Ansible

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

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

Например, несколько файлов group_vars могут выглядеть так:

#group_vars/dev
---
env: dev
#group_vars/stage
---
env: stage
#group_vars/web
---
function: web
#group_vars/database
---
function: database

Тогда у нас будет отдельный файл vars, который определяет важные переменные для каждой группы. Для ясности они обычно хранятся в отдельном каталоге vars. В отличие от файлов group_vars, при работе с include_vars файлы должны иметь расширение .yml.

Давайте представим, что в каждом файле vars нам нужно установить в переменной server_memory_size разные значения. Ваши серверы разработки, вероятно, будут меньше производственных серверов. Кроме того, веб-серверы и серверы баз данных могут иметь разные требования к памяти:

#vars/dev.yml
---
server_memory_size: 512mb
#vars/prod.yml
---
server_memory_size: 4gb
#vars/web.yml
---
server_memory_size: 1gb
#vars/database.yml
---
server_memory_size: 2gb

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

С vars_files пример плейбука будет выглядеть так:

---
- name: variable precedence test
hosts: all
vars_files:
- "vars/{{ env }}.yml"
- "vars/{{ function }}.yml"
tasks:
- debug: var=server_memory_size

Поскольку группы функционала загружаются последними, значение server_memory_size будет взято из файлов var/web.yml и var/database.yml.

ansible-playbook -i inventory example_play.yml
. . .
TASK [debug] *******************************************************************
ok: [host1] => {
"server_memory_size": "1gb"      # value from vars/web.yml
}
ok: [host2] => {
"server_memory_size": "1gb"      # value from vars/web.yml
}
ok: [host3] => {
"server_memory_size": "2gb"      # value from vars/database.yml
}
ok: [host4] => {
"server_memory_size": "2gb"      # value from vars/database.yml
}
. . .

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

---
- name: variable precedence test
hosts: all
vars_files:
- "vars/{{ function }}.yml"
- "vars/{{ env }}.yml"
tasks:
- debug: var=server_memory_size

При повторном запуске плейбука отображаются значения из файлов среды развертывания:

ansible-playbook -i inventory example_play.yml
. . .
TASK [debug] *******************************************************************
ok: [host1] => {
"server_memory_size": "512mb"      # value from vars/dev.yml
}
ok: [host2] => {
"server_memory_size": "4gb"        # value from vars/prod.yml
}
ok: [host3] => {
"server_memory_size": "512mb"      # value from vars/dev.yml
}
ok: [host4] => {
"server_memory_size": "4gb"        # value from vars/prod.yml
}
. . .

Аналогичный плейбук, который использует include_vars в качестве задачи, выглядит так:

---
- name: variable precedence test
hosts: localhost
tasks:
- include_vars:
file: "{{ item }}"
with_items:
- "vars/{{ function }}.yml"
- "vars/{{ env }}.yml"
- debug: var=server_memory_size

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

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

Что еще более важно, один раз использовав эту технику, вы обязательными должны использовать ее и дальше. Каждый плейбук нуждается в разделе, который явно загружает правильные файлы переменных в правильном порядке. Плейбуки без этого не смогут использовать связанные переменные. Более того, запуск команды ansible для специальных задач будет практически невозможен, если это хоть как-то зависит от переменных.

Рекомендуемая стратегия: использование групп и нескольких инвентаризаций

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

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

Базовая структура каталогов будет выглядеть примерно так:

.
├── ansible.cfg
├── environments/         # Parent directory for our environment-specific directories
│   │
│   ├── dev/              # Contains all files specific to the dev environment
│   │   ├── group_vars/   # dev specific group_vars files
│   │   │   ├── all
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts         # Contains only the hosts in the dev environment
│   │
│   ├── prod/             # Contains all files specific to the prod environment
│   │   ├── group_vars/   # prod specific group_vars files
│   │   │   ├── all
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts         # Contains only the hosts in the prod environment
│   │
│   └── stage/            # Contains all files specific to the stage environment
│       ├── group_vars/   # stage specific group_vars files
│       │   ├── all
│       │   ├── db
│       │   └── web
│       └── hosts         # Contains only the hosts in the stage environment

├── playbook.yml

└── . . .

Как видите, каждая среда отличается от других. Каталоги среды содержат файл инвентаризации (он условно назван hosts) и отдельный каталог group_vars.

Конечно, в дереве каталогов появляется очевидное дублирование: для каждой отдельной среды есть свои файлы web и db. Но в этом случае дублирование желательно. Изменения переменных можно развернуть в разных средах. Сначала переменные нужно изменить в одной среде, а потом переместить их в следующую после тестирования (так же, как если бы вы изменили код или конфигурацию). Переменные group_vars отслеживают текущие значения по умолчанию для каждой среды.

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

Настройка общих переменных для разных сред

Одна вещь, которую нельзя реализовать в рекомендуемой установке, — это совместное использование переменных в разных средах (cross-environment variable sharing). Существует несколько способов реализовать совместное использование переменных между средами. Одним из самых простых методов является способность Ansible использовать каталоги вместо файлов. Мы можем заменить файл all в каждом каталоге group_vars на каталог all.

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

Начните с создания файла общих переменных для нескольких сред где-нибудь в иерархии. В этом примере мы разместим его в каталоге environments . Поместите все переменные среды в этот файл:

cd environments
touch 000_cross_env_vars

Затем перейдите в один из каталогов group_vars, переименуйте файл all и создайте каталог all. Переместите переименованный файл в новый каталог:

cd dev/group_vars
mv all env_specific
mkdir all
mv env_specific all/

Далее можно создать символическую ссылку на файл переменной среды:

cd all/
ln -s ../../../000_cross_env_vars .

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

.
├── ansible.cfg
├── environments/
│   │
│   ├── 000_cross_env_vars
│   │
│   ├── dev/
│   │   ├── group_vars/
│   │   │   ├── all/
│       │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│   │   │   │   └── env_specific
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts
│   │
│   ├── prod/
│   │   ├── group_vars/
│   │   │   ├── all/
│   │   │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│   │   │   │   └── env_specific
│   │   │   ├── db
│   │   │   └── web
│   │   └── hosts
│   │
│   └── stage/
│       ├── group_vars/
│       │   ├── all/
│       │   │   ├── 000_cross_env_vars -> ../../../000_cross_env_vars
│       │   │   └── env_specific
│       │   ├── db
│       │   └── web
│       └── hosts

├── playbook.yml

└── . . .

Переменные, установленные в файле 000_cross_env_vars, будут доступны для каждой из сред с низким приоритетом.

Настройка стандартной инвентаризации среды

Можно определить файл инвентаризации по умолчанию в файле ansible.cfg. Это хорошо идея по нескольким причинам.

Во-первых, это позволяет вам отключить явные флаги инвентаризации для ansible и ansible-playbook. Так что вместо того, чтобы вводить:

ansible -i environments/dev -m ping

Вы можете вызвать стандартную инвентаризацию:

ansible -m ping

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

Чтобы установить инвентаризацию по умолчанию, откройте файл ansible.cfg. Он может быть в корневом каталоге проекта или в /etc/ansible/ansible.cfg в зависимости от вашей конфигурации.

Примечание: В приведенном ниже примере показано редактирование файла ansible.cfg в каталоге проекта. Если вы используете файл /etc/ansibile/ansible.cfg для своих изменений, измените путь в команде ниже. Используя /etc/ansible/ansible.cfg, если ваши файлы инвентаризации хранятся вне каталога /etc/ansible, при настройке значения inventory обязательно используйте абсолютный путь вместо относительного.

nano ansible.cfg

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

[defaults]
inventory = ./environments/dev

Теперь вы сможете использовать инвентаризацию по умолчанию без опции -i. Другие среды по-прежнему требуют флаг -i, что помогает защитить их от случайных изменений.

Заключение

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

Tags: