Некоторые нюансы при работе с Ansible


Недавно я решил освежить навыки работы с Ansible, причем не просто освежить, а взять и с нуля создать полноценные описания для новой инфраструктуры под клиентский проект, скажем так сделать с нуля полноценный Infrastructure as code. У меня уже было несколько шаблонов которые я использовал в своей практике для подготовки разного рода окружений для разработчиков, но полноценный IAC я еще никогда не организовывал.

Infrastructure as code реализация при помощи Ansible

Что можно сказать, в принципе все это выполнимо, но использование Ansible для построения Infrastructure as code напоминает мультфильм "Крылья ноги и хвосты", а точнее тот момент где "Лучше день потерять, а потом за пять минут долететь". В процессе реализации IAC я наткнулся на несколько казалось бы банальных вопросов ответа на которые я сходу не нашел.

Подготовка хоста для работы с Playbook-ами Ansible

На удаленном хосте обязательно должен быть установлен python:

# apt-get install python

Я вообще рекомендую подготовить минимальный шаблон с установленным python и созданным сервисным пользователем из которого в дальнейшем применяя различные playbook-и вы сможете сделать что угодно.

Работа с переменными для хостов и групп хостов

Для того чтобы посмотреть текущие назначенные хостам перемененные и их значение используется команда:

$ ansible-inventory -i inventory --list

В результате выполнения команды вы получите значения всех вычисленных переменных для всех хостов на основании inventory-файла.

Обычно конечно получается огромная простыня данных и в большинстве случаев вам наверное интереснее будет посмотреть вычисленные переменные для отдельно взятого хоста:

$ ansible-inventory -i inventory --host=10.212.2.62
{
    "ansible_connection": "ssh",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=accept-new",
    "ansible_ssh_pass": "{{deploy_password}}",
    "ansible_ssh_port": 22,
    "ansible_ssh_user": "administrator"
}

Общие переменные для всех хостов для хостов задаются в файле all.yml в каталоге group_vars. Все заданные переменные будут назначены всем хостам указанным в inventory и эти переменные перекрываются идентичными переменными из групп хостов и собственно отдельными хостами:

---
all_hosts: 'sintex2'

Выборочное применение Playbook

Обычно Playbook применяется ко всем хостам в группе которая указана в конфигурации:

---
- hosts: ZABBIX-HOSTS

  handlers:
    - name: restart_zabbix
      shell: 'systemctl restart zabbix-agent'
...

Но мы можем применить Ansible Play Book к определенному хосту, что удобно на этапе тестирования правил:

$ ansible-playbook ./install-and-config-zabbix.yml --limit=admin-neon.local.gita-dev.ru

Или даже использовать маску хостов (например по домену):

$ ansible-playbook ./install-and-config-zabbix.yml --limit=*.gita-dev.ru

Нестандартные параметры подключения к хосту

По умолчанию, мы можем указать только имя хоста к которому будем подключаться и по дефолту это означает, что подключаемся к удаленному хосту с использованием ssh-ключа и с использованием имени пользователя который запустил Ansible Playbook на стандартный порт SSH (22/TCP).

В большинстве случаев не всегда все так радостно и идеализировано и может потребоваться хотя бы передать имя пользователя на удаленном хосте:

[LXD-HOSTS]
m-1.gita-dev.ru ansible_connection=ssh ansible_ssh_user=root ansible_ssh_port=22

Или если у нас уже имеется некоторая инфраструктура, в которой на каждом хосте создан пользователь (сервисный), то мы можем указать на первое время (до деплоя ssh-ключей) и авторизацию по паролю, но тут есть несколько сложностей и просто так указать пароль пользователя вам не удасться и необходимо использовать так называемые хранилища (vault).

В файл inventory пароль передается в виде переменной шаблона:

10.212.2.62 ansible_connection=ssh ansible_ssh_user=administrator ansible_ssh_pass={{vault_deploy_password}}

А хранить зашифрованную vault-переменную (с версии Ansible 2.3) мы можем прямо в yaml-файле, правда ее придется сгенерировать при помощи команды:

$ ansible-vault encrypt_string --vault-id ./.vault_pass.txt 'xxxSecretxxxPasswordxxx' --name 'vault_deploy_password'

vault_pass.txt - содержит пароль для дешифровки переменной.

Вывод в консоль после выполнения этой команды необходимо добавить в файл описания переменных:

vault_deploy_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          33323962383233356134643464356436343262623932656666383761613235343836666630326134
          6531626661393538616661343634623432663833313661360a656130326662616531373664396166
          37613265643362666436613738303735336263316365643261353762336461316439383465316232
          3635356637383932650a363732353632396466643132383166393138373832376531643562656639
          3464

При попытке запросить значения переменных вы увидите, что значение этой переменной зашифровано:

$ ansible-inventory -i inventory --host=10.212.2.62
{
    "ansible_connection": "ssh",
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=accept-new",
    "ansible_ssh_pass": "{{vault_deploy_password}}",
    "ansible_ssh_port": 22,
    "ansible_ssh_user": "administrator",
    "vault_deploy_password": {
        "__ansible_vault": "$ANSIBLE_VAULT;1.1;AES256\n33323962383233356134643464356436343262623932656666383761613235343836666630326134\n6531626661393538616661343634623432663833313661360a656130326662616531373664396166\n37613265643362666436613738303735336263316365643261353762336461316439383465316232\n3635356637383932650a363732353632396466643132383166393138373832376531643562656639\n3464"
    }
}

Теперь правда вам придется запускать Playbook с указанием файла с ключом дешифровки, или в противном случае вы получите ошибку:

$ ansible-playbook ./basic-host-configuration.yml --limit=10.212.2.62

PLAY [ZABBIX-HOSTS] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
fatal: [10.212.2.62]: FAILED! => {"msg": "Attempting to decrypt but no vault secrets found"}
        to retry, use: --limit @/home/chernousov/Документы/data_vol/Development/ansible/basic-host-configuration.retry

PLAY RECAP ***************************************************************************************************************************************
10.212.2.62                : ok=0    changed=0    unreachable=0    failed=1

Предайте параметром  для дешифровки --vault-password-file:

$ ansible-playbook ./basic-host-configuration.yml --vault-password-file=./.vault_pass.txt --limit=10.212.2.62

Мы дополнительно можем автоматически принять ключ удаленного хоста при первом подключении (опция ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new'):

10.212.2.62 ansible_connection=ssh ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new'

Полностью отключать проверку ssh-ключенй я бы вам не рекомендовал, но если сильно хочется, то почему бы и нет:

ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Если вы получаете ошибку вида:

fatal: [10.212.2.62]: FAILED! => {"changed": false, "module_stderr": "Shared connection to 10.212.2.62 closed.\r\n", "module_stdout":
"sudo: a password is required\r\n", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

То дополните ваш инвентарный файл параметром ansible_sudo_pass и в особо тяжелых случаях получается подобная конструкция:

10.212.2.62 ansible_connection=ssh ansible_ssh_user=administrator ansible_sudo_pass={{vault_deploy_password}} ansible_ssh_pass={{vault_deploy_password}} ansible_ssh_port=22 ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new'

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

Работа с тегами в Playbook

Теги в Ansible применяются для разделения больших Playbook на логические элементы (в основном для тестирования) и вы можете разделить ваш плэйбук например на установку системного ПО и пользовательского:

  # Устанавливаем пакет Aptitude
  - name: Install Aptitude package
    apt: name=aptitude update_cache=yes
    become: yes
    tags: system_soft
 
  # Устанавливаем пакет MC
  - name: Install MC package
    apt: name=mc update_cache=yes
    become: yes
    tags: user_soft

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

$ ansible-playbook ./basic-host-configuration.yml --vault-password-file=./.vault_pass.txt --limit=admin-neon.local.gita-dev.ru --tags=user_soft

Особенности работы с Playbook-ами в Ansible

Если вы хотите создать Playbook который будет применяться ко всем хостам используйте объявление all в разделе hosts:

---
- hosts: all

Это конечно же малая толика того с чем я столкнулся и я обязательно об этом еще буду рассказывать если это конечно кому-то интересно :)