Имеется стандартный 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]
Всё работает.
Готово.