вторник, 21 июля 2015 г.

Ansible - встроенные возможности

В предыдущих сериях 1 2 3, были примеры того какие возможности в плане расширения предоставляет ansible, на примере работы с пакетами в archlinux. Сейчас я попробую решить пару - задач декларативными средствами ansible, принципиально не используя самописные плагины и вызов внешних скриптов напрямую. Заодно будет повод ознакомиться с тем, что собственно ansible предоставляет из коробки. Тем более, что они в playbook умудрились добавить поддержку циклов, условий, переменных и т.п. Интересно понять насколько удобно ими пользоваться.


Задача 1
Для хранения конфигов, удобно использовать символьные ссылки, что бы одна ссылка указывала на реальную директорию с настройками приложения, а вторая на директорию в ansible, которая будет заливаться в репозиторий. Синхронизация с такой организацией файлов становится очень удобной: меняем как угодно настройки приложений, потом в одном каталоге с ansible делаем commit сразу для всех настроек машины. На другой машине, для синхронизации достаточно сделать "git pull" и все настройки автоматически попадут в нужные места. Думаю идея понятна. Единственное, что необходимо сделать - это изначально создать нужные ссылки. Распишу по шагам, что для этого нужно, что бы было понятно что за манипуляции я буду проделывать в ansible:
  • В качестве конфигурации у нас будет набор пар директорий допустим такой:
[("~/.config/ansible/config/dir1", "~/tmp/config/dir1"),
 ("~/.config/ansible/config/dir2", "~/tmp/config/dir2")]
  • При запуске playbook с синхронизацией нужно пройтись по всем парам в конфигурации и если второй элемент - не является ссылкой, то удалить его и заменить ссылкой на первый элемент. Обращу внимание, что в случае, когда второй элемент все же окажется ссылкой, то трогать его не нужно.
Вот какой playbook получился:
- hosts: all

  tasks:
    - name: Check the symlink
      stat: path={{ item.dst | expanduser }}
      with_items:
        - { src: '~/.config/ansible/config/dir1', dst: '~/tmp/config/dir1' }
        - { src: '~/.config/ansible/config/dir2', dst: '~/tmp/config/dir2' }
      register: output

    - name: Remove the directory (only if it is exist)
      file: path="{{item.item.dst | expanduser }}" state=absent
      when: item.stat.exists and item.stat.isdir
      with_items: output.results

    - name: Create symlink
      file: path="{{ item.item.dst | expanduser }}"
            src="{{ item.item.src | expanduser }}"
            state=link
            owner=rean
            group=users
      with_items: output.results
Объясню каждую из задач по отдельности:
  • "Check the symlink" - организовываем цикл по парам директорий (исходной и конечной), применяем к директории "dst" модуль stat, что бы выяснить что находится по этому пути (лежит ли там что-то вообще, если лежит то, что это - директория или файл и т.п.). Что бы развернуть исходный путь в полный, применяем фильтр expanduser. Результаты, которые выдаёт модуль stat будем складывать в переменную output. Упрощенно, в псевдокоде в переменную output результат записывается примерно вот так:
output.results = []
for it in items:
    output.results.append({"item": it,
                           "stat": {"exists": os.path.exists(it.dst),
                                    "isdir": os.path.isdir(it.dst)}})
Т.е. в результирующей переменной получаем исходные данные с парами директорий и результаты их обработки, до всех значений можно в дальнейшем доступаться. Вот тут, можно почитать про ключевое слово "register" подробнее.
  • "Remove the directory (only if it is exist)" - организовываем цикл по результатам, которые были записаны в переменную "output". Из интересного - тут используется ключевое слово when, которое позволяет сделать удаление только если на предыдущем этапе выяснили, что по "конечному" пути находится директория. Т.е. мы удаляем только директории, а если там была уже создана ссылка - оставляем как есть.
  • "Create symlink" - ну и тут все совсем просто, делаем цикл по всем парам папок и создаём ссылки, если нужно. Нужность определяет модуль "file".
Ужасно, правда? Если бы модуль "file" умел перед созданием ссылки удалять директории с вложенными файлами - проблем бы не было. А так пришлось городить очень неявную конструкцию из трёх задач с использованием почти всех возможных примитивов ansible. Я убил на решение - несколько часов (хотя это скорее по незнанию). Но все равно, на чистом python или bash - решение заняло бы несколько минут.

Задача 2
Ещё пример типичной задачи - добавить в скрипт развёртывания ssh ключ. В открытом виде по соображениям безопасности его не выложишь, а подкладывать руками - не удобно. Нужно хранить в шифрованном виде. Ansible содержит встроенный модуль для шифрования. Например шифруется файл вот так:
$ echo "hello" > ~/.config/ansible/config/file.txt
$ ansible-vault encrypt ~/.config/ansible/config/file.txt
Во время выполнения второй команды, ansible спросит пароль и зашифрует им "file.txt", теперь его без опасения можно выкладывать хоть на GitHub (с известной долей паранойи конечно). Дальше было бы логично иметь возможность написать playbook, который этот файл расшифрует и положит в нужное место. Но увы и ах, несмотря на просьбы на github ansible с кучей плюсов, готовые pull-request, какие-то внешние плагины добавляющие что-то похожее, разработчики неумолимы, такой функциональности нет. Её вроде как обещают добавить во второй версии, но нужно то сейчас. А пока будьте добры запускайте отдельный command-line скрипт, который нужные файлы расшифрует на конечной машине:
$ ansible-vault decrypt ~/.config/ansible/config/file.txt

Задача 3
Должно же хоть что-то полноценно заработать без костылей. Попробуем задачу извлечения git репозитория с github. Идём в документацию, копируем пример:
- hosts: all
  user: rean

  tasks:
    - name: get git repo
      git: repo=git@github.com:ReanGD/LearningEnglish.git dest=~/tmp/checkout
Ух ты, с первого раза заработало. Ну хоть что-то.

Итоги: назвать удобными и самодостаточными поставляемые по умолчанию плагины и модули - язык не поворачивается. Пока читаешь документацию, все понятно и просто, как только начинаешь решать какие-то свои реальные задачи, почти всегда приходится рыться в google. Декларативный синтаксис - нефункционален и неудобен, за редким исключением руки тянутся к python.

Но не смотря ни на что, я все же попытаюсь довести до конца свой проект по сохранению конфигурации своей машины.

Комментариев нет:

Отправить комментарий