Управление конфигурациями с помощью Asnible

1. Настройка управляющей машины

На управляющей машине устанавливаем ansible:

# apt-get install ansible

В домашнем каталоге создаём каталог, в котором будем хранить все файлы для настройки удалённых компьютеров. Если нужно работать над проектом совместно с другими системными администраторами и отслеживать изменения, сделанные ими, то лучше сразу же создать в этом каталоге git-репозиторий:

$ mkdir playbooks
$ git init

Создадим файл конфигурации ansible.cfg и пропишем в него следующие настройки:

[defaults]
inventory = hosts
remote_user = ansible
private_key_file = .ssh/id_rsa

Сразу же сгенерируем ssh-ключ, который будем устанавливать на удалённые системы для их настройки при помощи ansible:

$ mkdir .ssh
$ ssh-keygen -f .ssh/id_rsa
$ chmod go= -R .ssh

Каталог .ssh помещать в git-репозиторий не стоит.

Создадим файл hosts, в котором будем отмечать узлы, управляемые при помощи ansible. Например, файл с одним-единственным узлом с именем mon может выглядеть следующим образом:

mon ansible_host=169.254.252.2

2. Настройка управляемой машины

На управляемой машине после настройки сети и установки OpenSSH-сервера необходимо установить несколько пакетов, которые будут использоваться ansible:

# apt-get install python2.7-minimal python2.7-stdlib python-minimal

Также установим sudo, чтобы при удалённой настройке можно было выполнять действия от имени пользователя root:

# apt-get install sudo

Добавим учётную запись специально для удалённой настройки:

# useradd -c 'Ansible account' -m ansible

Сразу же дадим возможность этой учётной записи использовать sudo для работы с правами пользователя root:

# visudo

Добавляем строчки:

Defaults:ansible !requiretty
ansible ALL=(root:ALL) NOPASSWD:ALL

Скопируем на удалённую машину SSH-ключ:

$ ssh-copy-id -i .ssh/id_rsa.pub ansible@169.254.252.2

3. Первый сценарий

Для первого сценария возьмём простейшую задачу - настройки файла /etc/resolv.conf на удалённом компьютере.

Создадим на управляющей машине в каталоге проекта файл сценария resolvconf.yml со следующим содержимым:

---
- name: Configure resolv.conf
  hosts: mon
  become: True
  tasks:
    - name: copy /etc/resolv.conf
      template:
        src: templates/resolv.conf
        dest: /etc/resolv.conf
        owner: root
        group: root
        mode: 0644

Создадим каталог templates для шаблонов файлов конфигурации:

$ mkdir templates

Создадим шаблон конфигурации templates/resolv.conf для файла /etc/resolv.conf управляемого компьютера:

{% if resolvconf.domain %}
domain {{ resolvconf.domain }}
{% endif %}
{% for ip in resolvconf.nameservers %}
nameserver {{ ip }}
{% endfor %}

Значения переменных хранятся в так называемом реестре ansible. Он представляет собой файл в формате YML и может находиться либо в каталоге host_vars, либо в каталоге group_vars. В каталоге host_vars хранятся настройки, специфичные для каждого отдельного управляемого узла. Внутри этого каталога создаётся фай, имя которого совпадает с указанным в файле hosts. В каталоге group_vars хранятся настройки, общие для определённой группы узлов. Внутри этого каталога создаётся файл, имя которого совпадает с именем группы, указанным в файле hosts.

Создадим каталог host_vars с переменными узлов:

$ mkdir host_vars

И внутри каталога создадим файл mon со следующим содержимым:

resolvconf:
  domain: stupin.su
  nameservers:
    - 169.254.252.1

Осталось выполнить наш первый простейший сценарий. Для этого воспользуемся командой ansible-playbook:

$ ansible-playbook resolvconf.yml

4. Переключение на непривилегированного пользователя

В прошлом сценарии имелась опция, которая заставляла выполнять действия от имени пользователя root:

become: True

В случае, если нужно выполнять действия от имени другого, непривилегированного пользователя, можно столкнуться с проблемами. Например, администратором баз данных PostgresSQL является системный пользователь postgres. Создать нового пользователя или базу данных можно только от его имени. Поэтому, задача такого вида:

- name: createuser -P <login>
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases

Завершится такой ошибкой:

unable to connect to database: ВАЖНО:  пользователь "postgres" не прошёл проверку подлинности (Peer)

Однако и простого указания опций переключения на пользователя postgres оказывается недостаточно:

- name: createuser -P <login>
  become: True
  become_user: postgres
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases

В этом случае попытка выполнить задачу завершается следующей ошибкой (отформатировано для наглядности):

Failed to set permissions on the temporary files
Ansible needs to create when becoming an unprivileged user (rc: 1, err:
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/': Операция не позволена
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/postgresql_user.py': Операция не позволена
).
For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user

После доработки в соответствии с материалами по ссылке, указанной в тексте ошибки, задача приобретает следующий вид:

- name: createuser -P <login>
  become: yes
  become_user: postgres
  vars:
    ansible_ssh_pipelining: true
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases

Но и этого оказывается недостаточно, т.к. возникает следующая ошибка:

failed: [mon] (item={u'login': u'zabbix', u'password': u'xxxxxxxxxxxxxxxx', u'name': u'zabbix'}) => {
    "failed": true, 
    "item": {
        "login": "zabbix", 
        "name": "zabbix", 
        "password": "xxxxxxxxxxxxxxxx"
    }, 
    "module_stderr": "sudo: a password is required\n", 
    "module_stdout": ""
}

MSG:

MODULE FAILURE

Как видно, ansible пытается переключиться на указанного пользователя при помощи sudo. Но, когда я настраивал sudo для ansible, то указал разрешение переключаться без указания пароля только на пользователя root:

ansible ALL=(root:ALL) NOPASSWD:ALL

Чтобы разрешить пользователю ansible переключаться ещё и на пользователя postgres, нужно добавить через команду visudo ещё одно разрешение:

ansible ALL=(postgres:ALL) NOPASSWD:ALL

После этого задача выполняется успешно.

5. Немедленный перезапуск сервиса при изменениях

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

Именно так получилось у меня при подготовке роли, настраивающей сервис PostgreSQL. После изменения номера порта может потребоваться создать новые базы данных или пользователей, но пока сервис не был перезапущен, подключиться к нему по новому порту не получится, а старый может быть не известен. В таком случае можно запомнить результаты выполненных действий в переменных при помощи выражения register:

- name: vim /etc/postgresql/9.6/main/pg_hba.conf
  template:
    src: etc/postgresql/9.6/main/pg_hba.conf
    dest: /etc/postgresql/9.6/main/pg_hba.conf
    owner: postgres
    group: postgres
    mode: 0640
  register: pg_hba 

- name: vim /etc/postgresql/9.6/main/postgresql.conf
  template:
    src: etc/postgresql/9.6/main/postgresql.conf
    dest: /etc/postgresql/9.6/main/postgresql.conf
    owner: postgres
    group: postgres
    mode: 0644
  register: postgresql

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

- name: systemctl restart postgresql
  service:
    name: postgresql
    state: restarted
  when: (pg_hba.changed or postgresql.changed)

6. Использованные материалы

7. P.S.

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

Написать автору