NGINX Ingress Controller 5.7.0: версия HTTP до пода без фрагмента в конфиге

9 ноября 2026 года Шон Брин в блоге NGINX разобрал, как NGINX Ingress Controller 5.7.0 выбирает версию HTTP на участке от контроллера до пода. Раньше её дописывали своим фрагментом в конфиге. Теперь версию можно взять из Service, из аннотации Ingress или из поля VirtualServer.

По умолчанию контроллер по-прежнему ходит в под по HTTP/1.1. Обычному сайту этого хватает. Часть сервисов уже принимает HTTP/2 без TLS: много запросов идут по одному соединению. Такой режим называют h2c. Старому бэкенду иногда нужен HTTP/1.0.

gRPC эта настройка не переключает. Сервисы из списка nginx.org/grpc-services и upstream с type: grpc и раньше уходили через grpc_pass. Он всегда работает по HTTP/2.

Аннотации читает NGINX Ingress Controller 5.7.0. В конфиге обычного nginx на VPS строку proxy_http_version по-прежнему пишут сами. Если сайт на WordPress с PHP и MariaDB стоит за этим контроллером, до пода доедет та версия, которую он подставит рядом с proxy_pass.

Три места, откуда берётся версия

Контроллер смотрит в три штатных места. Отдельный фрагмент в http или location ради версии до пода больше не обязателен.

У порта Service может стоять appProtocol: kubernetes.io/h2c. Тогда в location само появляется proxy_http_version 2;. Аннотацию на Ingress вешать не нужно. Другие значения appProtocol контроллер пропускает. Смотрит он только на kubernetes.io/h2c.

Вручную на весь Ingress ставят аннотацию nginx.org/proxy-http-version. В примерах значения "1.0", "1.1" и "2", все в кавычках. Про 1.0 сказано прямо: без кавычек YAML читает его как число, и Kubernetes объект не принимает.

В VirtualServer у каждого upstream есть поле proxy-http-version. То же поле есть у upstream в VirtualServerRoute. Команда, которой принадлежит маршрут, задаёт версию у себя, без общей аннотации на весь хост.

Что контроллер пишет в location

В заметке два сервиса, tea и coffee. У tea-svc порт называется h2c, targetPort 8080, на порту appProtocol: kubernetes.io/h2c. У coffee-svc такой метки нет. Для него остаётся HTTP/1.1.

Ingress cafe-ingress-app-protocol с ingressClassName: nginx отправляет путь /tea на tea-svc и /coffee на coffee-svc. Аннотации версии на этом Ingress нет. Хост в примере cafe.example.com.

Контроллер собирает файл /etc/nginx/conf.d/default-cafe-ingress-app-protocol.conf. В location "/tea" появляется proxy_http_version 2; и proxy_pass на апстрим tea. В location "/coffee" директивы версии нет. nginx остаётся на своём дефолте, HTTP/1.1.

Если сам под тоже nginx, h2c на том же порту включается двумя строками: listen 8080; и http2 on;. Порт принимает и h2c, и HTTP/1.x. Под, который h2c не слушает, на запрос ответит 502 Bad Gateway. Метку kubernetes.io/h2c ставят только туда, где протокол уже включён. Для сайта, который слушает только HTTP/1.1, её не ставят.

Демонстрационные поды из примера cafe.yaml умеют отвечать, какой протокол пришёл. В учебном curl на /tea видно Upstream protocol: HTTP/2.0, на /coffee видно HTTP/1.1. Это ответ тех подов, не страница WordPress. Адрес и порт контроллера в примере кладут в переменные IC_IP и IC_HTTP_PORT, а запрос идёт с --resolve, чтобы имя хоста осталось cafe.example.com.

Аннотация перебивает Service

Порядок короткий. Сначала аннотация nginx.org/proxy-http-version или поле VirtualServer. Потом appProtocol у Service. Потом дефолт HTTP/1.1.

У tea-svc стоит h2c, а на время отладки нужен обычный 1.1. Аннотацию вешают поверх. Контроллер пересобирает конфиг и перечитывает его. Путь /tea уходит по HTTP/1.1, хотя метка на Service осталась. Снять аннотацию, и /tea снова на HTTP/2. В команде снятия в конце имени стоит дефис. Так kubectl удаляет аннотацию.

Аннотация покрывает каждый path этого Ingress. Сайт и сервис с h2c на разных путях одного объекта получат одну версию. Развести их можно отдельным Ingress, minion или полем VirtualServer на один upstream.

kubectl annotate ingress cafe-ingress-app-protocol nginx.org/proxy-http-version="1.1" --overwrite
kubectl annotate ingress cafe-ingress-app-protocol nginx.org/proxy-http-version-
kubectl exec -n nginx-ingress <controller-pod> -- nginx -T | grep -A20 'location "/tea"'

Первая строка форсирует HTTP/1.1 на всём Ingress. Вторая снимает аннотацию. Третья показывает location в собранном конфиге. Пространство имён nginx-ingress и имя пода в третьей команде взяты из примера. Свои смотрят через kubectl get pods.

Своя версия у upstream в VirtualServer

Там аннотация не нужна. У upstream tea поле можно не задавать: HTTP/2 возьмётся из tea-svc. У coffee в примере стоит proxy-http-version: "1.0". У каждого upstream своя версия. Ресурс с apiVersion: k8s.nginx.org/v1, имя в примере cafe-vs, хост тот же cafe.example.com. Маршруты /tea и /coffee просто передают запрос в свой upstream.

Master, minion и кавычки у 1.0

На составном Ingress мастер держит домен, minion держит свой путь. У minion для tea аннотации версии нет, HTTP/2 снова берётся из Service. У minion для coffee рядом с nginx.org/mergeable-ingress-type: "minion" стоит nginx.org/proxy-http-version: "1.0". Значение в кавычках. Голое 1.0 YAML превращает в число, Kubernetes такой объект отвергает.

В общем файле /etc/nginx/conf.d/default-cafe-ingress-master.conf у /tea будет proxy_http_version 2;. У /coffee будет proxy_http_version 1.0; и строка proxy_set_header Connection close;.

Домен cafe.example.com может держать только один Ingress или один master. Перед примером с minion заметка просит удалить cafe-ingress-app-protocol. Соседний cafe.yaml из каталога mergeable-ingress-types лучше не применять поверх: у тех Service те же имена, но нет appProtocol, и HTTP/2 у tea пропадёт. Для примера берут cafe-secret.yaml, cafe-master.yaml, tea-minion.yaml и coffee-minion.yaml. Аннотацию "1.0" дописывают в coffee-minion.yaml.

Аннотация версии на master достаётся каждому minion, у которого своей нет. Она сильнее appProtocol, поэтому h2c у tea тоже перебьётся. Если версию должен выбирать Service, master оставляют без этой аннотации.

WebSocket остаётся на HTTP/1.1

WebSocket держится на механизме Upgrade из HTTP/1.1. У HTTP/1.0 и у HTTP/2 его нет. Сервис из списка nginx.org/websocket-services, для которого контроллер выбрал 1.0 или 2, получает предупреждение. В том числе когда HTTP/2 пришёл из appProtocol, а не из аннотации.

Событие видно в kubectl describe ingress. В примере reason AddedOrUpdatedWithWarning, источник nginx-ingress-controller. Речь про сервис chat-svc: он настроен на WebSocket, а соединение до пода получилось HTTP/2. WebSocket требует HTTP/1.1.

У VirtualServer такого события нет. Upstream на 1.0 или 2, включая версию из appProtocol, сокет не увезёт. Бэкенд с WebSocket не помечают kubernetes.io/h2c.

gRPC от этой настройки не сдвигается. Для сервисов из nginx.org/grpc-services аннотацию контроллер игнорирует и пишет warning. У VirtualServer поле игнорируется для upstream с type: grpc. Предупреждение попадает в status ресурса, не в события Ingress.

Заголовки контроллер подстраивает сам. На HTTP/2 он не отправляет Connection и Upgrade. Их запрещает RFC 9113, раздел 8.2.2. На HTTP/1.0 он шлёт Connection: close. Соединение до пода после ответа закрывается. Случайно поставленный "1.0" на Ingress с сайтом означает новый коннект на каждый запрос к поду. Держать соединение открытым в этом режиме контроллер не будет.

Спотыкаются обычно не на имени директивы. Метка h2c на поде, который слушает только HTTP/1.1, заканчивается 502. Забытые кавычки у 1.0, и объект не применяется. Одна аннотация на все пути, и версия уезжает даже туда, где её не ждали. WebSocket, помеченный как h2c, до сокета не доходит.

Свой кусок в конфиге ради proxy_http_version после 5.7.0 можно не класть. Перед выкладкой стоит открыть собранный location и посмотреть, какая версия там оказалась, особенно если на том же Ingress есть WebSocket.

Источники и ссылки


Комментарии загружаются…