Перейти к содержанию

Обо всем и не только

Архитектура Ansible

Цепочка следующая:


graph TD
A[Control node]
--> B[Inventory]
B --> C[Playbook]
C --> D[Role]
D --> E[Tasks]
E --> F[Modules]
F --> G[Managed hosts]

В типичном Linux сценарии:

  • agentless

  • подключается по SSH

  • выполняет модули на целевых машинах

  • стремится привести систему к описанному состоянию

  • использует inventory для определения хостов и групп.

Пример:

- name: Configure web servers
  hosts: web
  become: true

  roles:
    - nginx

hosts: web берется из inventory

become - повышение привилегий

основная логика вынесена в роль


inventory

не только:

[web]
server1
server2
а группы, переменные и разные окружения

inventories/
├── prod/
│   ├── hosts.yml
│   ├── group_vars/
│   └── host_vars/
└── stage/
    ├── hosts.yml
    ├── group_vars/
    └── host_vars/

например:

all:
  children:
    web:
      hosts:
        web01:
        web02:
    db:
      hosts:
        db01:

запуск

ansible-playbook -i inventories/prod/hosts.yml site.yml

нужно знать:

  • group_vars

  • host_vars

  • группы

  • вложенные группы

  • inventory variables

  • static/dynamic inventory - хотя бы концептуально

  • --limit


Переменные и predence

например

nginx_port: 80

использование:

- name: Configure nginx
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf

в шаблоне:

listen {{ nginx_port }};

нужно понимать назначение:

defaults/main.yml
vars/main.yml
group_vars/
host_vars/
vars_files
register
set_fact
-e / --extra-vars

и самое главное - variable predence


Idempotency

это одна из центральных концепций Ansible

плохо:

- name: Install nginx
  ansible.builtin.shell:
    cmd: apt install nginx -y

Лучше:

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

Первый запуск выдаст changed=5 а второй changed=0


Modules вместо shell/command

плохо: ``yaml shell: systemctl restart nginx

лучше:

```yaml
ansible.builtin.service:
  name: nginx
  state: restarted

Совет

Ещё лучше — если рестарт вообще требуется только после изменения конфигурации — handler.

Предупреждение

command и shell использовать можно. Важно понимать когда без них действительно разумнее, и контролировать changed_when, failed_when, creates, removes и т. п.


Handlers

- name: Deploy nginx config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Reload nginx

Handler:

handlers:

  - name: Reload nginx
    ansible.builtin.service:
      name: nginx
      state: reloaded

Если конфиг не изменился то changed=false и handler не вызывается.

Если изменился, то changed=true, ansible уведомляет handler.

совет

Также стоит знать listen:

Важно понимать, что handlers обычно выполняются в конце соответствующей части play, а при необходимости существует - meta: flush_handlers


Jinja2

Простой шаблон:

server {
    listen {{ nginx_port }};

    server_name {{ server_name }};
}

Условия:

{% if ssl_enabled %}
listen 443 ssl;
{% endif %}

Циклы:

{% for backend in backends %}
server {{ backend }};
{% endfor %}

Фильтры:

{{ username | default('www-data') }}

Нужно понимать:

  • {{ }}

  • {% %}

  • условия;

  • циклы;

  • filters;

  • default;

  • работу с list/dict;

  • шаблонизацию конфигов


Conditions

when:

например:

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
  when: ansible_facts['os_family'] == 'Debian'

и:

when:
  - nginx_enabled
  - ansible_facts['os_family'] == 'Debian'

совет

Нужно понимать boolean, is defined, is not defined, сравнения, проверки результатов зарегистрированных задач.


Loops

- name: Install packages
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - vim

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

ansible.builtin.apt:
  name:
    - nginx
    - curl
    - vim
  state: present

также:

loop_control:

Register

Очень важно для реальных сценариев:

- name: Check application
  ansible.builtin.command:
    cmd: systemctl is-active myapp
  register: app_status
  changed_when: false

После этого:

- name: Print status
  ansible.builtin.debug:
    var: app_status.stdout

и:

when: app_status.rc != 0

Нужно понимать основные поля результата:

  • stdout

  • stderr

  • rc

  • changed

  • failed


chandeg_when и failed_when

Например:

- name: Check nginx configuration
  ansible.builtin.command:
    cmd: nginx -t
  register: nginx_test
  changed_when: false

Проверка ничего не изменила, поэтому ansible не должен показывать changed

можно управлять ошибками:

failed_when: nginx_test.rc != 0

block/rescue/always

например:

- block:

    - name: Deploy application
      ansible.builtin.copy:
        src: app
        dest: /opt/app

  rescue:

    - name: Rollback
      ansible.builtin.command:
        cmd: /opt/rollback.sh

  always:

    - name: Write deployment status
      ansible.builtin.debug:
        msg: "Deployment finished"

по смыслу:

try
block

except
rescue

finally
always

Очень полезный инструмент для деплоев.


Roles

Важно понимать назначение каталогов

defaults
vars
tasks
handlers
templates
files
meta

также:

include_tasks
import_tasks
include_role
import_role

И хотя бы понимать разницу dynamic include vs static import.


Collections и Galaxy

FQCN:

ansible.builtin.copy:

вместо простого copy

Dependencies:

collections:
  - name: community.general
    version: "..."

и

ansible-galaxy collection install -r requirements.yml

Vault

важно знать команды:

ansible-vault create
ansible-vault edit
ansible-vault view
ansible-vault encrypt
ansible-vault decrypt
ansible-vault rekey

Например:

vars_files:
  - vault.yml

и:

ansible-playbook site.yml --ask-vault-pass

Плюс vault-id

dev
stage
prod

Предупреждение

В CI секрет должен приходить из защищённого механизма секретов/CI variables или внешнего secret manager.


Facts

gather_facts: true

Ansible собирает информацию о машине.

ansible_facts['distribution']
ansible_facts['os_family']
ansible_facts['architecture']

можно делать:

when: ansible_facts['os_family'] == 'Debian'

иногда оправдано выключить сбор инфы:

gather_facts: false

Delegation и run_once

например у тебя 200 серверов а миграцию БД нужно выполнить один раз

- name: Run DB migration
  ansible.builtin.command:
    cmd: /opt/app/migrate
  run_once: true

а выполнить задачу на другой машине:

delegate_to: db01

например:

- name: Run migration
  ansible.builtin.command:
    cmd: /opt/app/migrate
  run_once: true
  delegate_to: db01

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


Tags

например:

- name: Configure nginx
  ansible.builtin.template:
    ...
  tags:
    - nginx
    - config

запуск:

ansible-playbook site.yml --tags config

и:

ansible-playbook site.yml --skip-tags config

Check mode и diff

Перед продом:

ansible-playbook site.yml --check

и:

ansible-playbook site.yml --check --diff

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

--diff помогает увидеть изменения содержимого.

Особенности

check mode не является гарантией безопасного выполнения.

Не все модули/команды одинаково хорошо его поддерживают.


Безопасная раскатка

Плохо:

изменил роль
запустил на 200 prod

Хорошо:

lint
syntax-check
Molecule
stage
check/diff
canary
batch deployment
verification
full deployment

Например:

- hosts: web
  serial: 10

Ansible будет обрабатывать хосты партиями

можно использовать:

max_fail_percentage:

чтобы контролировать допустимые ошибки во время batch deployment


Molecule

нужно понимать цикл:

create
prepare
converge
idempotence
verify
destroy

verify должен реально проверить результат:

- name: Check nginx
  ansible.builtin.uri:
    url: http://localhost
    status_code: 200

Совет

То есть тестируем не: Ansible завершился без ошибки.

А: Система действительно оказалась в требуемом состоянии.


ansible-lint + syntax-check

минимум:

ansible-playbook --syntax-check site.yml

и:

ansible-lint

Описание

syntax-check проверяет корректность синтаксиса/структуры playbook.

ansible-lint проверяет гораздо больше правил качества и best practices.


CI/CD

Концепция:

stages:
  - lint
  - test
  - deploy

Lint:

lint:
  stage: lint
  script:
    - ansible-lint
    - ansible-playbook --syntax-check site.yml

Tests:

molecule:
  stage: test
  script:
    - molecule test

Prod:

deploy_prod:
  stage: deploy
  script:
    - ansible-playbook -i inventories/prod site.yml
  when: manual
  • кто может запускать prod;

  • откуда приходят SSH credentials;

  • откуда Vault password;

  • protected branches/environments;

  • артефакты и версии;

  • что делать при partial failure;

  • можно ли безопасно повторить job;

  • как избежать одновременных конфликтующих deployment.


Troubleshooting

Например:

UNREACHABLE

думаешь:

DNS?
SSH?
route?
firewall?
ключ?
пользователь?
ProxyJump?

получаешь:

Permission denied

Проверяешь:

ansible_user
SSH key
become
sudo
permissions

получаешь:

VARIABLE IS NOT DEFINED

ищешь:

inventory
group_vars
host_vars
defaults
vars
precedence
опечатку

Получаешь падение только:

30/200 hosts

Сначала:

Что общего у этих 30?
Какая ОС?
Какая версия пакета?
Какая группа inventory?
Доступность репозитория?
Свободное место?
Права?
Состояние сервиса?
Какая именно task упала?

Произодительность и масштабирование

например:

forks = 20
  • parallelism;

  • forks;

  • serial;

  • throttle;

  • strategy;

  • timeout;

  • почему нельзя бездумно увеличивать concurrency.


Error handling

Знать:

ignore_errors:

плохой подход везде ставить:

ignore_errors: true

лучше определить ожидаемое состояние

failed_when:

или нормально обработать ситуацию через

block
rescue
always

Привилегии

Обязательно:

become: true

и:

become_user:

Конфиги нужно валидировать ДО применения

недостаточно:

скопировал nginx.conf
reload nginx

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

- name: Deploy nginx configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: "nginx -t -c %s"
  notify: Reload nginx

идея:

template
validation
только корректный файл устанавливается
handler
reload