K8s 网络性能调优
本文整理K8S 环境中的网络性能调优策略,分别针对高并发场景和高吞吐场景,使用不同的调优参数。
高并发场景
TIME_WAIT 连接复用
如果短连接并发量较高,它所在 netns 中 TIME_WAIT 状态的连接就比较多,而 TIME_WAIT 连接默认要等 2MSL 时长才释放,长时间占用源端口,当这种状态连接数量累积到超过一定量之后可能会导致无法新建连接。所以建议开启 TIME_WAIT 复用,即允许将 TIME_WAIT 连接重新用于新的 TCP 连接:
net.ipv4.tcp_tw_reuse=1
在高版本内核中,net.ipv4.tcp_tw_reuse默认值为 2,表示仅为回环地址开启复用,基本可以粗略的认为没开启复用。
扩大源端口范围
高并发场景,对于 client 来说会使用大量源端口,源端口范围从net.ipv4.ip_local_port_range这个内核参数中定义的区间随机选取,在高并发环境下,端口范围小容易导致源端口耗尽,使得部分连接异常。通常 Pod 源端口范围默认是 32768-60999,建议将其扩大,调整为 1024-65535:
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
调大最大文件句柄数
在 linux 中,每个连接都会占用一个文件句柄,所以句柄数量限制同样也会限制最大连接数,对于像 Nginx 这样的反向代理,对于每个请求,它会与 client 和 upstream server 分别建立一个连接,即占据两个文件句柄,所以理论上来说 Nginx 能同时处理的连接数最多是系统最大文件句柄数限制的一半。系统最大文件句柄数由fs.file-max这个内核参数来控制,一些环境默认值可能为 838860,建议调大:
fs.file-max=1048576
调大全连接队列的大小
TCP 全连接队列的长度如果过小,在高并发环境可能导致队列溢出,使得部分连接无法建立。如果因全连接队列溢出导致了丢包,从统计的计数上是可以看出来的:
全连接队列的大小取决于net.core.somaxconn内核参数以及业务进程调用 listen 时传入的 backlog 参数,取两者中的较小值,一些编程语言通常是默认取net.core.somaxconn参数的值作为 backlog 参数传入 listen 系统调用(比如Go语言)。高并发环境可以考虑将其改到 65535:
sysctl -w net.core.somaxconn=65535
如何查看队列大小来验证是否成功调整队列大小?可以执ss -lntp看Send-Q的值。ss 用 -l 查看 LISTEN 状态连接时,Recv-Q 表示的当前已建连但还未被服务端调用 accept() 取走的连接数量,即全连接队列中的连接数;Send-Q 表示的则是最大的 listen backlog 数值,即全连接队列大小。如果 Recv-Q 大小接近 Send-Q 的大小时,说明连接队列可能溢出。
需要注意的是,Nginx 在 listen 时并没有读取 somaxconn 作为 backlog 参数传入,而是在 nginx 配置文件中有自己单独的参数配置,如果不设置,backlog 在 linux 上默认为 511,也就是说,即便你的 somaxconn 的很高,nginx 所监听端口的连接队列最大却也只有 511,高并发场景下还是可能导致连接队列溢出,所以建议配置下 nginx 的 backlog 参数。不过如果用的是 Nginx Ingress ,情况又不太一样,因为 Nginx Ingress Controller 会自动读取 somaxconn 的值作为 backlog 参数写到生成的 nginx.conf 中。Nginx配置文件中的参数配置:
高吞吐场景
调大 UDP 缓冲区
UDP socket 的发送和接收缓冲区是有上限的,如果缓冲区较小,高并发环境可能导致缓冲区满而丢包,从网络计数可以看出来:
还可以使用 ss -nump 查看当前缓冲区的情况:
rb212992 表示 UDP 接收缓冲区大小是 212992 字节,tb212992 表示 UDP 发送缓存区大小是 212992 字节。Recv-Q 和 Send-Q 分别表示当前接收和发送缓冲区中的数据包字节数。
UDP 发送缓冲区大小取决于: core.wmem_default 和 net.core.wmem_max 这两个内核参数。分别表示缓冲区的默认大小和最大上限。如果程序自己调用 setsockopt设置SO_SNDBUF来自定义缓冲区大小,最终取值不会超过 net.core.wmem_max;如果程序没设置,则会使用 net.core.wmem_default 作为缓冲区的大小。同理,UDP 接收缓冲区大小取决于: _default 和 net.core.rmem_max 这两个内核参数。分别表示缓冲区的默认大小和最大上限。 如果程序自己调用 setsockopt设置SO_RCVBUF来自定义缓冲区大小,最终取值不会超过 net.core.rmem_max;如果程序没设置,则会使用 net.core.rmem_default 作为缓冲区的大小。需要注意的是,这些内核参数在容器网络命名空间中是无法设置的,是 Node 级别的参数,需要在节点上修改,建议修改值:
如果程序自己有调用 setsockopt 去设置 SO_SNDBUF 或 SO_RCVBUF,建议设置到跟前面内核参数对应的最大上限值。
调大 TCP 缓冲区
TCP socket 的发送和接收缓冲区也是有上限的,不过对于发送缓冲区,即便满了也是不会丢包的,只是会让程序发送数据包时卡住,等待缓冲区有足够多空间释放出来,所以一般不需要优化发送缓冲区。对于接收缓冲区,在高并发环境如果较小,可能导致缓冲区满而丢包,从网络计数可以看出来:
还可以使用 ss -ntmp 查看当前缓冲区情况:
1. rb12582912 表示 TCP 接收缓冲区大小是 12582912 字节,tb12582912 表示 UDP 发送缓存区大小是 12582912 字节。
2. Recv-Q 和 Send-Q 分别表示当前接收和发送缓冲区中的数据包字节数。
如果存在 net.ipv4.tcp_rmem 这个参数,对于 TCP 而言,会覆盖 net.core.rmem_default 和 net.core.rmem_max 的值。这个参数网络命名空间隔离的,而在容器网络命名空间中,一般默认是有配置的,所以如果要调整 TCP 接收缓冲区,需要显式在 Pod 级别配置以下内核参数:
net.ipv4.tcp_rmem="4096 26214400 26214400"
- 单位是字节,分别是 min, default, max。
- 如果程序没用 setsockopt 更改 buffer 长度,就会使用 default 作为初始 buffer 长度(覆盖 net.core.rmem_default),然后根据内存压力在 min 和 max 之间自动调整。
- 如果程序使用了 setsockopt 更改 buffer 长度,则使用传入的长度 (仍然受限于 net.core.rmem_max)。
本文介绍了K8S 环境中不同场景的网络性能调优方法,主要是节点内核参数调整,可以在一定程度上避免网络丢包。另外,K8S网络性能调优还包括pod 的内核参数的调整,此部分内容有待实践后整理。