Имеется стандартный LEMP — NGINX, PHP-FPM.
Приложение — Yii-фреймворк, который деплоится из Jenkins Ansible-ролью с помощью модуля synchronize на хосты в каталог /data/projects/prjectname/frontend/web, который является root в конфиге виртуалхоста NGINX.
Задача: создать возможность деплоя из Jenkins приложения из разных бранчей — на хосте одновременно должны быть задеплоены разные версии приложения, доступ к которым будет определяться через специальный заголовок запросов.
Попробуем реализовать через map: при передаче в запросе заголовка с именем бранча — NGINX должен отдавать root вида /data/projects/prjectname/<BRANCHNAME>/frontend/web, если заголовка нет — то отдаём из /data/projects/prjectname/frontend/web.
Соответственно, и деплой должен выполнятся либо в /data/projects/prjectname/frontend/web, либо в каталог /data/projects/prjectname/<BRANCHNAME>/frontend/web.
Содержание
NGINX map
Обновляем nginx.conf:
...
map $http_ci_branch $app_branch {
default "";
~(.+) $1;
}
...
Проверяем синтаксис:
[simterm]
root@bttrm-dev-app-1:/home/admin# nginx -t nginx: [emerg] unknown "1" variable nginx: configuration file /etc/nginx/nginx.conf test failed
[/simterm]
Проверяем версию NGINX:
[simterm]
root@bttrm-dev-app-1:/home/admin# nginx -v nginx version: nginx/1.10.3
[/simterm]
Обновляем версию.
Удаляем установленный NGINX:
[simterm]
root@bttrm-dev-app-1:/home/admin# apt -y purge nginx
[/simterm]
Добавляем официальный репозиторий:
[simterm]
root@bttrm-dev-app-1:/home/admin# echo "deb http://nginx.org/packages/mainline/debian/ stretch nginx" >> /etc/apt/sources.list root@bttrm-dev-app-1:/home/admin# wget http://nginx.org/keys/nginx_signing.key root@bttrm-dev-app-1:/home/admin# apt-key add nginx_signing.key OK root@bttrm-dev-app-1:/home/admin# apt update
[/simterm]
Устанавливаем из него последнюю версию NGINX:
[simterm]
root@bttrm-dev-app-1:/home/admin# apt -y install nginx
[/simterm]
Проверяем:
[simterm]
root@bttrm-dev-app-1:/home/admin# nginx -v nginx version: nginx/1.17.0 root@bttrm-dev-app-1:/home/admin# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful root@bttrm-dev-app-1:/home/admin# systemctl start nginx
[/simterm]
Возвращаемся к конфигу.
В nginx.conf мы сейчас имеем:
...
underscores_in_headers on;
map $http_ci_branch $app_branch {
default "";
~(.+) $1;
}
...
Тут мы получаем переменную http_ci_branch (которая формируется из заголовка ci_branch, который мы передаём в запросе), и её значение сохраняем в переменной app_branch:
- default — если в
ci_branchне передано ничего, то вapp_branchсохраняем «» - иначе получаем значение
ci_branch((.+)), и записываем его вapp_branch
Для использования символа «_» в именах заголовков — включаем их поддержку с помощью «underscores_in_headers on;«.
Далее, обновляем конфиг виртуалхоста — добавляем подстановку значения переменной $app_branch в root виртуалхоста:
...
set $root_path /data/projects/projectname/$app_branch/frontend/web;
root $root_path;
...
Проверяем.
Создаём второй каталог — «develop«:
[simterm]
root@bttrm-dev-app-1:/home/admin# mkdir -p /data/projects/projectname/develop/frontend/web/
[/simterm]
Теперь у нас есть два каталога — /data/projects/projectname/frontend/web/ и /data/projects/projectname/develop/frontend/web/.
Первый отдаём, если в ci_branch не будет ничего, а второй — в случае, если в ci_branch приходит значение develop.
Содержимое файлов практически одинаковое.
Корневой каталог:
[simterm]
root@bttrm-dev-app-1:/etc/nginx# cat /data/projects/projectname/frontend/web/index.php
Root
<?php
$headers = getallheaders();
foreach($headers as $key=>$val){
echo $key . ': ' . $val . '<br>';
}
?>
[/simterm]
И каталог для develop:
[simterm]
root@bttrm-dev-app-1:/etc/nginx# cat /data/projects/projectname/develop/frontend/web/index.php
Develop
<?php
$headers = getallheaders();
foreach($headers as $key=>$val){
echo $key . ': ' . $val . '<br>';
}
?>
[/simterm]
Проверяем.
Сначала без заголовка:
[simterm]
$ curl https://dev.example.com/ Root Accept: */*<br>User-Agent: curl/7.65.1<br>X-Amzn-Trace-Id: Root=1-5d1205f9-66ee8ea4b58b3400e02ecac4<br>Host: dev.example.com<br>X-Forwarded-Port: 443<br>X-Forwarded-Proto: https<br>X-Forwarded-For: 194.183.169.27<br>Content-Length: <br>Content-Type: <br>
[/simterm]
И с заголовком:
[simterm]
$ curl -H "ci_branch:develop" https://dev.example.com/ Develop Ci-Branch: develop<br>Accept: */*<br>User-Agent: curl/7.65.1<br>X-Amzn-Trace-Id: Root=1-5d120606-7e42cd0b419e20071d7f8a97<br>Host: dev.example.com<br>X-Forwarded-Port: 443<br>X-Forwarded-Proto: https<br>X-Forwarded-For: 194.183.169.27<br>Content-Length: <br>Content-Type: <br>
[/simterm]
Окей — тут всё работает.
Ansible deploy
Что дальше?
Отдавать данные из разных каталогов с помощью NGINX получилось — теперь надо обновить деплой, что бы загружать данные в правильные каталоги.
Сейчас основная задача роли deploy в Ansible выглядит так:
...
- name: "Deploy application to the {{ aws_env }} environment hosts"
synchronize:
src: "app/"
dest: "/data/projects/{{ backend_project_name }}"
use_ssh_args: true
delete: true
rsync_opts:
- "--exclude=uploads"
...
backend_project_name передаётся в роль из плейбука:
...
- role: deploy
tags: deploy
backend_prodject_git_branch: "{{ lookup('env','APP_REPO_BRANCH') }}"
backend_project_git_repo: "{{ lookup('env','APP_REPO_RUL') }}"
backend_project_name: "{{ lookup('env','APP_PROJECT_NAME') }}"
when: "'backend-bastion' not in inventory_hostname"
Что бы новая схема заработала — надо в dest: "/data/projects/{{ backend_project_name }}" добавить ещё один каталог, но с условиями:
- применять его только на Dev или Staging окружении
- применять только если имя бранча != develop, т.к. develop является default-бранчём для Dev и Staging, и из develop-бранча код должен деплоиться напрямую в корень
/data/projects/{{ backend_project_name }}
Добавляем set_fact в плейбук роли:
...
- set_fact:
backend_branch: "{{ lookup('env','APP_REPO_BRANCH') }}"
when: "'develop' not in lookup('env','APP_REPO_BRANCH') and 'production' not in env"
...
Но в таком случае, если роль будет вызвана на Production или с бранчём develop — переменная backend_branch не будет задана вообще.
Задаём ей дефолтное значение «» — обновляем group_vars/all.yml:
... backend_branch: "" ...
Теперь она будет сначала задана с наименьшим приоритетом со значением «» (см. приоритеты тут>>>), а затем, если условие when в самой задаче с set_fact сработает — то оно перезапишет значение с реальным именем бранча.
Обновляем таски деплоя — добавляем обновление переменной {{ backend_branch }} и проверку её значения:
...
- set_fact:
backend_branch: "{{ lookup('env','APP_REPO_BRANCH') }}"
when: "'develop' not in lookup('env','APP_REPO_BRANCH') and 'production' not in env"
- name: Test task
debug:
msg: "Backend branch: {{ backend_branch }}"
- name: Test task
debug:
msg: "Deploy dir: {{ web_data_root_prefix }}/{{ backend_project_name }}/{{ backend_branch }}"
- meta: end_play
...
Проверяем — задачём переменную APP_REPO_BRANCH="blabla" и переменную с именем приложения:
[simterm]
$ export APP_PROJECT_NAME=projectname $ export APP_REPO_BRANCH="blabla"
[/simterm]
Запускаем скрипт:
[simterm]
$ ./ansible_exec.sh -t deploy
...
TASK [deploy : Test task] ****
ok: [dev.backend-app1-internal.example.com] => {
"msg": "Backend branch: blabla"
}
ok: [dev.backend-app2-internal.example.com] => {
"msg": "Backend branch: blabla"
}
ok: [dev.backend-console-internal.example.com] => {
"msg": "Backend branch: blabla"
}
skipping: [dev.backend-bastion.example.com]
TASK [deploy : Test task] ****
ok: [dev.backend-app1-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/blabla"
}
ok: [dev.backend-app2-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/blabla"
}
ok: [dev.backend-console-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/blabla"
}
...
[/simterm]
Окей.
Теперь меняем бранч на develop:
[simterm]
$ export APP_REPO_BRANCH="develop"
$ ./ansible_exec.sh -t deploy
...
TASK [deploy : Test task] ****
ok: [dev.backend-app1-internal.example.com] => {
"msg": "Backend branch: "
}
ok: [dev.backend-app2-internal.example.com] => {
"msg": "Backend branch: "
}
ok: [dev.backend-console-internal.example.com] => {
"msg": "Backend branch: "
}
skipping: [dev.backend-bastion.example.com]
TASK [deploy : Test task] ****
ok: [dev.backend-app1-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/"
}
ok: [dev.backend-app2-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/"
}
ok: [dev.backend-console-internal.example.com] => {
"msg": "Deploy dir: /data/projects/projectname/"
}
...
[/simterm]
Вроде работает?
Обновляем таску деплоя, добавляем {{ backend_branch }} к пути:
...
- name: "Deploy application to the {{ aws_env }} environment hosts"
synchronize:
src: "app/"
dest: "/data/projects/{{ backend_project_name }}/{{ backend_branch }}"
use_ssh_args: true
delete: true
rsync_opts:
- "--exclude=uploads"
...
Деплоим из Jenkins, бранч приложения — sentinel-cache-client:
Проверяем каталоги на сервере:
[simterm]
root@bttrm-dev-app-1:/etc/nginx# ll /data/projects/projectname/ total 1380 drwxr-xr-x 14 projectname projectname 4096 Jun 13 17:19 backend -rw-r--r-- 1 projectname projectname 167 Jun 13 17:19 codeception.yml ... -rw-r--r-- 1 projectname projectname 5050 Jun 13 17:19 requirements.php drwxr-xr-x 11 projectname projectname 4096 Jun 25 17:29 sentinel-cache-client drwxr-xr-x 3 projectname projectname 4096 Jun 13 17:22 storage -rw-r--r-- 1 root root 5 Jun 25 17:29 test.txt -rw-r--r-- 1 projectname projectname 2624 Jun 13 17:19 Vagrantfile ...
[/simterm]
Каталог sentinel-cache-client появился, отлично.
И в нём — тоже содержимое, только из бранча sentinel-cache-client:
[simterm]
root@bttrm-dev-app-1:/etc/nginx# ll /data/projects/projectname/sentinel-cache-client/ total 1380 drwxr-xr-x 14 projectname projectname 4096 Jun 13 17:19 backend -rw-r--r-- 1 projectname projectname 167 Jun 13 17:19 codeception.yml drwxr-xr-x 13 projectname projectname 4096 Jun 13 17:19 common -rw-r--r-- 1 projectname projectname 2551 Jun 13 17:19 composer.json -rw-r--r-- 1 projectname projectname 222276 Jun 13 17:19 composer.lock drwxr-xr-x 9 projectname projectname 4096 Jun 13 17:19 console drwxr-xr-x 6 projectname projectname 4096 Jun 13 17:19 docker ...
[/simterm]
Проверим.
В каталоге /data/projects/projectname/sentinel-cache-client/ создаём тестовый файлик:
[simterm]
root@bttrm-dev-app-1:/etc/nginx# echo "sentinel-cache-client" > /data/projects/projectname/sentinel-cache-client/frontend/web/test.php
[/simterm]
Проверяем без заголовка:
[simterm]
$ curl https://dev.example.com.com/test.php <html> <head><title>404 Not Found</title></head> ...
[/simterm]
И с заголовком бранча:
[simterm]
$ curl -H "ci_branch:sentinel-cache-client" https://dev.example.com.com/test.php sentinel-cache-client
[/simterm]
Всё работает.
Готово.





