Ansible: миграция RTFM 2.9 – монтирование EBS и настройка NGINX на Bastion

Автор: | 03/02/2018
 

Предыдущий пост серии – Ansible: миграция RTFM 2.8 – logrotate, unattended-upgrades и Let’s Encrypt для Bastion хоста.

Сейчас Bastion запущен и настроен, сегодня надо выполнить настройку NGINX.

Как и прежние посты этой серии – это скорее заметки для себя по выполненным обновлениям плюс примеры настроек и действий.

План таков:

  1. обновить роль common и добавить монтирование диска для Bastion с данными для NGINX
  2. обновить nginx роль для копирования и использования конфигов из этого каталога

Ссылки на коммиты файлов, которые получились в результате написания этого поста:

По первому пункту: к Bastion во время создания окружения подключается EBS раздел, который будет содержать конфиги виртуалхостов.

Сейчас этот раздел пустой – ещё ничего не делалось:

[simterm]

root@dev:/home/admin# lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdb    202:16   0   8G  0 disk

[/simterm]

/dev/xvdb – вот он.

Вообще хочется на Bastion использовать OpenBSD – люблю я *BSD ещё со времён FreeBSD 6.1, но пока закончить бы с настройкой вообще, а потом уже просто (угу…) обновить роли. Пока на всех хостах будет Debian.

Создание раздела на /dev/xvdb

Начнём с диска.

Создаём раздел:

[simterm]

root@dev:/home/admin# echo ';' | sfdisk /dev/xvdb
Checking that no-one is using this disk right now ... OK

Disk /dev/xvdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

>>> Created a new DOS disklabel with disk identifier 0x54a32780.
/dev/xvdb1: Created a new partition 1 of type 'Linux' and of size 8 GiB.
/dev/xvdb2: Done.

New situation:

Device     Boot Start      End  Sectors Size Id Type
/dev/xvdb1       2048 16777215 16775168   8G 83 Linux

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

[/simterm]

Проверям:

[simterm]

root@dev:/home/admin# lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdb    202:16   0   8G  0 disk 
└─xvdb1 202:17   0   8G  0 part

[/simterm]

Создаём файловую систему:

[simterm]

root@dev:/home/admin# mkfs.ext4 /dev/xvdb1
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 2096896 4k blocks and 524288 inodes
Filesystem UUID: 1a063fba-dc1c-4483-b36a-d16e4de00eac
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

[/simterm]

Добавляем каталог /data (сейчас вручную, потом это будет выполняться Ansible):

[simterm]

root@dev:/home/admin# mkdir /data

[/simterm]

Монтируем раздел:

[simterm]

root@dev:/home/admin# mount /dev/xvdb1 /data/

[/simterm]

Проверяем, пока тут ничего:

[simterm]

root@dev:/home/admin# ls -l /data/
total 16
drwx------ 2 root root 16384 Feb  3 13:44 lost+found

[/simterm]

Ручная настройка NGINX

Создаём каталог для конфигов NGINX:

[simterm]

root@dev:/home/admin# mkdir /data/data-nginx.d

[/simterm]

Перемещаем файл настроек виртуалхоста

[simterm]

root@dev:/home/admin# mv /etc/nginx/conf.d/dev.rtfm.co.ua.conf /data/data-nginx.d/

[/simterm]

Обновляем /etc/nginx/nginx.conf, добавляем этот каталог:

...
        gzip on;
        gzip_disable "msie6";

        include /etc/nginx/conf.d/*.conf;
        include /data/data-nginx.d/*.conf;
}

Проверяем, перечитываем конфиги:

[simterm]

root@dev:/home/admin# nginx -t && service nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

[/simterm]

Проверяем доступность хоста:

[simterm]

$ curl -I dev.rtfm.co.ua
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Sat, 03 Feb 2018 13:52:09 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Mon, 29 Jan 2018 16:40:45 GMT
Connection: keep-alive
ETag: "5a6f4e8d-264"
Accept-Ranges: bytes

[/simterm]

Ansible – разделение хостов

Теперь надо всё это вынести в роли Ansible.

Со времени последнего поста файл hosts немного изменился – добавился DB хост и команда для SSH для доступа к нему через Bastion хост (см. пост Ansible: подключение в приватную сеть через Bastion хост):

[rtfm-bastion-dev]
dev.rtfm.co.ua

[rtfm-db-dev]
db.dev.rtfm.co.ua

[rtfm-db-dev:vars]
ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p -q [email protected] -i {{ host_key }}"'

Теперь пора разделить задачи – часть будет выполняться на всех (logrotate, junattended-upgrades, common), часть – только на Bastion, DB или Services.

Сейчас в Jenkins это выполняется через --limit--limit=${ansibleHostLimit} (скрипт полностью тут>>>):

...
sh "set +x && ansible-playbook --limit=${ansibleHostLimit} --extra-vars api_key=${AMPLIFY_API_KEY} --private-key=credentials/${ansiblePemFile} ${ansiblePlaybookFile}"
...

Наверно лучше будет сделать отдельные плейбуки для каждого из трёх хостов: в --limit будет передаваться Dev или Production, а в переменной ${ansiblePlaybookFile} – плейбук для Bastion, DB или Services.

Т.е., для выполнения на Dev Bastion – команда будет выглядеть так:

[simterm]

$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml

[/simterm]

А для Production Bastion – так:

[simterm]

$ ansible-playbook --limit=rtfm-bastion-production --private-key=../../Bitbucket/aws-credentials/rtfm-production.pem rtfm-blog-ansible-bastion-provision.yml

[/simterm]

Dev и Prod будут иметь в hosts свои переменные.

И заодно переименуем файл плейбука:

[simterm]

$ git mv rtfm-blog-ansible-provision.yml rtfm-blog-ansible-bastion-provision.yml

[/simterm]

Переменные хостов и монтирование диска

Что бы Ansbile монтировал определённый раздел – в hosts добавим переменную data_volume_id для хоста rtfm-bastion-dev, в которой укажем раздел:

[rtfm-bastion-dev]
dev.rtfm.co.ua

[rtfm-bastion-dev:vars]
data_volume_id="/dev/xvdb1"
...

Каталог для монтирования у всех хостов будет один – /data, поэтому добавим блок [all:vars] в котором определим путь:

[all:vars]
data_volume_mount_path="/data"
...

Монтирование раздела можно добавить в роль common, выносим её в начало списка плейбука rtfm-blog-ansible-bastion-provision.yml:

- hosts: all
  become:
    true
  roles:
    - role: common
#    - role: thefinn93.letsencrypt
#      letsencrypt_email: [email protected]
#      letsencrypt_cert_domains: 
#        - "{{ inventory_hostname }}"
#      letsencrypt_webroot_path: /var/www/html/
#      letsencrypt_renewal_command_args: '--renew-hook "systemctl restart nginx"'
    - role: nginx
    - role: amplify
...

(с Let’s Encrypt пока не придумал, что делать, т.к. домен будет не один и светить их все в репозитории не хочется – пусть будет закомментирован)

Обновляем роль common, в файл tasks/main.yml добавляем монтирование диска:

- name: Mount data-volume "{{ data_volume_id }}" to "{{ data_volume_mount_path }}"
  mount:
    path: "{{ data_volume_mount_path }}"
    src: "{{ data_volume_id }}"
    state: mounted
    fstype: ext4
...

Проверяем синтаксис:

[simterm]

$ ansible-playbook --syntax-check --limit=rtfm-bastion-dev rtfm-blog-ansible-bastion-provision.yml 

playbook: rtfm-blog-ansible-bastion-provision.yml

[/simterm]

На Dev хосте создадим тестовый файлик:

[simterm]

root@dev:/home/admin# touch /data/data-nginx.d/test.file

[/simterm]

Отмонтируем раздел:

[simterm]

root@dev:/home/admin# umount /data/
root@dev:/home/admin# ls -l /data/ 
total 0

[/simterm]

Запускаем выполнение:

[simterm]

$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml

PLAY [all] ****

TASK [Gathering Facts] ****
ok: [dev.rtfm.co.ua]

TASK [common : Mount data-volume "/dev/xvdb1" to "/data"] ****
changed: [dev.rtfm.co.ua]
...

[/simterm]

Проверяем на хосте:

[simterm]

root@dev:/home/admin# ls -l /data/
total 20
drwxr-xr-x 2 root root  4096 Feb  3 14:38 data-nginx.d
drwx------ 2 root root 16384 Feb  3 13:44 lost+found
root@dev:/home/admin# ls -l /data/data-nginx.d/
total 4
-rw-r--r-- 1 root root 324 Feb  3 13:49 dev.rtfm.co.ua.conf
-rw-r--r-- 1 root root   0 Feb  3 14:38 test.file

[/simterm]

ОК, всё работает.

Обновление Jenkins

Что бы не забыть – сразу обновим Jenkins.

Сейчас задача одна, называется rtfm-blog-dev-ansible-provision – переименуем в rtfm-blog-bastion-dev-ansible-provision.

Затем обновим ANSIBLE_HOST_LIMIT со старого значения rtfm-dev на rtfm-bastion-dev (как в обновлённом hosts), и обновим имя файла плейбука с rtfm-blog-ansible-provision.yml на rtfm-blog-ansible-bastion-provision.yml:

Наверно – можно сейчас запушить все изменения.

Просмотреть лог красиво можно так:

[simterm]

$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

[/simterm]

(нагуглено тут>>>)

Пушим:

[simterm]

$ git push

[/simterm]

И проверяем билд:

Создание каталога /data и обновление роли nginx

Сейчас всё работает, т.к. сервер и каталог уже есть.

Если удалить стек и запустить создание всего с нуля – билд упадёт, т.к. каталога /data на сервере не будет.

На Dev хосте проверяем права на каталог:

[simterm]

root@dev:/home/admin# stat -c "%a %n" /data/
755 /data/

[/simterm]

Добавляем его создание в ту же роль common, перед монтированием диска:

- name: Create "{{ data_volume_mount_path }}" directory
  file:
    path: "{{ data_volume_mount_path }}"
    state: directory
    mode: 0755
    recurse: yes

- name: Mount data-volume "{{ data_volume_id }}" to "{{ data_volume_mount_path }}"
  mount:
...

В роли nginx меняем путь копирования файлов.

Сейчас задача выглядит так:

...
- name: Add NGINX {{ inventory_hostname }} virtualhost config
  template:
    src=templates/virtualhosts/{{ inventory_hostname }}.conf
    dest=/etc/nginx/conf.d/{{ inventory_hostname }}.conf
...

Меняем dest с  /etc/nginx/conf.d/ на {{ data_volume_mount_path }}/data-nginx.d/:

...
- name: Add NGINX {{ inventory_hostname }} virtualhost config
  template:
    src=templates/virtualhosts/{{ inventory_hostname }}.conf
    dest={{ data_volume_mount_path }}/data-nginx.d/{{ inventory_hostname }}.conf
...

Проверяем:

[simterm]

$ !810
ansible-playbook --syntax-check --limit=rtfm-bastion-dev rtfm-blog-ansible-bastion-provision.yml 

playbook: rtfm-blog-ansible-bastion-provision.yml

[/simterm]

(!810 – повторить из history команд)

Обновляем шаблон roles/nginx/templates/nginx.conf:

...
        include /etc/nginx/conf.d/*.conf;
        include {{ data_volume_mount_path }}/data-nginx.d/*.conf;
}

Запускаем:

[simterm]

$ !813
ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml
...

[/simterm]

Проверяем:

[simterm]

root@dev:/home/admin# cat /etc/nginx/nginx.conf | grep include
        include /etc/nginx/mime.types;
        include /etc/nginx/conf.d/*.conf;
        include /data/data-nginx.d/*.conf;

[/simterm]

Ну – вроде всё…

Для полной проверки – удаляем весь CloudFormation стек:

[simterm]

$ aws cloudformation delete-stack --stack-name rtfm-dev

[/simterm]

Создаём его заново из Jenkins задачи:

И повторяем Ansible provision:

Проверяем:

[simterm]

$ curl -I dev.rtfm.co.ua
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Sat, 03 Feb 2018 15:40:28 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Sat, 03 Feb 2018 15:38:36 GMT
Connection: keep-alive
ETag: "5a75d77c-264"
Accept-Ranges: bytes

[/simterm]

Готово – на сегодня хватит 🙂

Что ещё надо сделать на Bastion:

  1. Let’s Encrypt
  2. exim config
  3. hostname config
  4. before-after reboot notify
  5. .bashrc, .vimrc
  6. dnsutils
  7. simple-backup