• 限制容器中的进程


    进程的两个状态

    一个进程在活着的时候只有两个状态:运行态和睡眠态

    1、运行态的意思是,无论进程是正在运行中(也就是获得了 CPU 资源),还是进程在 run queue 队列里随时可以运行,都处于这个状态。

    使用ps命令,可以看到处于这个状态的进程显示的是R stat

    2、睡眠态是指,进程需要等待某个资源而进入的状态,要等待的资源可以是一个信号量(Semaphore), 或者是磁盘 I/O,这个状态的进程会被放入到 wait queue 队列里。

    睡眠状态还包括两个子状态:一个是可以被打断的(TASK_INTERRUPTIBLE),我们用 ps 查看到的进程,显示为 S stat。还有一个是不可被打断的(TASK_UNINTERRUPTIBLE),用 ps 查看进程,就显示为 D stat。

    进程除了在活着时候的两个状态运行态和睡眠态,进程在调用do_exit()退出的时候,还有两个状态:

    一个是EXIT_DEAD,也就是进程在真正结束退出的那一瞬间的状态;第二个是 EXIT_ZOMBIE 状态,这是进程在 EXIT_DEAD 前的一个状态,而僵尸进程,也就是处于这个状态中。

    容器的本质就是进程吗,因此需要限制容器中的进程数量,恶意在容器中创建过多的进程,会直接影响宿主机上其他容器和其他程序的工作。限制容器中进程数量需要借助于pid cgroup子系统。在使用cgroups时需要先挂载,例如在centos下pid cgroup子系统被挂载到了/sys/fs/cgroup/pids下,在这个目录下是各个pids控制组目录,每个控制组目录下还可以有子目录,各个控制组形成了一个树状的层级关系。

    关注pids.max文件的值,默认值是max表示不限制,可以为其写入一个数值,这个数值就是此pids控制组中最大进程数。

    一台 Linux 机器上的进程总数目是有限制的。如果超过这个最大值,那么系统就无法创建出新的进程了,比如你想 SSH 登录到这台机器上就不行了。

    这个最大值可以我们在 /proc/sys/kernel/pid_max 这个参数中看到。

    Linux 内核在初始化系统的时候,会根据机器 CPU 的数目来设置 pid_max 的值。

    比如说,如果机器中 CPU 数目小于等于 32,那么 pid_max 就会被设置为 32768(32K);如果机器中的 CPU 数目大于 32,那么 pid_max 就被设置为 N*1024 (N 就是 CPU 数目)。

    对于 Linux 系统而言,容器就是一组进程的集合。如果容器中的应用创建过多的进程或者出现 bug,就会产生类似 fork bomb 的行为。这个 fork bomb 就是指在计算机中,通过不断建立新进程来消耗系统中的进程资源,它是一种黑客攻击方式。这样,容器中的进程数就会把整个节点的可用进程总数给消耗完。

    这样,不但会使同一个节点上的其他容器无法工作,还会让宿主机本身也无法工作。所以对于每个容器来说,我们都需要限制它的最大进程数目,而这个功能由 pids Cgroup 这个子系统来完成。

    KubernetesPod中的限制进程数量

    Kubernetes也提供了限制Pod中进程数量的功能,可以在k8s节点级别进行PID限制,也可以配置Pod级别的PID限制。

    节点级别的pid限制

    节点级别的PID限制,允许配置为k8s节点操作系统预留的一定的PID数量。k8s使用特性门控(FeatureGate)中的SupportNodePidsLimit来实现这功能,SupportNodePidsLimit这个特性门口在k8s 1.20以后的版本里面自动开启。 要为节点操作系统预留一定的数量的PID,例如在kubelet的配置文件中有以下配置:

    ......
    systemReserved:
      cpu: 0m
      memory: 0Mi
      pid: '1000'
    kubeReserved:
      cpu: 0m
      memory: 0Mi
      pid: '100'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    systemReserved用来配置为k8s node的系统预留的资源,kubeReserved用来配置为k8s系统组件预留的资源。这里配置为系统预留1000个pid,为k8s系统组件配置预留100个pid。

    pod级别的pid限制

    Pod级别的PID限制通过配置kubelet配置文件中podPidsLimit实现,默认值是-1表示不限制,下面配置为每个pod最大进程数是100:

    podPidsLimit: 100
    
    • 1

    可以到某个pod所在k8s节点的上的这个pod的pid cgroup目录里去查看, pids.max的值是100:

    cd /sys/fs/cgroup/pids/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda15fb63c_4335_49d2_957f_a90b1348197e.slice
    cat pids.max
    100
    
    • 1
    • 2
    • 3

    我们对这个值做好限制,容器就不会因为创建出过多进程而影响到其他容器和宿主机了。

    清理容器中的僵尸进程

    进程号资源在宿主机上是有限的,残留的僵尸进程多了以后,给系统带来最大问题就是它占用了进程号。这就意味着,残留的僵尸进程,在容器里仍然占据着进程号资源,很有可能会导致新的进程不能运转。

    造成僵尸进程的原因

    父进程在创建完子进程之后就不管了,这就是造成子进程变成僵尸进程的原因。

    找到原因,就好解决了。就可以让父进程去管理子进程就可以了。

    所以,在 Linux 中的进程退出之后,如果进入僵尸状态,我们就需要父进程调用 wait() 这个系统调用,去回收僵尸进程的最后的那些系统资源,比如进程号资源。

    那么,我们在刚才那段代码里,主进程进入 sleep(100) 之前,加上一段 wait() 函数调用,就不会出现僵尸进程的残留了。

    for (i = 0; i < total; i++) {
    		int status;
        wait(&status);
    }
    
    • 1
    • 2
    • 3
    • 4

    而容器中所有进程的最终父进程,就是我们所说的 init 进程,由它负责生成容器中的所有其他进程。因此,容器的 init 进程有责任回收容器中的所有僵尸进程。

    但是 wait() 系统调用有一个问题,wait() 系统调用是一个阻塞的调用,也就是说,如果没有子进程是僵尸进程的话,这个调用就一直不会返回,那么整个进程就会被阻塞住,而不能去做别的事了。

    Linux 还提供了一个类似的系统调用 waitpid(),这个调用的参数更多。

    其中就有一个参数 WNOHANG,它的含义就是,如果在调用的时候没有僵尸进程,那么函数就马上返回了,而不会像 wait() 调用那样一直等待在那里。

    总结:

    1.,父进程在创建完子进程之后就不管了,而每一个 Linux 进程在退出的时候都会进入一个僵尸状态,这时这些进入僵尸状态的进程就因为无法回收变成僵尸进程。

    2.僵尸进程是无法直接被kill掉的,需要父进程调用wait()或watipid()回收。

    3.清理僵尸进程的两个思路 (1)kill掉僵尸进程的父进程,此时僵尸进程会归附到init(1)进程下,而init进程一般都有正常的wait()或watipid()回收机制。 (2)利用dumb-init/tini之类的小型init服务来解决僵尸进程

    进程对每种信号的处理

    进程对每种信号的处理,包括三个选择:调用系统缺省行为、捕获、忽略。而这里的选择,其实就是程序中如何去调用 signal() 这个系统调用。

    缺省

    第一个选择就是缺省,如果我们在代码中对某个信号,比如 SIGTERM 信号,不做任何 signal() 相关的系统调用,那么在进程运行的时候,如果接收到信号 SIGTERM,进程就会执行内核中 SIGTERM 信号的缺省代码。

    对于 SIGTERM 这个信号来说,它的缺省行为就是进程退出(terminate)。

    内核中对不同的信号有不同的缺省行为,一般会采用退出(terminate),暂停(stop),忽略(ignore)这三种行为中的一种。

    捕获

    第二个选择是捕获,捕获指的就是我们在代码中为某个信号,调用 signal() 注册自己的 handler。这样进程在运行的时候,一旦接收到信号,就不会再去执行内核中的缺省代码,而是会执行通过 signal() 注册的 handler。

    忽略

    第三个选择是忽略,如果要让进程“忽略”一个信号,我们就要通过 signal() 这个系统调用,为这个信号注册一个特殊的 handler,也就是 SIG_IGN 。

    **注意:**SIGKILL 和 SIGSTOP 信号是两个特权信号,它们不可以被捕获和忽略,这个特点也反映在 signal() 调用上。

  • 相关阅读:
    Mysql同环比计算详解
    mysqlbinlog使用记录
    Gartner:机器人流程自动化RPA三大误区
    重装系统后要安装哪些驱动
    setoolkit启动
    postgresql源码学习(36)—— 事务日志11 - 日志归档
    [MRCTF2020]Ezpop(详解)
    Oracle/PLSQL: Asin Function
    vue3项目,vite+vue3+ts+pinia(10)-elementplus布局
    Python 音频处理以及可视化 Amplitude,MFCC,Mel Spectrogram, librosa 库
  • 原文地址:https://blog.csdn.net/qq_34939308/article/details/126766918