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

в 22:20, , рубрики: Ansible, nginx, reverse-proxy, Серверное администрирование, системное администрирование

Ansible и reverse-proxy сервера - 1
Примерно полгода назад, пришлось разработать схему обратного проксирования сайтов, с многих нод (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-мастеров.
Поэтому ниже опрос.

Автор: BOPOHA

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js