容器实战高手课笔记 ----来源《极客时间》
01 | 认识容器:容器的基本操作和实现原理
1. Namespace 和 Cgroups,是容器的两大特性。容器其实就是 Namespace+Cgroups
(1) Namespace 实现各种资源的隔离
(2) Cgroups 对容器使用某种资源量的多少做限制。
2. 容器的基本操作
(1) 用文件Dockerfile 来建立一个容器的镜像,然后用这个镜像来启动一个容器。
(2) docker exec 这个命令进入容器的运行空间,查看进程启动,检查配置文件,检验服务状态。
02 | 理解进程(1):为什么我在容器中不能kill 1号进程?
1. Linux的1号进程,是第一个用户态的进程。它创建了 Namespace 中的其他进程。
2. Linux 有 31 个基本信号,进程处理信号的方式有三个:忽略、捕获和缺省行为。
3. 两个特权信号 SIGKILL(9) 和 SIGSTOP(19) 不能被忽略或者捕获。因为它们的作用是为 Linux kernel 和超级用户提供删除任意进程的特权。
4. 不能kill 1号进程,是因为内核把SIGKILL信号忽略掉了。符合两个条件的话会被忽略:
(1) 使用默认的捕获处理函数。(SIGKILL信号不允许用户自定义处理函数)
(2) 被打上了SIGNAL_UNKILLABLE标签。(1号进程启动时固定会被打上这个标签)
5. 换言之:
(1) 1 号进程永远不会响应 SIGKILL 和 SIGSTOP 这两个特权信号
(2) 对于其他的信号,如果用户自己注册了 handler,1 号进程可以响应(比如15号信号SIGTERM)
03|理解进程(2):为什么我的容器里有这么多僵尸进程?
1. 程序在退出时,会先进入EXIT_ZOMBIE 状态。在父进程做了回收处理之后,再进入EXIT_DEAD状态。 用 ps 命令可以查看,僵尸进程的状态是 Z (EXIT_ZOMBIE)
2. Linux系统的进程数目是有限制的,最大值在 /proc/sys/kernel/pid_max 这里设置。僵尸进程如果不清理,就会消耗系统中的进程数资源,导致新进程无法启动。
3. 僵尸进程需要父进程调用 wait() 来清理,这也是容器中 init 进程(1号进程) 的核心功能。
04 | 理解进程(3):为什么我在容器中的进程被强制杀死了?
1. 默认状态下,停止一个容器的时候,容器 init 进程会收到 SIGTERM 信号,而容器中其他进程会收到 SIGKILL 信号。
2. 为了让容器内的进程能优雅地关闭,可以需要在停止容器时,让容器中的程序收到的信号是 SIGTERM(15),而不是 SIGKILL(9),就可以捕获并自定义处理函数。
3. 具体实现是,在容器的 init 进程自定义SIGTERM 信号的处理函数,把关闭其他进程的信号 从 SIGKILL 改成 SIGTERM。
05|容器CPU(1):怎么限制容器的CPU使用?
1. CPU Cgroup 是 Cgroups 其中的一个 Cgroups 子系统,它是用来限制进程的 CPU 使用量的。
2. top命令能输出CPU的各项使用情况: us(用户态CPU使用),sy(系统调用CPU使用),wa(等待 I/O 的时间),hi(硬中断开销),si(软中断开销)。 可见,IO和软硬中断,都不会计入进程的CPU时间。
3. CPU Cgroup 中的主要参数:
(1) cpu.cfs_period_us:调度周期,一般值是100000,以微秒为单位,也就是100ms。
(2) cpu.cfs_quota_us:一个调度周期里这个控制组被允许的运行时间上限值。
(3) cpu.shares:控制组可用 CPU 的相对比例(权重值),当系统上 CPU 完全被占满的时候,才会起作用。
06 | 容器CPU(2):如何正确地拿到容器CPU的开销?
1. top 命令只能显示整个宿主中各项 CPU 的使用率,不能显示单个容器的各项 CPU 的使用率。
2. 原因:top 是通过读取 /proc/stat 文件来得到 CPU 使用率,但/proc/stat 文件是整个节点全局的状态文件,不属于任何一个 Namespace,因此在容器中无法通过读取 /proc/stat 文件来获取单个容器的 CPU 使用率。
3. 单个容器的 CPU 使用率,可以从 CPU Cgroup 每个控制组里的统计文件 cpuacct.stat 中获取
07 | Load Average:加了CPU Cgroup限制,为什么我的容器还是很慢?
1. top 输出里的 Load Average = 可运行队列进程平均数 + 休眠队列中不可打断的进程平均数
2. 可运行队列进程:R状态; 休眠队列中不可打断的进程: D状态
3. 在Linux 内核中把进程设置为 D 状态,主要集中在 disk I/O 访问和信号量锁的访问上。当进程处于 D 状态时,就说明进程还没获得资源。
4. Cgroups无法限制D状态进程导致系统性能下降的原因是: Cgroups 仅能约束用户态资源的使用,无法干预内核态的资源调度。
08 | 容器内存:我的容器为什么被杀了?
1. 运行docker inspect命令,journalctl -k 命令,查看 /var/log/message日志,可以查看容器退出的原因,能确认是不是被OOM杀掉。
2. OOM是什么?内存不足时,会杀死一个正在运行的进程来释放一些内存。
原因: malloc() 申请的是内存的虚拟地址,系统只是给了一个地址范围,并没有得到真正的物理内存。只有程序往这个地址写入数据的时候,才会分配物理内存。才会出现内存不足,需要杀程序。
3. 选择一个进程来OOM,有一个标准计算公式。大致上是设置的校准值越大,已使用内存越多,越容易被OOM
4. 限制内存使用的 Memory Cgroup 里两个基本参数:
(1) memory.limit_in bytes: 限制控制组里所有进程可使用内存的最大值
(2) memory.oom_control:控制组中的进程内存使用达到上限值时,会不会触发 OOM Killer,默认会触发。
5. 设置不触发OOM之后,容器里控制组里的进程申请物理内存时,进程会处于一个停止状态,不能往下运行。
09 | Page Cache:为什么我的容器内存使用量总是在临界点?
1. Memory Cgroup 在统计每个控制组的内存时包含两部分:RSS 和 Page Cache。
(1) RSS 是进程实际占用的物理内存
(2) Page Cache 是读写磁盘文件后,作为缓存而继续保留在内存中的,目的是提高磁盘文件的读写性能。
2. 回收操作: 当内存紧张或者 Memory Cgroup 控制组的内存达到上限的时候,Linux 会释放 Page Cache 的内存,不需要担心内存不够。
3. 判断一个容器的内存使用状况时,可以把 Page Cache 这部分内存使用量忽略,更多考虑 RSS 的使用量。
10 | Swap:容器可以使用Swap空间吗?
1. Swap的好处:可以应对瞬时突发的内存增大需求,不至于因为内存一时不够而触发 OOM ,导致进程被杀死。
2. Swap的坏处:如果容器中的程序发生了内存泄漏,那么 Memory Cgroup 本来可以及时杀死这个进程不影响整个节点。但因为Swap到磁盘上了,这个内存泄漏的进程没被杀死,进而影响了整个节点的性能。
3. Linux 中的 swappiness 参数值的作用是:当系统需要回收内存时,是优先释放 Page Cache 内存,还是优先释放匿名内存(没有对应磁盘文件的内存,比如用 malloc() 申请得到的内存)即swap。
4. swappiness 的范围是 0 到 100,它是一个权重,用来定义 Page Cache 内存和匿名内存的释放的比例。但即使为0时,仍然不能彻底禁止swap(匿名内存的释放)。
5. 容器中使用的是 memory.swappiness 来设置控制组下面匿名内存和 page cache 的回收。而且为0时,可以彻底禁止swap(匿名内存的释放)。
(未完待续)