容器中的nginx优化
在物理机上配置Nginx时通常会将Nginx的worker进程数配置为CPU核心数并且会将每个worker绑定到特定CPU上,这可以有效提升进程的Cache命中率,从而减少内存访问损耗。在Nginx配置中一般指定worker_processes指令的参数为auto,来自动检测系统的CPU核心数从而启动相应个数的worker进程。在Linux系统上Nginx获取CPU核心数是通过系统调用 sysconf(_SC_NPROCESSORS_ONLN) 来获取的,对于容器来说目前还只是一个轻量级的隔离环境,它并不是一个真正的操作系统,所以容器内也是通过系统调用sysconf(_SC_NPROCESSORS_ONLN)来获取的,这就导致在容器内,使用Nginx如果worker_processes配置为auto,看到的也是宿主机的CPU核心数。
通常当我们绑定好CPU并设置nginx进程是CPU核数的两倍,用于提高整体的并发
小米运维使用了一种的解决的方式是:劫持系统调用sysconf,在类Unix系统上可以通过LD_PRELOAD这种机制预先加载个人编写的的动态链接库,在动态链接库中劫持系统调用sysconf并根据cgroup信息动态计算出可用的CPU核心数。
而XCFS目前仅支持改变容器的CPU视图(/proc/cpuinfo文件内容)并且只有--cpuset-cpus参数可以生效,对于系统调用sysconf(_SC_NPROCESSORS_ONLN)返回的同样还是物理机的CPU核数。
我抱着试一试的态度,对此作了一些测试,如下:
开始测试
我们在4核CPU的机器做测试,对容器的CPU资源做限制(如果不做CPU限制默认使用所有的资源),我们使用cpuset: '1,3'绑定cpu-2和cpu-4上,然后观察仍然是4个,对容器设置 cpu-shares 和 cpu-quota 也会得到同样的结果。 我们进行测试
配置cpu限制
cpuset: '1,3'
[root@linuxea-VM-Node_10_10_240_144 /data/mirror]$ cat docker-compose.yml
version: '2'
services:
nginx_createrepo:
image: marksugar/nginx_createrepo
# build:
# context: https://raw.githubusercontent.com/LinuxEA-Mark/docker-createrepo/master/Dockerfile
container_name: nginx_createrepo
restart: always
network_mode: "host"
cpuset: '1,3'
cpu_quota: 400000
volumes:
- /data/mirrors:/data
environment:
- NGINX_PORT=80
- SERVER_NAME=localhost
nginx配置worker_processes auto;
我们先用 cpuset: '1,3'绑定cpu2和cpu4,按照我们之前的逻辑,两颗CPU,在worker_processes auto的情况下,nginx进程应该是两个的在过滤下更明显
[root@linuxea-VM-Node_10_10_240_144 ~]$ docker exec -it nginx_createrepo ps aux|grep nginx
root 13 0.0 0.0 45912 6024 ? S 08:01 0:00 nginx: master p
www 35 0.0 0.3 66324 23704 ? S 08:01 0:00 nginx: worker p
www 36 0.0 0.3 66324 23704 ? S 08:01 0:00 nginx: worker p
www 37 0.0 0.3 66324 23704 ? S 08:01 0:00 nginx: worker p
www 38 0.0 0.3 66324 23704 ? S 08:01 0:00 nginx: worker p
container_cpu_detection
我们克隆小米开源的container_cpu_detection(容器cpu检测)测试下代码克隆
[root@linuxea-VM-Node_10_10_240_144 /data/mirror]$ git clone https://github.com/agile6v/container_cpu_detection.git
正克隆到 'container_cpu_detection'...
remote: Counting objects: 46, done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 46 (delta 19), reused 41 (delta 17), pack-reused 0
Unpacking objects: 100% (46/46), done.
[root@linuxea-VM-Node_10_10_240_144 /data/mirror]$
make
[linuxea-VM-Node_10_10_240_144 /data/mirror/container_cpu_detection]$ make
gcc -std=c99 -Wall -shared -g -fPIC -ldl detection.c -o detection.so
gcc sysconf_linuxea.c -o sysconf_linuxea
[root@linuxea-VM-Node_10_10_240_144 /data/mirror/container_cpu_detection]$ sed -i 's/test/linuxea/g' * && mv sysconf_test.c sysconf_linuxea.c
挂载到容器里面,添加这些参数到容器:资源限制
cpuset: '1,3'cpu_quota: 200000
变量传递
- DETECTION_TARGETS=nginx
- LD_PRELOAD=/usr/lib/detection.so
文件挂载
- /data/container_cpu_detection/detection.so:/usr/lib/detection.so
- /data/container_cpu_detection/sysconf_linuxea:/tmp/sysconf_linuxea
完整的compose如下
[root@linuxea-VM-Node_10_10_240_144 /data/mirror]$ cat docker-compose.yml
version: '2'
services:
nginx_createrepo:
image: marksugar/nginx_createrepo
# build:
# context: https://raw.githubusercontent.com/LinuxEA-Mark/docker-createrepo/master/Dockerfile
container_name: nginx_createrepo
restart: always
network_mode: "host"
cpuset: '1,3'
cpu_quota: 200000
volumes:
- /data/mirrors:/data
- /data/container_cpu_detection/detection.so:/usr/lib/detection.so
- /data/container_cpu_detection/sysconf_linuxea:/tmp/sysconf_linuxea
environment:
- NGINX_PORT=80
- SERVER_NAME=localhost
- DETECTION_TARGETS=nginx
- LD_PRELOAD=/usr/lib/detection.so
up起来,查看CPU个数仍然不变
[root@linuxea-VM-Node_10_10_240_144 /data/container_cpu_detection]$ docker exec -it nginx_createrepo top
top - 09:02:09 up 41 days, 2:40, 0 users, load average: 0.07, 0.02, 0.00
Tasks: 8 total, 1 running, 7 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0/0.0 0[ ]
%Cpu1 : 0.0/0.0 0[ ]
%Cpu2 : 0.0/0.8 1[ ]
%Cpu3 : 0.0/0.0 0[ ]
KiB Mem : 33.5/7188532 [|||||||||||||||||| ]
KiB Swap: 0.0/4190204 [ ]
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 13756 2716 2500 S 0.0 0.0 0:00.02 startup.sh
10 root 20 0 116972 16976 7104 S 0.0 0.2 0:00.20 supervisord
13 root 20 0 13756 2640 2444 S 0.0 0.0 0:00.00 bash
14 root 20 0 48028 6192 5140 S 0.0 0.1 0:00.01 nginx
15 root 20 0 10664 392 324 S 0.0 0.0 0:00.00 inotifywait
38 www 20 0 68440 23732 2380 S 0.0 0.3 0:00.00 nginx
39 www 20 0 68440 23732 2380 S 0.0 0.3 0:00.03 nginx
131 root 20 0 58212 3900 3384 R 0.0 0.1 0:00.03 top
在看nginx进程已经如限制的核心数一致
[root@linuxea-VM-Node_10_10_240_144 /data/mirror]$ docker exec -it nginx_createrepo ps aux|grep nginx
root 13 0.0 0.0 48028 6016 ? S 13:19 0:00 nginx: master p
www 32 0.0 0.3 68440 23696 ? S 13:20 0:00 nginx: worker p
www 33 0.0 0.3 68440 23696 ? S 13:20 0:00 nginx: worker p
cpu资源约束信息
--cpu-quota=
对容器施加CPU CFS配额。--cpu-period限制前容器限制为每秒的微秒数。作为有效上限。如果您使用Docker 1.13或更高版本,请--cpus改用。
--cpuset-cpus
限制容器可以使用的特定CPU或核心。如果您有多个CPU,则容器可以使用的以逗号分隔的列表或连字符分隔的CPU范围。第一个CPU编号为0.有效值可能是0-3(使用第一个,第二个,第三个和第四个CPU)或1,3(使用第二个和第四个CPU)。
--cpu-shares
将此标志设置为大于或小于默认值1024的值,以增加或减少容器的重量,并使其可以访问主机的CPU周期的较大或较小比例。仅在CPU周期受限时才会强制执行此操作。当有足够的CPU周期时,所有容器都会根据需要使用尽可能多的CPU。这样,这是一个软限制。--cpu-shares不会阻止容器以群集模式进行调度。它为可用的CPU周期优先考虑容器CPU资源。它不保证或保留任何特定的CPU访问权限。
--cpu-period=
指定CPU CFS调度程序周期,它与并用 --cpu-quota。默认为100微秒。大多数用户不会更改默认设置。如果您使用Docker 1.13或更高版本,请--cpus改用。
-cpus=
指定容器可以使用的可用CPU资源量。例如,如果主机有两个CPU并且您已设置--cpus="1.5",则容器最多保证一个半CPU。这相当于设置--cpu-period="100000"和--cpu-quota="150000"。可在Docker 1.13及更高版本中使用。
-c
, --cpu-shares=0
:CPU份额(相对权重)--cpus=0.000
: CPU数量。数字是小数。0.000表示没有限制--cpu-period=0
: 限制CPU CFS(完全公平计划程序)期间--cpuset-cpus=""
:允许执行的CPU(0-3,0,1)--cpuset-mems=""
: 允许执行的存储器节点(MEM)(0-3,0,1)。仅对NUMA系统有效。 --cpu-quota=0
:限制CPU CFS(完全公平计划程序)配额--cpu-rt-period=0
: 限制CPU实时周期。在几微秒内。需要设置父cgroup并且不能高于父cgroup。还要检查rtprio ulimits--cpu-rt-runtime=0
:限制CPU实时运行时。在几微秒内。需要设置父cgroup并且不能高于父cgroup。还要检查rtprio ulimits参考:
https://docs.docker.com/engine/reference/run/#cpuset-constraint
https://docs.docker.com/config/containers/resource_constraints/#configure-the-default-cfs-scheduler
https://docs.docker.com/compose/compose-file/compose-file-v2/#cpu-and-other-resources
https://github.com/agile6v/container_cpu_detection。