Docker 容器资源管理,你真的学会了吗?( 三 )


可以看到,虽然我们仍然使用 --cpus 指定了 1.5 CPU,但由于使用 --cpuset-cpus 限制只允许它跑在第一个 CPU 上,所以这两个测试进程也就只能评分该 CPU 了 。本文节选自专栏 。
小结
通过上述的示例,我介绍了如何通过 --cpus 参数限制容器可使用的 CPU 资源;通过 --cpuset-cpus 参数可指定容器内进程运行所用的 CPU 核心;通过 docker update 可直接更新一个正在运行的容器的相关配置 。
现在我们回到前面使用 docker run --help | grep CPU,查看 Docker 支持的对容器 CPU 相关参数的选项:
(MoeLove)~ docker run --help |grep CPU--cpu-period intLimit CPU CFS (Completely Fair Scheduler) period--cpu-quota intLimit CPU CFS (Completely Fair Scheduler) quota--cpu-rt-period intLimit CPU real-time period in microseconds--cpu-rt-runtime intLimit CPU real-time runtime in microseconds-c, --cpu-shares intCPU shares (relative weight)--cpus decimalNumber of CPUs--cpuset-cpus stringCPUs in which to allow execution (0-3, 0,1)
--cpus 是在 Docker 1.13 时新增的,可用于替代原先的 --cpu-period 和 --cpu-quota 。这三个参数通过 cgroups 最终会实际影响 Linux 内核的 CPU 调度器 CFS(Completely Fair Scheduler, 完全公平调度算法)对进程的调度结果 。
一般情况下,推荐直接使用 --cpus,而无需单独设置 --cpu-period 和 --cpu-quota,除非你已经对 CPU 调度器 CFS 有了足够多的了解,提供 --cpus 参数也是 Docker 团队为了可以简化用户的使用成本增加的,它足够满足我们大多数的需求 。
而 --cpu-shares 选项,它虽然有一些实际意义,但却不如 --cpus 来的直观,并且它会受到当前系统上运行状态的影响,为了不因为它给大家带来困扰,此处就不再进行介绍了 。
--cpu-rt-period 和 --cpu-rt-runtime 两个参数,会影响 CPU 的实时调度器 。但实时调度器需要内核的参数的支持,并且配置实时调度器也是个高级或者说是危险的操作,有可能会导致各种奇怪的问题,此处也不再进行展开 。
管理容器的内存资源
前面已经介绍了如何管理容器的 CPU 资源,接下来我们看看如何管理容器的内存资源 。相比 CPU 资源来说,内存资源的管理就简单很多了 。
同样的,我们先看看有哪些参数可供我们配置,对于其含义我会稍后进行介绍:
(MoeLove)~ docker run --help |egrep 'memory|oom'--kernel-memory bytesKernel memory limit-m, --memory bytesMemory limit--memory-reservation bytesMemory soft limit--memory-swap bytesSwap limit equal to memory plus swap: '-1' to enable unlimited swap--memory-swappiness intTune container memory swappiness (0 to 100) (default -1)--oom-kill-disableDisable OOM Killer--oom-score-adj intTune host's OOM preferences (-1000 to 1000)
OOM
在开始进行容器内存管理的内容前,我们不妨先聊一个很常见,又不得不面对的问题:OOM(Out Of Memory) 。
当内核检测到没有足够的内存来运行系统的某些功能时候,就会触发 OOM 异常,并且会使用 OOM Killer 来杀掉一些进程,腾出空间以保障系统的正常运行 。
这里简单介绍下 OOM killer 的大致执行过程,以便于大家理解后续内容 。
内核中 OOM Killer 的代码,在 torvalds/linux/mm/oom_kill.c 可直接看到,这里以 Linux Kernel 5.2 为例 。
引用其中的一段注释:
If we run out of memory, we have the choice between either killing a random task (bad), letting the system crash (worse).
OR try to be smart about which process to kill. Note that we don't have to be perfect here, we just have to be good.
翻译过来就是,当我们处于 OOM 时,我们可以有几种选择,随机地杀死任意的任务(不好),让系统崩溃(更糟糕)或者尝试去了解可以杀死哪个进程 。注意,这里我们不需要追求完美,我们只需要变好(be good)就行了 。
事实上确实如此,无论随机地杀掉任意进程或是让系统崩溃,那都不是我们想要的 。
回到内核代码中,当系统内存不足时,out_of_memory() 被触发,之后会调用 select_bad_process() 函数,选择一个 bad 进程来杀掉 。
那什么样的进程是 bad 进程呢?总是有些条件的 。select_bad_process() 是一个简单的循环,其调用了 oom_evaluate_task() 来对进程进行条件计算,最核心的判断逻辑是其中的 oom_badness() 。
unsignedlongoom_badness(struct task_struct *p, struct mem_cgroup *memcg,constnodemask_t *nodemask, unsignedlong totalpages){long points;long adj;if (oom_unkillable_task(p, memcg, nodemask))return0;p = find_lock_task_mm(p);if (!p)return0;/** Do not even consider tasks which are explicitly marked oom* unkillable or have been already oom reaped or the are in* the middle of vfork*/adj = (long)p->signal->oom_score_adj;if (adj == OOM_SCORE_ADJ_MIN ||test_bit(MMF_OOM_SKIP, &p->mm->flags) ||in_vfork(p)) {task_unlock(p);return0;}/** The baseline for the badness score is the proportion of RAM that each* task's rss, pagetable and swap space use.*/points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +mm_pgtables_bytes(p->mm) / PAGE_SIZE;task_unlock(p);/* Normalize to oom_score_adj units */adj *= totalpages / 1000;points += adj;/** Never return 0 for an eligible task regardless of the root bonus and* oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).*/return points > 0 ? points : 1;}


推荐阅读