- Директива add_upstream в блоке upstream. Это то, с чего все начиналось.
- Конфигурация
events { worker_connections 1024; } http { upstream u1 { server localhost:8020; } upstream u2 { server localhost:8030; } upstream u3 { server localhost:8040; } upstream ucombined { add_upstream u1; add_upstream u2; add_upstream u3 backup; } server { listen 127.0.0.1:8010; server_name main; location / { proxy_pass http://ucombined; } } server { listen 127.0.0.1:8020; server_name server1; location / { echo "Passed to $server_name"; } } server { listen 127.0.0.1:8030; server_name server2; location / { echo "Passed to $server_name"; } } server { listen 127.0.0.1:8040; server_name server3; location / { echo "Passed to $server_name"; } } }
- Тест
for i in `seq 10` ; do curl 'http://localhost:8010/' ; done Passed to server1 Passed to server1 Passed to server2 Passed to server2 Passed to server1 Passed to server1 Passed to server2 Passed to server2 Passed to server1 Passed to server1
Правильно. Если вам интересно, почему каждый сервер опрашивается по два раза подряд, то ответ таков. Мой системный файл /etc/hosts содержит следующие две строки.127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
Это значит, что loopback интерфейс имеет два адреса — для IPv4 и IPv6 (по крайней мере в /etc/hosts, который nginx читает на старте). Для каждого адреса nginx создает отдельный элемент-сервер в списке round robin peers. Достаточно закомментировать вторую строку в /etc/hosts и перезапустить nginx, чтобы получить настоящий round robin цикл в этом тесте.
- Конфигурация
- Директива combine_server_singlets в блоке upstream. Эта штука позволяет плодить апстримы в невероятных количествах :) Представьте, что у вас есть такой апстрим
upstream u1 { server localhost:8020; server localhost:8030; server localhost:8040; }
и вы хотите создать три следующих производных апстрима-синглета (не надо спрашивать зачем, у меня была такая задача и я точно знаю, что она имеет смысл).upstream u11 { server localhost:8020; server localhost:8030 backup; server localhost:8040 backup; } upstream u12 { server localhost:8020 backup; server localhost:8030; server localhost:8040 backup; } upstream u13 { server localhost:8020 backup; server localhost:8030 backup; server localhost:8040; }
Не нужно их создавать вручную! Достаточно поместить новую директиву внутрь порождающего апстримаupstream u1 { server localhost:8020; server localhost:8030; server localhost:8040; combine_server_singlets; }
и апстримы-синглеты будут созданы автоматически. Для тонкой настройки имен порожденных апстримов директива предоставляет два опциональных параметра: суффикс и разрядное выравнивание порядкового номера апстрима.- Конфигурация
events { worker_connections 1024; } http { upstream u1 { server localhost:8020; server localhost:8030; server localhost:8040; combine_server_singlets; combine_server_singlets _tmp_ 2; } server { listen 127.0.0.1:8010; server_name main; location /1 { proxy_pass http://u11; } location /2 { proxy_pass http://u1_tmp_02; } location /3 { proxy_pass http://u1$cookie_rt; } } server { listen 127.0.0.1:8020; server_name server1; location / { add_header Set-Cookie "rt=1"; echo "Passed to $server_name"; } } server { listen 127.0.0.1:8030; server_name server2; location / { add_header Set-Cookie "rt=2"; echo "Passed to $server_name"; } } server { listen 127.0.0.1:8040; server_name server3; location / { add_header Set-Cookie "rt=3"; echo "Passed to $server_name"; } } }
- Тест
curl 'http://localhost:8010/1' Passed to server1 curl 'http://localhost:8010/2' Passed to server2 curl 'http://localhost:8010/3' Passed to server1 curl -D- -b 'rt=2' 'http://localhost:8010/3' HTTP/1.1 200 OK Server: nginx/1.8.0 Date: Tue, 01 Dec 2015 10:59:00 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: rt=2 Passed to server2 curl -D- -b 'rt=3' 'http://localhost:8010/3' HTTP/1.1 200 OK Server: nginx/1.8.0 Date: Tue, 01 Dec 2015 10:59:10 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: rt=3 Passed to server3
Обмен кукой rt дает подсказку, где синглетные апстримы могут быть полезны.
- Конфигурация
- Апстрэнды (upstrands). Это такие комбинированные апстримы, внутри которых составляющие их апстримы не теряют свою целостность и идентичность. Слово upstrand образовано из двух составляющих: up
streamи strand и означает пучок или жилу апстримов. Я касался деталей реализации апстрэндов в этой статье на английском языке. В двух словах, апстрэнд представляет собой высокоуровневую структуру, которая может опрашивать составляющие ее апстримы по кругу (round robin) до тех пор, пока не найдет апстрим, удовлетворяющий заданному условию — код ответа апстрима (HTTP response) не должен входить в список, заданный директивой next_upstream_statuses. Технически апстрэнды являются блоками — такими же как и апстримы. Они точно так же задаются внутри секции http конфигурации nginx, но вместо серверов составляющими их компонентами являются обычные апстримы. Апстримы добавляются в апстрэнд с помощью директивы upstream. Если имя апстрима начинается с символа тильда, то оно рассматривается как регулярное выражение. Отдельные апстримы внутри апстрэнда могут быть помечены как бэкапные, также имеется возможность блэклистить апстримы на определенное время с помощью параметра blacklist_interval. Опрос нескольких апстримов внутри апстрэнда реализован с помощью механизма подзапросов (subrequests). Этот механизм запускается в результате доступа к встроенной переменной upstrand_NAME, где NAME соответствует имени существующего апстрэнда. Я предполагаю, что в основном апстрэнды будут применяться в директиве proxy_pass модуля nginx proxy, однако здесь нет искусственных ограничений: доступ к механизму запуска подзапросов через переменную позволяет использовать апстрэнды в любом пользовательском модуле. На случай, если имя апстрэнда заранее неизвестно (например, оно приходит в куке), предусмотрена директива dynamic_upstrand, которая записывает имя следующего апстрима предполагаемого апстрэнда в свой первый аргумент-переменную на основании оставшегося списка аргументов (имя апстрэнда будет соответствовать первому не пустому аргументу из этого списка). Директива доступна на уровнях конфигурации server, location и location-if. Апстрэнды предоставляют несколько статусных переменных, среди них upstrand_addr, upstrand_status, upstrand_cache_status, upstrand_connect_time, upstrand_header_time, upstrand_response_time, upstrand_response_length — все они соответствуют аналогичным переменным из модуля upstream, только хранят значения всех посещенных апстримов в рамках данного HTTP запроса — и upstrand_path, в которой записан хронологический порядок (путь) посещения апстримов в рамках данного запроса. Статусные переменные полезны для анализа работы апстрэндов в access логе. А теперь пример конфигурации и curl-тест.- Конфигурация
events { worker_connections 1024; } http { upstream u01 { server localhost:8020; } upstream u02 { server localhost:8030; } upstream b01 { server localhost:8040; } upstream b02 { server localhost:8050; } upstrand us1 { upstream ~^u0 blacklist_interval=10s; upstream b01 backup; next_upstream_statuses 5xx; } upstrand us2 { upstream b02; next_upstream_statuses 5xx; } log_format fmt '$remote_addr [$time_local]\n' '>>> [path] $upstrand_path\n' '>>> [addr] $upstrand_addr\n' '>>> [response time] $upstrand_response_time\n' '>>> [status] $upstrand_status'; server { listen 127.0.0.1:8010; server_name main; error_log /tmp/nginx-test-upstrand-error.log; access_log /tmp/nginx-test-upstrand-access.log fmt; dynamic_upstrand $dus1 $arg_a us2; location /us { proxy_pass http://$upstrand_us1; } location /dus { dynamic_upstrand $dus2 $arg_b; if ($arg_b) { proxy_pass http://$dus2; break; } proxy_pass http://$dus1; } } server { listen 8020; server_name server1; location / { echo "Passed to $server_name"; #return 503; } } server { listen 8030; server_name server2; location / { echo "Passed to $server_name"; #return 503; } } server { listen 8040; server_name server3; location / { echo "Passed to $server_name"; } } server { listen 8050; server_name server4; location / { echo "Passed to $server_name"; } } }
- Тест
for i in `seq 6` ; do curl 'http://localhost:8010/us' ; done Passed to server1 Passed to server2 Passed to server1 Passed to server2 Passed to server1 Passed to server2
В логах nginx мы увидимtail -f /tmp/nginx-test-upstrand-* ==> /tmp/nginx-test-upstrand-access.log <== ==> /tmp/nginx-test-upstrand-error.log <== ==> /tmp/nginx-test-upstrand-access.log <== 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u01 >>> [addr] (u01) 127.0.0.1:8020 >>> [response time] (u01) 0.000 >>> [status] (u01) 200 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u02 >>> [addr] (u02) 127.0.0.1:8030 >>> [response time] (u02) 0.000 >>> [status] (u02) 200 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u01 >>> [addr] (u01) 127.0.0.1:8020 >>> [response time] (u01) 0.000 >>> [status] (u01) 200 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u02 >>> [addr] (u02) 127.0.0.1:8030 >>> [response time] (u02) 0.001 >>> [status] (u02) 200 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u01 >>> [addr] (u01) 127.0.0.1:8020 >>> [response time] (u01) 0.000 >>> [status] (u01) 200 127.0.0.1 [01/Dec/2015:16:52:03 +0300] >>> [path] u02 >>> [addr] (u02) 127.0.0.1:8030 >>> [response time] (u02) 0.001 >>> [status] (u02) 200
А теперь давайте закомментируем директивы echo и раскомментируем директивы return 503 в локейшнах двух первых бэкендов (server1 и server2), перезапустим nginx и протестируем снова.for i in `seq 6` ; do curl 'http://localhost:8010/us' ; done Passed to server3 Passed to server3 Passed to server3 Passed to server3 Passed to server3 Passed to server3
Логи nginx.==> /tmp/nginx-test-upstrand-access.log <== 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] u01 -> u02 -> b01 >>> [addr] (u01) 127.0.0.1:8020 (u02) 127.0.0.1:8030 (b01) 127.0.0.1:8040 >>> [response time] (u01) 0.001 (u02) 0.000 (b01) 0.000 >>> [status] (u01) 503 (u02) 503 (b01) 200 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200 127.0.0.1 [01/Dec/2015:16:58:06 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200
Ждем десять секунд — заблэклисченные апстримы должны разблэклиститься, и повторяем снова.for i in `seq 2` ; do curl 'http://localhost:8010/us' ; done Passed to server3 Passed to server3
Логи nginx.127.0.0.1 [01/Dec/2015:17:01:44 +0300] >>> [path] u01 -> u02 -> b01 >>> [addr] (u01) 127.0.0.1:8020 (u02) 127.0.0.1:8030 (b01) 127.0.0.1:8040 >>> [response time] (u01) 0.000 (u02) 0.000 (b01) 0.001 >>> [status] (u01) 503 (u02) 503 (b01) 200 127.0.0.1 [01/Dec/2015:17:01:44 +0300] >>> [path] b01 >>> [addr] (b01) 127.0.0.1:8040 >>> [response time] (b01) 0.000 >>> [status] (b01) 200
А теперь протестируем работу динамических апстрэндов (предварительно вернув оригинальные настройки локейшнов двух первых бэкендов).curl 'http://localhost:8010/dus?a=us1' Passed to server1 curl 'http://localhost:8010/dus?a=us2' Passed to server4 curl 'http://localhost:8010/dus?a=foo&b=us1' Passed to server2 curl 'http://localhost:8010/dus' Passed to server4 curl 'http://localhost:8010/dus?b=foo' <html> <head><title>500 Internal Server Error</title></head> <body bgcolor="white"> <center><h1>500 Internal Server Error</h1></center> <hr><center>nginx/1.8.0</center> </body> </html>
В первом запросе мы через аргумент a попали на один из апстримов апстрэнда us1 — им оказался апстрим u01, который содержит единственный сервер server1. Во втором запросе, тоже через аргумент a, мы попали на апстрэнд us2 — апстрим b02 — сервер server4. В третьем запросе мы задействовали новый динамический апстрим dus2 через аргумент b, который отправил нас на второй апстрим (round robin же) u02 апстрэнда us1 и сервер server2. В четвертом запросе мы не предоставили аргументов и сработал последний не пустой элемент динамического апстрэнда dus1 — us2 с его единственным апстримом b02 и единственным сервером server4. В последнем запросе я показал, что может произойти, если динамический апстрэнд вернет пустое значение. В данном случае значение dus2 оказалось пустым и директива proxy_pass, попытавшись выполнить проксирование на неверно сформированный адрес http://, вернула ошибку 500.
- Конфигурация
вторник, 1 декабря 2015 г.
Не такой уж простой модуль nginx для создания комбинированных апстримов
Когда-то давно я написал статью о простом модуле nginx для создания комбинированных апстримов. В ней шла речь о реализации простой директивы add_upstream на уровне блока upstream в конфигурации nginx, которая позволяет добавлять серверы из других апстримов: очень удобно, когда вам требуется собрать апстримы, скомбинированные из нескольких других апстримов, без копирования объявлений составляющих их серверов.
На данный момент я больше не могу назвать этот модуль простым, поскольку кроме расширенной функциональности, в его реализации появились разнообразные механизмы nginx, такие как фильтрация заголовков и тела ответов, доступ к переменным и подзапросы (subrequests). Теперь модуль называется модулем комбинированных апстримов, он выложен на гитхабе и снабжен подробной документацией на английском языке. В этой статье я хочу перечислить все возможности данного модуля с примерами их использования.
Подписаться на:
Комментарии к сообщению (Atom)
Комментариев нет:
Отправить комментарий