重学容器30: 容器资源限制之限制容器的进程数量
2021-07-29
容器的单进程模型 #
容器的单进程模型是指推荐在一个容器里只运行一个进程
。
容器的单进程模型
并不是说容器里只能运行一个进程,而是因为容器本身没有管理多个进程的能力,推荐在一个容器里只运行一个进程。
在正常的Linux操作系统里都有个1号进程init,例如CentOS的systemd,所有的其他进程都是1号进程的子进程或者是子进程的子进程。
1号进程具有进程管理能力,例如对僵尸进程的管理。而容器中的1号进程就是容器运行的应用本身,它是没有进程管理能力的,所以推荐在容器中只运行一个进程。
容器进程数的限制 #
子进程的创建后,一般需要父进程通过系统调用wait()
或waitpid()
来等待子进程结束,以回收子进程的系统资源;另外还可以通过异步的方式,子进程结束后向父进程发送SIGCHLD
信号,而父进程中需要注册一个SIGCHLD
信号的处理函数在函数中回收子进程的系统资源。
僵尸进程是指进程结束系统资源没有被回收就变成了僵尸进程。僵尸进程会一直占有系统资源,例如进程id,操作系统有最大进程数的限制,超过这个限制系统将不能再创建任何进程。
如果子进程先于父进程退出,且父进程没有对子进程的残留资源进行回收的话,就会产生僵尸进程。如果父进程先于主进程退出,这个时候正在运行的子进程成为孤儿进程,此时操作系统会将孤儿进程的父进程设置为1(如systemd),这样可以确保systemd对孤儿进程的资源进行回收。
前面简单了解僵尸进程危害,虽然有推荐在一个容器里只运行一个进程
这个指导建议,但不可避免有人在构建的容器镜像中启动很多个进程,或者因为bug导致启动容器时后运行过程中创建了很多子进程,因此就需要在启动容器时对容器的进程数量进行限制。
先看一下操作系统中的进程总数限制,一般服务器的CPU数量小于32,进程总数限制会被设置为32768,大于32会被设置为cpu数量乘以1024:
1cat /proc/sys/kernel/pid_max
232768
容器的本质就是进程,因此需要限制容器中的进程数量,恶意在容器中创建过多的进程,会直接影响宿主机上其他容器和其他程序的工作。限制容器中进程数量需要借助于pid cgroup子系统。
在使用cgroups时需要先挂载,例如在centos下pid cgroup子系统被挂载到了/sys/fs/cgroup/pids
下,,在这个目录下是各个pids控制组目录,每个控制组目录下还可以有子目录,各个控制组形成了一个树状的层级关系。
例如在/sys/fs/cgroup/pids
下创建一个名为foo的pids控制组目录,控制组目录中有如下内容:
1cd /sys/fs/cgroup/pids
2
3mkdir foo
4cd foo && ls
5cgroup.clone_children cgroup.event_control cgroup.procs notify_on_release pids.current pids.max tasks
关注pids.max
文件的值,默认值是max
表示不限制,可以为其写入一个数值,这个数值就是此pids控制组中最大进程数。
使用nerdctl启动containerd容器时,有两个选项与容器中进程相关:
1--pid value PID namespace to use
2--pids-limit value Tune container pids limit (set -1 for unlimited) (default: -1)
--pid
指定容器使用pid namespace是用来做隔离的,而--pids-limit
用来限制容器中的最大进程数量。
测试启动一个容器,并限制最大进程数量是1:
1nerdctl run -it --rm --pids-limit=1 alpine:3.14 sh
2/ # ps
3sh: can't fork: Resource temporarily unavailable
上面的命令尝试启动了一个alpine容器,容器中的进程是sh,同时设置了最大进程数是1,在容器中尝试使用ps命令就会报sh: can't fork: Resource temporarily unavailable
,即无法创建ps子进程。
如何KubernetesPod中的限制进程数量 #
既然容器的单进程模型是推荐在一个容器里只运行一个进程,Kubernetes中提供了Pod抽象,可以将多个容器编排到一个Pod里。 Kubernetes也提供了限制Pod中进程数量的功能,可以在k8s节点级别进行PID限制,也可以配置Pod级别的PID限制。
节点级别的PID限制,允许配置为k8s节点操作系统预留的一定的PID数量。k8s使用特性门控(FeatureGate)中的SupportNodePidsLimit来实现这功能,SupportNodePidsLimit这个特性门口在k8s 1.20以后的版本里面自动开启。 要为节点操作系统预留一定的数量的PID,例如在kubelet的配置文件中有以下配置:
1......
2systemReserved:
3 cpu: 0m
4 memory: 0Mi
5 pid: '1000'
6kubeReserved:
7 cpu: 0m
8 memory: 0Mi
9 pid: '100'
systemReserved
用来配置为k8s node的系统预留的资源,kubeReserved
用来配置为k8s系统组件预留的资源。这里配置为系统预留1000个pid,为k8s系统组件配置预留100个pid。
Pod级别的PID限制通过配置kubelet配置文件中podPidsLimit
实现,默认值是-1表示不限制,下面配置为每个pod最大进程数是100:
1podPidsLimit: 100
可以到某个pod所在k8s节点的上的这个pod的pid cgroup目录里去查看, pids.max
的值是100:
1cd /sys/fs/cgroup/pids/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda15fb63c_4335_49d2_957f_a90b1348197e.slice
2cat pids.max
3100