一、init 进程

1. 简介

init 进程又称1号进程是第一个用户态的进程,它最基本的功能就是创建出Linux 系统中其他所有的进程,并且管理这些进程。在Namespace 中由它直接或者间接创建其他的进程。

2. 容器 init 进程 对 SIGKILL和 SIGTERM信号的处理

2.1 Linux 信号

2.1.1 简介

用一句概括,信号(Signal)其实就是 Linux 进程收到的一个通知。

  1. [root@node160 ~]# kill -l
  2. 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
  3. 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
  4. 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
  5. 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  6. 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
  7. 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
  8. 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
  9. 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
  10. 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  11. 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  12. 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
  13. 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
  14. 63) SIGRTMAX-1 64) SIGRTMAX

2.1.2 信号处理

方式 描述
忽略 忽略不处理,两个信号例外(SIGKILL和 SIGSTOP)
捕获 用户进程可以注册自己针对这个信号的handler,两个信号例外(SIGKILL和 SIGSTOP)
缺省行为 每个信号都定义了一个缺省行为


2.1.3 SIGTERM(15) 和 SIGKILL(9)

  • SIGTERM(15) 可以被捕获,用户进程可以注册handler 进程进行处理。可以杀死init 进程。
  • SIGKILL(9) 特权信号 不能被忽略也不能被捕获, 不能杀死init 进程

    2.2 总结

  • kill -9 1 在容器中是不工作的,内核阻止了1号进程对SIGKILL 特权信号的响应

  • kill 1 分两种情况,如果1号进程没有注册SIGTERM 的handler,那么对SIGTERM 信号不响应;如果注册了handler,那么就可以响应 SIGTERM 信号

    3. 容器init 进程对僵尸进程的处理

    3.1 僵尸进程

    进程在调用do_exit() 退出的时候,有两个状态:EXIT_DEAD 和 EXIT_ZOMBIE(僵尸进程),EXIT_ZOMBIE 状态,这是进程在 EXIT_DEAD 前的一个状态。

    3.2 linux限制进程数目

  • CPU 数目小于等于 32 /proc/sys/kernel/pid_max 设置32768

  • CPU 数目大于32 pid_max 被设置为 N * 1024(N 是CPU的数目)

    3.3 限制容器中进程的数目

    pids Cgroup 通过 Cgroup 文件系统的方式向用户提供操作接口,一般它的Cgroup 文件系统挂载点在 /sys/fs/cgroup/pids。
    在一个容器建立之后,创建容器的服务会在 /sys/fs/cgroup/pids 下建立一个子目录,就是一个控制组,控制组里最关键的一个文件就是 pids.max。我们可以向这个文件写入数值,而这个值就是这个容器中允许的最大进程数目。

    3.3 解决容器中的僵尸进程

    3.1 问题

    残留的僵尸进程,在容器里仍然占据着进程资源,很可能会导致新的进程不能运转。

    3.2 原因

    父进程在创建完子进程之后就不管了

    3.3 解决

    容器的init 进程负责回收容器中所有的僵尸进程,避免产生僵尸进程。
    父进程调用wait() 和 waitpid() 系统用来清理,这是init 进程必须具备的一个功能。比如tini

  • wait() 阻塞的调用,没有僵尸进程会发生阻塞

  • waitpid() 有一个参数WNOHANG,没有僵尸进程会返回

    4. 容器init 进程对信号的转发

    4.1 问题

Containerd 在停止容器的时候会向容器的init 进程发送一个 SIGTERM 信号。init 进程收到的是SIGTERM 信号,其他进程收到的是SIGKILL 信号。这样就会导致容器中的其他进程不能graceful shutdown。

4.2 原因

Linux 进程收到SIGTERM 信号并且使进程退出,会调用do_exit() 函数,释放资源;随后调用一个exit_notify() 函数,用来通知这个进程相关的子进程。对于容器来说,处于退出状态的init 进程会调用zap_pid_ns_processes() 函数向Namespace 中的其他进程都发送一个SIGKILL 信号。

未命名文件 (2).png

4.3 解决

让容器的init进程来转发SIGTERM 信号。

二、CPU计算资源

1. CPU Cgroup 对容器计算资源的限制

1.1 简介

每个进程的CPU Usage 只包含用户态(us或ni) 和 内核态(sy)两部分,其他的系统CPU开销并不包含在进程的CPU内,CPU Cgroup 只对进程的CPU做了限制,通过树状控制组方式。

未命名文件 (2).png

1.2 Limit CPU 和 Request CPU

CPU cgroup 对进程调度一般使用CFS(完全公平调度器)。涉及到三个参数。

名称 默认值 说明
cpu.cfs_periods_us 100000 us 调度周期,默认值为100ms
cpu.cfs_quota_us / 在一个调度周期里这个控制组被允许运行的时间,比如50ms,那么需要 50ms/100ms=0.5个CPU
cpu.shares 1024 控制组之间的分配比例,缺省是1024;假设groups3的cpu.shares 是1024,groups4的cpu.shares 是3072,那么group3:group4=1:3,只有所有节点都跑满有效
  • Limit CPU :容器所在 Cgroup 控制组中的 CPU 上限值,由cpu.cfs_quota_us 和 cpu.cfs_period_us 控制
  • Request CPU: 控制组中的 cpu.shares 的值,在CPU Cgroup 中cpu.share==1024 表示1个CPU的比例,那么Request CPU 的值就是n, 给cpu.shares 的赋值对应就是n*10242.

2. 如何查看容器中CPU的Metrics

2.1 问题

容器中top 查看到的系统的CPU使用率是宿主机上的。

2.2 原因

  • 进程CPU使用率 (/proc/[pid]/stat)

    计算方式: (进程的 ticks / 单个 CPU 总 ticks) * 100.0

  • 系统 CPU 使用率 (/proc/stat)

    计算方式: 得到瞬时各项( user/system/nice/idle/iowait/irq/softirq/steal 8 项 ) CPU 使用率的 ticks值,相加得到一个总值,单项值除以总值就是各项 CPU 的使用率

而 /proc/stat 不在容器的NameSpace 中,所以导致了查询到的系统CPU使用率是总体的。

2.3 解决

CPU Cgroup 的控制组 有个cpuacct.stat,这里包含了两个统计值,这两个值分别是这个控制组里所有进程的内核态 ticks 和用户态的 ticks。使用 CPU 使用率 =((utime_2 – utime_1) + (stime_2 – stime_1)) 100.0 / (HZ et * 1 ) 计算。

3.CPU Cgroup 限制容器资源的盲点

3.1 问题

问题:对容器已经用CPU Cgroup 限制了它的 CPU Usage,容器里的进程还是可以造成整个系统很高的Load Average

3.2 原因

Load Average= 可运行队列进程平均数 + D状态进程平均数,其中D状态进程也称为 TASK_UNINTERRUPTIBLE 进程,标识进程为等待某个系统资源而进入睡眠状态。

3.3 解决

对 D 状态进程进行监控。