如何解决容器中 nginx worker process 自动设置的问题


Docker 技术鼻祖系列

原文链接:https://ieevee.com/tech/2020/05/11/nginx-process-auto.html

1.  问题描述

nginx 容器化时,有一个普遍会遇到的问题:如何自动设置 nginx worker process 的数量?

nginx 官方容器镜像的 nginx.conf 配置文件中,会有一条 worker process 配置:

它会配置 nginx 仅启动 1 个 worker。这在 nginx 容器为 1 核时,可以良好的工作。

当我们希望 nginx 给更高的配置,例如 4c 或者 16c,我们需要确保 nginx 也能启动响应个数的 worker process。有两个办法:

  1. 修改 nginx.conf,将 worker_processes 的个数调整为对应的 cpu 核数。

  2. 修改 nginx.conf,将 worker_processes 修改为 auto。

第一个方法可行,但是需要修改配置文件,nginx 需要 reload。实际部署时,必须将 nginx.conf 作为配置文件挂载,对一些不太熟悉 nginx 的用来说,心智负担会比较重。

第二个方法,在 Kubernetes 上会遇到一些问题。通过在容器中观察可以发现,nginx 启动的 worker process,并没有遵循我们给 Pod 设置的 limit,而是与 Pod 所在 node 的 cpu 核数保持一致。

这在宿主机的 cpu 核数比较多,而 Pod 的 cpu 配置较小时,会因为每个 worker 分配的时间片比较少,带来明显的响应慢的问题。

2. 问题原因

我们知道,在 Kubernetes 为容器配置 cpu 的 limits 为 2 时,容器其实并不是真正的 “分配了”2 个 cpu,而是通过 cgroup 进行了限制。

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 500m
    memory: 256Mi

我们到这个 Pod 所在宿主机上去查看相关信息。

$ docker inspect 17f5f35c3500|grep -i cgroup
            "Cgroup": "",
            "CgroupParent": "/kubepods/burstable/podb008ccda-9396-11ea-bc20-ecf4bbd63ee8",
            "DeviceCgroupRules": null,
$ cd /sys/fs/cgroup/cpu/kubepods/burstable/podb008ccda-9396-11ea-bc20-ecf4bbd63ee8
$ cat cpu.cfs_quota_us
$ cat cpu.cfs_period_us

可以看到,实际是通过 cpu.cfs_quota_us/cpu.cfs_period_us 来限制该 Pod 能使用的 cpu 核数的。

但是 nginx 的 worker_processes,是通过 sysconf(_SC_NPROCESSORS_ONLN) 来查询宿主机上的 cpu 个数的(getconf _NPROCESSORS_ONLN),我们通过 strace 来观察下这个过程。

$ 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 个数的。

默认 Kubernetes 上,/sys/devices/system/cpu/online文件实际就是宿主机的,因此,nginx 启动的 worker process 个数,与宿主机 cpu 个数一致,也就不奇怪了。

3. 解决方案

解决方案实际也不难想到,修改容器中的/sys/devices/system/cpu/online就行了。

社区的 lxcfs 已经解决了这个问题。

lxcfs

LXCFS 是一个小型的 FUSE 文件系统,其目的是让 Linux 容器感觉更像一个虚拟机。LXCFS 会关注的 procfs 中的关键文件:

/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就可以了。

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 实际处理的函数如下。

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。

4. 结论

因此,当有 lxcfs 的加持时,nginx 可以放心的将 worker_processes 配置为auto,不需要担心启动了过多的 worker processes。


你可能还喜欢

点击下方图片即可阅读

最华丽的 Kubernetes 桌面客户端:Lens

云原生是一种信仰 ????

码关注公众号

后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!

点击 "阅读原文" 获取更好的阅读体验!

❤️给个「在看」,是对我最大的支持❤️
<think>我们正在解决Docker镜像启动时卡在"start worker process"的问题。根据用户提供的引用和问题描述,我们需要分析可能导致此问题的原因并提供解决方案。 可能的原因: 1. 应用程序启动时需要较长时间初始化(例如数据库连接、资源加载等) 2. 应用程序配置问题(例如等待某个服务或端口) 3. 资源限制(内存、CPU不足导致进程卡住) 4. 死锁或程序逻辑错误 5. 日志输出缓冲区问题(导致日志没有及时输出) 解决步骤: 1. 检查容器日志: 使用`docker logs <container_id>`查看容器日志,注意加上`-f`参数可以实时查看,但如果是卡住可能不会有新日志。重点查看卡住之前的最后几条日志。 2. 进入容器内部检查: 如果容器没有完全退出,可以尝试使用`docker exec -it <container_id> /bin/bash`进入容器,检查进程状态和资源使用情况。 3. 调整Docker运行参数: 尝试在运行容器时增加`--privileged`(有时权限问题会导致进程卡住)或调整资源限制(如内存、CPU)。 4. 检查应用程序配置: 查看应用程序的配置文件,确认是否有等待外部服务(如数据库)的配置,而外部服务不可用导致一直等待。 5. 使用调试模式启动应用程序: 如果可能,修改Dockerfile,使应用程序以调试模式启动,以便输出更多信息。 6. 检查启动脚本: 检查启动脚本(如entrypoint.sh)中是否有错误,比如某个命令没有退出(例如tail -f命令)。 7. 分析堆栈跟踪: 如果进程还在运行,可以尝试获取进程的堆栈信息(例如使用`jstack`对于Java应用,或`gdb`等)。 8. 检查网络连接: 使用`docker run`时加入`--network=host`使用主机网络,排除网络问题。 9. 尝试简化镜像: 如果镜像基于复杂的基础镜像,尝试使用更简单的基础镜像(如alpine)来排除基础镜像问题。 10. 检查文件系统挂载: 检查是否有挂载的卷(-v参数)导致的问题,尝试不挂载任何卷运行。 根据引用[3]中提到的,我们可以使用`--entrypoint`参数覆盖默认的入口点,进入容器内部进行排查。 例如: docker run -it --entrypoint /bin/bash <image_name> 然后手动执行启动命令,观察启动过程。 另外,引用[1]和[2]提供了镜像构建和容器操作的相关命令,我们可以利用这些命令来构建和运行容器。 具体操作步骤建议: 步骤1:查看日志 docker logs <container_id> --tail 100 -f 步骤2:进入容器调试(如果容器还在运行) docker exec -it <container_id> /bin/bash 步骤3:如果容器没有运行,则使用覆盖入口点的方式启动一个新容器: docker run -it --entrypoint /bin/sh <image_name> 然后手动执行应用程序的启动命令,观察输出。 步骤4:检查资源使用情况 在宿主机上使用`docker stats`查看容器的资源使用情况。 步骤5:如果应用程序是Web服务,检查端口是否正常监听 在容器内执行:netstat -tuln 步骤6:检查应用程序依赖的服务 例如,如果应用程序需要连接数据库,确保数据库可访问。 步骤7:如果以上步骤无法解决,考虑在Dockerfile中添加调试工具(如vim, net-tools, procps等),重新构建镜像并运行。 示例Dockerfile修改(在原有基础上增加工具): FROM tinyhttpd:v0.1-7 RUN apk add --no-cache curl vim net-tools procps # 假设基础镜像基于alpine # 或者如果是基于debian/ubuntu的镜像:RUN apt-get update && apt-get install -y curl vim net-tools procps 然后重新构建镜像并运行。 步骤8:如果问题仍然存在,可能需要检查应用程序本身的代码,特别是启动过程中“start worker process”的部分。 由于用户的问题描述中提到了“start worker process”,这通常与Web服务器(如Nginx、Gunicorn等)启动工作进程有关。因此,我们可以重点检查工作进程的配置和日志。 例如,对于Gunicorn,可以尝试增加`--log-level=debug`来获取更详细的日志。 总结:需要根据具体的应用程序和环境进行排查,以上步骤为通用排查方法。</think>### Docker镜像启动卡在"start worker process"的解决方法 当Docker容器启动卡在"start worker process"时,通常表示工作进程启动过程中遇到阻塞。以下是系统化的排查和解决方法: #### 1. **检查容器日志(关键步骤)** ```bash docker logs --tail 50 <container_id> # 查看最后50行日志 docker logs -f <container_id> # 实时跟踪日志 ``` - 重点观察卡住前的最后一条有效日志 - 常见阻塞原因: - 数据库连接失败(如等待MySQL) - 配置文件错误(如无效端口绑定) - 依赖服务未就绪(如Redis未启动) #### 2. **进入容器调试** ```bash docker run -it --entrypoint=/bin/sh <image_name> # 覆盖入口点 ``` 在容器内手动执行启动命令: ```bash # 示例(根据实际应用调整): /usr/sbin/nginx -g "daemon off;" # 前台运行Nginx python app.py # 直接运行Python应用 ``` - 观察终端输出的具体错误 - 检查`/proc/<pid>/status`确认进程状态 #### 3. **资源限制排查** ```bash docker run --memory=512m --cpus=1 <image> # 测试资源限制 ``` - 使用`docker stats`监控资源使用: ```bash CONTAINER ID CPU % MEM USAGE / LIMIT cccb2108056e 0.01% 150MiB / 512MiB # 示例输出[^2] ``` - 常见问题: - 内存不足导致OOM Killer终止进程 - CPU配额不足使进程饥饿 #### 4. **网络问题诊断** ```bash docker run --network=host <image> # 使用主机网络测试 ``` 在容器内检查: ```bash nc -zv 数据库IP 3306 # 测试数据库连通性 curl -I http://localhost # 测试本地服务 ``` #### 5. **文件权限修复** ```bash docker run -v /host/path:/container/path:Z <image> # SELinux权限 docker run --user $(id -u):$(id -g) <image> # 指定用户[^3] ``` - 检查关键目录权限: ```bash ls -ld /var/run # 确保PID文件目录可写 ``` #### 6. **应用层优化** - **启动脚本**:添加超时检测 ```bash #!/bin/sh timeout 30s /app/start.sh || echo "启动超时" ``` - **日志级别**:提升为DEBUG(如Nginx的`error_log debug;`) - **健康检查**:在Dockerfile中添加: ```dockerfile HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -f http://localhost/ || exit 1 ``` #### 7. **高级调试手段** - **堆栈跟踪**: ```bash docker exec <container> kill -SIGUSR1 <worker_pid> # 触发日志转储 ``` - **核心转储**: ```bash docker run --ulimit core=-1 <image> # 启用核心转储 ``` #### 典型解决方案流程 ```mermaid graph TD A[启动卡住] --> B[检查日志] B --> C{发现错误?} C -->|是| D[根据错误修复] C -->|否| E[进入容器调试] E --> F{阻塞原因?} F -->|资源不足| G[调整内存/CPU] F -->|网络问题| H[检查端口/防火墙] F -->|权限问题| I[修复文件权限] F -->|应用BUG| J[代码修复] ``` ### 常见根本原因统计 | 原因类型 | 占比 | 典型表现 | |----------------|------|------------------------| | 外部依赖未就绪 | 40% | 数据库连接超时 | | 配置错误 | 25% | 无效端口绑定 | | 资源不足 | 20% | OOM Killer终止进程 | | 权限问题 | 10% | Permission denied | | 应用BUG | 5% | 死锁/无限循环 | > **提示**:当使用`docker stop`强制停止容器时,可能残留僵尸进程,建议用`docker rm -f`彻底清理后再测试[^2]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值