Итак, у вас есть кластер Kubernetes, а для проброса внешнего трафика сервисам внутри кластера вы уже настроили Ingress-контроллер NGINX, ну, или пока только собираетесь это сделать. Класс!
Я тоже через это прошел, и поначалу все выглядело очень просто: установленный NGINX Ingress-контроллер был на расстоянии одного helm install
. А затем оставалось лишь подвязать DNS к балансировщику нагрузки и создать необходимые Ingress-ресурсы.
Спустя несколько месяцев весь внешний трафик для всех окружений (dev, staging, production) направлялся через Ingress-серверы. И все было хорошо. А потом стало плохо.
Все мы отлично знаем, как это бывает: сначала вы заинтересовываетесь этой новой замечательной штукой, начинаете ей пользоваться, а затем начинаются неприятности.
Мой первый сбой в Ingress
Сперва позвольте предупредить: если вы еще не обеспокоены переполнениями очереди приема (accept queue overflows), начинайте беспокоиться.
Не забывайте об очередях
Случилось вот что: стоящее за NGINX приложение начало отвечать с большими задержками, что, в свою очередь, привело к заполнению NGINX listen backlog. Из-за этого NGINX начал сбрасывать соединения, в том числе и те, что пытался установить Kubernetes для проверки работоспособности сервиса (liveness/readiness probes).
А что происходит, когда под не отвечает на такие запросы? Kubernetes думает, что что-то пошло не так, и перезапускает его. Проблема в том, что это одна из тех ситуаций, когда перезапуск пода только усугубляет ситуацию: очередь приема продолжает переполняться, Kubernetes продолжает перезапускать поды, и в итоге их затягивает в водоворот падений и последующих рестартов.
Переполнения очереди TCP listen, согласно netstat
Какие уроки можно извлечь из этой ситуации?
- Изучите конфигурацию своего NGINX до последней буквы. Выясните, что там должно быть и чего не должно. Не нужно слепо доверять настройкам по умолчанию.
- Большинство Linux-дистрибутивов из коробки не настроены на работу в качестве высоконагруженных веб-серверов. Перепроверьте значения соответствующих параметров ядра с помощью
sysctl -a
. - Измеряйте задержку своих сервисов и устанавливайте соответствующие таймауты на основе ожидаемого максимального значения + запас на небольшие отклонения.
- Настраивайте свои приложения таким образом, чтобы в случае перегрузки они начинали отклонять запросы или аккуратно снижать нагрузку. Например, в приложениях на NodeJS увеличения задержек в цикле сообщений могут означать, что сервер уже с трудом справляется с обработкой текущего трафика.
- Используйте более одного NGINX Ingress-контроллера.
Важность мониторинга
Мой совет №0: никогда
не запускайте в production Kubernetes-кластер (или что-то аналогичное), не настроив качественный мониторинг его работы. Сам по себе мониторинг от проблем не избавит, но собранная телеметрия значительно облегчает нахождение первопричин сбоев, что позволяет исправлять их в процессе дальнейшей работы.
Несколько полезных метрик из `nodenetstat`*
Если и вы поддались повальному увлечению Prometheus, для сбора метрик уровня ноды можете воспользоваться node_exporter. Это удобный инструмент, который позволяет выявлять в том числе и только что описанные проблемы.
Некоторые метрики, получаемые с NGINX Ingress-контроллера
NGINX Ingress-контроллер и сам способен генерировать метрики для Prometheus. Не забудьте наладить их сбор.
Знай свой конфиг
Красота Ingress-контроллера том, что вы можете положиться на эту замечательную программу в вопросе генерации и перезагрузки конфигурации прокси-сервера и больше по этому поводу не беспокоиться. Вам даже не обязательно быть знакомым с нижележащей технологией (NGINX в данном случае). Правда? Нет!
Если вы этого еще не сделали, обязательно посмотрите на сгенерированную для вас конфигурацию. Для NGINX Ingress-контроллера можно получить содержимое /etc/nginx/nginx.conf
с помощью kubectl
.
$ kubectl -n <namespace> exec <nginx-ingress-controller-pod-name> -- cat /etc/nginx/nginx.conf > ./nginx.conf
# $ cat ./nginx.conf
daemon off;
worker_processes auto;
pid /run/nginx.pid;
worker_rlimit_nofile 1047552;
worker_shutdown_timeout 10s ;
events {
multi_accept on;
worker_connections 16384;
use epoll;
}
http {
real_ip_header X-Forwarded-For;
# ...
}
# ...
Теперь попробуйте найти что-нибудь несовместимое с вашей установкой. Хотите пример? Давайте начнем с worker_processes auto
;
Оптимальное значение зависит от множества факторов, включая (но не ограничиваясь ими) число процессорных ядер, число жестких дисков с данными и картину нагрузок. Если затрудняетесь в выборе правильного значения, можно начать с установки его равным числу процессорных ядер (значение “auto” пытается определить его автоматически).
Первая проблема: в настоящий момент (будет ли это когда-либо исправлено?) NGINX ничего не знает о cgroups, а значит, в случае auto
будет использовано значение количества физических ядер CPU
хоста, а не количества «виртуальных» процессоров, как определено в Kubernetes resource requests/limits.
Проведем эксперимент. Что будет, если мы попробуем загрузить следующий файл конфигурации NGINX на двухъядерном сервере в контейнере, ограниченном только одним CPU? Сколько рабочих процессов будет запущено?
# $ cat ./minimal-nginx.conf
worker_processes auto;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
$ docker run --rm --cpus="1" -v `pwd`/minimal-nginx.conf:/etc/nginx/nginx.conf:ro -d nginx
fc7d98c412a9b90a217388a094de4c4810241be62c4f7501e59cc1c968434d4c
$ docker exec fc7 ps -ef | grep nginx
root 1 0 0 21:49 pts/0 00:00:00 nginx: master process nginx -g daemon off;
nginx 6 1 0 21:49 pts/0 00:00:00 nginx: worker process
nginx 7 1 0 21:49 pts/0 00:00:00 nginx: worker process
Таким образом, если вы планируете ограничить ресурсы процессора, которые доступны NGINX Ingress, не стоит позволять nginx создавать большое количество рабочих процессов в одном контейнере. Лучше всего явно указать их необходимое количество с помощью директивы worker_processes
.
Теперь рассмотрим директиву listen
. Значение параметра backlog
явно не указано и по умолчанию для Linux равно 511
. Если параметр ядра net.core.somaxconn
равен, скажем, 1024
, то backlog
нужно присвоить соответствующее значение. Другими словами, убедитесь в том, что конфигурация nginx настроена с учетом параметров ядра.
Но на этом не останавливайтесь. Такое упражнение необходимо провести для каждой строчки сгенерированного файла конфигурации. Только посмотрите на все те параметры, которые позволяет поменять Ingress-контроллер. Исправляйте без колебаний все, что не подходит для вашего случая. Большинство параметров NGINX могут быть настроены с помощью записей и/или аннотаций ConfigMap
.
Параметры ядра
С Ingress или без него, всегда проверяйте и настраивайте параметры нод в соответствии с ожидаемой нагрузкой.
Это достаточно сложная тема, поэтому я не планирую здесь подробно ее раскрывать. Дополнительные материалы по этом вопросу можно найти в разделе Ссылки.
Kube-Proxy: Таблица Conntrack
Тем, кто использует Kubernetes, думаю, не нужно объяснять, что такое Сервисы и для чего они предназначены. Однако полезно будет рассмотреть некоторые особенности их работы.
В каждой ноде Kubernetes-кластера выполняется kube-proxy, который отвечает за реализацию виртуального IP для Сервисов типа, отличного от ExternalName. В Kubernetes v1.0 прокси выполнялся исключительно в пространстве пользователя. В Kubernetes v1.1 был добавлен iptables-прокси, однако это не был режим по умолчанию. Начиная с Kubernetes v1.2, iptables-прокси используется по умолчанию.
Другими словами, отправленные на IP сервиса пакеты направляются (напрямую или через балансировщик) на соответствующий Endpoint
(пары address:port
подов, которые соответствуют label selector сервиса) с помощью правил iptables, управляемых kube-proxy. Соединения с IP-адресами сервиса отслеживаются ядром с помощью модуля nf_conntrack
, и эта информация хранится в RAM.
Поскольку различные параметры conntrack должны быть согласованы друг с другом (например, nf_conntrack_maxw
и nf_conntrack_buckets
), kube-proxy, начиная работу, устанавливает разумные значения по умолчанию.
$ kubectl -n kube-system logs <some-kube-proxy-pod>
I0829 22:23:43.455969 1 server.go:478] Using iptables Proxier.
I0829 22:23:43.473356 1 server.go:513] Tearing down userspace rules.
I0829 22:23:43.498529 1 conntrack.go:98] Set sysctl 'net/netfilter/nf_conntrack_max' to 524288
I0829 22:23:43.498696 1 conntrack.go:52] Setting nf_conntrack_max to 524288
I0829 22:23:43.499167 1 conntrack.go:83] Setting conntrack hashsize to 131072
I0829 22:23:43.503607 1 conntrack.go:98] Set sysctl 'net/netfilter/nf_conntrack_tcp_timeout_established' to 86400
I0829 22:23:43.503718 1 conntrack.go:98] Set sysctl 'net/netfilter/nf_conntrack_tcp_timeout_close_wait' to 3600
I0829 22:23:43.504052 1 config.go:102] Starting endpoints config controller
...
Это неплохой вариант, но вам может потребоваться увеличить значения этих параметров, если мониторинг показывает, что у вас заканчивается место, выделенное для conntrack. Однако необходимо помнить, что увеличение значений этих параметров ведет к повышенному потреблению памяти, так что поаккуратнее там.
Мониторинг использования conntrack
Делиться (не) значит заботиться
До недавнего времени у нас был только один экземпляр NGINX Ingress, ответственный за проксирование запросов ко всем приложениям во всех окружениях (dev, staging, production). Я убедился на собственном опыте, что это плохая идея. Не складывайте все яйца в одну корзину.
Думаю, то же самое может быть сказано и по поводу использования одного кластера для всех окружений, однако мы выяснили, что такой подход приводит к более эффективному расходованию ресурсов. Мы запускали dev/staging-поды на уровне QoS с негарантированной доставкой (best-effort QoS tier), используя таким образом ресурсы, оставшиеся от production-приложений.
Обратной стороной медали здесь является то, что мы оказываемся ограничены в действиях, которые можно выполнять по отношению к кластеру. Например, если нам потребуется провести нагрузочное тестирование staging-сервиса, придется быть очень осторожными, чтобы не повлиять на боевые сервисы, запущенные в этом же кластере.
Несмотря на то что контейнеры дают в общем случае хороший уровень изоляции, они по-прежнему зависят от разделяемых ресурсов ядра, которые являются объектом злоупотреблений.
Одна установка Ingress на каждое окружение
Мы уже говорили о том, что нет причин, по которым не стоит использовать по одному Ingress-контроллеру на каждое окружение. Это дает дополнительный уровень защиты на тот случай, если с сервисами в dev и/или staging начнутся проблемы.
Некоторые преимущества этого подхода:
- Появляется возможность использовать различные настройки для разных окружений.
- Позволяет тестировать обновления Ingress перед применением их в production.
- Можно избежать раздувания конфигурации NGINX записями множества вышестоящих серверов и сервисов, связанных с эфемерными и/или нестабильными окружениями.
- Ускоряются перезагрузки конфигурации и сокращается количество этих событий в течение дня (позже мы обсудим, почему нужно стремиться к минимизации количества перезагрузок).
Ingress-классы в помощь
Одним из способов заставить разные ingress
-контроллеры управлять разными Ingress-ресурсами является использование разных имен классов ingress для каждой отдельной установки ingress, а затем аннотирование Ingress
-ресурсов с целью указать, кто кем должен управлять.
# Ingress controller 1
apiVersion: extensions/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- args:
- /nginx-ingress-controller
- --ingress-class=class-1
- ...
# Ingress controller 2
apiVersion: extensions/v1beta1
kind: Deployment
spec:
template:
spec:
containers:
- args:
- /nginx-ingress-controller
- --ingress-class=class-2
- ...
# This Ingress resource will be managed by controller 1
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: class-1
spec:
rules: ...
# This Ingress resource will be managed by controller 2
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: class-2
spec:
rules: ...
Проблемы с перезагрузками Ingress
К этому моменту у нас был запущен выделенный ingress-контроллер для production-окружения. Все было хорошо до тех пор, пока мы не решили перенести одно WebSocket-приложение на Kubernetes + ingress.
Скоро я заметил странную тенденцию в использовании памяти production-подами ingress.
Что здесь, черт побери, происходит?!
По какой причине потребление памяти так сильно выросло? С помощью kubectl exec
я зашел в один из ingress-контейнеров и обнаружил группу рабочих процессов, висящих в статусе shutting down
.
root 17755 17739 0 19:47 ? 00:00:00 /usr/bin/dumb-init /nginx-ingress-controller --default-backend-service=kube-system/broken-bronco-nginx-ingress-be --configmap=kube-system/broken-bronco-nginx-ingress-conf --ingress-class=nginx-ingress-prd
root 17765 17755 0 19:47 ? 00:00:08 /nginx-ingress-controller --default-backend-service=kube-system/broken-bronco-nginx-ingress-be --configmap=kube-system/broken-bronco-nginx-ingress-conf --ingress-class=nginx-ingress-prd
root 17776 17765 0 19:47 ? 00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nobody 18866 17776 0 19:49 ? 00:00:05 nginx: worker process is shutting down
nobody 19466 17776 0 19:51 ? 00:00:01 nginx: worker process is shutting down
nobody 19698 17776 0 19:51 ? 00:00:05 nginx: worker process is shutting down
nobody 20331 17776 0 19:53 ? 00:00:05 nginx: worker process is shutting down
nobody 20947 17776 0 19:54 ? 00:00:03 nginx: worker process is shutting down
nobody 21390 17776 1 19:55 ? 00:00:05 nginx: worker process is shutting down
nobody 22139 17776 0 19:57 ? 00:00:00 nginx: worker process is shutting down
nobody 22251 17776 0 19:57 ? 00:00:01 nginx: worker process is shutting down
nobody 22510 17776 0 19:58 ? 00:00:01 nginx: worker process is shutting down
nobody 22759 17776 0 19:58 ? 00:00:01 nginx: worker process is shutting down
nobody 23038 17776 1 19:59 ? 00:00:03 nginx: worker process is shutting down
nobody 23476 17776 1 20:00 ? 00:00:01 nginx: worker process is shutting down
nobody 23738 17776 1 20:00 ? 00:00:01 nginx: worker process is shutting down
nobody 24026 17776 2 20:01 ? 00:00:02 nginx: worker process is shutting down
nobody 24408 17776 4 20:01 ? 00:00:01 nginx: worker process
Чтобы понять, что произошло, надо сделать шаг назад и взглянуть на то, как в NGINX реализован процесс перезагрузки конфигурации.
Получив сигнал, главный процесс проверяет правильность синтаксиса нового конфигурационного файла и пытается применить конфигурацию, содержащуюся в нем. Если это ему удается, главный процесс запускает новые рабочие процессы и отправляет сообщения старым рабочим процессам с требованием завершиться. В противном случае, главный процесс откатывает изменения и продолжает работать со старой конфигурацией. Старые рабочие процессы, получив команду завершиться, прекращают принимать новые запросы и продолжают обслуживать текущие запросы до тех пор, пока все такие запросы не будут обслужены. После этого старые рабочие процессы завершаются.
Напомню, что мы проксируем WebSocket-соединения, которые по своей природе являются долгоживущими. WebSocket-соединение может поддерживаться часами, а то и днями, в зависимости от приложения. NGINX-сервер не знает, можно ли разорвать соединение во время перезагрузки, мы должны облегчить ему эту работу. (Например, можно применить стратегию принудительного завершения соединений, бездействующих определенное количество времени, как на клиенте, так и на сервере. Не оставляйте такие вещи на потом.)
Вернемся к нашей проблеме. Если у нас так много рабочих процессов в стадии завершения, значит, конфигурация ingress перезагружалась много раз, а рабочие процессы не могли завершить работу из-за долгоживущих соединений.
Так на самом деле и было. Мы выяснили, что NGINX Ingress-контроллер периодически генерировал различные файлы конфигурации по причине изменения очередности вышестоящих серверов и IP-адресов серверов.
I0810 23:14:47.866939 5 nginx.go:300] NGINX configuration diff
I0810 23:14:47.866963 5 nginx.go:301] --- /tmp/a072836772 2017-08-10 23:14:47.000000000 +0000
+++ /tmp/b304986035 2017-08-10 23:14:47.000000000 +0000
@@ -163,32 +163,26 @@
proxy_ssl_session_reuse on;
- upstream production-app-1-80 {
+ upstream upstream-default-backend {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.71.14:3000 max_fails=0 fail_timeout=0;
- server 10.2.32.22:3000 max_fails=0 fail_timeout=0;
+ server 10.2.157.13:8080 max_fails=0 fail_timeout=0;
}
- upstream production-app-2-80 {
+ upstream production-app-3-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.110.13:3000 max_fails=0 fail_timeout=0;
- server 10.2.109.195:3000 max_fails=0 fail_timeout=0;
+ server 10.2.82.66:3000 max_fails=0 fail_timeout=0;
+ server 10.2.79.124:3000 max_fails=0 fail_timeout=0;
+ server 10.2.59.21:3000 max_fails=0 fail_timeout=0;
+ server 10.2.45.219:3000 max_fails=0 fail_timeout=0;
}
upstream production-app-4-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.109.177:3000 max_fails=0 fail_timeout=0;
server 10.2.12.161:3000 max_fails=0 fail_timeout=0;
- }
-
- upstream production-app-5-80 {
- # Load balance algorithm; empty for round robin, which is the default
- least_conn;
- server 10.2.21.37:9292 max_fails=0 fail_timeout=0;
- server 10.2.65.105:9292 max_fails=0 fail_timeout=0;
+ server 10.2.109.177:3000 max_fails=0 fail_timeout=0;
}
upstream production-app-6-80 {
@@ -201,61 +195,67 @@
upstream production-lap-production-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.45.223:8000 max_fails=0 fail_timeout=0;
+ server 10.2.21.36:8000 max_fails=0 fail_timeout=0;
server 10.2.78.36:8000 max_fails=0 fail_timeout=0;
+ server 10.2.45.223:8000 max_fails=0 fail_timeout=0;
server 10.2.99.151:8000 max_fails=0 fail_timeout=0;
- server 10.2.21.36:8000 max_fails=0 fail_timeout=0;
}
- upstream production-app-7-80{
+ upstream production-app-1-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.79.126:3000 max_fails=0 fail_timeout=0;
- server 10.2.35.105:3000 max_fails=0 fail_timeout=0;
- server 10.2.114.143:3000 max_fails=0 fail_timeout=0;
- server 10.2.50.44:3000 max_fails=0 fail_timeout=0;
- server 10.2.149.135:3000 max_fails=0 fail_timeout=0;
- server 10.2.45.155:3000 max_fails=0 fail_timeout=0;
+ server 10.2.71.14:3000 max_fails=0 fail_timeout=0;
+ server 10.2.32.22:3000 max_fails=0 fail_timeout=0;
}
- upstream production-app-8-80 {
+ upstream production-app-2-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.53.23:5000 max_fails=0 fail_timeout=0;
- server 10.2.110.22:5000 max_fails=0 fail_timeout=0;
- server 10.2.35.91:5000 max_fails=0 fail_timeout=0;
- server 10.2.45.221:5000 max_fails=0 fail_timeout=0;
+ server 10.2.110.13:3000 max_fails=0 fail_timeout=0;
+ server 10.2.109.195:3000 max_fails=0 fail_timeout=0;
}
- upstream upstream-default-backend {
+ upstream production-app-9-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.157.13:8080 max_fails=0 fail_timeout=0;
+ server 10.2.78.26:3000 max_fails=0 fail_timeout=0;
+ server 10.2.59.22:3000 max_fails=0 fail_timeout=0;
+ server 10.2.96.249:3000 max_fails=0 fail_timeout=0;
+ server 10.2.32.21:3000 max_fails=0 fail_timeout=0;
+ server 10.2.114.177:3000 max_fails=0 fail_timeout=0;
+ server 10.2.83.20:3000 max_fails=0 fail_timeout=0;
+ server 10.2.118.111:3000 max_fails=0 fail_timeout=0;
+ server 10.2.26.23:3000 max_fails=0 fail_timeout=0;
+ server 10.2.35.150:3000 max_fails=0 fail_timeout=0;
+ server 10.2.79.125:3000 max_fails=0 fail_timeout=0;
+ server 10.2.157.165:3000 max_fails=0 fail_timeout=0;
}
- upstream production-app-3-80 {
+ upstream production-app-5-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.79.124:3000 max_fails=0 fail_timeout=0;
- server 10.2.82.66:3000 max_fails=0 fail_timeout=0;
- server 10.2.45.219:3000 max_fails=0 fail_timeout=0;
- server 10.2.59.21:3000 max_fails=0 fail_timeout=0;
+ server 10.2.21.37:9292 max_fails=0 fail_timeout=0;
+ server 10.2.65.105:9292 max_fails=0 fail_timeout=0;
}
- upstream production-app-9-80 {
+ upstream production-app-7-80 {
# Load balance algorithm; empty for round robin, which is the default
least_conn;
- server 10.2.96.249:3000 max_fails=0 fail_timeout=0;
- server 10.2.157.165:3000 max_fails=0 fail_timeout=0;
- server 10.2.114.177:3000 max_fails=0 fail_timeout=0;
- server 10.2.118.111:3000 max_fails=0 fail_timeout=0;
- server 10.2.79.125:3000 max_fails=0 fail_timeout=0;
- server 10.2.78.26:3000 max_fails=0 fail_timeout=0;
- server 10.2.59.22:3000 max_fails=0 fail_timeout=0;
- server 10.2.35.150:3000 max_fails=0 fail_timeout=0;
- server 10.2.32.21:3000 max_fails=0 fail_timeout=0;
- server 10.2.83.20:3000 max_fails=0 fail_timeout=0;
- server 10.2.26.23:3000 max_fails=0 fail_timeout=0;
+ server 10.2.114.143:3000 max_fails=0 fail_timeout=0;
+ server 10.2.79.126:3000 max_fails=0 fail_timeout=0;
+ server 10.2.45.155:3000 max_fails=0 fail_timeout=0;
+ server 10.2.35.105:3000 max_fails=0 fail_timeout=0;
+ server 10.2.50.44:3000 max_fails=0 fail_timeout=0;
+ server 10.2.149.135:3000 max_fails=0 fail_timeout=0;
+ }
+
+ upstream production-app-8-80 {
+ # Load balance algorithm; empty for round robin, which is the default
+ least_conn;
+ server 10.2.53.23:5000 max_fails=0 fail_timeout=0;
+ server 10.2.45.221:5000 max_fails=0 fail_timeout=0;
+ server 10.2.35.91:5000 max_fails=0 fail_timeout=0;
+ server 10.2.110.22:5000 max_fails=0 fail_timeout=0;
}
server {
По этой причине NGINX Ingress-контроллер перезагружал конфигурацию несколько раз в минуту, забивая память завершающимися рабочими процессами до тех пор, пока под не становился жертвой OOM killer.
После того как я обновил NGINX Ingress-контроллер на версию с исправлением и указал параметр командной строки --sort-backends=true
, дела пошли на поправку.
Количество ненужных перезагрузок после установки исправленной версии упало до нуля.
Спасибо @aledbf за помощь в нахождении и исправлении этой ошибки!
Продолжаем минимизировать перезагрузки конфигурации
Важно помнить, что перезагрузки конфигурации — операции затратная, и их лучше избегать, особенно при работе с WebSocket-соединениями. По этой причине мы решили установить отдельный Ingress-контроллер специально для долгоживущих соединений.
В нашем случае изменения в WebSocket-приложениях происходят намного реже, чем во всех остальных, и отдельный контроллер позволяет нам не трогать эти долгоживущие соединения лишний раз.
Отдельная установка также позволяет нам по-разному настраивать Ingress-контроллеры, оптимизируя их под тип обслуживаемых соединений.
Тонкая настройка автомасштабирования подов
Поскольку в NGINX Ingress вышестоящие серверы прописываются в виде IP-адресов подов, каждый раз при изменении списка конечных точек сервиса Ingress-конфигурация генерируется заново и перезагружается. Поэтому если при нормальной нагрузке часто происходят автомасштабирования (autoscaling), может понадобиться изменение HorizontalPodAutoscalers
.
Horizontal pod autoscaler в работе на пиковых нагрузках
Еще одна вещь, которую люди часто не осознают, — это то, что у horizontal pod autoscaler есть специальный таймер, который не позволяет масштабироваться несколько раз в течение короткого времени.
Name: <app>
Namespace: production
Labels: <none>
Annotations: <none>
CreationTimestamp: Fri, 23 Jun 2017 11:41:59 -0300
Reference: Deployment/<app>
Metrics: ( current / target )
resource cpu on pods (as a percentage of request): 46% (369m) / 60%
Min replicas: 8
Max replicas: 20
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale False BackoffBoth the time since the previous scale is still within both the downscale and upscale forbidden windows
ScalingActive True ValidMetricFound the HPA was able to succesfully calculate a replica count from cpu resource utilization (percentage of request)
ScalingLimited True TooFewReplicas the desired replica count was less than the minimum replica count
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
14d 10m 39 horizontal-pod-autoscaler Normal SuccessfulRescale New size: 10; reason: cpu resource utilization (percentage of request) above target
14d 3m 69 horizontal-pod-autoscaler Normal SuccessfulRescale New size: 8; reason: All metrics below target
Согласно значению по умолчанию для флага --horizontal-pod-autoscaler-upscale-delay
в kube-controller-manager минимальный интервал между событиями масштабирования приложения равняется 3 минутам.
Таким образом, если нагрузка на ваше приложение действительно сильно возросла, может потребовать примерно 4 минуты (3 минуты задержки autoscaler + примерно 1 минута на синхронизацию метрик), прежде чем autscaler отреагирует на это, чего может оказаться вполне достаточно для серьезного ухудшения работы сервиса.
Ваше мнение?
Можете что-нибудь посоветовать? Буду рад!
Ссылки
- Tuning NGINX for Performance
- How TCP backlog works in Linux
- How TCP Sockets Work
- Netfilter Conntrack Memory Usage
- Optimizing web servers for high throuhgput and low latency
- Container isolation gone wrong
- Оригинал: Pain(less) NGINX Ingress.
Автор: Игорь Олемской