This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
<audio id="audio" title="加餐福利 | 课后思考题答案合集" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/f1/61f34e746b673b086b17bdd71a76e5f1.mp3"></audio>
你好,我是程远,好久不见。
距离我们的专栏更新结束,已经过去了不少时间。我仍然会在工作之余,到这门课的留言区转一转,回答同学的问题。大部分的疑问,我都通过留言做了回复。
除了紧跟更新的第一批同学,也很开心有更多新朋友加入到这个专栏的学习中。那课程的思考题呢,为了给你留足思考和研究的时间,我选择用加餐的方式,给你提供参考答案。
这里我想和你说明的是,我这里给你提供的参考答案,都是我能够直接给你特定答案的问题。至于操作类的题目,有的我引用了同学回复的答案。
另外一类操作题,是为了帮你巩固课程内容知识的,相信你可以从课程正文里找到答案。我还是建议你自己动手实战,这样你的收获会更大。
## 必学部分思考题
[第2讲](https://time.geekbang.org/column/article/309423)
Q对于这一讲的最开始有这样一个C语言的init进程它没有注册任何信号的handler。如果我们从Host Namespace向它发送SIGTERM会发生什么情况呢
A即使在宿主机上向容器1号进程发送SIGTERM在1号进程没有注册handler的情况下这个进程也不能被杀死。
这个问题的原因是这样的:开始要看内核里的那段代码,“ !(force &amp;&amp; sig_kernel_only(sig))”,
虽然由不同的namespace发送信号 虽然force是1了但是sig_kernel_only(sig)对于SIGTERM来说还是0这里是个&amp;&amp;, 那么 !(1 &amp;&amp; 0) = 1。
```
#define sig_kernel_only(sig) siginmask(sig, SIG_KERNEL_ONLY_MASK)
#define SIG_KERNEL_ONLY_MASK (\
rt_sigmask(SIGKILL) | rt_sigmask(SIGSTOP))
```
[第3讲](https://time.geekbang.org/column/article/310060)
Q如果容器的init进程创建了子进程BB又创建了自己的子进程C。如果C运行完之后退出成了僵尸进程B进程还在运行而容器的init进程还在不断地调用waitpid()那C这个僵尸进程可以被回收吗
A这道题可以参考下面两位同学的回答。
Geek2014用户的回答
>
这时C是不会被回收的只有等到B也被杀死C这个僵尸进程也会变成孤儿进程被init进程收养进而被init的wait机制清理掉。
莫名同学的回答:
>
C应该不会被回收waitpid仅等待直接children的状态变化。
>
为什么先进入僵尸状态而不是直接消失觉得是留给父进程一次机会查看子进程的PID、终止状态退出码、终止原因比如是信号终止还是正常退出等、资源使用信息。如果子进程直接消失那么父进程没有机会掌握子进程的具体终止情况。
>
一般情况下,程序逻辑可能会依据子进程的终止情况做出进一步处理:比如 Nginx Master 进程获知 Worker 进程异常退出则重新拉起来一个Worker进程。
[第4讲](https://time.geekbang.org/column/article/310804)
Q请你回顾一下基本概念中最后的这段代码你可以想一想在不做编译运行的情况下它的输出是什么
```
#include &lt;stdio.h&gt;
#include &lt;signal.h&gt;
typedef void (*sighandler_t)(int);
void sig_handler(int signo)
{
if (signo == SIGTERM) {
printf("received SIGTERM\n\n");
// Set SIGTERM handler to default
signal(SIGTERM, SIG_DFL);
}
}
int main(int argc, char *argv[])
{
//Ignore SIGTERM, and send SIGTERM
// to process itself.
signal(SIGTERM, SIG_IGN);
printf("Ignore SIGTERM\n\n");
kill(0, SIGTERM);
//Catch SIGERM, and send SIGTERM
// to process itself.
signal(SIGTERM, sig_handler);
printf("Catch SIGTERM\n");
kill(0, SIGTERM);
//Default SIGTERM. In sig_handler, it sets
//SIGTERM handler back to default one.
printf("Default SIGTERM\n");
kill(0, SIGTERM);
return 0;
}
```
A可以参考用户geek 2014同学的答案。输出结果如下
Ignore SIGTERM<br>
Catch SIGTERM<br>
received SIGTERM<br>
Default SIGTERM
[第5讲](https://time.geekbang.org/column/article/311054)
Q我们还是按照文档中定义的控制组目录层次结构图然后按序执行这几个脚本
- [create_groups.sh](https://github.com/chengyli/training/blob/main/cpu/cgroup_cpu/create_groups.sh)
- [update_group1.sh](https://github.com/chengyli/training/blob/main/cpu/cgroup_cpu/update_group1.sh)
- [update_group4.sh](https://github.com/chengyli/training/blob/main/cpu/cgroup_cpu/update_group4.sh)
- [update_group3.sh](https://github.com/chengyli/training/blob/main/cpu/cgroup_cpu/update_group3.sh)
那么在一个4个CPU的节点上group1/group3/group4里的进程分别会被分配到多少CPU呢?
A分配比例是: 2 : 0.5 : 1.5
**可以参考geek 2014的答案**
>
group1 的shares为1024quota 3.5尝试使用4
>
group2的shares默认为1024quota设置为-1不受限制也即是如果CPU上只有group2的话那么group2可以使用完所有的CPU实际上根据group3和group4group2最多也就能用到1.5+3.5 core
>
故而group1和group2各分配到2。把group2分到的2CPU看作总量再次分析group3和group4。group3和group3尝试使用的总量超过2所以按照shares比例分配group3使用1/(1+3) * 2 = 0.5group4使用3/(1+3) * 2 = 1.5
[第6讲](https://time.geekbang.org/column/article/313255)
Q写一个小程序在容器中执行它可以显示当前容器中所有进程总的CPU使用率。
A上邪忘川的回答可以作为一个参考。
```
#!/bin/bash
cpuinfo1=$(cat /sys/fs/cgroup/cpu,cpuacct/cpuacct.stat)
utime1=$(echo $cpuinfo1|awk '{print $2}')
stime1=$(echo $cpuinfo1|awk '{print $4}')
sleep 1
cpuinfo2=$(cat /sys/fs/cgroup/cpu,cpuacct/cpuacct.stat)
utime2=$(echo $cpuinfo2|awk '{print $2}')
stime2=$(echo $cpuinfo2|awk '{print $4}')
cpus=$((utime2+stime2-utime1-stime1))
echo "${cpus}%"
```
[第8讲](https://time.geekbang.org/column/article/315468)
Q在我们的例子[脚本](https://github.com/chengyli/training/blob/main/memory/oom/start_container.sh)基础上你可以修改一下在容器刚一启动就在容器对应的Memory Cgroup中禁止OOM看看接下来会发生什么
A通过“**memory.oom_control**”禁止OOM后在容器中的进程不会发生OOM但是也无法申请出超过“memory.limit_in_bytes”内存。
```
# cat start_container.sh
#!/bin/bash
docker stop mem_alloc;docker rm mem_alloc
docker run -d --name mem_alloc registry/mem_alloc:v1
sleep 2
CONTAINER_ID=$(sudo docker ps --format "{{.ID}}\t{{.Names}}" | grep -i mem_alloc | awk '{print $1}')
echo $CONTAINER_ID
CGROUP_CONTAINER_PATH=$(find /sys/fs/cgroup/memory/ -name "*$CONTAINER_ID*")
echo $CGROUP_CONTAINER_PATH
echo 536870912 &gt; $CGROUP_CONTAINER_PATH/memory.limit_in_bytes
echo 1 &gt; $CGROUP_CONTAINER_PATH/memory.oom_control
cat $CGROUP_CONTAINER_PATH/memory.limit_in_bytes
```
[第10讲](https://time.geekbang.org/column/article/317216)
Q在一个有Swap分区的节点上用Docker启动一个容器对它的Memory Cgroup控制组设置一个内存上限N并且将memory.swappiness设置为0。这时如果在容器中启动一个不断读写文件的程序同时这个程序再申请1/2N的内存请你判断一下Swap分区中会有数据写入吗
AMemory Cgroup参数memory.swappiness起到局部控制的作用因为已经设置了memory.swappiness参数全局参数swappiness参数失效那么容器里就不能使用swap了。
[第11讲](https://time.geekbang.org/column/article/318173)
Q在这一讲OverlayFS的[例子](https://github.com/chengyli/training/blob/main/filesystem/overlayfs/test_overlayfs.sh)的基础上建立2个lowerdir的目录并且在目录中建立相同文件名的文件然后一起做一个overlay mount看看会发生什么
A这里引用上邪忘川同学的实验结果。
实验过程如下结果是lower1目录中的文件覆盖了lower2中同名的文件, 第一个挂载的目录优先级比较高
```
[[root@localhost ~]# cat overlay.sh
#!/bin/bash
umount ./merged
rm upper lower1 lower2 merged work -r
mkdir upper lower1 lower2 merged work
echo "I'm from lower1!" &gt; lower1/in_lower.txt
echo "I'm from lower2!" &gt; lower2/in_lower.txt
echo "I'm from upper!" &gt; upper/in_upper.txt
# `in_both` is in both directories
echo "I'm from lower1!" &gt; lower1/in_both.txt
echo "I'm from lower2!" &gt; lower2/in_both.txt
echo "I'm from upper!" &gt; upper/in_both.txt
sudo mount -t overlay overlay \
-o lowerdir=./lower1:./lower2,upperdir=./upper,workdir=./work \
./merged
[root@localhost ~]# sh overlay.sh
[root@localhost ~]# cat merged/in_lower.txt
I'm from lower1!
```
[第12讲](https://time.geekbang.org/column/article/318978)
Q在正文知识详解的部分我们使用"xfs_quota"给目录打了project ID并且限制了文件写入的数据量。那么在做完限制之后我们是否能用xfs_quota命令查询到被限制目录的project ID和限制的数据量呢
Axfs_quota不能直接得到一个目录的quota大小的限制只可以看到project ID上的quota限制不过我们可以用[这段程序](https://github.com/chengyli/training/blob/main/filesystem/quota/get_projectid.c)来获得目录对应的project ID。
```
# xfs_quota -x -c 'report -h /'
...
Project ID Used Soft Hard Warn/Grace
---------- ---------------------------------
#0 105.6G 0 0 00 [------]
#101 0 0 10M 00 [------]
# ./get_proj /tmp/xfs_prjquota
Dir: /tmp/xfs_prjquota projectid is 101
```
[第13讲](https://time.geekbang.org/column/article/320123)
Q这是一道操作题通过这个操作你可以再理解一下 blkio Cgroup与 Buffered I/O的关系。
在Cgroup V1的环境里我们在blkio Cgroup V1的例子基础上把fio中“-direct=1”参数去除之后再运行fio同时运行iostat查看实际写入磁盘的速率确认Cgroup V1 blkio无法对Buffered I/O限速。
A: 这是通过iostat看到磁盘的写入速率是可以突破cgroup V1 blkio中的限制值的。
[第17讲](https://time.geekbang.org/column/article/324122)
Q在这节课的最后我提到“由于ipvlan/macvlan网络接口直接挂载在物理网络接口上对于需要使用iptables规则的容器比如Kubernetes里使用service的容器就不能工作了”请你思考一下这个判断背后的具体原因。
Aipvlan/macvlan工作在网络2层而iptables工作在网络3层。所以用ipvlan/macvlan为容器提供网络接口那么基于iptables的service服务就不工作了。
[第18讲](https://time.geekbang.org/column/article/324357)
Q在这一讲中我们提到了Linux内核中的tcp_force_fast_retransmit()函数那么你可以想想看这个函数中的tp-&gt;recording和内核参数 /proc/sys/net/ipv4/tcp_reordering是什么关系它们对数据包的重传会带来什么影响
```
static bool tcp_force_fast_retransmit(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
return after(tcp_highest_sack_seq(tp),
tp-&gt;snd_una + tp-&gt;reordering * tp-&gt;mss_cache);
}
```
A: 在TCP链接建立的时候tp-&gt;reordering默认值是从/proc/sys/net/ipv4/tcp_reordering默认值为3获取的。之后根据网络的乱序情况进行动态调整最大可以增长到/proc/sys/net/ipv4/tcp_max_reordering (默认值为300)的大小。
[第20讲](https://time.geekbang.org/column/article/327107)
Q我在这一讲里提到了rootless container不过对于rootless container的支持还存在着不少的难点比如容器网络的配置、Cgroup的配置你可以去查阅一些资料看看podman是怎么解决这些问题的。
A可以阅读一下[这篇文档](https://github.com/containers/podman/blob/master/rootless.md)。
## 专题加餐
[专题03](https://time.geekbang.org/column/article/340142)
Q我们讲ftrace实现机制时说过内核中的“inline函数”不能被ftrace到你知道这是为什么吗那么内核中的“static函数”可以被ftrace追踪到吗
Ainline函数在编译的时候被展开了所以不能被ftrace到。而static函数需要看情况如果加了编译优化参数“-finline-functions-called-once”对于只被调用到一次的static函数也会当成inline函数处理那么也不能被ftrace追踪到了。
[专题04](https://time.geekbang.org/column/article/340934)
Q想想看当我们用kprobe为一个内核函数注册了probe之后怎样能看到对应内核函数的第一条指令被替换了呢
A**首先可以参考莫名同学的答案:**
>
关于思考题想到一个比较笨拙的方法gdb+qemu调试内核。先进入虚拟机在某个内核函数上注册一个kprobe然后gdb远程调试内核查看该内核函数的汇编指令disass是否被替换。应该有更简单的方法这方面了解不深。
另外我们用gdb远程调试内核看也可以。还可以通过 /proc/kallsyms找到函数的地址然后写个kernel module把从这个地址开始后面的几个字节dump出来比较一下probe函数注册前后的值。

View File

@@ -0,0 +1,119 @@
<audio id="audio" title="用户故事 | 莫名:相信坚持的力量,终会厚积薄发" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/90/85/90b5359yy1f17b63f9bed65f0a733f85.mp3"></audio>
你好,我是莫名同学,坐标杭州。
我已经工作六年多了,曾经参与过对象存储、大数据等产品的开发。最近两年,我开始接触容器云平台系统开发与性能优化方向的工作。
最初是偶然的一次机会我关注了eBay技术荟公众号认真拜读过作者的eBay云计算网事系列文章受益匪浅从中汲取了不少排查网络疑难杂症的新思路。但意犹未尽一直期待有机会看到作者更多的分享。
后来,又看到极客时间**《容器实战高手课》**的课程预告,作者正是程远老师,而且课程内容也和我现在做的工作不谋而合。我满怀期待地翻了一遍开篇词,激起了不少共鸣,于是毫不犹豫地购买了该专栏。
我比较赞同作者“一个态度,两个步骤”的方法论。可以说,近年来我也是这个方法论的忠实践行者。正如作者所言,**容器的大多数问题最终都可以映射到Linux操作系统相应模块上。**因此想要在容器方面技高一筹意味着需要苦练内功深入了解隐藏在容器背后的Linux操作系统知识原理。
记得我刚参加工作的前几年我没有直接接触过容器技术对Linux了解也不多仅仅停留在使用层面遇到问题经常一头雾水。
庆幸的是从2017年末起我开始沉下心来学习Linux相关技术从基础知识到内核源码一步一步地向前进。这个过程中也逐渐养成了遇到问题刨根问底的思维习惯而这种态度也正是这个专栏里一直倡导的。
2019年初我开始真正接触容器技术凭借平时积累了比较扎实的Linux知识功底遇到的多数容器问题都能够迎刃而解“两个步骤”在平时的系统性能分析与优化工作中得以体现。
## 我为什么要学习专栏?
选择学习专栏的主要原因有两个,一方面是完善知识体系,另一方面是拓宽自己的技术思路。
经过前期了解我认为作者技术经验丰富。他经历了Kubernetes容器云平台在eBay的具体落地过程解决过大大小小的各类问题是货真价实的容器实战高手。在学习专栏之前我自己简单整理过一套容器知识结构体系。不过相比之下作者列出的容器知识结构体系更加完备。
所以,我期待借鉴作者的思路重新审视自己的理解,查漏补缺、温故知新,并在专栏更新过程中通过留言和作者进一步交流心得体会。相信经过系统的学习,我也能更好地建立自己的知识架构。
另外,既然这门课是实战课,自然少不了动手实战,我期待可以在案例中,借鉴老师解决问题的思路。
作者在eBay云计算网事系列文章中曾提过Kubernetes容器云平台在大规模应用场景下的偶发性问题十分棘手如果手忙脚乱地进行各种尝试可能会事倍功半、越理越乱。事实的确如此在复杂的Linux环境下传统的性能分析工具应对这些偶发性问题已经略显捉襟见肘了。
过去一年里我也曾使用perf、ftrace、BCC/bpftrace等Linux性能分析工具解决过网络抖动、存储写入延迟等偶发性问题。但是仍然担心自己的思路存在局限性所以很期待作者专题加餐的分享看看这些工具在容器网络不稳定的真实案例中要怎么综合运用拓宽自己定位、解决类似问题的思路。
## 我是怎么学习专栏的
我应该是订阅比较早的同学了,现在想想,能够坚持学完整个专栏,而且在更新期间跟老师做了积极互动,主要有这样三个方法。
<img src="https://static001.geekbang.org/resource/image/d1/29/d16e5761504a9cc4dff7ed5382db9329.png" alt="">
### 1.坚持学习,用好碎片时间
专栏的首刷,我基本上是利用碎片化的时间完成的。每周一、三、五专栏更新时,我会在早上通勤路上先把内容过一遍,从整体上把握文章思路。
由于碎片时间很难保持较高专注度,我倾向于选择轻松一些的阅读方式,不刻意做过多的深度思考,但是会把自己比较赞同或疑惑的知识点先标记下来,以便后续重点学习。
专栏内容结构简练、清晰一共二十多篇其实我觉得坚持下来并不难。专栏更新过程中我利用工作闲暇时针对最有收获的几篇文章反复刷了若干次结合文章示例动手实践并翻阅相关的Linux、Docker源码将首刷时留下的疑点逐一击破。
两个月下来,这样的学习让我受益匪浅,进一步拓宽了自己的容器知识结构体系,也学到了分析常见容器性能问题的新思路。
**我相信,当一个人开始把坚持学习当作一种习惯,并且善于合理利用碎片时间提升自我的时候,他已经走在超越大多数人的道路上。**
### 2.知行合一,善于思考总结
每个专栏作者几乎都会反复强调:学习专栏时,请动手实践文章中提到的示例以及课后思考题。所以,学习中思考与实践的重要性不言而喻。
数学家华罗庚说过,他的学习方法很简单,就是“读书要从薄到厚,再从厚到薄”。其实专栏的学习也是同样的道理。在我看来,专栏每篇文章都浓缩着作者历经磨砺后的思考与总结,有些很难一次性消化,所以需要我们在不断实践与思考中领悟。
我觉得,专栏起到的是一个**领航**的作用,想要相关的知识点我们可以根据自己的需要继续做功课,这样才能不断精进。遇到有疑问的地方,我会通过官方文档、源码等方式深挖这些知识点背后原理,希望可以由点及面,直达问题本质。
有了这样的问题导向,我才会进行更多深度思考,在研究、解决问题的过程中,也加深了自己对知识的理解。下面举几个我曾经思考并想办法弄清楚的问题:
- 什么是容器绑定挂载?容器创建过程中涉及哪些挂载行为和传播方式?
- 为什么说虚拟网络设备veth的行为像一根网线
- 如何使用strace追踪容器创建的大致过程
- 拥有独立Network Namespace的容器如何反过来操控宿主机的防火墙、路由表等
- 为什么Pid Namespace可以使得容器的init进程的PID总是为1
- ...
### 3.敢于怀疑,拒绝教条主义
延续刚才说的知行合一。其实我们学习的时候,对有疑问的内容拒绝教条主义,这也很重要,我举个例子吧。
第12讲学习容器Quota技术的时候文章里用到的例子是xfs quota。但我认为目前容器quota技术存在以下局限性容器通常采用overlay存储驱动仅支持在宿主文件系统xfs上开启quota功能这意味着overlay over ext4不支持project quota功能。
虽然ext4自身支持project quota功能Linux4.5版本之后。但是overlay over ext4却不支持project quota功能。
经过我的尝试采用overlay over ext4启动容器会报以下错误
Docker:Error response from daemon: --storage-opt is supported only for overlay over xfs with 'pquota' mount option.
[官方文档](https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container)里也有提到For the overlay2 storage driver, the size option is only available if the backing fs is xfs and mounted with the pquota mount option大意是overlay存储驱动仅支持基于xfs开启project quota功能
## 专栏中最有收获的文章是哪几篇?
首先是[第2讲](https://time.geekbang.org/column/article/309423)我印象最深的是这篇文章最后的总结了提到容器里1号进程对信号处理的两个要点
1.在容器中1号进程永远不会响应SIGKILL和SIGSTOP这两个特权信号<br>
2.对于其他的信号如果用户自己注册了handler1号进程可以响应。
其中要点2打破了我的固有认知。我原先一直认为在容器中1号进程无法被杀死而这一讲结合相关内核源码与若干示例充分证明了这一要点思路清晰令人受益良多。
在[第9讲](https://time.geekbang.org/column/article/316436)学习Memory Cgroup的时候课程里提到每个容器的Memory Cgroup在统计每个控制组的内存使用时包含了两部分RSS和Page Cache。
不过我认为Momory Cgroup除了包括RSS 和Page Cache还包括了对内核内存的限制作者给出的例子情况比较简单基本没有使用slab倘若在容器中打开海量小文件内核内存inode、dentry等会被计算在内。
内存使用量计算公式memory.kmem.usage_in_bytes表示该memcg内核内存使用量
memory.usage_in_bytes = memory.stat[rss] + memory.stat[cache] + memory.kmem.usage_in_bytes
另外Memory Cgroup OOM不是真正依据内存使用量memory.usage_in_bytes而是依据working set使用量减去非活跃 file-backed 内存working set计算公式是
working_set=memory.usage_in_bytes-total_inactive_file。
接下来要说说[第14讲](https://time.geekbang.org/column/article/321330)文章首先抛出一个引人关注的现象应用程序从虚拟机迁移到容器并施加Memory Cgroup 限制时,文件写入出现较大的延时波动。
我印象最深的是这一讲通过程序复现、理论分析、工具追踪等一系列手段最终得出一个令我深有同感的结论在对容器做Memory Cgroup限制内存大小的时候不仅要考虑容器中进程实际使用的内存量还要考虑容器中程序I/O的量合理预留足够的内存作为Buffered I/O的Page Cache。
其实学习容器技术没不久我就已经了解到容器网络延迟相比宿主机高出10%左右,但并未深入思考过网络延迟变高的具体原因。
而专栏[第17讲](https://time.geekbang.org/column/article/324122)内容讲网络延时的时候从veth driver的内核源码入手合理解释了容器网络性能损失的来龙去脉感觉收获很大可以说是透过现象挖到了本质。受到这一讲的启发对排查网络延迟问题我又多了新的思路。
## 总结
在这个专栏学习结束后,我回过头来看,很高兴已经达到当初选择学习专栏的目的。
阅读过程整体比较顺畅尤其觉得作者列出的Linux源码恰到好处产生不少共鸣因为在探究容器问题过程中我也曾经阅读过这些代码。
感谢极客时间为我们提供了如此优秀的学习平台,也感谢作者为我们带来了非常精彩的容器实战课程。希望订阅这门课程的同学,也能有所收获,把学到的知识用到工作里。
**我觉得,无论是学习容器,还是学习其他技术知识,就好像升级打怪一样,苦练基本功,手感和意识才会逐渐提升。最后,希望我们都能成为容器高手,让我们相信坚持的力量,终会厚积薄发!**

View File

@@ -0,0 +1,85 @@
<audio id="audio" title="结束语 | 跳出舒适区,突破思考的惰性" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/be/a1/beddeab3d00c48bab6f4699cb1e08ba1.mp3"></audio>
你好,我是程远。
今天是我们专栏必学内容的最后一讲。当你读到这一讲内容的时候刚好是元旦。首先我要祝你元旦快乐2021年一切顺利
在过去的二十多讲内容里,我们从基础开始,一起学习了容器进程、内存、存储、网络以及安全这几部分的内容。在每一讲里,我们都会从一个实际问题或者现象出发,然后一步步去分析和解决问题。一路走来,真是百感交集,我有好多心里话,但又不知该从何说起。
所以最后一讲,我想和你聊聊我个人的一些成长感悟,在辞旧迎新的元旦,正适合回顾过去和展望未来。所以这既是专栏的一次总结交流,也是我们开启新征程的“号角”。
在多年以前,我在书里读到一句话,说的是“每个人都有潜在的能量,只是很容易被习惯所掩盖,被时间所迷离,被惰性所消磨。”
今天再次回看这段话,还真是一语中的,感触良多,回想起专栏写作的整个过程,这件事带给我的最大感悟就是:**跳出自己的舒适区,才能有所突破。**
## 突破舒适区是很难的事儿
我们都知道,突破舒适区是一件很难的事儿。这里我给你分享一个我自己的故事,也许你也会从这个故事里找到自己的影子。
记得在2年前我参加过eBay的一个内部培训培训的目标就是要让自己有所“突破”。我必须承认这个培训是我经历过的所有培训中最接地气的一个培训在培训过程里我也是情绪激昂的准备带着学到的东西回到工作里去大展身手好好突破一番的。
不过等培训结束,再回到日常工作的时候,之前的雄心壮志、激情澎湃又被日常的琐事所淹没,积蓄的那股劲儿又慢慢被消磨了。周围的同事会开玩笑地对我说:“程远啊,我觉得你没有突破啊。”
其实,我心里也知道,所谓的“突破”就要跳出自己的舒适区。不过我始终不知道怎么跳出来,哪怕自己手上的工作再多,工作到再晚,但这仍然是处于自己舒适区。这是因为这一切的工作节奏还有思考的问题,都是我自己熟悉的。
这种熟悉很可能让我们沉湎其中,裹足不前。那问题来了,意识到自己处于舒适区,产生想要“跳出去”的念头的确是良好开局,难的是怎么有效突破。这就要聊到突破方法路径的问题了,我想结合自己的感悟给你说一说。
## 主动迎接挑战,在实战中进步
不知道你有没有听过热力学里熵增的定律,大概说的是:封闭系统的熵(能量)会不可逆地增加,最终导致整个系统崩溃。那怎么才能保持这个系统的活力呢?就是能量交换,不断去引入外部的能量,也就是负熵。
我们可以引申一下,自然会想到走出舒适区这件事,也是同样的道理。我们要有一种冒险家的勇气,主动去迎接挑战,在实战里迫使自己不断进步。
其实选择做这样一个专栏对我来说就是走出舒适区的一项“挑战”。在今年7月份那还是我们这个专栏筹备的前期我当时就一个想法就是把我这些年来在容器方面的积累给记录下来。
从7月份决定写容器这个专栏开始到现在差不多也有半年的时间了我真的觉得在工作的同时把写专栏的这件事给坚持下来真的是一件不容易的事情。**这里不仅仅是一个简单的时间投入问题,更多的是迫使自己再去思考的问题。**
估计你也发现了,我每一讲都涉及不少知识点。我在专栏写作的过程中,花时间最多的就是怎么把问题说清楚,这里要解释哪些关键知识点,适合用什么样的例子做解释,每个知识点要讲到什么程度,需要查阅哪些代码和资料来保证自己所讲内容的正确性。
这样的思考模式和我日常思考工作问题的模式是完全不同的。但也正是借着这样的机会我才从自己原先的舒适区里跳了出来工作之余同时也在思考写专栏的问题每天都有大量的context switch也就是上下文切换。
我很高兴自己可以坚持下来,完成了专栏的主体部分。可以说,这门课既是容器的实战课,也是我自己走出舒适区的实战训练。
## 突破舒适区,本质是突破思考的惰性
这次的专栏写作,还让我意识到,**突破舒适区的本质就是突破思考的惰性。只有不断思考,才能推着自己不断往前走,才能让我们更从容地解决工作上的问题。**
在2020年的12月初Kubernetes宣布不再支持dockershim也就是说Kubernetes节点上不能再直接用Docker来启动容器了。当时我看到这条新闻觉得这是理所当然的因为我们的容器云平台上在2019年初就从Docker迁移到了Containerd。
不过后来我在专栏留言回复的过程中连续有三位同学留言问我怎么看Kubernetes的这个决定这让我又回忆起了当初我们团队是怎么做的迁移决定。
这件事还要追溯到2018年的时候我们发现kubelet通过CRI接口就可以集成Containerd了于是我们就开始思考是不是应该用Containerd来替换Docker呢?
当时我们看到的好处有两点。第一点是这样替换之后**架构上的优势**CRI可以说是kubelet连接Runtime的标准了而用Dockershim接Docker再转Containerd这样很累赘。第二点好处就是**降低了维护成本**。Containerd只是Docker中的一部分维护Containerd明显要比维护庞大的Docker容易。
当然这么做的挑战也是很大的。当时我们在生产环境中已经有2万台物理机节点以及几十万个容器而且那时候业界还几乎没有人在生产环境中用kubelet直接调用Containerd。没有前人的尝试可以借鉴只能咬牙打一场硬仗。
后来我们通过一个多月的测试发现直接使用Containerd无论是稳定性还是性能都没有问题。有了实际测试做保障我们在2019年初又花了3个月时间才把生产环境上的Docker全部替换成Containerd。
这样的结果看似轻描淡写,一两句话就带过了。但实际过程里,已经不是过五关斩六将了,而是一直在发现问题、解决问题,大大小小的战役才汇聚成了最后的战果。其实,我在这个专栏里和你分享的一些容器问题,也来源于我们当时的迁移实践。
现在回想起来当初的这个决定无疑是非常正确的了。不过再想想如果当时看到Kubernetes的变化我们没有主动思考等到现在Kubernetes宣布不再支持Dockershim才去做应对结果又会怎样呢
这个问题我觉得用数字来说话更直观。刚才提到当时迁移的时候有2万台物理机节点以及几十万个容器。但如果等到现在才迁移我们需要面对的就是6万台物理机和上百万的容器了。
你看,无论是写专栏也好,还是我们实际工作也好,呆在舒适区里,短期成本看着挺小,不需要你大动干戈,消耗脑细胞和精力。但是,当你习惯了这种思考的惰性,就会变成温水煮青蛙而不自知,等到外部条件发生变化时会很被动。
## 最后的彩蛋
前面我们聊了很多突破舒适区的事儿,不知道你有没有被触动呢?
其实学习也好,工作也罢,就是要有一种突破意识,走出舒适区,才能“开疆拓土”。那为了让你我都知行合一,我还要给你聊聊后面的专题加餐安排。
在开篇词我也提到了这个安排。虽然这一讲是我们课程的结束语,但我们课程的内容并没有结束。在这个专题里,我选择了一个真实案例。那这个案例我是怎么选的呢?
其实这是2020年初我们在生产环境里遇到的一个真实的容器网络问题。我觉得这是一个很好的调试案例它的好就在于可以用到Linux内核的最主要的几个调试工具包括perfftrace和ebpf。我们逐个使用这些工具就可以层层递进地揭开问题的本质。
通过这个案例的学习,我会带你掌握每种工具的特性。这样你在理解了容器基本原理的基础上,就能利用这些好的工具系统化地分析生产环境中碰到的容器问题了,就像我们开篇中说的那样——变黑盒为白盒。
写完结束语之后,我会认真为你准备这个专题加餐。而这一个月的时间,你还可以继续消化理解课程主体部分的内容,打牢基础,这样对你学习后面的专题加餐也有很大帮助。
最后的最后,我想和你说的是,希望你我都能主动思考,不断突破自己,走出舒适区,一起共勉吧!
这里我为你准备了一份[毕业问卷](https://jinshuju.net/f/socZck),题目不多,希望你可以花两分钟填一下。也十分期待能听到你的声音,说说你对这门课程的想法和建议。
[<img src="https://static001.geekbang.org/resource/image/a4/b6/a477edbc0cb9a0902715e7e3c5a666b6.jpg" alt="">](https://jinshuju.net/f/socZck)

View File

@@ -0,0 +1,16 @@
你好,我是程远。
《容器实战高手课》这门课程的必学内容已经全部结束了,感谢你一直以来的认真学习和支持!
我给你准备了一个结课小测试,希望可以帮助你检测一下这段时间的学习成果。
这套测试题共有 20 道题目10道单选题10道多选题满分 100 分,系统自动评分。
还等什么,快点击下面按钮开始测试吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=357&amp;exam_id=966)
最后,这里有一份[毕业问卷](https://jinshuju.net/f/socZck),题目不多,希望你可以花两分钟填一下。也十分期待能听到你的声音,说说你对这门课程的想法和建议。
[<img src="https://static001.geekbang.org/resource/image/a4/b6/a477edbc0cb9a0902715e7e3c5a666b6.jpg" alt="">](https://jinshuju.net/f/socZck)