Last active
May 12, 2022 14:27
-
-
Save tony-sol/e24d5ec750c427ee133a3afdc9be56cb to your computer and use it in GitHub Desktop.
Revisions
-
tony-sol renamed this gist
May 12, 2022 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
tony-sol created this gist
Mar 31, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,538 @@ # Ansible Основы Ansible для новичка Эта статья не замена [https://docs.ansible.com/](https://docs.ansible.com/), а скорее 101 - база, которая поможет начать понимать основные принципы работы # Что такое Ansible Ansible - в каноничном определении, система управления конфигурациями. Чуть более понятно и обобщенно - это инструмент с помощью которого можно автоматизировать практически любую задачу, которую можно выполнить “руками в консоли” - от подготовки сервера до деплоя конечного приложения. Важно - Ansible не является “средством запуска shell-скриптов”, скрипты в целом для Ansible - считаются плохой практикой (крайне полезная [статья](https://habr.com/ru/post/536340/) на тему). # Как работает Ansible В отличии от Puppet, Ansible использует SSH для выполнения play на целевых хостах и не требует какого-либо агента. Из этого следует важная особенности - там где puppet-agent сам ходил в мастер, предоставлял факты о своем хосте и запрашивал изменения, в случае с Ansible - изменения не “проиграют”, пока явно их не “сыграть”. Исходя из того что используется SSH, логично что машина, с которой будет пушиться конфигурация, должен иметь SSH доступ на управляемый хост и при этом даже не обязательно быть sudoers (правда это сильно ограничивает в том, что может быть исполнено). # Из чего строится проект Ansible и как с ним работать Основным элементом Ansible является **play** - то что нужно “сыграть”, в какое состояние нужно привести какой-то аспект системы. Убедиться что на хосте существует директория, файл с содержимым, запущенный docker контейнер, etc. - это все play. Play чаще всего не существует сам по себе - из него строится **playbook** и прямой перевод говорит сам за себя - “сборник, того что нужно сыграть” и если play это “логический” элемент, то playbook это его переложение “на файл”. При этом сам play состоит из **tasks** и **roles**, и если с tasks относительно просто - это вызов указанного модуля, “*функции”* с параметрами и переменными, то roles можно воспринимать скорее как пакет - отдельная группа файлов с task’ами, handler’ами, переменными с четкой иерархией и структурой. У Ansible для этого есть даже свой “пакетный менеджер” - `ansible-galaxy`, те кто хоть раз сталкивался с `pip` сразу заметят сходство. Важно - использование одновременно tasks и roles считается плохой практикой. (крайне полезная [статья](https://habr.com/ru/post/508762/) на тему 2) Но знать “что сыграть” не достаточно, нужно также знать “где сыграть”, в терминал Ansible это **inventory** - список хостов и их групп, на которых будет “сыгран” play. Рассмотрим на конкретных примерах и навчнем с файла inventory. Файлы inventory поддерживаются в форматах ini и yaml | toml ```bash [project:children] cli web [cli:children] cli_preprod cli_prod [cli_preprod] cli1.domain.tech [cli_prod] cli2.domain.tech [web:children] web_preprod web_prod [web_preprod] web1.domain.tech [web_prod] web2.domain.tech ``` ```yaml all: children: project: children: cli: children: cli_preprod: hosts: cli1.domain.tech cli_prod: hosts: cli2.domain.tech web: children: web_preprod: hosts: web1.domain.tech web_prod: hosts: web2.domain.tech ``` Не рекомендуется использовать “-” в именах групп - это не ошибка, но надоедливый Warning: > [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details > Здесь мы задаем общую родительскую группу project (на самом деле, она нужна лишь для удобства чтения, и дальше будет видно почему), которая имеет 2х наследников - cli и web, каждый из которых, в свою очередь, так же группа со своими наследниками - *_preprod и *_prod. Чтобы проверить, что inventory собран правильно, выполним первую команду: ```bash $ ansible-inventory --graph -i project.hosts @all: |--@project: | |--@cli: | | |--@cli_preprod: | | | |--cli1.domain.tech | | |--@cli_prod: | | | |--cli2.domain.tech | |--@web: | | |--@web_preprod: | | | |--web1.domain.tech | | |--@web_prod: | | | |--web2.domain.tech |--@ungrouped: ``` По построенному графу видно, что у нас нет хостов вне групп, а значит в данном случае project == all. Играя playbook с таким inventory, можно точно указать, где именно нужно выполнить - только на cli, или только на web, или только preprod, или просто конкретный хост. Попробуем выполнить первую задачу - пингануть хосты. CLI Ansible предусматривает 2 утилиты для проигрывания: `ansible` и `ansible-playbook` - первая для того чтобы “быстро и просто” выполнить какой-либо модуль, вторая - для того чтобы сыграть целиком playbook. ```bash $ ansible -m ping -i project.hosts.yaml all cli1.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } cli2.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } web1.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } web2.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ``` ```bash $ ansible -m ping -i project.hosts.yaml cli_preprod cli1.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ``` ```bash $ ansible -m ping -i project.hosts.yaml cli2.domain.tech cli2.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ``` ```bash $ ansible -m ping -i cli2.domain.tech, all cli2.domain.tech | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ``` Выше показан запуск модуля ping на все хосты из inventory, конкретную группу и отдельный хост, а также хак - как запустить модуль для хоста без использования файла inventory. Писать каждый раз `-m ping` уже не удобно, а для выполнения задачи модулей почти всегда будет сильно больше одного. Команду конечно можно добавить в Makefile, но не будет извращаться и напишем playbook: ```yaml # ping.yaml --- - name: "our play" # имя для play hosts: "all" # группа хостов/хост по умолчанию tasks: - name: "ping hosts" # имя task ping: # использование встроенного модуля ping ``` И запустим на нашем inventory: ```bash $ ansible-playbook ping.yaml -i project.hosts PLAY [our playbook] ******************************************************************************* TASK [Gathering Facts] **************************************************************************** ok: [cli1.domain.tech] ok: [cli2.domain.tech] ok: [web1.domain.tech] ok: [web2.domain.tech] TASK [ping hosts] ********************************************************************************* ok: [cli1.domain.tech] ok: [cli2.domain.tech] ok: [web1.domain.tech] ok: [web2.domain.tech] PLAY RECAP **************************************************************************************** cli1.domain.tech : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 cli2.domain.tech : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 web1.domain.tech : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 web2.domain.tech : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Лимитирование inventory для `ansible-playbook` работает также как и для `ansible` (за исключением необходимости ключа `-l`) Иногда бывает так, что базовых прав пользователя недостаточно для выполнения нужного действия, нужно sudo. Для примера изменим playbook, чтобы он создавал директорию /opt/test на конечном хосте: ```yaml --- - name: "our playbook" hosts: "all" tasks: - name: "ping hosts" ping: - name: "ensure direcories" file: state: directory path: "/opt/test" ``` И запустим для группы cli_preprod: ```bash $ ansible-playbook ping.yaml -i project.hosts -l cli_preprod -DC PLAY [our playbook] ******************************************************************************* TASK [Gathering Facts] **************************************************************************** ok: [cli1.domain.tech] TASK [ping hosts] ********************************************************************************* ok: [cli1.domain.tech] TASK [ensure direcories] ************************************************************************** --- before +++ after @@ -1,4 +1,4 @@ { "path": "/opt/test", - "state": "absent" + "state": "directory" } changed: [cli1.domain.tech] PLAY RECAP **************************************************************************************** cli1.domain.tech : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Обратите внимание на recap - `ok | changed | unreachable | failed | skipped | rescued | ignored` - сводка проигрывания playbook и вытекающее из этого предупреждение: не стоит сразу проигрывать playbook, если нет уверенности что используемые tasks не приведут систему в нежелательное состояние (будет остановлен/запущен не тот демон, удален/добавлен не тот пользователь, etc.). Поэтому рекомендую сперва проигрывать в check_mode с включением diff_mode - `--diff|-D --check|-C` . (Однако не каждый task может быть выполнен с check|diff модом, за подробностями - [в документацию)](https://docs.ansible.com/ansible/latest/collections/index.html) Если зайти на хост и проверить содержимое /opt, директории test там не окажется - check_mode наглядно. ```bash [[email protected] opt]$ ll total 16 drwxr-xr-x 4 root root 4096 Feb 17 01:59 ./ drwxr-xr-x 19 root root 4096 Feb 8 19:15 ../ drwxr-xr-x 2 root root 4096 Dec 3 17:23 bin/ drwx--x--x 4 root root 4096 Oct 28 18:22 containerd/ lrwxrwxrwx 1 root root 11 Oct 26 10:55 puppetlabs -> /etc/puppet/ ``` Допустим мы уверены что хотим создать директорию, и никаких сайд-эффектов recap не показал - запускаем без `-DC`: ```bash $ ansible-playbook ping.yaml -i project.hosts -l cli_preprod PLAY [our playbook] ********************************************************************************************************************************************* TASK [Gathering Facts] ****************************************************************************************************************************************** ok: [cli1.domain.tech] TASK [ping hosts] *********************************************************************************************************************************************** ok: [cli1.domain.tech] TASK [ensure direcories] **************************************************************************************************************************************** fatal: [cli1.domain.tech]: FAILED! => {"changed": false, "msg": "There was an issue creating /opt/test as requested: [Errno 13] Permission denied: b'/opt/test'", "path": "/opt/test"} PLAY RECAP ****************************************************************************************************************************************************** cli1.domain.tech : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 ``` 1. ansible-playbook 2. ??? 3. ??? 4. ~~PROFIT!!!~~ failed: Что не так? Попробуем сделать это руками: ```bash [[email protected] opt]$ mkdir test mkdir: cannot create directory ‘test’: Permission denied [[email protected] opt]$ sudo !! && ll sudo mkdir test && ll total 20 drwxr-xr-x 5 root root 4096 Feb 17 02:10 ./ drwxr-xr-x 19 root root 4096 Feb 8 19:15 ../ drwxr-xr-x 2 root root 4096 Dec 3 17:23 bin/ drwx--x--x 4 root root 4096 Oct 28 18:22 containerd/ lrwxrwxrwx 1 root root 11 Oct 26 10:55 puppetlabs -> /etc/puppet/ drwxr-xr-x 2 root root 4096 Feb 17 02:10 test/ ``` Вывод - нам нужна [эскалация привилегий](https://docs.ansible.com/ansible/latest/user_guide/become.html). Есть несоклько вариантов сделать это - можно указать `become: true` в декларации конкретного task/play/ или сыграть playbook с флагом `—become|-b`: ```bash $ ansible-playbook ping.yaml -i project.hosts -l cli_preprod -b PLAY [our playbook] ********************************************************************************************************************************************* TASK [Gathering Facts] ****************************************************************************************************************************************** ok: [cli1.domain.tech] TASK [ping hosts] *********************************************************************************************************************************************** ok: [cli1.domain.tech] TASK [ensure direcories] **************************************************************************************************************************************** ok: [cli1.domain.tech] PLAY RECAP ****************************************************************************************************************************************************** cli1.domain.tech : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Хорошо, но допустим у нас большой playbook, но нужно выполнить оттуда только несколько play - легким способом сделать такое выполнение удобным являются **tags** - передавая список `—tags|-t` аргументом `ansible-playbook`. Но для начала изменим playbook - разобъем tasks по отдельным play и добавим им tags: ```yaml --- - name: "ping hosts" hosts: "all" tags: [check] tasks: - ping: - name: "ensure direcories" hosts: "all" tags: [dirs] tasks: - file: state: directory path: "/opt/test" ``` Тогда для того чтобы только пингануть хосты, достаточно указать `-t=check` - в таком случае все play не имеющие check в своих tags будут проигнорированы. ```bash $ ansible-playbook ping.yaml -i project.hosts -l cli_preprod -b -t=check PLAY [ping hosts] ********************************************************************************* TASK [Gathering Facts] **************************************************************************** ok: [cli1.domain.tech] TASK [ping] *********************************************************************** ok: [cli1.domain.tech] PLAY [ensure direcories] ************************************************************************** PLAY RECAP **************************************************************************************** cli1.domain.tech : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Итак, мы проверяем что хосты пингуются и что на них по указанному пути существует директория. Теперь мы хотим, чтобы внутри этой директории располагался файл с конфигурацией нашего приложения. Изменим playbook под это: ```yaml --- - name: "ping hosts" hosts: "all" tags: [check] tasks: - ping: - name: "ensure direcories" hosts: "all" tags: [dirs] tasks: - file: state: directory path: "/opt/test" - name: "render config file" hosts: "all" tags: [configs] tasks: - copy: dest: "/opt/test/config.yaml" src: "config.yaml" ``` ```yaml # config.yaml --- app: name: project env: prod secret: s3cr37 ``` После проигрывания playbook, файл будет лежать по пути copy.dest на каждом хосте, для которого был сыгран play “render config file”. Но хранить секреты в явновном виде - очевидно плохая идея; к счастью Ansible умеет шифровать и расшифровывать секреты, для этого существует утилита `ansible-vault`. Для начала, нам необходим ключ шифрования, в качестве примера созданим простой ключ: ```bash $ openssl rand -hex 8 > .vault ``` и зашифруем с его помощью секреты в `config.yaml`: ```bash $ ANSIBLE_VAULT_PASSWORD_FILE=.vault cat config.yaml | yq '.app.secret' | tr -d "\n" | ansible-vault encrypt Encryption successful $ANSIBLE_VAULT;1.1;AES256 36633139366237646539356365376435353066363637663963353737333561386461643834663861 3063303333393837633135636430313236653432336333640a623036613933373561313834626437 36303133326330613030646437366534353866653966373132653363306539346431313962336162 3333663466303139360a663837643633353837613961376535663837306161663232373137383030 3236 ``` Все, начиная с `$ANSIBLE_VAULT` включительно - зашифрованный ключ, который достаточно вставить в `config.yaml` через указание `!vault |`: ```yaml --- app: name: project env: prod secret: !vault | $ANSIBLE_VAULT;1.1;AES256 36633139366237646539356365376435353066363637663963353737333561386461643834663861 3063303333393837633135636430313236653432336333640a623036613933373561313834626437 36303133326330613030646437366534353866653966373132653363306539346431313962336162 3333663466303139360a663837643633353837613961376535663837306161663232373137383030 3236 ``` Сыграем наш playbook, и зайдем проверить что получилось: ```bash [[email protected] test]$ cat config.yaml --- app: name: project env: prod secret: !vault | $ANSIBLE_VAULT;1.1;AES256 36633139366237646539356365376435353066363637663963353737333561386461643834663861 3063303333393837633135636430313236653432336333640a623036613933373561313834626437 36303133326330613030646437366534353866653966373132653363306539346431313962336162 3333663466303139360a663837643633353837613961376535663837306161663232373137383030 3236 ``` Секрет остался зашифрованным - логично, ведь нужно явно указать в playbook о необходимости его расшифровки. Обновим наш playbook: ```yaml --- - name: "ping hosts" hosts: "all" tags: [check] tasks: - ping: - name: "ensure direcories" hosts: "all" tags: [dirs] tasks: - file: state: directory path: "/opt/test" - name: "read config file template and render" hosts: "all" tags: [configs] tasks: - name: "config | read config file template" include_vars: file: config.yaml name: config_yml - name: "config | render config file" copy: dest: "/opt/test/config.yaml" content: "---\n{{ config_yml | string | from_yaml | to_nice_yaml(indent=2) }}" ``` И сыграем его, не забыв указать путь по ключа в переменной окружения `ANSIBLE_VAULT_PASSWORD_FILE`, которая указывает на файл ключа, с помощью которого, секрет будет расшифрован: ```bash $ ANSIBLE_VAULT_PASSWORD_FILE=.vault ansible-playbook ping.yaml -i project.hosts -l cli_preprod -b -t=configs PLAY [ping hosts] ************************************************************************************************* PLAY [ensure direcories] ****************************************************************************************** PLAY [read config file template and render] *********************************************************************** TASK [Gathering Facts] ******************************************************************************************** ok: [cli1.domain.tech] TASK [config | read config file template] ************************************************************************* ok: [cli1.domain.tech] TASK [config | render config file] ******************************************************************************** changed: [cli1.domain.tech] PLAY RECAP ******************************************************************************************************** cli1.domain.tech : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` и проверим результат: ```bash [[email protected] test]$ cat config.yaml --- app: env: prod name: project secret: s3cr37 ``` Готово - так, мы можем хранить секреты прямо в репозитории и расшифровывать их непосредственно при развертывании конфигурации приложения. # Итог Безусловно, здесь затронута только самая верхушка айсберга - ни слова про `gathering_facts`, `run_once`, `when`, `handlers` и тонну других полезных вещей, но это а) куда как лучше и правильнее описано в официальной документации и б) не нужно в рамках 101, ведь главной целью было дать лишь основные представления об Ansible и его применении для конечного разработчика. RTMF, folks!