Задача — установить 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) – пример




