Ansible

Установка и настройка

Установка и настройка

Установка

Было написано, что необходим только python. Где-то предлагают устанавливать через pip, brew, 

На Debian-подобных системах:

apt-get install ansible

Настройка управляемых хостов

На каждом хосте:

  1. Если sudo нет, то 
    apt install sudo

  2. Если создается новый пользователь, то 
    sudo adduser ansibleuser
    sudo usermod -aG sudo ansibleuser
  3. Если существующий пользователь, то добавить в группу sudo: 
    usermod -aG sudo sergey
  4. Разрешить пользователю повышать привилегии без ввода пароля.  Создать файл
    sudo nano /etc/sudoers.d/ansibleuser
  5. И добавить текст 
    ansibleuser ALL=(ALL) NOPASSWD:ALL
  6. Установить пароль на пользователя 
    passwd ansibleuser
  7. Еще создать директорию /home/ansibleuser

Теперь можно использовать в скриптах become: true

Настройка сервера

ssh-keygen
ssh-copy-id username@remote_host
  1. Создать ключ доступа
  2. Скопировать на каждый управляемый хост, используя логин на хост. Удобнее, если для задач управления на всех хостах сделать одинаковый логин.

В настройке каждого хоста нужно использовать соответствующий логин, для которого настроен ключ доступа.

Установка и настройка

Настройка ansible

Приоритет поиска файла настроек
  1. ANSIBLE_CONFIG (environment variable if set)
  2. ansible.cfg (in the current directory)
  3. ~/.ansible.cfg (in the home directory)
  4. /etc/ansible/ansible.cfg (default)
Генерация настроек
 
ansible-config init --disabled > ansible.cfg
С плагинами
ansible-config init --disabled -t all > ansible.cfg
# и ; комментарии. Но ; обычно используется для комментария значения по умолчанию.
Файл инвентаризации
hosts 
Размещение файла может задаваться переменной окружения $ANSIBLE_HOSTS, либо ключ -i при запуске 
После : пишется тип данных для группы. Например группа [example], [example:vars] это переменные
Для обычной группы должны быть указаны доменные имена с поднятым ssh сервером. Если порт отличается от 22, то должен быть указан 
:vars переменные для группы
:children группы-потомки

Пример
# Обычная группа
[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 дополнительно выведет переменные

Файлы размещения переменных:
Файлы с переменными групп хранятся в директории “group_vars/имя_группы”;
Файлы с переменными хостов в директории “hosts_vars/имя_хоста”;
Формат файла одинаковый, дочерние переменные заменяются на родительские.
Директории на уровне playbook
---
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 адрес ключа
Ссылки:
Описание основного config файла
Динамический реестр

Запуск и элементы управления ansible скриптами

Ansible скрипт не описывает что нужно сделать, а описывает как должно быть.
Запуск одного действия
ansible [host/group/all] [action parameters]
Действия 
-a "shell command" shell command
-m exec_name installed module
-i <filename> inventory file
--become все команды + sudo
--become-user user1 обязательно с become, переключается на выбранного пользователя
-f 1 использование одного потока. По умолчанию процессы параллельно для всех хостов. 
Пример:
ansible multi -a "hostname"

Запуск через ansible playbook

Список действий сохраняется в файле *.yml (playbook)
ansible-playbook filename.yml
Структура playbook файла
---
- 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: 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

  1. Создается папка roles, внутри папки с названиями ролей, внутри каждой папки - defaults files handlers meta templates tasks vars
  2. в playbook добавляется 
       roles:
        - имя

Назначения папок

Папка Назначение
defaults Устанавливает переменные по умолчанию для включенных ролей. Обычно defaults/main.yml 
---
# Used only for Debian/Ubuntu installation, as the -t option for apt.
nginx_default_release: ""

# Used only for Redhat installation, enables source Nginx repo.
nginx_yum_repo_enabled: true

# Use the official Nginx PPA for Ubuntu, and the version to use if so.
nginx_ppa_use: false
nginx_ppa_version: stable
files

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

roles/myrole/
├── files/
│   └── install.sh
└── tasks/
    └── main.yml

- name: Copy script
  ansible.builtin.copy:
    src: install.sh
    dest: /tmp/install.sh
    mode: "0755"

- name: Execute script
  ansible.builtin.command:
    cmd: /tmp/install.sh

 

handlers Обработчики, выполняемые  в самом конце
meta для метаданных роли, которые используются для управления зависимостями. Например, вы можете определить список ролей, которые должны быть применены до вызова текущей роли.
templates

Шаблоны генерации файлов на удалённых хостах. Шаблоны jinja2. 

Например нужно создать файл /etc/docker/daemon.json

Создаем шаблон в roles/docker/templates/daemon.json.j2

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "{{ docker_log_size }}",
    "max-file": "{{ docker_log_files }}"
  }
}

Добавляем переменные в roles/docker/defaults/main.yml 

docker_log_size: "10m"
docker_log_files: "3"

Создаем задачу 

- name: Deploy daemon.json
  ansible.builtin.template:
    src: daemon.json.j2
    dest: /etc/docker/daemon.json
    owner: root
    group: root
    mode: "0644"
  notify: Restart Docker

Создаем handlers roles/docker/handlers/main.yml 

- name: Restart Docker
  ansible.builtin.systemd:
    name: docker
    state: restarted

 В результате на сервере появится json файл.

Описание Jinja2

tasks

Содержит файлы с задачами, которые определяются в разделе tasks обычного плейбука Ansible. Сначала просматривается файл tasks/main.yml В нем указывается условия импорта других файлов

---
# Variable setup.
- name: Include OS-specific variables.
  include_vars: "{{ ansible_os_family }}.yml"

- name: Define nginx_user.
  set_fact:
    nginx_user: "{{ __nginx_user }}"
  when: nginx_user is not defined

# Setup/install tasks.
- include_tasks: setup-RedHat.yml
  when: ansible_os_family == 'RedHat'

- include_tasks: setup-Ubuntu.yml
  when: ansible_distribution == 'Ubuntu'

- include_tasks: setup-Debian.yml
  when: ansible_os_family == 'Debian'

# Vhost configuration.
- import_tasks: vhosts.yml

# Nginx setup.
- name: Copy nginx configuration in place.
  template:
    src: "{{ nginx_conf_template }}"
    dest: "{{ nginx_conf_file_path }}"
    owner: root
    group: "{{ root_group }}"
    mode: 0644
  notify:
    - reload nginx

- name: Ensure nginx service is running as configured.
  service:
    name: nginx
    state: "{{ nginx_service_state }}"
    enabled: "{{ nginx_service_enabled }}"

Пути указываются относительно директории tasks

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

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

Ссылки:

Пример преобразования одного файла в роль

Хранилище ролей

Есть сайт с ролями почти для всего. Сайт с ролями ansible Можно найти нужную роль на сайте, затем установить при помощи 

ansible-galaxy install current_role_name -p .

Создание роли:

ansible-galaxy init new_role_name

Пример создания новой роли

Пусть будет задача в установке docker. Реализуем из инструкции по Docker: общая информация и установка

Инициализируем новую роль 

ansible-galaxy init just_new_role

Структура директорий создана. Проанализируем инструкцию.

В константы будем постепенно добавлять нужные. Итоговый 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"

Жизненный цикл тестирования:

Достижение непрерывного развертывания

Пример схемы CI/CD

image.png

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

image.png

Формирует отчеты.

Файл inventory и переменные

Список хостов с тэгами (группами), на которые впоследствии ссылаются в задачах. 

Последовательность поиска файла

По умолчанию: /etc/ansible/hosts

Однако проще указывать файл при запуске playbook 

ansible-playbook -i my_inventory.ini playbook.yml

Наследование переменных

Варианты запуска 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) не играет роли. Порядок анализа:

Важно:

  1. Нельзя смешивать форматы в одном файле
  2. Способ отличия форматов:
    INI-формат: начинается с секций в квадратных скобках [group]
    YAML-формат: начинается с --- (опционально) или со слова all: / children: / hosts:
  3. Автодетект может ошибаться. Если будет файл, валидный как INI и как YAML, то Ansible выберет INI.

ini формат:

Ожидается, что hosts в формате ini.

[example]
www.example.com
192.168.0.1
192.168.0.2:2222

Внутри [] название группы, далее состав этой группы (доменное имя или IP). Указание порта определяет порт ssh сервера. 

Группы по умолчанию

Вложенные группы

При указании :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:

Поддерживает диапазоны аналогично 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>

Динамический файл

Это обычный исполняемый файл, который:

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"

Важные правила

Элементы 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

 

 

 

Есть несколько элементов