关于nginx-ingress-controller中worker参数的差异分析

问题描述

生产上发现ingress有个pod超时,重启pod正常以后过几十秒状态又变为异常,检查容器日志报错项:

2021/03/19 01:51:07 [emerg] 263#263: epoll_create() failed (24: No file descriptors available)

对比POD里边的nginx.conf参数,发现有些worker开头的参数比较奇怪。例如worker_processes很大,但是worker_rlimit_nofile很小。

daemon off;
worker_processes 64;
worker_rlimit_nofile 1024;
worker_shutdown_timeout 240s ;
events {
        multi_accept        on;
        worker_connections  16384;
        use                 epoll;
}

临时将worker_rlimit_nofile的值设置为102400,再reload了nginx发现就正常了。

分析过程

  • 由于对nginx-ingress-controller不是很熟悉,所以查看了部署的chart包,发现是没有任何相关配置的。猜想应该是自动生成的。
  • 根据docker部署的经验,worker_processes应该是根据宿主机的cpu计算出来的,而不是通过POD的资源限制。
  • 查看了POD的部署情况,发现的确是没有设置资源限制。
  • 尝试进行资源限制后,发现就正常了。

细节分析

翻了一下nginx-ingress-controller的源码,查找这些值是怎么计算出来的。其中重要的文件如下:

可以发现一些细节。

worker参数的计算方式

  • worker_processes 等于 cpu core
  • worker_rlimit_nofile 等于 最大打开文件数(ulimit –n)/ worker_processes - 1024(如果计算出来小于1024就设置为1024)
  • worker_connections 默认是16384,如果设置0的话,会被调整为worker_rlimit_nofile*3.0/4.0;

由于当前Ingress controller没有资源限制,看到的资源是看到宿主机的cpu core,一般都挺大的,例如64。根据实现规则,假设系统的open files限制,假设配置102400,那么 1024000/64-1024 =576.小于1024,那么就设置为1024。所以会看到一个很小的nofile设置。

关键参数的获取细节

  • 关于cpu core的获取方式,最初也是用直接用的宿主机的,后面就支持了cgroup了,见PR2990
  • 关于最大打开文件数的获取方式,也是变过几次,可以参考追踪nginx ingress最大打开文件数问题,最新的方式和ulimit -n是一致的,和sysctl没有关系。

当前的nginx-ingress-controller增加了一个init容器来进行最大打开文件数的控制:

sysctl -w fs.file-max=1048576

最终在容器里边看到的情况是

bash-5.0$ ulimit -n
809600
bash-5.0$ sysctl fs.file-max
fs.file-max = 1048576

程序读取的就是809600,这个值是怎么来的呢。宿主机看到的是,也是完全不搭边,如下所示。

dggpsprahi09192:~ # ulimit -n 
1024
dggpsprahi09192:~ # sysctl  fs.file-max
fs.file-max = 1048576

那么肯定是docker还做了一点手脚了。查阅suse、docker的材料。最终发现还有几个地方可以控制。

  • 通过docker服务设置
dggpsprahi09192:~ # grep Limit /usr/lib/systemd/system/docker.service
# Having non-zero Limit*s causes performance problems due to accounting overhead
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
StartLimitBurst=3
StartLimitInterval=60s
  • 通过docker daemon设置

路径在/etc/docker/daemon.json,不过这个宿主机没有这个文件。

	"default-ulimits": {
		"nofile": {
			"Name": "nofile",
			"Hard": 64000,
			"Soft": 64000
		}
	},
  • 通过docker启动参数设置
dggpsprahi09192:/opt/docker # cat /etc/sysconfig/docker
DOCKER_OPTS=" -b none --icc=false --log-level='info' --iptables=true -s devicemapper --default-ulimit nofile=809600:809600 --default-ulimit nproc=131072:131072 --live-restore --storage-driver=devicemapper --storage-opt dm.basesize=10G --storage-opt dm.mountopt=nodiscard --storage-opt dm.fs=ext4 --storage-opt dm.blkdiscard=false --storage-opt=dm.thinpooldev=/dev/mapper/docker-thinpool --log-opt max-size=10m --log-opt max-file=5 --log-driver=json-file --log-level=info --userland-proxy=false"

注意里边有设置–default-ulimit nofile=809600:809600,就是控制最大打开文件数的,和docker中看到的保持一致。

worker参数的手工配置

可以直接通过configmap来配置,覆盖自动计算的方式。参数如下:

  • WorkerProcesses string json:"worker-processes,omitempty"
  • MaxWorkerConnections int json:"max-worker-connections,omitempty"
  • MaxWorkerOpenFiles int json:"max-worker-open-files,omitempty"

详细可以参考官方文档的ConfigMaps

总结

了解nginx-ingress-controller的参数是如何设置之后,考虑到宿主机上的docker都是标准设置的,所以容器里边看到的ulimit -n是一致,结果是可预计的。 另一方面ingress controller不是独享宿主机资源的,还是要限制资源。最终决定通过限制资源来解决,保持资源是Guaranteed的,其他具体的worker参数根据资源自动计算。