Примерно полгода назад, пришлось разработать схему обратного проксирования сайтов, с многих нод (n>20) на несколько (n<=3) боевых серверов. Недавно столкнулся в аналогичным запросом от коллеги. Поэтому решил поделиться, и все собрать в статью.
Уровень статьи — для начинающих.
Как результат, был необходим простой инструмент для добавления новых нод и обновления перечня доменов.
Профит от такого решения должен быть, при использовании кеширования на сервере, и DNS с геолокацией.
Поиск информации по теме reverse-proxy, часто сводится к статьям по настройке “nginx to apache” (на локальный apache или на удаленный upstream-сервер), CDN-прокси сервисов (cloudflare, *cdn, cloudfront, etc.). В данном случае это не совсем подходило.
Особенность заключается в необходимости предоставлять множество разных IP (из различных географических локаций) для доменов одного-двух серверов.
Для решения задачи были куплены несколько
Все остальные манипуляции с серверам выполняются удаленно, при помощи 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
Пройдемся по всем файлам.
[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].
Структура с расчетом на расширение ролей и задач.
[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.
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, и не требует такого преинсталла, как делалось ранее:
---
- hosts: centos
tasks:
- name: install EPEL
yum: name=epel-release
- name: install keyczar
yum: name=python-keyczar
Далее, стандартный плейбук, при работе с ролями, и таск update_os:
---
- hosts: proxy-nodes
roles:
- update_hostname
- update_os
- update_nginx_configs
---
- 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, поэтому приходится помогать:
---
- 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:
---
- name: reload nginx
service: name=nginx state=reloaded
Следующий файл создает зону кеширования в разделе http, и инклюдит будущие домены для проксирования:
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;
Шаблон для доменов примерно такой:
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. Откуда берутся последние два, видно из следующего файла:
---
- 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-мастеров.
Поэтому ниже опрос.
Автор: BOPOHA