问题描述
nginx容器化之后,有一个普遍遇到的问题: 如何设置nginx worker process的数量?
nginx官方容器镜像的nginx.conf配置文件中,会有一条worker process配置:
nginx
# /etc/nginx/nginx.conf
worker_processes 1;
它会配置nginx仅启动一个woker。这在nginx容器为1核时,可以良好的工作。
当我们希望nginx给更高的配置,例如4c或者16c,我们需要确保nginx也能启动响应个数的work process。有两个办法:
- 修改 nginx.conf,将worker_processes 的个数调整为cpu核数
- 修改nginx.conf,将woeker_processes的个数修改为auto
第一个方法,在k8s上需要对nginx进行reload。实际部署的时候,必须将nginx.conf作为配置文件挂载,对一些不熟悉Nginx的用户来说,心智负担比较重。
第二个方法,在k8s也会遇到一些问题,通过容器观察可以发现,nginx启动的worker process并没有遵循我们给Pod设置的Limit,而是与Pod所在node的cpu核数保持一致,这在宿主机cpu核数比较多,但是Pod的cpu配置较小的情况下,会 因为每个worker分配的时间片比较小,对性能带来明显的影响
nginx
/ # ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 9528 5504 ? Ss 09:11 0:00 nginx: master process nginx -g daemon off;
nginx 30 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
nginx 31 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
nginx 32 0.0 0.0 9992 2684 ? S 09:11 0:00 nginx: worker process
nginx 33 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
nginx 34 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
nginx 35 0.0 0.0 9992 2684 ? S 09:11 0:00 nginx: worker process
nginx 36 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
nginx 37 0.0 0.0 9992 2556 ? S 09:11 0:00 nginx: worker process
root 38 0.0 0.0 1708 1024 pts/0 Ss 09:40 0:00 sh
root 45 0.0 0.0 2524 1664 pts/0 R+ 09:40 0:00 ps -aux
/ # cd /etc/nginx/
/etc/nginx # ls
conf.d fastcgi.conf fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params
/etc/nginx # cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
/etc/nginx #
问题原因
我们知道,在k8s为容器配置cpu的limits为2时,容器其实并不是真正的分配了两个cpu, 而是通过cgroup进行了限制。
yaml
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
查看cgroup资源限制
- 获取pod名字
bash
[root@k8smaster-ims ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-c98766567-26mjm 1/1 Running 0 3h21m
- 根据pod名字获取容器ID
bash
[root@k8smaster-ims ~]# crictl ps |grep nginx-c98766567-26mjm
18962c2ba08b4 bc1521b43e9bc 3 hours ago Running nginx 0 ee2f2d827d4b7 nginx-c98766567-26mjm
[root@k8smaster-ims ~]#
- 根据容器ID获取cgroupsPath
bash
crictl inspect 18962c2ba08b4 | grep -A5 cgroupsPath
"cgroupsPath": "kubepods-burstable-podee600507_4f0f_4016_b522_ae24df7be346.slice:cri-containerd:18962c2ba08b4f42701896e30a8d03d19e9e27d23adb9e49cb4a294937e36d82",
"namespaces": [
{
"type": "pid"
},
{
- 进入到公共目录
/sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/,然后按照cgroupsPath进入到具体容器的目录
bash
cd /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podee600507_4f0f_4016_b522_ae24df7be346.slice/cri-containerd-18962c2ba08b4f42701896e30a8d03d19e9e27d23adb9e49cb4a294937e36d82.scope
- 查看资源显示
bash
# CPU 限制
cat cpu.max
# 输出格式:max_usec period_usec
# 例:20000 100000 → 20000us = 0.02s = 0.2 CPU = 200m ✅
# CPU 权重(requests)
cat cpu.weight
# 范围 1-10000,默认 100
# k8s 映射:requests.cpu=100m → weight ≈ 2
# 内存限制
cat memory.max
# 例:268435456 = 256Mi ✅
# 内存当前使用
cat memory.current
# 内存 OOM 次数
cat memory.events
# 看 oom 和 oom_kill 计数
# CPU throttle 统计(看有没有被限制)
cat cpu.stat
# nr_throttled / throttled_usec
问题分析
通过cgroup信息可以看到,cgroup实际允许nginx服务只能有 0.2各个核,和 256M的内存使用量。
但是nginx的worker_processes,是通过sysconf(_SC_NPROCESSORS_ONLN) 来查询宿主机上的cpu个数的(getconf _NPROCESSORS_ONLN)
bash
# strace getconf _NPROCESSORS_ONLN
execve("/bin/getconf", ["getconf", "_NPROCESSORS_ONLN"], [/* 23 vars */]) = 0
brk(0) = 0x606000
...
open("/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3
read(3, "0-31\n", 8192) = 5
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6a922a0000
write(1, "32\n", 332
可见,getconf _NPROCESSORS_ONLN 实际上是通过读取文件 /sys/devices/system/cpu/online 来获取cpu的个数的。
默认kuberneters上的/sys/devices/system/cpu/online就是宿主机的,因此,nginx启动的 worker processes个数与宿主机cpu个数一致,也就呵护情理了。
解决方案
解决方案实际也不难想到,修改容器中的/sys/devices/system/cpu/online就行了。
社区的lxcfs已经解决了这个问题。
lxcfs
LXCFS是一个小型的FUSE文件系统,其目的是让Linux容器感觉更像一个虚拟机。LXCFS会关注的procfs中的关键文件:
bash
/proc/cpuinfo
/proc/diskstats
/proc/meminfo
/proc/stat
/proc/swaps
/proc/uptime
/sys/devices/system/cpu/online
可以看到,我们需要的/sys/devices/system/cpu/online文件,也在lxcfs关注列表中。
lxcfs的使用方法也比较简单,只要将宿主机的/var/lib/lxc/lxcfs/proc/online挂载到容器的/sys/devices/system/cpu/online就可以了。
bash
containers:
- args:
- infinity
command:
- sleep
volumeMounts:
- mountPath: /sys/devices/system/cpu/online
name: lxcfs-2
readOnly: true
volumes:
- hostPath:
path: /var/lib/lxc/lxcfs/proc/online
type: ""
name: lxcfs-2
当我们在容器中读取/sys/devices/system/cpu/online文件时,由于kubelet将该文件绑定了/var/lib/lxc/lxcfs/proc/online,该read请求会交给lxcfs daemon来处理。
lxcfs实际处理的函数如下。
c
int max_cpu_count(const char *cg)
{
__do_free char *cpuset = NULL;
int rv, nprocs;
int64_t cfs_quota, cfs_period;
int nr_cpus_in_cpuset = 0;
read_cpu_cfs_param(cg, "quota", &cfs_quota);
read_cpu_cfs_param(cg, "period", &cfs_period);
cpuset = get_cpuset(cg);
if (cpuset)
nr_cpus_in_cpuset = cpu_number_in_cpuset(cpuset);
if (cfs_quota <= 0 || cfs_period <= 0){
if (nr_cpus_in_cpuset > 0)
return nr_cpus_in_cpuset;
return 0;
}
rv = cfs_quota / cfs_period;
/* In case quota/period does not yield a whole number, add one CPU for
* the remainder.
*/
if ((cfs_quota % cfs_period) > 0)
rv += 1;
nprocs = get_nprocs();
if (rv > nprocs)
rv = nprocs;
/* use min value in cpu quota and cpuset */
if (nr_cpus_in_cpuset > 0 && nr_cpus_in_cpuset < rv)
rv = nr_cpus_in_cpuset;
return rv;
}
根据前面的信息,可以看到最终返回的值为 200000/100000 = 2。
结论
因此,当有lxcfs的加持时,nginx可以放心的将worker_processes配置为auto,不需要担心启动了过多的worker processes。