Docker的进程和Cgroup概念
容器里的进程组织或关系
0号进程:containerd-shim
角色:
- containerd-shim 是容器的父进程,负责管理容器的生命周期,接收容器内的执行的指令。
- 它通过调用 runc 创建容器,并处理容器内的指令。
特点:
- 容器依赖:
- containerd-shim 是容器的祖宗进程,如果它挂掉,整个容器也会退出。
- 进程管理:
- 如果容器的1号进程结束,containerd-shim 会回收容器命名空间中的所有进程。
- 如果1号进程未结束,但其子进程结束,子进程会成为僵尸进程,需要由1号进程回收。
- 信号处理:
- docker stop 会向容器的1号进程发送 SIGTERM(-15)信号。
- 如果1号进程没有信号转发能力,它会向容器内的所有进程发送 SIGKILL(-9)信号。
- 如果1号进程有信号转发能力,它会向容器内的所有进程转发 SIGTERM(-15) 信号。
- docker stop 会向容器的1号进程发送 SIGTERM(-15)信号。
1号进程:容器内的第一个进程
角色:
- 1号进程是容器内的第一个进程,代表容器的生命周期。
- 它通常是用户指定的应用程序进程。
特点:
- 生命周期:
- 1号进程结束,容器也会结束。
- 1号进程必须在前台运行,否则容器会退出。
- 功能缺陷(与操作系统进程的区别):
- 容器内的1号进程不一定是所有用户进程的祖先。
- 如果1号进程成为孤儿进程,它会被init进程(0号进程)收养。
- 用户开发的1号进程可能缺乏回收僵尸进程和转发信号的能力。
应具备的能力:
- 回收僵尸进程::定期调用 wait 或 waitpid 回收僵尸子进程。
- 信号转发:将接收到的信号(如 SIGTERM)转发给子进程。
~# docker container inspect text | grep -i pid
pid:41404---------对应的就是容器内的1号进程
~# ps -elf | grep 41404
pid:41404 ppid:41382---------对应的就是容器内的0号进程
进程收到信号后的三种反应
信号是操作系统向进程发送的一种通知,用于通知进程发生了某种事件。可以用于进程间通信或控制进程行为。
- 忽略(Ignore):
- 进程对信号不做任何处理。
- 示例:忽略 SIGTERM 信号,进程不会被终止。
- 捕获(Catch):
- 进程可以注册自定义的处理函数(handler)来处理捕获的信号。
- 当信号到达时,触发 handler 执行。
- 示例:
trap 'echo "signal received"' SIGTERM
- 默认行为(Default):
- 每个信号都有默认行为,由操作系统定义。
- 示例:
- SIGTERM 的默认行为是终止进程。
- SIGKILL 的默认行为是强制终止进程。
两个特权信号
SIGKILL (-9)
- 作用: 强制终止进程。
- 特点:
- 无法被忽略。
- 无法被捕获。
- 使用场景: 当进程无响应时,强制终止进程。
SIGSTOP (-19):
- 作用: 暂停进程的运行
- 特点:
- 无法被忽略。
- 无法被捕获。
- 恢复运行: 发送 SIGCONT (-18) 信号。
SIGTERM(-15)信号是可以被进程忽略或者捕获的
在容器内执行 kill 命令的行为
kill -9 1
- 无法杀死容器内的1号进程。
- 原因: 容器内的1号进程被打上了 SIGNAL_UNKILLABLE 标签。
kill -19 1
- 无法暂停容器内的1号进程。
- 原因: 容器内的1号进程被打上了 SIGNAL_UNKILLABLE 标签。
kill -15 1
- 有可能杀死容器内的1号进程。
- 如果1号进程没有注册 SIGTERM 信号的处理函数,它会忽略该信号。
- 如果1号进程注册了 SIGTERM 信号的处理函数,它会执行该函数。
Cgroup 介绍
Cgroup(Control Group)是 Linux 内核提供的一种机制,用于限制、控制和监控进程组的资源使用。它允许系统管理员对一组进程的资源使用进行精细化管理,包括 CPU、内存、磁盘 I/O 等。Cgroup 是容器技术(如 Docker、Kubernetes)中实现资源隔离和管理的基础。
为何要用 Cgroup?Cgroup 的主要作用是限制容器或进程组对宿主机资源的使用,防止某个容器或进程过度占用资源,从而影响其他容器或进程的正常运行。通过 Cgroup,可以确保宿主机上的多个容器或进程能够公平、稳定地共享系统资源。
在 Linux 系统中,可以通过以下命令查看和管理 Cgroup
# 查看当前的 Cgroup 控制层级
cat /proc/cgroups
# 创建一个新的 Cgroup
mkdir /sys/fs/cgroup/cpu/my_cgroup
# 将一个进程加入到 Cgroup 中
echo "PID" > /sys/fs/cgroup/cpu/my_cgroup/tasks
# 设置 CPU 限制
echo "2" > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_quota_us
CPU Cgroup 中与 CFS 相关的参数
CFS(Completely Fair Scheduler)是 Linux 内核中的一种调度算法,用于公平地分配 CPU 时间给各个进程。Cgroup 中与 CFS 相关的参数决定了进程组对 CPU 的使用率。
- cpu.cfs_period_us:表示 CPU 的时间周期,单位为微秒 (μs)。例如,设置为 100ms (100,000 μs),表示一个周期为 100 毫秒。
- cpu.cfs_quota_us:表示在该时间周期内,控制组内的进程最多可以使用的 CPU 时间。例如,设置为 50ms (50,000 μs),表示在 100ms 的周期内,进程最多可以使用 50ms 的 CPU 时间。此时,CPU 使用率为 50ms / 100ms = 0.5,即 50%。
- cpu.shares:用于控制同一层级下的多个控制组之间的 CPU 资源分配。当宿主机上的 CPU 资源不足时,cpu.shares 会生效,决定各个控制组之间的 CPU 资源分配比例。cpu.shares 的值越大,分配的 CPU 资源越多。
总结:
- cpu.cfs_quota_us与cpu.cfs_period_us这两个值决定了每个控制组所有进程可使用cpu资源的最大值
- cpu.shares这个值决定了cpu cgroup子系统下控制组可用cpu的相对比例,不过只有当系统上cpu被占满时,这个比例才会在各个控制组间起作用
Kubernetes 中的资源管理
在 Kubernetes 中,Pod 的资源请求和限制可以通过 requests 和 limits 来设置。
- requests:表示 Pod 对资源的最低需求。Kubernetes 会根据 requests 来调度 Pod,确保节点上有足够的资源。requests 对应 Cgroup 中的 cpu.shares,表示初始的 CPU 资源申请量。实际使用量可以超过 requests,但不会低于它。
- limits:表示 Pod 对资源使用的上限。Kubernetes 会通过 Cgroup 限制 Pod 的资源使用,确保不会超过 limits 设置的值。limits 对应 Cgroup 中的 cpu.cfs_quota_us 和 cpu.cfs_period_us,表示 CPU 使用的硬性上限。
但是要注意,limits 设置的上限是否能达到,还取决于宿主机的实际资源情况。如果宿主机资源不足,Pod 可能无法达到 limits 设置的上限。
memory cgroup
每个容器都有对应的 memory cgroup 控制组,位于 /sys/fs/cgroup/memory/system.slice/docker - xxx
,用于管理容器内存。
主要参数:
- memory.limit_in_bytes:设置容器内所有进程可占用的物理内存上限,子 group 最多只能设置到父级 group 的该值。
- memory.oom_control:默认为 0,表示开启 OOM 机制;可设为 1 关闭,通过 echo 1 > memory.oom_control 实现。
- memory.usage_in_bytes:只读参数,显示容器内所有进程占用的物理内存总量。
示例: 容器启动时,rss 为 100 M,page cache 为 899 M,内存总使用量为 999 M;随着进程运行,申请更多内存后,rss 增至 200 M,page cache 减至 699 M,内存总使用量仍为 899 M,但实际进程占用的物理内存增大。
容器的可用磁盘进行配额
默认情况,容器内的可用磁盘空间是没有限制的。
容器内的文件系统 = lowerdir + upperdir
写入操作
- 在容器内写容器文件系统里写东西(任何目录都没有挂载任何外部存储卷)
- 那此时写入的数据,都写到了upperdir层,也就是写到的宿主机上。
- 所以如果不加以限制,很有可能会导致宿主机磁盘空间写满。
如何解决问题?有两种方式:
- 对容器的可用磁盘进行配额
- 对容器写操作的目录,应该挂载一个专门的外部存储卷(推荐)