优化

原文链接:https://mp.weixin.qq.com/s/H7nfSEswggu92myHiRqWHg

k8s 的 Nginx Ingress 调优

概述

Nginx Ingress Controller 基于 Nginx 实现了 Kubernetes Ingress API,Nginx 是公认的高性能网关,但如果不对其进行一些参数调优,就不能充分发挥出高性能的优势。Nginx Ingress 工作原理:image.png

内核参数调优

我们先看看通过内核的哪些参数能够提高 Ingress 的性能。保证在高并发环境下,发挥 Ingress 的最大性能。

调大全连接队列的大小

TCP 全连接队列的最大值取决于 somaxconn 和 backlog 之间的最小值,也就是 min(somaxconn, backlog)。在高并发环境下,如果队列过小,可能导致队列溢出,使得连接部分连接无法建立。要调大 Nginx Ingress 的连接队列,只需要调整 somaxconn 内核参数的值即可,但我想跟你分享下这背后的相关原理。Nginx 监听 socket 时没有读取 somaxconn,而是有自己单独的参数配置。在 nginx.conf 中 listen 端口的位置,还有个叫 backlog 参数可以设置,它会决定 nginx listen 的端口的连接队列大小。 server {     listen  80  backlog=1024;     …

backlog 是 listen(int sockfd, int backlog) 函数中的 backlog 大小,Nginx 默认值是 511,可以通过修改配置文件设置其长度;还有 Go 程序标准库在 listen 时,默认直接读取 somaxconn 作为队列大小。就是说,即便你的 somaxconn 配的很高,nginx 所监听端口的连接队列最大却也只有 511,高并发场景下可能导致连接队列溢出。所以在这个在 Nginx Ingress 中, Nginx Ingress Controller 会自动读取 somaxconn 的值作为 backlog 参数写到生成的 nginx.conf 中: https://github.com/kubernetes/ingress-nginx/blob/controller-v0.34.1/internal/ingress/controller/nginx.go#L592 也就是说,Nginx Ingress 的连接队列大小只取决于 somaxconn 的大小,这个值在 Nginx Ingress 默认为 4096,建议给 Nginx Ingress 设为 65535: sysctl -w net.core.somaxconn=65535

扩大源端口范围

根据《linux 中 TCP 三次握手与四次挥手介绍及调优》的介绍,我们知道客户端会占用端口。在高并发场景会导致 Nginx Ingress 使用大量源端口与 upstream 建立连接。源端口范围是在内核参数 net.ipv4.ip_local_port_range 中调整的。在高并发环境下,端口范围过小容易导致源端口耗尽,使得部分连接异常。Nginx Ingress 创建的 Pod 源端口范围默认是 32768-60999,建议将其扩大,调整为 1024-65535: sysctl -w net.ipv4.ip_local_port_range=“1024 65535”

TIME_WAIT

根据《linux 中 TCP 三次握手与四次挥手介绍及调优》的介绍,我们知道客户端会占用端口。当在 netns 中 TIME_WAIT 状态的连接就比较多的时候,源端口就会被长时间占用。因为而 TIME_WAIT 连接默认要等 2MSL 时长才释放,当这种状态连接数量累积到超过一定量之后可能会导致无法新建连接。所以建议给 Nginx Ingress 开启 TIME_WAIT 复用,即允许将 TIME_WAIT 连接重新用于新的 TCP 连接: sysctl -w net.ipv4.tcp_tw_reuse=1

减小 FIN_WAIT2 状态的参数 net.ipv4.tcp_fin_timeout 的时间和减小 TIME_WAIT 状态的参数 net.netfilter.nf_conntrack_tcp_timeout_time_wait 的时间 ,让系统尽快释放它们所占用的资源。 sysctl -w net.ipv4.tcp_fin_timeout=15 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

调大增大处于 TIME_WAIT 状态的连接数

Nginx 一定要关注这个值,因为它对你的系统起到一个保护的作用,一旦端口全部被占用,服务就异常了。tcp_max_tw_buckets 能帮你降低这种情况的发生概率,争取补救时间。在只有 60000 多个端口可用的情况下,配置为: sysctl -w net.ipv4.tcp_max_tw_buckets = 55000

调大最大文件句柄数

Nginx 作为反向代理,对于每个请求,它会与 client 和 upstream server 分别建立一个连接,即占据两个文件句柄,所以理论上来说 Nginx 能同时处理的连接数最多是系统最大文件句柄数限制的一半。系统最大文件句柄数由 fs.file-max 这个内核参数来控制,默认值为 838860,建议调大: sysctl -w fs.file-max=1048576

配置示例

给 Nginx Ingress Controller 的 Pod 添加 initContainers 来设置内核参数: initContainers:       - name: setsysctl         image: busybox         securityContext:           privileged: true         command:         - sh         - -c         - |           sysctl -w net.core.somaxconn=65535           sysctl -w net.ipv4.ip_local_port_range=“1024 65535”           sysctl -w net.ipv4.tcp_max_tw_buckets = 55000           sysctl -w net.ipv4.tcp_tw_reuse=1           sysctl -w fs.file-max=1048576           sysctl -w net.ipv4.tcp_fin_timeout=15           sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

应用层配置调优

除了内核参数需要调优,Nginx 本身的一些配置也需要进行调优,下面我们来详细看下。

调高 keepalive 连接最大请求数

keepalive_requests 指令用于设置一个 keep-alive 连接上可以服务的请求的最大数量,当最大请求数量达到时,连接被关闭。默认是 100。这个参数的真实含义,是指一个 keep alive 建立之后,nginx 就会为这个连接设置一个计数器,记录这个 keep alive 的长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则 nginx 会强行关闭这个长连接,逼迫客户端不得不重新建立新的长连接。

简单解释一下:QPS=10000 时,客户端每秒发送 10000 个请求(通常建立有多个长连接),每个连接只能最多跑 100 次请求,意味着平均每秒钟就会有 100 个长连接因此被 nginx 关闭。同样意味着为了保持 QPS,客户端不得不每秒重新新建 100 个连接。因此,就会发现有大量的 TIME_WAIT 的 socket 连接(即使此时 keep alive 已经在 client 和 nginx 之间生效)。因此对于 QPS 较高的场景,非常有必要加大这个参数,以避免出现大量连接被生成再抛弃的情况,减少 TIME_WAIT。 如果是内网 Ingress,单个 client 的 QPS 可能较大,比如达到 10000 QPS,Nginx 就可能频繁断开跟 client 建立的 keepalive 连接,然后就会产生大量 TIME_WAIT 状态连接。我们应该尽量避免产生大量 TIME_WAIT 连接,所以,建议这种高并发场景应该增大 Nginx 与 client 的 keepalive 连接的最大请求数量,在 Nginx Ingress 的配置对应 keep-alive-requests,可以设置为 10000,参考: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#keep-alive-requests 同样的,Nginx 与 upstream 的 keepalive 连接的请求数量的配置是 upstream-keepalive-requests,参考: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#upstream-keepalive-requests 但是,一般情况应该不必配此参数,如果将其调高,可能导致负载不均,因为 Nginx 与 upstream 保持的 keepalive 连接过久,导致连接发生调度的次数就少了,连接就过于 “固化”,使得流量的负载不均衡。

调高 keepalive 最大空闲连接数

Nginx 针对 upstream 有个叫 keepalive 的配置,它不是 keepalive 超时时间,也不是 keepalive 最大连接数,而是 keepalive 最大空闲连接数。当这个数量被突破时,最近使用最少的连接将被关闭。

简单解释一下:有一个 HTTP 服务,作为 upstream 服务器接收请求,响应时间为 100 毫秒。如果要达到 10000 QPS 的性能,就需要在 nginx 和 upstream 服务器之间建立大约 1000 条 HTTP 连接。nginx 为此建立连接池,然后请求过来时为每个请求分配一个连接,请求结束时回收连接放入连接池中,连接的状态也就更改为 idle。我们再假设这个 upstream 服务器的 keepalive 参数设置比较小,比如常见的 10. A、假设请求和响应是均匀而平稳的,那么这 1000 条连接应该都是一放回连接池就立即被后续请求申请使用,线程池中的 idle 线程会非常的少,趋近于零,不会造成连接数量反复震荡。B、显示中请求和响应不可能平稳,我们以 10 毫秒为一个单位,来看连接的情况(注意场景是 1000 个线程+100 毫秒响应时间,每秒有 10000 个请求完成),我们假设应答始终都是平稳的,只是请求不平稳,第一个 10 毫秒只有 50,第二个 10 毫秒有 150:

  1. 下一个 10 毫秒,有 100 个连接结束请求回收连接到连接池,但是假设此时请求不均匀 10 毫秒内没有预计的 100 个请求进来,而是只有 50 个请求。注意此时连接池回收了 100 个连接又分配出去 50 个连接,因此连接池内有 50 个空闲连接。
  2. 然后注意看 keepalive=10 的设置,这意味着连接池中最多容许保留有 10 个空闲连接。因此 nginx 不得不将这 50 个空闲连接中的 40 个关闭,只留下 10 个。
  3. 再下一个 10 个毫秒,有 150 个请求进来,有 100 个请求结束任务释放连接。150 - 100 = 50,空缺了 50 个连接,减掉前面连接池保留的 10 个空闲连接,nginx 不得不新建 40 个新连接来满足要求。

C、同样,如果假设相应不均衡也会出现上面的连接数波动情况。 它的默认值为 32,在高并发下场景下会产生大量请求和连接,而现实世界中请求并不是完全均匀的,有些建立的连接可能会短暂空闲,而空闲连接数多了之后关闭空闲连接,就可能导致 Nginx 与 upstream 频繁断连和建连,引发 TIME_WAIT 飙升。在高并发场景下可以调到 1000,参考: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#upstream-keepalive-connections

网关超时

ingress nginx 与 upstream pod 建立 TCP 连接并进行通信,其中涉及 3 个超时配置,我们也相应进行调优。proxy-connect-timeout 选项 设置 nginx 与 upstream pod 连接建立的超时时间,ingress nginx 默认设置为 5s,由于在 nginx 和业务均在内网同机房通信,我们将此超时时间缩短一些,比如 3 秒。参考:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-connect-timeout proxy-read-timeout 选项设置 nginx 与 upstream pod 之间读操作的超时时间,ingress nginx 默认设置为 60s,当业务方服务异常导致响应耗时飙涨时,异常请求会长时间夯住 ingress 网关,我们在拉取所有服务正常请求的 P99.99 耗时之后,将网关与 upstream pod 之间读写超时均缩短到 3s,使得 nginx 可以及时掐断异常请求,避免长时间被夯住。参考:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-read-timeout https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-send-timeout

调高单个 worker 最大连接数

max-worker-connections 控制每个 worker 进程可以打开的最大连接数,默认配置是 16384。在高并发环境建议调高,比如设置到 65536,这样可以让 nginx 拥有处理更多连接的能力,参考: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#max-worker-connections

优化重试机制

nginx 提供了默认的 upstream 请求重试机制,默认情况下,当 upstream 服务返回 error 或者超时,nginx 会自动重试异常请求,并且没有重试次数限制。由于接入层 nginx 和 ingress nginx 本质都是 nginx,两层 nginx 都启用了默认的重试机制,异常请求时会出现大量重试,最差情况下会导致集群网关雪崩。接入层 nginx 一起解决了这个问题:接入层 nginx 必须使用 proxy_next_upstream_tries 严格限制重试次数,ingress nginx 则使用 proxy-next-upstream=“off"直接关闭默认的重试机制。参考:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-next-upstream

开启 brotli 压缩

参考: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#enable-brotli 压缩是时间换空间的通用方法。用 cpu 时间来换取大量的网络带宽,增大吞吐量。Brotli 是 Google 开发的一种压缩方法,于 2015 年发布。我们常用的压缩算法是 gzip(Ingress-nginx 也是默认使用 gzip),据说 brotli 要比 gzip 高出 20%至 30%的压缩率。默认的压缩算法是 gzip,压缩级别为 1,如需要启用 brotli,需要配置以下三个参数:

  • enable-brotli: true 或 false,是否启用 brotli 压缩算法
  • brotli-level: 压缩级别,范围 1~11,默认为 4,级别越高,越消耗 CPU 性能。
  • brotli-types: 由 brotli 即时压缩的 MIME 类型

配置示例

Nginx 全局配置通过 configmap 配置(Nginx Ingress Controller 会 watch 并自动 reload 配置): apiVersion: v1 kind: ConfigMap metadata: name: nginx-ingress-controller data: keep-alive-requests: “10000” upstream-keepalive-connections: “200” max-worker-connections: “65536” proxy-connect-timeout: “3” proxy-read-timeout: “3” proxy-send-timeout: “3” proxy-next-upstream: “off” enable-brotli: “true” brotli-level: “6” brotli-types: “text/xml image/svg+xml application/x-font-ttf image/vnd.microsoft.icon application/x-font-opentype application/json font/eot application/vnd.ms-fontobject application/javascript font/otf application/xml application/xhtml+xml text/javascript application/x-javascript text/plain application/x-font-truetype application/xml+rss image/x-icon font/opentype text/css image/x-win-bitmap”

参考资料