[Перевод] Как управлять многоступенчатым окружением с помощью Ansible

?v=1

Введение

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

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

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

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

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

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

Полагаемся исключительно на групповые переменные

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


  • окружения развертывания (локальные, dev, stage, prod и т. д.)
  • функциональность хоста (веб-серверы, серверы баз данных и т. д.)
  • регион центра обработки данных (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

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

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

Использование Ansible-конструкций, допускающих явный порядок загрузки

В Ansible есть несколько конструкций, которые позволяют явно упорядочивать загрузку переменных, а именно vars_files и include_vars. Их можно использовать в Ansible plays для явной загрузки дополнительных переменных в порядке, определенном в файле. Директива 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.

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

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

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

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

example_play.yml

---
- 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
Output. . .
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
}
. . .

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

example_play.yml

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

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

ansible-playbook -i inventory example_play.yml

Copy

Output. . .
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 требует, чтобы вы поместили переменные, которые жестко привязаны к группам, в другом месте. Местоположение group_vars становится заглушкой для фактических переменных, находящихся в каталоге vars. Это еще раз добавляет сложности и снижает ясность. Пользователь должен сопоставить правильные файлы переменных с хостом, что Ansible делает автоматически при использовании group_vars.

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

Рекомендуемая стратегия 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 отслеживают текущие значения по умолчанию для каждого окружения.

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

Установка переменных для разных окружений

Одна вещь, которая невозможна в рекомендуемой настройке, — это совместное использование переменных в разных окружениях. Есть несколько способов реализовать совместное использование переменных между окружениями. Один из самых простых — использовать способность 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, при установке значения инвентаря обязательно используйте абсолютный путь вместо относительного.

nano ansible.cfg

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

[defaults]
inventory = ./environments/dev

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

Заключение

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

© Habrahabr.ru