Перейти к основному содержимому

Как я использую Ansible для управления инфраструктурой?

·1151 слово·6 минут
Работа Ansible
Юрий Семеньков
Автор
Юрий Семеньков
DevOps, tech, geek, mentor
Оглавление

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

Здесь я хотел рассказать как именно я структурирую inventory-файлы, переменные, роли и как это все запускаю. Может это не супер-бест-практисы, но это работает довольно давно, неплохо поддерживается. У Ansible есть свои минусы, но тут не об этом.

Это НЕ гайд «как надо делать».
Здесь не будет пошаговых инструкций. Здесь — демонстрация того, как делаю я. Может быть это даст вам каких-нибудь идей, или вы захотите поделиться своим мнением в комментариях в Телеграм-чате.

В интернете over9000 гайдов типа создаете группу хостов web-servers, создаете плейбук, который запускаете на этой группе и устанавливаете nginx. Для базы данных тоже самое. Сколько таких плейбуков и групп получится, если у вас 100 серверов с разным наполнением? Нужно что-то другое.

Роли
#

Во-первых: роли. Роль — это набор заданий, параменных, шаблонов которые объединены в отдельный модуль. Например роль для установки и настройки Nginx, роль для управления пользователями, роль для установки и настройки PostgreSQL.

Роль должна выполнять одну задачу.

Роль должна быть универсальной: мы можем запустить ее на любом хосте и она сделает то, что должна.

С помощью переменных в роль можно передавать различные настройки.

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

Inventory
#

Во-вторых: inventory. Про него я уже писал. Вот тут. Инвентарь — это файлы, где мы описываем нашу инфраструктуру. Базово — это список групп и хостов, на которых будут запускаться наши плейбуки. Но так же в inventory мы можем указывать переменные для наших хостов. Тут и начинается iNfRaStRuCtUrE aS cOdE.

Напомню структуру инвентори здорового человека:

ansible/inventories
├── dev
│   ├── dev.yml
│   ├── group_vars
│   │   ├── all.yml
│   └── host_vars
│       ├── server00dev.et.yml
│       └── frontend01dev.et
│           ├── common.yml
│           ├── logrotate.yml
│           └── promtail.yml
├── dwh_cluster
│   ├── dwh.yml
│   ├── group_vars
│   │   ├── all.yml
│   │   ├── dwh_cluster.yml
│   │   └── dwh_cluster_wo_dwh00.yml
│   └── host_vars
│       ├── dwh00.et
│       │   ├── backups.yml
│       │   ├── blackbox-exporter.yml
│       │   └── common.yml
│       ├── dwh01.et.yml
│       └── dwh50.et.yml
...

Я держу поддиректории под окружения. В каждой поддиректории есть .yml файл, который содержит только список хостов. Максимум туда добавляем настройки подключения, например ansible_host, etc…:

all:
  children:
    server00:
      hosts:
        server00dev.et:

Есть директории host_vars и group_vars в которых хранятся файлы с названиями хостов из инвентори, в них находятся все переменные-настройки хостов. Так же если host_var-файл разрастается и становится сложно его поддерживать, его можно превратить в директорию с несколькими файлами (см. выше в примере).

Что в host_var-файле у меня есть интересного? Одна из самых важных, на мой взгляд, переменных:

host_roles:
  - openjdk
  - promtail
  - haproxy
  - postgresql

Она указывает КАКИЕ РОЛИ нужно запустить на ЭТОМ хосте.

Так же есть, например, переменная users, в которой я перечисляю дополнительных пользователей, которых нужно создать на сервере. В этом же файле я указываю “настройки” для моих ролей, например:

postgresql_version: 16
postgresql_global_config_options:
  - { option: listen_addresses, value: '0.0.0.0' }
  - { option: max_connections, value: '500' }

или

iptables_additional_rules:
  - -A INPUT -s 172.16.0.0/12 -m comment --comment "allow from docker containers" -j ACCEPT
  - -A INPUT -s 10.40.12.0/24 -m comment --comment "allow from vpn network" -j ACCEPT

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

promtail_config_scrape_configs:
  - job_name: backend_dev_rest
    static_configs:
      - targets:
          - localhost
        labels:
          job: backend_dev_rest
...

Это позволяет мне иметь при необходимости уникальный (кастомный) конфиг для какого-либо сервиса вместо оригинально (или в дополнение к нему). Например на всех хостах у меня есть дефолтный конфиг promtail - он указан в roles/promtail/defaults, а в host_vars я его дополняю.

Плейбук
#

В-третьих: как это запускать?

У меня есть “общий” плейбук common.yml, в котором по сути есть всего один таск include_role. В этот таск предается список ролей содержащий:

  • обязательные роли, которые должны быть прокатаны на всех хостах, например node_exporter, zabbix_agent, настройки ssh, установка правильного хостнейма, настройки DNS, NTP и так далее.
  • роли из переменной host_roles

Так как в host_roles у меня лежит список (тип данных) ролей, то этот список просто расширит уже имеющийся список дефолтных. Я добавляю jinja-фильтр default([]), чтобы смержить туда пустой список, если переменная host_roles вообще не задана, иначе будет ошибка. Итогово это выглядит так:

- name: All hosts playbook
  hosts: all
  become: true
  gather_subset: ['default_ipv4', 'distribution_release']
  ignore_unreachable: false

  tasks:
    - name: Include roles
      ansible.builtin.include_role:
        name: "{{ item }}"
      loop:
        - common_software
        - users
        - "{{ host_roles | default([]) }}"
        - "{{ group_roles | default([]) }}"
        - ntp
        - node-exporter
        - zabbix_agent
        - sshd

Теперь, если я хочу настроить сервер с нуля (ну, не совсем с нуля, конечно), то я просто запускаю:

ansible-playbook common.yml -i inventories/dev -l server00dev.et -D

И он прокатится только на одном сервере. Уберу -l server00dev.et, и плейбук запустится на всех дев-серверах.

Автоматизация
#

В-четвертых: как это автоматизировать? Это немного не укладывается в рамки стандартного CI/CD (где сборка/загрузка/деплой) - я скорее про автоматическую раскатку этого поделия на серверы.

Предположим, что мы описали и добавили все наши серверы в inventory, разделили его на окружения: inventories/dev/dev.yml, inventories/dev/prod.yml. В host_vars и group_vars добавили нужные нам роли для хостов, конфиги и параметры для сервисов. Как будем это запускать?

Весь инфраструтурный код я храню в отдельном репозитории, который гордо обозвал ansible. Я хочу, чтобы при изменении кода у меня автоматически запускался ansible-playbook common.yml.

Я себе выбрал такие условия:

  • если меняется что-то в inventories/dev, то запустить common.yml с параметром -i inventories.dev
  • аналогично для prod-окружения

Так как я использую Gitlab, то у меня есть .gitlab-ci.yml файл. В нем я описываю условия, которые стриггерят запуск плейбука:

deploy_dev:
  stage: deploy
  before_script:
    - mkdir -p ~/.ssh
    - echo "$ADMIN_SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
  script:
    - ansible-playbook -i inventories/dev common.yml --diff
  rules:
    - if: '$CI_PIPELINE_SOURCE == "web"'
      when: manual
    - if: '$CI_COMMIT_BRANCH == "master"'
      changes:
        - 'inventories/dev/**/*'
        - 'roles/node-exporter/**/*'
        - 'roles/users/**/*'
        ...

Таким образом этот пайплайн запустится автоматически в случае изменений в файлах под inventories/dev, а так же при изменениях в некоторых ролях — например я глобально обновил версию node-exporter в defaults.

Общий workflow (реальный и идеальный)
#

  • Инженер клонирует себе репозиторий
  • Создает отдельную ветку, вносит необходимые изменения.
  • Проверяет через check-mode со своего компьютера — вот этот пункт - плохой паттерн, так как он требует доступа на сервера, и может вызывать configuration drift. Проверку должен выполнять CI.
  • Пушит код в Gitlab, создает MR.
  • В Gitlab проходят тесты (идеальный вариант), другой инженер проводит код-ревью.
  • MR мержится в master-ветку
  • Gitlab запускает соответствующий пайплайн, чтобы привести инфрастркутуру к целевому состоянию

Что надо добавлять?
#

  • Тестирование. Как минимум — прогон через check-mode. Как идеал — тестирование ролей на временной инфрастркутуре.
  • Линтеры. Нужно прогонять код, чтобы он соответствовал правильному синтаксису.

Было полезно? Может появились какие-то идеи? Или вы делаете у себя в инфрастркутуре по-другому? Поделись этим в 👉 телеграм-чате! Мне очень интересно было бы почитать.

Related

Настройка ansible.cfg. Зачем и как?
·364 слов·2 минут
Работа
Аутентификация и авторизация. В чем разница?
·222 слов·2 минут
Работа
SLI, SLO и SLA. В чем разница?
·242 слов·2 минут
Работа