learn.lianglianglee.com/专栏/容器实战高手课/05 容器CPU(1):怎么限制容器的CPU使用?.md.html
2022-05-11 19:04:14 +08:00

740 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>05 容器CPU1怎么限制容器的CPU使用.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/容器实战高手课/00 开篇词 一个态度两个步骤,成为容器实战高手.md.html">00 开篇词 一个态度两个步骤,成为容器实战高手.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/01 认识容器:容器的基本操作和实现原理.md.html">01 认识容器:容器的基本操作和实现原理.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/02 理解进程1为什么我在容器中不能kill 1号进程.md.html">02 理解进程1为什么我在容器中不能kill 1号进程.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/03 理解进程2为什么我的容器里有这么多僵尸进程.md.html">03 理解进程2为什么我的容器里有这么多僵尸进程.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/04 理解进程3为什么我在容器中的进程被强制杀死了.md.html">04 理解进程3为什么我在容器中的进程被强制杀死了.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/容器实战高手课/05 容器CPU1怎么限制容器的CPU使用.md.html">05 容器CPU1怎么限制容器的CPU使用.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/06 容器CPU2如何正确地拿到容器CPU的开销.md.html">06 容器CPU2如何正确地拿到容器CPU的开销.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/07 Load Average加了CPU Cgroup限制为什么我的容器还是很慢.md.html">07 Load Average加了CPU Cgroup限制为什么我的容器还是很慢.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/08 容器内存:我的容器为什么被杀了?.md.html">08 容器内存:我的容器为什么被杀了?.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/09 Page Cache为什么我的容器内存使用量总是在临界点.md.html">09 Page Cache为什么我的容器内存使用量总是在临界点.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/10 Swap容器可以使用Swap空间吗.md.html">10 Swap容器可以使用Swap空间吗.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/11 容器文件系统:我在容器中读写文件怎么变慢了.md.html">11 容器文件系统:我在容器中读写文件怎么变慢了.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/12 容器文件Quota容器为什么把宿主机的磁盘写满了.md.html">12 容器文件Quota容器为什么把宿主机的磁盘写满了.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/13 容器磁盘限速:我的容器里磁盘读写为什么不稳定.md.html">13 容器磁盘限速:我的容器里磁盘读写为什么不稳定.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/14 容器中的内存与IO容器写文件的延时为什么波动很大.md.html">14 容器中的内存与IO容器写文件的延时为什么波动很大.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/15 容器网络我修改了procsysnet下的参数为什么在容器中不起效.md.html">15 容器网络我修改了procsysnet下的参数为什么在容器中不起效.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/16 容器网络配置1容器网络不通了要怎么调试.md.html">16 容器网络配置1容器网络不通了要怎么调试.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/17 容器网络配置2容器网络延时要比宿主机上的高吗.md.html">17 容器网络配置2容器网络延时要比宿主机上的高吗.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/18 容器网络配置3容器中的网络乱序包怎么这么高.md.html">18 容器网络配置3容器中的网络乱序包怎么这么高.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/19 容器安全1我的容器真的需要privileged权限吗.md.html">19 容器安全1我的容器真的需要privileged权限吗.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/20 容器安全2在容器中我不以root用户来运行程序可以吗.md.html">20 容器安全2在容器中我不以root用户来运行程序可以吗.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐01 案例分析怎么解决海量IPVS规则带来的网络延时抖动问题.md.html">加餐01 案例分析怎么解决海量IPVS规则带来的网络延时抖动问题.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐02 理解perf怎么用perf聚焦热点函数.md.html">加餐02 理解perf怎么用perf聚焦热点函数.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐03 理解ftrace1怎么应用ftrace查看长延时内核函数.md.html">加餐03 理解ftrace1怎么应用ftrace查看长延时内核函数.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐04 理解ftrace2怎么理解ftrace背后的技术tracepoint和kprobe.md.html">加餐04 理解ftrace2怎么理解ftrace背后的技术tracepoint和kprobe.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐05 eBPF怎么更加深入地查看内核中的函数.md.html">加餐05 eBPF怎么更加深入地查看内核中的函数.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐06 BCC入门eBPF的前端工具.md.html">加餐06 BCC入门eBPF的前端工具.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐福利 课后思考题答案合集.md.html">加餐福利 课后思考题答案合集.md.html</a>
</li>
<li>
<a href="/专栏/容器实战高手课/结束语 跳出舒适区,突破思考的惰性.md.html">结束语 跳出舒适区,突破思考的惰性.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>05 容器CPU1怎么限制容器的CPU使用</h1>
<p>你好,我是程远。从这一讲开始,我们进入容器 CPU 这个模块。</p>
<p>我在第一讲中给你讲过,容器在 Linux 系统中最核心的两个概念是 Namespace 和 Cgroups。我们可以通过 Cgroups 技术限制资源。这个资源可以分为很多类型,比如 CPUMemoryStorageNetwork 等等。而计算资源是最基本的一种资源,所有的容器都需要这种资源。</p>
<p>那么,今天我们就先聊一聊,怎么限制容器的 CPU 使用?</p>
<p>我们拿 Kubernetes 平台做例子,具体来看下面这个 pod/container 里的 spec 定义,在 CPU 资源相关的定义中有两项内容,分别是 Request CPU 和 Limit CPU。</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
name: app
image: images.my-company.example/app:v4
env:
resources:
requests:
memory: &quot;64Mi&quot;
cpu: &quot;1&quot;
limits:
memory: &quot;128Mi&quot;
cpu: &quot;2&quot;
</code></pre>
<p>很多刚刚使用 Kubernetes 的同学,可能一开始并不理解这两个参数有什么作用。</p>
<p>这里我先给你说结论,在 Pod Spec 里的&quot;Request CPU&quot;&quot;Limit CPU&quot;的值,最后会通过 CPU Cgroup 的配置,来实现控制容器 CPU 资源的作用。</p>
<p>那接下来我会先从进程的 CPU 使用讲起,然后带你在 CPU Cgroup 子系统中建立几个控制组,用这个例子为你讲解 CPU Cgroup 中的三个最重要的参数&quot;cpu.cfs_quota_us&quot;&quot;cpu.cfs_period_us&quot;&quot;cpu.shares&quot;</p>
<p>相信理解了这三个参数后,你就会明白我们要怎样限制容器 CPU 的使用了。</p>
<h2>如何理解 CPU 使用和 CPU Cgroup</h2>
<p>既然我们需要理解 CPU Cgroup那么就有必要先来看一下 Linux 里的 CPU 使用的概念,这是因为 CPU Cgroup 最大的作用就是限制 CPU 使用。</p>
<h3>CPU 使用的分类</h3>
<p>如果你想查看 Linux 系统的 CPU 使用的话,会用什么方法呢?最常用的肯定是运行 Top 了。</p>
<p>我们对照下图的 Top 运行界面,在截图第三行,&quot;%Cpu(s)&quot;开头的这一行,你会看到一串数值,也就是&quot;0.0 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st&quot;,那么这里的每一项值都是什么含义呢?</p>
<p><img src="assets/a67fae56ce2f4e7078d552c58c9f9dc2.png" alt="img" /></p>
<p>下面这张图里最长的带箭头横轴,我们可以把它看成一个时间轴。同时,它的上半部分代表 Linux 用户态User space下半部分代表内核态Kernel space。这里为了方便你理解我们先假设只有一个 CPU 吧。</p>
<p><img src="assets/7dbd023628f5f4165abc23c1d67aca99.jpeg" alt="img" /></p>
<p>我们可以用上面这张图,把这些值挨个解释一下。</p>
<p>假设一个用户程序开始运行了,那么就对应着第一个&quot;us&quot;框,&quot;us&quot;&quot;user&quot;的缩写,代表 Linux 的用户态 CPU Usage。普通用户程序代码中只要不是调用系统调用System Call这些代码的指令消耗的 CPU 就都属于&quot;us&quot;</p>
<p>当这个用户程序代码中调用了系统调用,比如说 read() 去读取一个文件,这时候这个用户进程就会从用户态切换到内核态。</p>
<p>内核态 read() 系统调用在读到真正 disk 上的文件前,就会进行一些文件系统层的操作。那么这些代码指令的消耗就属于&quot;sy&quot;,这里就对应上面图里的第二个框。&quot;sy&quot;&quot;system&quot;的缩写,代表内核态 CPU 使用。</p>
<p>接下来,这个 read() 系统调用会向 Linux 的 Block Layer 发出一个 I/O Request触发一个真正的磁盘读取操作。</p>
<p>这时候,这个进程一般会被置为 TASK_UNINTERRUPTIBLE。而 Linux 会把这段时间标示成&quot;wa&quot;,对应图中的第三个框。&quot;wa&quot;&quot;iowait&quot;的缩写,代表等待 I/O 的时间,这里的 I/O 是指 Disk I/O。</p>
<p>紧接着,当磁盘返回数据时,进程在内核态拿到数据,这里仍旧是内核态的 CPU 使用中的&quot;sy&quot;,也就是图中的第四个框。</p>
<p>然后,进程再从内核态切换回用户态,在用户态得到文件数据,这里进程又回到用户态的 CPU 使用,&quot;us&quot;,对应图中第五个框。</p>
<p>好,这里我们假设一下,这个用户进程在读取数据之后,没事可做就休眠了。并且我们可以进一步假设,这时在这个 CPU 上也没有其他需要运行的进程了,那么系统就会进入&quot;id&quot;这个步骤,也就是第六个框。&quot;id&quot;&quot;idle&quot;的缩写,代表系统处于空闲状态。</p>
<p>如果这时这台机器在网络收到一个网络数据包网卡就会发出一个中断interrupt。相应地CPU 会响应中断,然后进入中断服务程序。</p>
<p>这时CPU 就会进入&quot;hi&quot;,也就是第七个框。&quot;hi&quot;&quot;hardware irq&quot;的缩写,代表 CPU 处理硬中断的开销。由于我们的中断服务处理需要关闭中断,所以这个硬中断的时间不能太长。</p>
<p>但是发生中断后的工作是必须要完成的如果这些工作比较耗时那怎么办呢Linux 中有一个软中断的概念softirq它可以完成这些耗时比较长的工作。</p>
<p>你可以这样理解这个软中断从网卡收到数据包的大部分工作都是通过软中断来处理的。那么CPU 就会进入到第八个框,&quot;si&quot;。这里&quot;si&quot;&quot;softirq&quot;的缩写,代表 CPU 处理软中断的开销。</p>
<p>这里你要注意,无论是&quot;hi&quot;还是&quot;si&quot;,它们的 CPU 时间都不会计入进程的 CPU 时间。这是因为本身它们在处理的时候就不属于任何一个进程。</p>
<p>好了,通过这个场景假设,我们介绍了大部分的 Linux CPU 使用。</p>
<p>不过,我们还剩两个类型的 CPU 使用没讲到,我想给你做个补充,一次性带你做个全面了解。这样以后你解决相关问题时,就不会再犹豫,这些值到底影不影响 CPU Cgroup 中的限制了。下面我给你具体讲一下。</p>
<p>一个是&quot;ni&quot;,是&quot;nice&quot;的缩写,这里表示如果进程的 nice 值是正值1-19代表优先级比较低的进程运行时所占用的 CPU。</p>
<p>另外一个是&quot;st&quot;&quot;st&quot;&quot;steal&quot;的缩写,是在虚拟机里用的一个 CPU 使用类型,表示有多少时间是被同一个宿主机上的其他虚拟机抢走的。</p>
<p>综合前面的内容,我再用表格为你总结一下:</p>
<p><img src="assets/a4f537187a16e872ebcc605d972672a3.jpeg" alt="img" /></p>
<h3>CPU Cgroup</h3>
<p>在第一讲中,我们提到过 Cgroups 是对指定进程做计算机资源限制的CPU Cgroup 是 Cgroups 其中的一个 Cgroups 子系统,它是用来限制进程的 CPU 使用的。</p>
<p>对于进程的 CPU 使用, 通过前面的 Linux CPU 使用分类的介绍,我们知道它只包含两部分: 一个是用户态,这里的用户态包含了 us 和 ni还有一部分是内核态也就是 sy。</p>
<p>至于 wa、hi、si这些 I/O 或者中断相关的 CPU 使用CPU Cgroup 不会去做限制,那么接下来我们就来看看 CPU Cgoup 是怎么工作的?</p>
<p>每个 Cgroups 子系统都是通过一个虚拟文件系统挂载点的方式挂到一个缺省的目录下CPU Cgroup 一般在 Linux 发行版里会放在 /sys/fs/cgroup/cpu 这个目录下。</p>
<p>在这个子系统的目录下每个控制组Control Group 都是一个子目录各个控制组之间的关系就是一个树状的层级关系hierarchy</p>
<p>比如说我们在子系统的最顶层开始建立两个控制组也就是建立两个目录group1 和 group2然后再在 group2 的下面再建立两个控制组 group3 和 group4。</p>
<p>这样操作以后,我们就建立了一个树状的控制组层级,你可以参考下面的示意图。</p>
<p><img src="assets/8b86bc86706b0bbfe8fe157ee21b6454.jpeg" alt="img" /></p>
<p>那么我们的每个控制组里,都有哪些 CPU Cgroup 相关的控制信息呢?这里我们需要看一下每个控制组目录中的内容:</p>
<pre><code> # pwd
/sys/fs/cgroup/cpu
# mkdir group1 group2
# cd group2
# mkdir group3 group4
# cd group3
# ls cpu.*
cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat
</code></pre>
<p>考虑到在云平台里呢大部分程序都不是实时调度的进程而是普通调度SCHED_NORMAL类型进程那什么是普通调度类型呢</p>
<p>因为普通调度的算法在 Linux 中目前是 CFS Completely Fair Scheduler即完全公平调度器。为了方便你理解我们就直接来看 CPU Cgroup 和 CFS 相关的参数,一共有三个。</p>
<p>第一个参数是 cpu.cfs_period_us它是 CFS 算法的一个调度周期,一般它的值是 100000以 microseconds 为单位,也就 100ms。</p>
<p>第二个参数是 cpu.cfs_quota_us它“表示 CFS 算法中,在一个调度周期里这个控制组被允许的运行时间,比如这个值为 50000 时,就是 50ms。</p>
<p>如果用这个值去除以调度周期(也就是 cpu.cfs_period_us50ms/100ms = 0.5,这样这个控制组被允许使用的 CPU 最大配额就是 0.5 个 CPU。</p>
<p>从这里能够看出cpu.cfs_quota_us 是一个绝对值。如果这个值是 200000也就是 200ms那么它除以 period也就是 200ms/100ms=2。</p>
<p>你看,结果超过了 1 个 CPU这就意味着这时控制组需要 2 个 CPU 的资源配额。</p>
<p>我们再来看看第三个参数, cpu.shares。这个值是 CPU Cgroup 对于控制组之间的 CPU 分配比例,它的缺省值是 1024。</p>
<p>假设我们前面创建的 group3 中的 cpu.shares 是 1024而 group4 中的 cpu.shares 是 3072那么 group3:group4=1:3。</p>
<p>这个比例是什么意思呢?我还是举个具体的例子来说明吧。</p>
<p>在一台 4 个 CPU 的机器上,当 group3 和 group4 都需要 4 个 CPU 的时候,它们实际分配到的 CPU 分别是这样的group3 是 1 个group4 是 3 个。</p>
<p>我们刚才讲了 CPU Cgroup 里的三个关键参数,接下来我们就通过几个例子来进一步理解一下,代码你可以在这里找到。</p>
<p>第一个例子,我们启动一个消耗 2 个 CPU200%)的程序 threads-cpu然后把这个程序的 pid 加入到 group3 的控制组里:</p>
<pre><code>./threads-cpu/threads-cpu 2 &amp;
echo $! &gt; /sys/fs/cgroup/cpu/group2/group3/cgroup.procs
</code></pre>
<p>在我们没有修改 cpu.cfs_quota_us 前,用 top 命令可以看到 threads-cpu 这个进程的 CPU 使用是 199%,近似 2 个 CPU。</p>
<p><img src="assets/1e95db3f15fc4cf1573f8ebe22db38b8.png" alt="img" /></p>
<p>然后,我们更新这个控制组里的 cpu.cfs_quota_us把它设置为 150000150ms。把这个值除以 cpu.cfs_period_us计算过程是 150ms/100ms=1.5, 也就是 1.5 个 CPU同时我们也把 cpu.shares 设置为 1024。</p>
<pre><code>
echo 150000 &gt; /sys/fs/cgroup/cpu/group2/group3/cpu.cfs_quota_us
echo 1024 &gt; /sys/fs/cgroup/cpu/group2/group3/cpu.shares
</code></pre>
<p>这时候我们再运行 top就会发现 threads-cpu 进程的 CPU 使用减小到了 150%。这是因为我们设置的 cpu.cfs_quota_us 起了作用,限制了进程 CPU 的绝对值。</p>
<p>但这时候 cpu.shares 的作用还没有发挥出来,因为 cpu.shares 是几个控制组之间的 CPU 分配比例,而且一定要到整个节点中所有的 CPU 都跑满的时候,它才能发挥作用。</p>
<p><img src="assets/3c153bba9d7668c22048602d730d627e.png" alt="img" /></p>
<p>好,下面我们再来运行第二个例子来理解 cpu.shares。我们先把第一个例子里的程序启动同时按前面的内容一步步设置好 group3 里 cpu.cfs_quota_us 和 cpu.shares。</p>
<p>设置完成后,我们再启动第二个程序,并且设置好 group4 里的 cpu.cfs_quota_us 和 cpu.shares。</p>
<p>group3</p>
<pre><code>./threads-cpu/threads-cpu 2 &amp; # 启动一个消耗2个CPU的程序
echo $! &gt; /sys/fs/cgroup/cpu/group2/group3/cgroup.procs #把程序的pid加入到控制组
echo 150000 &gt; /sys/fs/cgroup/cpu/group2/group3/cpu.cfs_quota_us #限制CPU为1.5CPU
echo 1024 &gt; /sys/fs/cgroup/cpu/group2/group3/cpu.shares
</code></pre>
<p>group4</p>
<pre><code>./threads-cpu/threads-cpu 4 &amp; # 启动一个消耗4个CPU的程序
echo $! &gt; /sys/fs/cgroup/cpu/group2/group4/cgroup.procs #把程序的pid加入到控制组
echo 350000 &gt; /sys/fs/cgroup/cpu/group2/group4/cpu.cfs_quota_us #限制CPU为3.5CPU
echo 3072 &gt; /sys/fs/cgroup/cpu/group2/group3/cpu.shares # shares 比例 group4: group3 = 3:1
</code></pre>
<p>好了,现在我们的节点上总共有 4 个 CPU而 group3 的程序需要消耗 2 个 CPUgroup4 里的程序要消耗 4 个 CPU。</p>
<p>即使 cpu.cfs_quota_us 已经限制了进程 CPU 使用的绝对值group3 的限制是 1.5CPUgroup4 是 3.5CPU1.5+3.5=5这个结果还是超过了节点上的 4 个 CPU。</p>
<p>好了说到这里我们发现在这种情况下cpu.shares 终于开始起作用了。</p>
<p>在这里 shares 比例是 group4:group3=3:1在总共 4 个 CPU 的节点上按照比例group4 里的进程应该分配到 3 个 CPU而 group3 里的进程会分配到 1 个 CPU。</p>
<p>我们用 top 可以看一下,结果和我们预期的一样。</p>
<p><img src="assets/8424b7fb4c84679412f75774060fcca3.png" alt="img" /></p>
<p>好了,我们对 CPU Cgroup 的参数做一个梳理。</p>
<p>第一点cpu.cfs_quota_us 和 cpu.cfs_period_us 这两个值决定了每个控制组中所有进程的可使用 CPU 资源的最大值。</p>
<p>第二点cpu.shares 这个值决定了 CPU Cgroup 子系统下控制组可用 CPU 的相对比例,不过只有当系统上 CPU 完全被占满的时候,这个比例才会在各个控制组间起作用。</p>
<h2>现象解释</h2>
<p>在解释了 Linux CPU Usage 和 CPU Cgroup 这两个基本概念之后,我们再回到我们最初的问题 “怎么限制容器的 CPU 使用”。有了基础知识的铺垫,这个问题就比较好解释了。</p>
<p>首先Kubernetes 会为每个容器都在 CPUCgroup 的子系统中建立一个控制组,然后把容器中进程写入到这个控制组里。</p>
<p>这时候&quot;Limit CPU&quot;就需要为容器设置可用 CPU 的上限。结合前面我们讲的几个参数么,我们就能知道容器的 CPU 上限具体如何计算了。</p>
<p>容器 CPU 的上限由 cpu.cfs_quota_us 除以 cpu.cfs_period_us 得出的值来决定的。而且在操作系统里cpu.cfs_period_us 的值一般是个固定值Kubernetes 不会去修改它,所以我们就是只修改 cpu.cfs_quota_us。</p>
<p>&quot;Request CPU&quot;就是无论其他容器申请多少 CPU 资源,即使运行时整个节点的 CPU 都被占满的情况下,我的这个容器还是可以保证获得需要的 CPU 数目,那么这个设置具体要怎么实现呢?</p>
<p>显然我们需要设置 cpu.shares 这个参数:在 CPU Cgroup 中 cpu.shares == 1024 表示 1 个 CPU 的比例,那么 Request CPU 的值就是 n给 cpu.shares 的赋值对应就是 n*1024。</p>
<h2>重点总结</h2>
<p>首先,我带你了解了 Linux 下 CPU Usage 的种类.</p>
<p>这里你要注意的是每个进程的 CPU Usage 只包含用户态us 或 ni和内核态sy两部分其他的系统 CPU 开销并不包含在进程的 CPU 使用中,而 CPU Cgroup 只是对进程的 CPU 使用做了限制。</p>
<p>其实这一讲我们开篇的问题“怎么限制容器的 CPU 使用”,这个问题背后隐藏了另一个问题,也就是容器是如何设置它的 CPU Cgroup 中参数值的?想解决这个问题,就要先知道 CPU Cgroup 都有哪些参数。</p>
<p>所以,我详细给你介绍了 CPU Cgroup 中的主要参数包括这三个cpu.cfs_quota_uscpu.cfs_period_us 还有 cpu.shares。</p>
<p>其中cpu.cfs_quota_us一个调度周期里这个控制组被允许的运行时间除以 cpu.cfs_period_us用于设置调度周期得到的这个值决定了 CPU Cgroup 每个控制组中 CPU 使用的上限值。</p>
<p>你还需要掌握一个 cpu.shares 参数,正是这个值决定了 CPU Cgroup 子系统下控制组可用 CPU 的相对比例,当系统上 CPU 完全被占满的时候,这个比例才会在各个控制组间起效。</p>
<p>最后,我们明白了 CPU Cgroup 关键参数是什么含义后Kubernetes 中&quot;Limit CPU&quot;&quot;Request CPU&quot;也就很好解释了:</p>
<p>Limit CPU 就是容器所在 Cgroup 控制组中的 CPU 上限值Request CPU 的值就是控制组中的 cpu.shares 的值。</p>
<h2>思考题</h2>
<p>我们还是按照文档中定义的控制组目录层次结构图,然后按序执行这几个脚本:</p>
<p>create_groups.sh</p>
<p>update_group1.sh</p>
<p>update_group4.sh</p>
<p>update_group3.sh</p>
<p>那么,在一个 4 个 CPU 的节点上group1/group3/group4 里的进程,分别会被分配到多少 CPU 呢?</p>
<p>欢迎留言和我分享你的思考和疑问。如果你有所收获,也欢迎分享给朋友,一起学习和交流。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/容器实战高手课/04 理解进程3为什么我在容器中的进程被强制杀死了.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/容器实战高手课/06 容器CPU2如何正确地拿到容器CPU的开销.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709977893f103cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>