Задача – установить NGINX на удалённый хост с помощью Ansbile.
Содержание
Подготовка
На рабочей машине – устанавливаем Ansible:
[simterm]
$ sudo pacman -S ansible
[/simterm]
В Jenkins – будем использовать Docker образ (или плагин).
Проверяем:
[simterm]
$ ansible --version ansible 2.3.2.0 config file = /etc/ansible/ansible.cfg configured module search path = Default w/o overrides python version = 2.7.13 (default, Jul 21 2017, 03:24:34) [GCC 7.1.1 20170630]
[/simterm]
Создание Inventory
Пока есть только один Dev сервер, позже добавим Production.
Документация по Ansbile Inventory – тут>>>.
В корне репозитория создаём каталоги:
[simterm]
$ mkdir -p ansible/jm-monitoring $ cd ansible/jm-monitoring/
[/simterm]
Переходим в него, и создаём файл hosts
в который добавляем URL-ы серверов:
[dev] dev.monitor.domain.ms [production] monitor.domain.ms
hosts
у меня в INI, хотя остальные конфиги в YAML – Ansible позволяет использовать оба формата одновременно.
Тут же добавляем переменную user
, что бы не указывать его каждый раз (по умолчанию – Ansbile использует имя пользователя, от которого запускается):
[dev] dev.monitor.domain.ms ansible_connection=ssh ansible_user=jmadmin [production] monitor.domain.ms
Далее – указываем Ansible где искать файл hosts
. Создаём файл ansible.cfg
с блоком [defaults]
:
[defaults] inventory = hosts
Выполнение команд
Теперь, когда inventory создан – можно начать выполнение команд на хостах.
Попробуем пропинговать Dev – используем модуль ping
и с помощью опции --private-key
– путь к RSA ключу сервера:
[simterm]
$ ansible dev -m ping --private-key=../../.ssh/jm-monitoring dev.monitor.domain.ms | SUCCESS => { "changed": false, "ping": "pong" }
[/simterm]
Что бы выполнить произвольную команду на хосте, без использования модуля – используем -a
:
[simterm]
$ ansible dev -a uptime --private-key=../../.ssh/jm-monitoring dev.monitor.domain.ms | SUCCESS | rc=0 >> 14:03:49 up 31 min, 2 users, load average: 0.00, 0.01, 0.00
[/simterm]
Playbook
Документация по плейбукам – тут>>>, и когда-то переводил вот тут>>>.
Создаём файл плейбука с именем provision.yml
:
- hosts: all become: true become_method: sudo roles: - nginx
Тут:
... become: true ...
Выполнять от root
.
... become_method: sudo ...
Использовать sudo
(хотя sudo
используется по умолчанию).
Roles
В provision.yml
мы добавили роль nginx
, которая будет влючать в себя установку NGINX.
Позже – добавим роли для установки Prometheus, Docker, настройки окружения и т.д.
Для каждой роли – Ansible будет искать стандартный набор каталогов – files
, tasks
, handlers
и т.д.
Для nginx
роли создаём каталог tasks
– пока этого хватит:
[simterm]
$ mkdir -p roles/nginx/tasks
[/simterm]
Структура сейчас выглядит так:
[simterm]
$ tree -d 1 roles/ 1 [error opening dir] roles/ └── nginx └── tasks
[/simterm]
В каждом из каталогов Ansible будет искать файл main.yml
– создаём файл roles/nginx/tasks/main.yml
и описываем в нём задачу:
- name: Install Nginx package: name: nginx state: latest
Тут мы используем модуль package
, который используется менеджер пакетов ОС.
Запускаем плейбук на выполнение.
Используем --limit
, что бы указать хосты (тут – dev, один сервер из hosts
файла созданного ранее), и --private-key
, как ранее с absible
:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.monitor.domain.ms] TASK [nginx : Install Nginx] **** changed: [dev.monitor.domain.ms] PLAY RECAP **** dev.monitor.domain.ms : ok=2 changed=1 unreachable=0 failed=0
[/simterm]
Готово.
Проверяем состояние сервиса:
[simterm]
$ ansible dev -a 'systemctl status nginx.service' --private-key=../../.ssh/jm-monitoring dev.monitor.domain.ms | SUCCESS | rc=0 >> ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2017-09-19 15:05:06 UTC; 2min 51s ago Main PID: 7198 (nginx) CGroup: /system.slice/nginx.service ├─7198 nginx: master process /usr/sbin/nginx -g daemon on; master_process on └─7199 nginx: worker process
[/simterm]
Проверяем состояние NGINX:
[simterm]
$ curl -sI dev.monitor.domain.ms HTTP/1.1 200 OK Server: nginx/1.10.3 (Ubuntu) Date: Tue, 19 Sep 2017 15:07:03 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 19 Sep 2017 15:05:05 GMT Connection: keep-alive ETag: "59c13221-264" Accept-Ranges: bytes
[/simterm]
Переменные
Следующим шагом – требуется добавить файл настроек виртуалхоста. Реализовываться это будет через templates, но перед тем, как приступить к их написанию – необходимо добавить некоторые переменные, которые будут использоваться для наполнения шаблонов.
Переменные в Ansible могут быть заданы в плейбуке, в inventory-файле, в tasks
. Важную роль играет приоритет и порядок задания переменных. Подробнее – тут>>>.
Ещё один момент – это область действия переменных:
- Global: глобальные переменные, заданные через
ansible.cfg
, переменные окружения и командную строку - Play: переменные, определённые для плейбуков и ролей
- Host: и переменные для хостов, заданные через inventory или
tasks
Создаём каталог vars
:
[simterm]
$ mkdir roles/nginx/vars
[/simterm]
Для файла nginx.conf
роли nginx
пока зададим такие переменные в файле main.yml
:
listen_http: 80 listen_https: 443 nginx_user: www-data worker_processes: auto worker_connections: 512
Это – глобальные переменные роли nginx
, значения которых будут одинаковы на всех хостах с NGINX.
Пока с переменными всё – переходим к шаблону.
Templates
Шаблонизатор Ansible – Jinja2. Документация – тут>>>.
Нам потребуется два шаблона – nginx.conf
, что бы переопределить логгирование (добавим log proxy
), и шаблон для виртуалхоста (хотя пока бекендов нет – будем редиректить на Google).
Создаём каталог templates
:
[simterm]
$ mkdir roles/nginx/templates
[/simterm]
В нём создаём файл nginx.conf
:
user {{ nginx_user: }}; worker_processes {{ worker_processes }}; pid /run/nginx.pid; events { worker_connections {{ worker_connections }} ; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; log_format proxy '[$time_local] $remote_addr - $server_name to: ' '$upstream_addr: $request upstream_response_time ' '$upstream_response_time msec $msec request_time $request_time'; gzip on; gzip_disable "msie6"; include /etc/nginx/conf.d/*.conf; }
Собственно – вот наши переменные:
user {{ nginx_user: }};
и т.д.
Далее – настройки виртуалхоста. Т.к. параметры в нём будут зависеть от хоста – добавим переменные в inventory файл, например – адрес upstream
.
В hosts
переменные могут быть добавлены для каждого хоста – Host Variables, например:
[dev] dev.monitor.domain.ms ansible_connection=ssh ansible_user=jmadmin hostname=dev.monitor.domain.ms
Тут мы добавили переменную hostname=dev.monitor.domain.ms
, которую потом используем для конфига виртуалхоста Dev хоста.
Либо для всех хостов в группе dev, Group Variables:
[dev] dev.monitor.domain.ms ansible_connection=ssh ansible_user=jmadmin hostname=dev.monitor.domain.ms [dev:vars] upstream_name-google upstream_url=google.com ...
Добавляем шаблон конфига виртуалхоста:
upstream {{ upstream_name }} { server {{ upstream_url }}; } server { listen {{ listen_http }} ; server_name www.{{ hostname }}; return 301 $scheme://{{ hostname }}$request_uri; } server { server_name {{ hostname }}; listen {{ listen_http }} ; # listen {{ listen_https }} ssl; access_log /var/log/nginx/{{ hostname }}-access.log proxy; error_log /var/log/nginx/{{ hostname }}-error.log notice; # ssl on; # ssl_certificate /etc/letsencrypt/live/{{ hostname }}/fullchain.pem; # ssl_certificate_key /etc/letsencrypt/live/{{ hostname }}/privkey.pem; root /var/www/{{ hostname }}; location ~ /.well-known { allow all; } location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://{{ upstream_name }}$request_uri; } }
Настройки SSL пока закомментированы – добавим их чуть позже.
Возвращаемся к tasks
, и добавляем копирование шаблонов:
- name: Install Nginx package: name: nginx state: latest - name: Replace NGINX config template: src=templates/nginx.conf dest=/etc/nginx/nginx.conf - name: Add NGINX virtualhost config template: src=templates/nginx_vhost.conf dest=/etc/nginx/conf.d/{{ hostname }}.conf
На этом этапе уже можно проверить – всё ли будет работать. Для этого у ansible-playbook
есть опция --check
(dry-run), выполняем:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml --check ERROR! Attempted to read "hosts" as YAML: Syntax Error while loading YAML. The error appears to have been in '/home/setevoy/Work/BER.jm/azure-infrastructure/jm-monitoring/ansible/jm-monitoring/hosts': line 2, column 1, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: [dev] dev.monitor.domain.ms ansible_connection=ssh ansible_user=jmadmin hostname=dev.monitor.domain.ms ^ here Attempted to read "hosts" as ini file: hosts:5: Expected key=value, got: upstream_name-google
[/simterm]
Эм…
А!
... [dev:vars] upstream_name-google ...
Тире вместо =.
Ещё раз:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml --check PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.monitor.domain.ms] TASK [nginx : Install Nginx] **** ok: [dev.monitor.domain.ms] TASK [nginx : Replace NGINX config] **** fatal: [dev.monitor.domain.ms]: FAILED! => {"changed": false, "failed": true, "msg": "AnsibleError: template error while templating string: expected token 'end of print statement', got ':'. String: user {{ nginx_user: }};\nworker_processes {{ worker_processes }};\npid /run/nginx.pid;\n\nevents {\n worker_connections {{ worker_connections }} ;\n}\n\nhttp {\n\n sendfile on;\n tcp_nopush on;\n tcp_nodelay on;\n keepalive_timeout 65;\n types_hash_max_size 2048;\n\n include /etc/nginx/mime.types;\n default_type application/octet-stream;\n\n ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n ssl_prefer_server_ciphers on;\n\n access_log /var/log/nginx/access.log;\n error_log /var/log/nginx/error.log;\n\n log_format proxy '[$time_local] $remote_addr - $server_name to: '\n '$upstream_addr: $request upstream_response_time '\n '$upstream_response_time msec $msec request_time $request_time';\n\n gzip on;\n gzip_disable \"msie6\";\n\n include /etc/nginx/conf.d/*.conf;\n}\n\n"} to retry, use: --limit @/home/setevoy/Work/BER.jm/azure-infrastructure/jm-monitoring/ansible/jm-monitoring/provision.retry PLAY RECAP **** dev.monitor.domain.ms : ok=2 changed=0 unreachable=0 failed=1
[/simterm]
Теперь ошибка в шаблоне nginx.conf
:
... String: user {{ nginx_user: }}; ...
Двоеточие.
И ещё раз:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml --check PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.monitor.domain.ms] TASK [nginx : Install Nginx] **** ok: [dev.monitor.domain.ms] TASK [nginx : Replace NGINX config] **** changed: [dev.monitor.domain.ms] TASK [nginx : Add NGINX virtualhost config] **** changed: [dev.monitor.domain.ms] PLAY RECAP **** dev.monitor.domain.ms : ok=4 changed=2 unreachable=0 failed=0
[/simterm]
Теперь всё нормально.
Handlers
После того, как конфиги NGINX будут скопированы на хост – надо указать NGINX на необходимость перечитать файлы настроек – выполнить nginx reload
.
Тут используем handlers (обработчики).
Создаём каталог handlers
:
[simterm]
$ mkdir roles/nginx/handlers
[/simterm]
И файл main.yml
, в котором используем модуль service
:
- name: NGINX reload service: name=nginx state=reloaded
Далее в tasks
требуется указать – когда выполнять reload
, используем notify
.
Возвращаемся к tasks
, добавляем notify
и имя обработчика:
- name: Install Nginx package: name: nginx state: latest - name: Replace NGINX config template: src=templates/nginx.conf dest=/etc/nginx/nginx.conf notify: - NGINX reload - name: Add NGINX virtualhost config template: src=templates/nginx_vhost.conf dest=/etc/nginx/conf.d/{{ hostname }}.conf notify: - NGINX reload
Вроде ОК – выполняем проверку ещё раз:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml --check PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.monitor.domain.ms] TASK [nginx : Install Nginx] **** ok: [dev.monitor.domain.ms] TASK [nginx : Replace NGINX config] **** changed: [dev.monitor.domain.ms] TASK [nginx : Add NGINX virtualhost config] **** changed: [dev.monitor.domain.ms] RUNNING HANDLER [nginx : NGINX reload] **** changed: [dev.monitor.domain.ms] PLAY RECAP **** dev.monitor.domain.ms : ok=5 changed=3 unreachable=0 failed=0
[/simterm]
Хорошо – запускаем на выполнение:
[simterm]
$ ansible-playbook --limit=dev --private-key=../../.ssh/jm-monitoring provision.yml PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.monitor.domain.ms] TASK [nginx : Install Nginx] **** ok: [dev.monitor.domain.ms] TASK [nginx : Replace NGINX config] **** changed: [dev.monitor.domain.ms] TASK [nginx : Add NGINX virtualhost config] **** changed: [dev.monitor.domain.ms] RUNNING HANDLER [nginx : NGINX reload] **** changed: [dev.monitor.domain.ms] PLAY RECAP **** dev.monitor.domain.ms : ok=5 changed=3 unreachable=0 failed=0
[/simterm]
Проверяем файл виртуахоста на сервере:
[simterm]
$ ansible dev -a 'ls -l /etc/nginx/conf.d' --private-key=../../.ssh/jm-monitoring dev.monitor.domain.ms | SUCCESS | rc=0 >> total 4 -rw-r--r-- 1 root root 985 Sep 20 10:00 dev.monitor.domain.ms.conf
[/simterm]
Проверяем как работает редирект NGINX:
[simterm]
$ curl -s dev.monitor.domain.ms | grep google *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px} <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
[/simterm]
Всё отлично.
В принципе – на этом всё.
SSL и files настроим уже в следующий раз.
Структура файлов и каталогов сейчас получается такой:
$ tree . ├── ansible.cfg ├── hosts ├── provision.retry ├── provision.yml └── roles └── nginx ├── handlers │ └── main.yml ├── tasks │ └── main.yml ├── templates │ ├── nginx.conf │ └── nginx_vhost.conf └── vars └── main.yml
Ссылки по теме
Automating Server Setup with Ansible
Installing NGINX and NGINX Plus With Ansible
Provisioning remote machines using Ansible
Ansible: роли (roles) – пример