Продолжаем миграцию.
Предыдущие посты:
- AWS: миграция RTFM 2.1 – CloudFormation для EC2 c Jenkins
- Ansible: миграция RTFM 2.2 – RTFM Jenkins provision
- AWS: миграция RTFM 2.3 – инфраструктура для RTFM и создание CloudFormation шаблона – VPC, subnets, EC2
- Jenkins: миграция RTFM 2.4 – Jenkins Pipeline для CloudFormation RTFM стека
- AWS: миграция RTFM 2.5 – настройка NAT на Bastion EC2 как замена NAT Gateway
- Jenkins: миграция RTFM 2.6 – Jenkins Pipeline для Ansible
- AWS: миграция RTFM 2.7 – CloudFormation и Ansible – наcтройка NAT
- Ansible: миграция RTFM 2.8 – logrotate, unattended-upgrades и Let’s Encrypt для Bastion хоста
- Ansible: миграция RTFM 2.9 – монтирование EBS и настройка NGINX на Bastion
- Ansible: миграция RTFM 2.10 – Let’s Encrypt, NGINX SSL, hostname и exim
После последнего поста по теме — в конце февраля я сделал ещё несколько минорных изменений и по закончил по сути с хостом Bastion — добавил всякую мелочь в роль common
.vimrc
и настройки email для root, файл на начало написания этого поста выглядел
Теперь можно приступить к настройке первого из «внутренних» серверов — начну с Services, т.к. он будет основным из всех трёх по количеству служб, которые на нём будут работать.
Сегодня планируется выполнить:
- монтирование data-диска для EC2 Services
- создать плейбук для Services, добавить общие роли
- добавить роль для установки PHP-FPM
В целом схема сейчас вырисовывается такая:
(забегая наперёд: нет — пришлось прикручивать AWS EFS)
Получившаяся в результате роль есть в Гитхабе, пока в отдельном бранче,
Содержание
data-disk
Сейчас EBS подключен (во время создания CloudFormation стека), но раздела на нём ещё нет:
Создаём раздел:
Создаём файловую систему:
Проверяем:
С диском на этом пока всё — остальное выполнит Ansbile.
Services inventory
Домен services.dev.rtfm.co.ua уже добавлен, и направлен на IP в приватной сети:
Пока — вручную, у регистратора, Freehost.com.ua, потом rtfm.co.ua мигрирует на AWS Route53, и IP будет задаваться через CloudFormation.
Теперь добавим Services хост в inventory файл — hosts
... [rtfm-services-dev] services.dev.rtfm.co.ua ...
Т.к. доступ к нему будет только через Bastion хост — добавим переменную ansible_ssh_common_args
, подробнее — в посте Ansible: подключение в приватную сеть через Bastion хост:
... [rtfm-services-dev:vars] ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p -q admin@dev.rtfm.co.ua -i {{ host_key }}"' data_volume_id="/dev/xvdb1" set_hostname="rtfm-services-dev"
В data_volume_id
— указываем разел data-диска, см. тут>>>, а в set_hostname
— имя хоста, см. тут>>>.
Проверяем:
Через --extra-vars
передаём переменную, в которой указывается ключ для авторизации на серверах.
ОК, подключение есть — можно создавать плейбук.
Services playbook
Начало плейбука будет аналогично уже созданному для Bastion — добавляем в него роль common
, которая выполнит настройку хоста, можно его вообще скопировать:
Далее в Jenkins будет отдельная задача, в которой через переменную ${ansiblePlaybookFile}
будет передаваться имя файла плейбука rtfm-blog-ansible-services-provision.yml
.
Убираем лишнее, оставляем роли:
common
: подключение data-диска, установка пакетов типаcurl
,mailx
, настройка имени хоста и т.д.exim
: для отправки уведомленийamplify
: NGINX Amplify мониторинг — хотя NGINX тут нет, но будет выводить данные о системе + потом добавится PHP-FPM, а Amplify его умеетmanala.logrotate
: для ротации логов, только меняемnginx
наmysql
jnv.unattended-upgrades
: для апдейтов системы
Получается так:
- hosts: all become: true roles: - role: common notify_email: notify@domain.kiev.ua - role: exim - role: amplify - role: manala.logrotate manala_logrotate_configs: - file: mysql config: - /var/log/mysql/*.log: - size: 100M - missingok - rotate: 5 - compress - delaycompress: - notifempty - create: 0640 mysql root - sharedscripts - daily - postrotate systemctl reload mysql.service - endscript - role: jnv.unattended-upgrades unattended_mail: notify@domain.kiev.ua unattended_automatic_reboot: true unattended_automatic_reboot_time: 05:00 unattended_clean_interval: 10
Ansible: условия выполнения — register
и when
Для роли amplify
тут надо сделать небольшое изменение: т.к. на Services не будет NGINX и каталога /etc/nginx/conf.d
, то задача Copy stub_status.conf будет фейлиться и «ронять» весь провижен.
Что бы избежать этого — обновляем роль amplify
, файл roles/amplify/tasks/main.yml
, добавляем задачу Check /etc/nginx/conf.d directory в которой используем `register` для создания переменной nginx_conf_d
:
... - name: Check /etc/nginx/conf.d directory stat: path: /etc/nginx/conf.d register: nginx_conf_d ...
Следующей задачей тут добавим немного вывода на консоль:
... - debug: msg: "/etc/nginx/conf.d found" when: nginx_conf_d.stat.islnk is defined ...
Собственно — вот условие выполнения:
when: nginx_conf_d.stat.islnk is defined
Если каталог найден — то задача выполняется.
Аналогично применяем условие в Copy stub_status.conf:
... - name: Copy stub_status.conf copy: src: files/stub_status.conf dest: /etc/nginx/conf.d/stub_status.conf when: nginx_conf_d.stat.islnk is defined ...
Проверяем синтаксис:
Что бы не указывать сейчас host_key
каждый раз — закинем его в переменную (в Jenkins будет в параметрах):
И ключ для Amplify-агента:
Запускаем выполнение:

Данные в NGINX Amplify:
Ansible PHP и PHP-FPM
Одним из основных сервисов на Services будет PHP-FPM.
Конфиги будут храниться на подключаемом data-диске, поэтому в роль Ansible надо добавить только его установку и копирование дефолтного файла настроек.
Создадим свою роль для установки и настройки PHP и PHP-FPM:
Добавляем main.yml
, описываем установку пакетов:
- name: Install PHP related packages apt: name={{item}} state=present with_items: - php - php-mysql - php-fpm - php-curl
Актуальная на сегодня версия PHP — 7, все пакеты установятся для неё.
Добавляем роль в rtfm-blog-ansible-services-provision.yml
:
... - role: php-fpm
Запускаем для проверки, потом добавим остальное:
ОК, проверяем на хосте:
Проверяем порты:
Сейчас PHP-FPM слушает только локальные сокеты, но т.к. NGINX и PHP у нас на разных хостах — надо включить TCP порты.
Добавляем конфиг для пула rtfm-dev, создаём каталог в /data
, который находится на подключенном EBS и где будут храниться все конфиги хостов:
В нём добавляем конфиг /data/data-php-fpm.d/dev.rtfm.co.ua.conf
:
[dev.rtfm.co.ua] user = rtfm group = rtfm listen = 10.0.129.188:9000 listen.allowed_clients = 10.0.1.81 listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 ;pm.process_idle_timeout = 10s; ;pm.max_requests = 500 catch_workers_output = yes chdir = / pm.status_path = /status slowlog = /var/log/php/dev.rtfm.co.ua-php-slow.log php_flag[display_errors] = off php_admin_value[display_errors] = 'stderr' php_admin_value[error_log] = /var/log/php/dev.rtfm.co.ua-php-error.log php_admin_flag[log_errors] = on php_admin_value[session.save_path] = /var/lib/php/sessions/rtfm php_admin_value[upload_max_filesize] = 64M php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/sessions
Тут сразу видно — чего не хватает в роли php-fpm
:
- на хосте Services нет пользователя
rtfm
- и нет каталога
/var/log/php/
php7-fpm
не использует конфиги из/data/data-php-fpm.d/
И тут загвоздка… Добавить создание пользователей можно через роль Ansible, но она у меня в Github-е, а светить всех пользователей — идея так себе.
Наверно решение будет передавать через --extra-vars
Python-список, в который передавать список имён пользователей из параметров джобы Jenkins-а, а потом в цикле создавать их?
Попробуем.
Добавляем в roles/php-fpm/tasks/main.yml
:
... - name: Create PHP-FPM users user: name={{ item }} with_items: - "{{ php_users }}"
В rtfm-blog-ansible-services-provision.yml
можно закомментировать всё, кроме - role: php-fpm
, что бы не тратить время на нежуные пока операции.
Запускаем с добавлением переменной php_users
, в которой списком передаём двух (пока) пользователей:
Проверяем на хосте:
ОК, работает.
Вообще надо посмотреть в сторону того, что бы передавать все extra-vars
через файл, который будет храниться в приватном репозитории Bibuket, как это происходит с ключами доступа для инстансов — см. Jenkins: миграция RTFM 2.6 – Jenkins Pipeline для Ansible: там в groovy-скриптах используется переменная ANSIBLE_BB_CREDENTIALS_REPO_URL
, в которой передаётся Bitbuket репозиторий. Jenkins во время выполнения джобы клонирует этот репозиторий к себе, и использует из него ключи доступа.
Аналогично можно будет сделать для всех секретных переменных, но не сейчас. Ссылка на документацию
Возвращаемся к php-fpm
.
Следующим надо добавить создание каталога /var/log/php/
, в роль php-fpm
добавляем:
- name: Create PHP-FPM logs directory file: path=/var/log/php state=directory
И теперь добавить *.conf
файлы из каталога /data/data-php-fpm.d/
в /etc/php/7.0/fpm/php-fpm.conf
.
Используем lineinfile
:
... - name: Add /data/data-php-fpm.d/ to /etc/php/7.0/fpm/php-fpm.conf lineinfile: dest: /etc/php/7.0/fpm/php-fpm.conf insertafter: '^include=/etc/php/7.0/fpm/pool.d/*.conf' line: 'include=/data/data-php-fpm.d/*.conf' state: present
Последним — перезапускаем php-fpm
на хосте:
... - name: php7.0-fpm restart service: name=php7.0-fpm state=restarted
Тут, кстати, сразу возможная бага в будущем: когда unattended-upgrades
обновит PHP-FPM на хосте — имя сервиса поменяется, и при запуске Ansible на этой задаче упадёт провижен. Но будем решать проблемы по мере возникновения, пока хочется закончить с общей настройкой.
Проверяем:
Запускаем:
Проверяем порты:
Пул php-fpm
:
Теперь проверим как NGINX работает с PHP-FPM.
На Services добавляем файл phpinfo.php
:
Переходим на хост Bastion, обновляем конфиг /data/data-nginx.d/dev.rtfm.co.ua.conf
, добавляем апстрим к Services и добавляем location php
, приводим конфиг к такому виду:
upstream services-dev-php-fpm { server 10.0.129.188:9000; } server { listen 80; server_name dev.rtfm.co.ua; return 301 https://dev.rtfm.co.ua$request_uri; } server { server_name dev.rtfm.co.ua; listen 443 ssl; access_log /var/log/nginx/dev.rtfm.co.ua-access.log proxy; error_log /var/log/nginx/dev.rtfm.co.ua-error.log notice; ssl_certificate /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem; ssl_certificate_key /data/letsencrypt/live/dev.rtfm.co.ua/privkey.pem; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; add_header X-Content-Type-Options nosniff; root /var/www/html; index index.html index.htm index.nginx-debian.html; location ~ /.well-known { allow all; } location / { try_files $uri $uri/ =404; } location ~ .php$ { include /etc/nginx/fastcgi_params; fastcgi_index index.php; fastcgi_pass services-dev-php-fpm; fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; } }
Проверяем, перезапускаем:
Проверяем:
Эм… Ну, ОК, — не удивлён 🙂
Пробуем telnet
:
И вспоминаем про Security Group в rtfm-blog-cf-template.json
... "ServicesSecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", ... "GroupDescription" : "Enable SSH access from Bastion", "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "SourceSecurityGroupId" : { "Ref" : "BastionSecurityGroup" }, "Description": "Allow SSH from Bastion" } ...
Открываем быстренько доступ через AWS Console:
Пробуем telnet
ещё раз:
ОК, пробуем файл:
Гуд, работает.
И тут до меня дошло, что часть файлов-то будет обслуживаться NGINX, а часть — PHP-FPM. Более того — NGINX будет искать файлы сначала у себя, и вообще на бекенд должен отдавать только файлы *.php
(налицо недостаточное планирование архитектуры заранее, хотя мысль о файлах мелькала, но решение отложил «на потом»).
А значит что? А значит, что у Services и Bastion должна быть общая расшаренная папка, в которой будут храниться файлы сайтов, какой-то EFS, примонтированный к обоим интансам. А EBS диски, которые подключены в /data
— будут только содержать конфиги (и SSL ключи для Let’s Encrypt на Bastion).
Попробуем сейчас вручную, уже в следующий раз — надо обновить CloudFormation шаблоны — добавить 9000 порт и EFS.
Добавление EFS
Создаём новую Security Group для EFS, к которой есть доступ из групп Bastion-а и Services — она будет подключена к сетевому интерфейсу EFS:
Создаём EFS:
На Bastion и Services устанавливаем nfs-common
:
На обоих создаём каталог:
И на обоих подключаем EFS:
Проверяем — создаём файл с Services:
Проверяем его наличие на Bastion:
(надо в common добавить настройку времени)
Загружаем архив WordPress:
Распаковываем, и переносим всё в /efswptest/
:
Обновляем root
в /data/data-nginx.d/dev.rtfm.co.ua.conf
:
... add_header X-Content-Type-Options nosniff; root /efswptest/; index index.html index.htm index.nginx-debian.html; ...
Перезагружаем конфиги NGINX:
Проверяем:
Вот только меня берут сомнения по поводу быстродействия такой системы — через EFS… Хотя статика планируется быть в AWS CloudFront, так что может быть всё будет не слишком печально.
В общем — будем посмотреть. Проведу нагрузочные тесты, если будет плохо — то придётся PHP-FPM таки запускать на Bastion, на одном хосте с NGINX и пользовать единый EBS раздел, как это есть сейчас, а Services будет для OpenVPN, почты и прочего.
Значит, следующими задачами будут:
- протестировать скорость работы того же WordPress, используя NGINX+PHP-FPM на разных хостах и код в EFS, и оба сервиса и код локально
- если использовать EFS — то добавить создание ресурса EFS и его Secutirty Group в CloudFormation
- если не использовать EFS — то вынести роль PHP в плейбук Bastion-а
rtfm-blog-ansible-bastion-provision.yml
- обновить Jenkins билды — добавить провижен Services
Пока всё.