# теория · шаг 1 из 5
Что такое stream и чем он не HTTP
Коротко
stream - второй корневой блок рядом с http, работающий на уровне
соединения: принял, выбрал сервер, отдал бэкенду и дальше перекладывает байты.
Ни location, ни URI, ни заголовков - и это не «так не принято», а отказ
загрузиться. listen 53 и listen 53 udp - два разных сокета. У UDP-сессии
нет конца, поэтому длину назначаешь ты: proxy_responses закрывает её сразу
после нужного числа ответов, без него она висит до proxy_timeout.
Уже гонял базы через nginx - листай до «Теперь сам». Разборы про коды в логе stream и про длину UDP-сессии.
Два блока на одном порту, оба без имени. Что скажет nginx -t?
server { listen 7100; proxy_pass pg-primary:5432; }
server { listen 7100; proxy_pass pg-standby:5432; }
Половина руководств обещает отказ при старте: «в stream два сервера на порту - ошибка конфигурации». На 1.31.5 конфиг принимается:
nginx: [warn] conflicting server name "" on 0.0.0.0:7100, ignored
nginx: configuration file /etc/nginx/nginx.conf test is successful
Второй блок выброшен, весь трафик идёт в первый. Предупреждение уезжает в
вывод nginx -t, которого при systemctl reload никто не видит.
На что это похоже
http-блок - это приёмная: конверт вскрывают, читают адресата на первой странице и по нему решают, кому нести. Правил там много, потому что содержимое конверта понятно.
stream-блок - патч-панель: провод из розетки 5432 воткнут в порт базы. Что бежит по проводу, панель не знает и знать не должна. Поэтому в ней нет ни одной ручки, которая требовала бы понимания содержимого, - и именно поэтому через неё проходит что угодно: PostgreSQL, Redis, SMTP, gRPC поверх TLS, игровой протокол собственного сочинения.
Механизм: путь соединения
Правило
Блок выбирается по паре «адрес и порт» плюс транспорт, а с 1.25.5 ещё и по
имени из SNI. Дальше запрос проходит доступ и уходит бэкенду целиком. Всё,
что в http относится к содержимому - location, add_header, кеш, сжатие,
try_files, - в stream отсутствует, и проверяется это при старте, а не молча.
nginx: [emerg] "location" directive is not allowed here in /etc/nginx/nginx.conf:4
Разбор: коды ответа, которых клиент не видит
Расхожая формулировка «в stream нет кодов ответа» верна ровно наполовину.
Клиент их действительно не видит: у TCP нет места, куда положить код, поэтому
отказ выглядит как оборванное соединение. А вот в логе код есть - переменная
$status в stream существует и означает исход сессии.
Четыре сессии одного прогона стенда, формат
$remote_addr $protocol $status $bytes_sent/$bytes_received $session_time ups=$upstream_addr:
172.25.0.4 TCP 200 13/1 0.001 ups=172.25.0.2:6001
172.25.0.4 TCP 403 0/0 0.000 ups=-
172.25.0.4 TCP 503 0/0 0.000 ups=-
172.25.0.4 TCP 502 0/0 0.000 ups=172.25.0.2:6999
| что случилось | $status |
что увидел клиент |
|---|---|---|
| сессия прошла нормально | 200 | данные бэкенда |
deny не пустил |
403 | connection reset by peer |
limit_conn не пустил |
503 | connection reset by peer |
| бэкенд не отозвался | 502 | connection reset by peer |
Три разные причины дают клиенту один и тот же симптом. Различить их можно только со своей стороны, и только если лог включён.
Разбор: сколько живёт UDP-сессия
У TCP конец сессии определяет сам протокол. У UDP его нет: пришла датаграмма, ушла датаграмма, а сколько ещё ждать - вопрос к конфигу. Стенд: бэкенд отвечает дважды, первый ответ сразу, второй через секунду.
server {
listen 7061 udp;
proxy_responses 1;
proxy_timeout 5s;
proxy_pass 10.0.0.53:53;
}
| настройка | что получил клиент | сколько жила сессия |
|---|---|---|
proxy_responses 1 |
только первый ответ | 0,001 с |
proxy_responses 2 |
оба ответа | 1,003 с |
без proxy_responses |
оба ответа | 6,003 с |
Последняя строка складывается из времени последнего ответа и proxy_timeout:
1 + 5 = 6. То есть без proxy_responses каждая запись в кеше nginx о клиенте
занимает память пять лишних секунд после того, как всё уже сказано. На DNS с
тысячей запросов в секунду это тысячи мёртвых сессий одновременно.
Обратная сторона видна в первой строке: proxy_responses 1 обрубает вторую
датаграмму. Для DNS это верно (ответ один), для протокола, отвечающего пачкой,
- потеря данных без единой записи в логе.
Что ломается без этого
Лог молчит, потому что его никто не включал. Три сессии в stream и один
запрос в http на одном и том же стенде дали в access.log ровно одну строку -
от http. В stream логирование по умолчанию выключено, и «в логах ничего нет»
означает не «трафика не было», а «ты не написал access_log».
Формат лога из http не грузится. Половина переменных, к которым ты привык, в stream не существует:
| есть | нет |
|---|---|
$status, $protocol, $session_time |
$request, $uri, $host |
$server_port, $connection |
$request_time, $args |
$bytes_sent, $upstream_bytes_sent |
всё семейство $http_* |
Скопированный log_format роняет старт с unknown "request" variable - и это
хорошая новость: ошибка громкая. Хуже с $host: строку map $host $backend;
переносят из http-конфига чаще всего, а в stream её нет вовсе.
Зачем это в работе
Три задачи, где stream - правильный ответ. Пара «основной и резервный» для базы: приложение знает один адрес, переключение живёт в конфиге nginx. Единственный вход в контур: наружу торчит один хост, за ним Redis, SMTP и всё остальное без своих балансировщиков. TLS-passthrough: соединение доходит до бэкенда шифрованным, и сертификат остаётся там, где ему место.
И одна, где stream - неправильный: HTTP. Соблазн «просто пробросить порт»
велик, но за него платишь всем сразу - кешем, заголовками о клиенте, повтором
запроса на другой бэкенд, разбором по URL. Для HTTP всегда http-блок.
Теперь сам
Нужно пустить через nginx DNS-резолвер 10.0.0.53. DNS живёт и на UDP, и на
TCP. Напиши блоки и объясни, почему proxy_responses стоит только у одного.
Ответ: нужны два server - listen 53 udp; и listen 53;, потому что это
разные сокеты, и блок с udp не примет TCP-соединение. proxy_responses 1
ставится только в UDP-блоке: у обычного DNS-ответа одна датаграмма, и без этой
строки сессия проживёт весь proxy_timeout. В TCP-блоке директива не нужна -
конец обмена там определяет сам протокол.
Главное
stream работает на уровне соединения: выбрал сервер по адресу, порту и
транспорту, проверил доступ, отдал бэкенду байты. location, заголовки и всё
остальное «про содержимое» отсутствуют - конфиг с ними не загрузится. Два
блока на одном порту без имени дают предупреждение, а не отказ: второй
молча игнорируется. $status в stream есть и различает 200, 403, 503 и 502, но
клиент видит один симптом - оборванное соединение; лог по умолчанию выключен, а
переменных $request, $uri и $host в stream нет. listen ... udp -
отдельный сокет, и длину UDP-сессии задаёт proxy_responses: без него она
живёт до proxy_timeout уже после последнего ответа.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий