Ansible и reverse-proxy сервера

от автора


Примерно полгода назад, пришлось разработать схему обратного проксирования сайтов, с многих нод (n>20) на несколько (n<=3) боевых серверов. Недавно столкнулся в аналогичным запросом от коллеги. Поэтому решил поделиться, и все собрать в статью.
Уровень статьи — для начинающих.

Как результат, был необходим простой инструмент для добавления новых нод и обновления перечня доменов.
Профит от такого решения должен быть, при использовании кеширования на сервере, и DNS с геолокацией.

Поиск информации по теме reverse-proxy, часто сводится к статьям по настройке “nginx to apache” (на локальный apache или на удаленный upstream-сервер), CDN-прокси сервисов (cloudflare, *cdn, cloudfront, etc.). В данном случае это не совсем подходило.
Особенность заключается в необходимости предоставлять множество разных IP (из различных географических локаций) для доменов одного-двух серверов.

Для решения задачи были куплены несколько VPS в необходимых различных локациях (дешевые, спасибо lowendbox.com & lowendstock.com, но с необходимым бендвичем).
VPS пока использовались на Centos-6-x32, но как только epel выкатит пакеты для Centos-7 32-bit, будем обновляться.
Все остальные манипуляции с серверам выполняются удаленно, при помощи ansible.

Структура проекта Ansible

В соответствии с принятой практикой, имеем такую файловую структуру:

$ find -type f ./roles/update_os/tasks/main.yml ./roles/update_nginx_configs/tasks/main.yml ./roles/update_nginx_configs/files/proxy.conf ./roles/update_nginx_configs/templates/domain.conf.j2 ./roles/update_nginx_configs/handlers/main.yml ./roles/update_hostname/tasks/main.yml ./ansible.cfg ./hosts ./proxy-nodes.yml 

Пройдемся по всем файлам.

./hosts

[test] localhost ansible_connection=local  [centos:children] proxy-nodes  [proxy-nodes] xxx.xxx.xxx.xxx  ansible_connection=ssh ansible_ssh_user=root  ansible_ssh_pass=xxxxxx node_hostname=proxy-node-001.www.co yyy.yyy.yyy.yyy  ansible_connection=ssh ansible_ssh_user=root  ansible_ssh_pass=yyyyyy node_hostname=proxy-node-010.www.co zzz.zzz.zzz.zzz  ansible_connection=ssh ansible_ssh_user=root  ansible_ssh_pass=zzzzzz node_hostname=proxy-node-029.www.co 

Тут отображена мета-группа [centos] и отдельно указана группа [proxy-nodes] как дочерня группа к [centos].
Структура с расчетом на расширение ролей и задач.

./ansible.cfg

[defaults] pipelining                = True hostfile                  = hosts [ssh_connection] ssh_args                  = -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s control_path              = ~/.ansible/cp/ansible-ssh-%%h-%%p-%%r 

Тут тоже ничего особенного.
Настройка сервера идет отпользователя root, поэтому можно смело включить pipelining.
hostfile — для умешения входных параметров при работе в консоли,
ssh_args — для уменьшения говорливости при релоадах хостов, и настройки persistent connection, среди них самый важный — ControlPath.

ControlPath — лучше один раз увидеть

$ ssh -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o ControlPath=/tmp/habr.socket root@192.168.124.185
Warning: Permanently added ‘192.168.124.185’ (RSA) to the list of known hosts.
root@192.168.124.185’s password:
Last login: Thu Mar 10 22:46:41 2016
[root@test001 ~]# service sshd stop
Stopping sshd: [ OK ]
[root@test001 ~]# exit
logout
Shared connection to 192.168.124.185 closed.
$ ssh -o ControlPath=/tmp/habr.socket root@192.168.124.185
Last login: Thu Mar 10 22:48:12 2016 from 192.168.124.1
[root@test001 ~]# exit
logout
Shared connection to 192.168.124.185 closed.
$ ssh root@192.168.124.185
ssh: connect to host 192.168.124.185 port 22: Connection refused
$ ssh -o ControlPath=/tmp/habr.socket root@192.168.124.185
Last login: Thu Mar 10 22:48:47 2016 from 192.168.124.1
[root@test001 ~]# service sshd start
Starting sshd: [ OK ]
[root@test001 ~]# exit
logout
Shared connection to 192.168.124.185 closed.
$ ssh root@192.168.124.185
Warning: Permanently added ‘192.168.124.185’ (RSA) to the list of known hosts.
root@192.168.124.185’s password:

Это позволяет работать быстрее, чем с опцией «accelerate: true». Несмотря на документацию, Centos 6 уже почти год корректно работает с ControlPersist, и не требует такого преинсталла, как делалось ранее:

пример ./prepare-accelerate.yml для подготовки ноды к опции accelerate: true в плейбуке ./proxy-nodes.yml

--- - hosts: centos    tasks:    - name: install EPEL     yum: name=epel-release    - name: install keyczar     yum: name=python-keyczar

Далее, стандартный плейбук, при работе с ролями, и таск update_os:

./proxy-nodes.yml

--- - hosts: proxy-nodes   roles:     - update_hostname     - update_os     - update_nginx_configs 

./roles/update_os/tasks/main.yml

---  - name: repo install EPEL   yum: name=epel-release  - name: repo install nginx-release-centos-6   yum: state=present name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm  - name: packages install some   yum: name={{ item }}   with_items:     - nginx     - yum-update  - name: packages upgrade all   yum: name=* state=latest 

Считаю, что данные файлы не нуждаются в комментировании.

Роль update_hostname

Так уж повелось, что ноды как-то именуются. Как можно было видеть, в файле hosts указан, говорящий за себя, параметр node_hostname. К сожалению, ansible еще не может изменить хостнейм в соответствии с FQDN, поэтому приходится помогать:

./roles/update_hostname/tasks/main.yml

---  - name: set hostname   hostname: name={{ node_hostname }}  - name: add hostname to /etc/hosts   lineinfile: dest=/etc/hosts regexp='.*{{ node_hostname }}$' line="{{ ansible_default_ipv4.address }} {{ node_hostname }}" state=present create=yes   when: ansible_default_ipv4.address is defined 

Теперь hostname -f не ругается, а именно такая проверка существует в некоторых панелях управления.

Роль update_nginx_configs

Последняя роль — update_nginx_configs.
Тут мы описываем обработчик, для релода nginx:

./roles/update_nginx_configs/handlers/main.yml

--- - name: reload nginx   service: name=nginx state=reloaded

Следующий файл создает зону кеширования в разделе http, и инклюдит будущие домены для проксирования:

./roles/update_nginx_configs/files/proxy.conf

proxy_cache_path  /tmp  levels=1:2    keys_zone=PROXY:10m inactive=24h  max_size=4g use_temp_path=off; include /etc/nginx/conf.d/proxy/*.conf;

Шаблон для доменов примерно такой:

./roles/update_nginx_configs/templates/domain.conf.j2

server {         listen {{  ansible_default_ipv4.address  }}:80;         server_name {{  item.domain  }} www.{{  item.domain  }};         access_log  /var/log/nginx/{{  item.domain  }}.access.log main ;         error_log   /var/log/nginx/{{  item.domain  }}.error.log;          location / {                 proxy_pass http://{{  item.remoteip  }}:80/;                 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;                 client_max_body_size 10m;                 proxy_connect_timeout 90;                  proxy_cache             PROXY;                 proxy_cache_valid       200 302 1d;                 proxy_cache_valid       404    30m;                 proxy_cache_valid       any     1m;                 proxy_cache_use_stale   error timeout invalid_header updating http_500 http_502 http_503 http_504;         } } 

Тут в целом ничего особенного, параметры проксирования и кеша подбираются под проект. Среди динамически параметров видим всего три: ansible_default_ipv4.address, item.domain и item.remoteip. Откуда берутся последние два, видно из следующего файла:

./roles/update_nginx_configs/handlers/main.yml

---  - name: create non existing dir /etc/nginx/conf.d/proxy/   file: path=/etc/nginx/conf.d/proxy/ state=directory mode=0755  - copy: src=proxy.conf dest=/etc/nginx/conf.d/ owner=nginx group=nginx  backup=yes  - name: re-create domain templates   template: src=domain.conf.j2 dest=/etc/nginx/conf.d/proxy/{{ item.domain }}.conf owner=nginx group=nginx  backup=yes   with_items:     - { domain: 'nginx.org'       , remoteip: '206.251.255.63' }     - { domain: 'docs.ansible.com', remoteip: '104.25.170.30'  }   notify: reload nginx  - name: validate nginx conf   command: nginx -t   changed_when: false

Вот и завершающие этапы: проверили, что директория для доменов существует, обновили конфиг с настройками зоны кеширования, в цикле с with_items прошлись по всем парам domain-remoteip и пересоздали конфиги.
Последним этапом идет валидация конфига, и при успешном результате запустится обработчик reload nginx.
К сожалению, эту валидацию не получается использовать при генерации template или копировании конфига proxy.conf.
Опции validate=«nginx -t -c %s», или даже validate=«nginx -t -c /etc/nginx/nginx.conf -p %s» не так хороши, как в случае генерации конфига httpd.conf.

Поехали!

В случае обновления или изменения списка доменов в задаче «re-create domain templates», выполняем:

ansible-playbook proxy-nodes.yml

без каких-либо дополнительных параметров.
После добавления новой ноды необходимо выполнить команду:

ansible-playbook proxy-nodes.yml —limit=bbb.bbb.bbb.bbb

где указать IP новой ноды.

Заключение

Спросив google, я не получил ответ о подобных сервисах от хостинг-провайдеров.
А ведь целевая аудитория может быть очень разная, от CEO до различных adult web-мастеров.
Поэтому ниже опрос.

Как вы думаете, будет ли пользоваться такой сервис спросом?

Никто ещё не голосовал. Воздержался 1 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

ссылка на оригинал статьи https://habrahabr.ru/post/269123/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *