与容器相关的那些事(二)

Updated on with 0 views and 0 comments

很多同学对Docker和Kubernetes的第一印象可能是:这不是运维该掌握的技术吗,我们这些高逼格的程序猿为啥要学这个,学这个不是内卷吗?咱们先来看看下面这个栗子,然后想想自己怕不怕摸着黑做开发。

1 资源limits和requests的栗子

Flink默认生成的资源配置如下:

6131b973400849af8737ac4a9fad0835.png

可以看到,limits 和 requests 的设置是相同的,也就是说Flink创建的Pod属于Guaranteed这一类,关于Guaranteed的说明见下图:

d42061d71ebd4afeab8d22f8ee1b06f3.png

再来看看相关的CPU管理策略如下图所示:

0974242c91bc4e4b8bd3a7dfaa26806e.png

由此可见,Flink默认的资源配置会使得Pod独占CPU资源,这可能会造成CPU不能充分被利用

(爱学习的同学肯定会想搞清楚资源request究竟是啥作用,还有QoS又是干啥用的。。。)

2 一个容器里可以跑多个进程吗?

先来看看虚拟机和容器化对比:

2c03c2e7c25b46a18dbc6bd05d95943e.png

请大家想想,容器的本质是什么?容器是指一个实实在在的Container吗?

之前写过一篇博文对容器有过一个简单的介绍,出门右转看 容器是什么。看过的同学应该对 NamespaceCgroups 有印象,再结合上面与虚拟化技术的对比图,我们会发现容器并没有搞出一套虚拟的系统,甚至没有为容器里的进程划分独立的空间。容器所做的只是隔离和限制容器里的进程,使其“看不见”外面,自以为“与世隔绝”罢了。

简单来说,容器是一个“单进程”模型。注意,“单进程”模型并不是指容器里只能运行一个进程,而是指容器没有管理多个进程的能力。

一般地,用户的应用进程实际上就是容器里 PID=1 的进程。除非 PID=1 的进程有能力创建后续的进程,否则,在一个容器中没办法同时运行两个不同的应用进程。

3 谈谈Docker的崛起

2013~2014年,PaaS热潮涌起,吸引了包括百度、京东、华为、IBM 等一大批国内外技术厂商,开启了以开源PaaS为核心构建平台层服务能力的变革。

PaaS项目被大家接纳的一个主要原因,就是它提供了一种名叫“应用托管”的能力。当时普遍的用法是租一批AWS或OpenStack的虚拟机,运维人员只需要在这些机器上部署一个 Cloud Foundry 项目(当时PaaS的事实标准),然后开发者只要执行一条命令就能把本地的应用部署到云上。

由于需要在一个虚拟机上启动很多个来自不同用户的应用,Cloud Foundry 会调用操作系统的Namespace和Cgroups机制为每一个应用单独创建一个称作“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程。由此可见,Namespace和Cgroups并不是Docker最先使用的。

但是,因为虚拟机环境问题,Cloud Foundry 为了实现一键部署,需要针对每种语言、每种框架,甚至每个版本的应用维护一个打好的包。这个痛点成为了Docker崛起的关键。

在后面短短几个月里,Docker创造性地搞出了Docker镜像,带来了一场降维打击,使得 Cloud Foundry 以及所有的PaaS社区还没来得及成为它的竞争对手,就直接被宣告出局了。

实际上,Docker镜像不过是简单粗暴地打包了应用运行所需要的整个操作系统(比如挂载 Ubuntu16.04 的ISO,在容器启动之后,查看根目录下的内容就是 Ubuntu 16.04 的所有目录和文件),从而保证了本地环境和云端环境的高度一致,避免了匹配两种不同运行环境之间差异。

容器镜像还有一个更为专业的名字,叫作 rootfs(根文件系统)。需要注意的是,rootfs只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核

6a52df508c074d10bbebab0710063517.png

4 Kubernetes会取代Docker吗?

先看看Kubernetes的全局架构:

de1d3421796148dda789e3680837b370.png

从Kubernetes架构设计上看,Kubernetes并没有打算重复造轮子而对已有的容器技术进行替代,它更关注的是对运行在大规模集群中的各种任务根据其关系进行作业编排管理,所以任何实现了CRI、CNI、CSI等协议标准的容器技术都可以无缝地与Kubernetes集成。

而Docker最大的成功是它定义了“容器镜像”,开创性地解决了困扰业界多年的应用打包难题。

从这个角度看,Docker与Kubernetes的关系并不是替代的关系,而是平台与组件的关系,Kubernetes可以利用现有的Docker容器运行时技术,但却并不完全依赖Docker。而这正是Kubernetes被称作容器编排技术,而不仅仅是容器技术的原因。

简单来说,Kubernetes项目的本质是为用户提供一个具有普遍意义的容器编排工具

5 Pod并非模仿容器造的轮子

前面提到容器是“单进程”模型,没有管理多个进程的能力。针对这个问题,Kubernetes的解决方案是引入了一个超越容器的概念Pod,即一个Pod可以包含多个容器,这里包含的容器只需要负责一个进程就行。这样一来,我们就不用花精力去解决一个容器里跑多个进程的复杂问题了。

Pod是Kubernetes中最基础的编排对象,是Kubernetes最小的调度单元,也是Kubernetes实现容器编排的载体,其本质上是一组共享了某些系统资源(网络和存储)的容器集合。具体来说,Pod包含的成员容器共享的是同一个 Network Namespace,并且可以声明共享同一个Volume。

注意,重启Pod中的容器不应与重启Pod混淆,因为Pod不是进程,而是容器运行的环境。

在Kubernetes中围绕Pod可以延伸出其他核心概念,具体如下图所示:

8735dfd2ea70466094da60f380f28c4c.png

6 Pod内部如何共享Namespace?

在Docker中要让两个容器共享网络Namespace,可以使用 --net 参数,如下所示:

69af7e700d6e445d87e7ea6c860d5412.png

但是,这样一来,容器B就必须比容器A先启动,也就是说,一个Pod里的多个容器就不是对等关系,而是拓扑关系了。

因此,在Kubernetes项目里,为了实现共享Namespace需要使用一个中间容器,这个容器就是pause容器(也称为Infra容器)。在一个Pod中,pause容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与pause容器关联在一起。下图可以证明pause容器的存在:

441a948523274ca28b694965a8178e19.png

借助pause容器,在同一个Pod内,所有容器共享一个IP地址和端口空间,并且可以通过 localhost 发现对方。 他们也能通过如SystemV信号量或POSIX共享内存这类标准的进程间通信方式互相通信。

a9913def75c142b390c6ea2e55ace972.png

注意,在 Kubernetes 1.8 版本之前,默认是启用 PID Namespace 共享的。然而在 Kubernetes 1.8 版本以后,情况刚好相反,默认情况下kubelet标志 --docker-disable-shared-pid=true,如果要开启,需要设置成false。

7 浅析容器跨主机通信

使用如下命令创建两个一次性的Pod:

kubectl --kubeconfig ${k8s_config} \
    run -it --image busybox ${pod_name} \
    --restart=Never --rm \
    /bin/sh

说明:--rm 表示Pod退出后就会被删除掉

两个Pod创建成功后各自宿主机IP如下图所示:

1632193dbfd64813ae45789bb2c10016.png

在名称为 net-test-02 的Pod(以下简称为Pod2)里可以ping通另一个Pod(以下简称为Pod1):

b692f233de794a7e91788a50cd222ed0.png

Pod2所在宿主机信息:

c8ef1d37a2b14873a1337f333710c05a.png

直接在Pod2的宿主机上 ping Pod1 也是通的:

1e0f9401c1ca4c37b4f96546157fa098.png

在宿主机上查看路由信息:

62c38f360d564ed78cd5116b915985b2.png

基于以上信息,我们来分析下容器跨主机通信过程:

  • Pod1的内部IP为 10.70.2.55/26,Pod2的内部IP为 10.70.0.43/26,显然这俩IP不属于同一个网段
  • 注意看Pod2的路由表,在Pod2上 ping Pod1 会选择第3条路由,即通过Pod2内部eth0设备发出,下一跳为 10.70.0.1
  • 之前简单分享过容器网络相关内容(出门右转看 网络接口veth),与Pod2内部eth0配对的veth是 vethd90d13fc,其挂载在 cni0 网桥上
  • Pod2发出的报文经由 cni0 网桥出现在宿主机eth0上
  • 查看宿主机的路由信息,发往 10.70.0.43 的报文会选择第1条路由,即通过宿主机eth0设备,下一跳为 192.168.28.253
  • Pod1所属的宿主机ip为 192.168.29.252,显然跟Pod2的宿主机不在同一个网段,因为我们使用了阿里云的VPC,所以可以简单认为这两个网段是互通的
  • 这里使用的Kubernetes集群是阿里云提供的ACK版本,用到的Flannel插件采用的是阿里云VPC模式,报文经过阿里云VPC的路由表直接转发,不需要 VxLan 等隧道封装技术封装报文,所以比Flannel默认的VxLan模式具有更高的通信性能
  • Flannel插件有3种常见的模式有UDP、VxLan和Host-gw,上面所说的阿里云VPC模式可以类比Host-gw模式,简单来说就是将目的容器所在宿主机的IP地址设置成下一跳
  • Pod1的宿主机接收到报文后,根据路由规则发给 cni0 网桥,cni0 网桥会扮演二层交换机的角色,把报文转发到虚拟网卡上,这样一来,Pod1的网络协议栈就会收到这个报文

8 学习容器技术有感

以上是这一期关于容器那些事的分享内容,大家应该会发现,学习容器相关技术能够帮助我们更好地理解操作系统底层以及网络相关知识,而这些知识可以潜移默化地帮助我们开启程序猿的“上帝视角”,对我们提高自身技术很有意义。你可能不相信,为了深入学习Kubernetes,我学会了Go语言。总之,不要再45度角仰望星空,期盼遥不可及的救世主,学习容器可以让我们成为更好的自己,绝不是内卷!


标题:与容器相关的那些事(二)
作者:yanghao
地址:http://solo.fancydigital.com.cn/articles/2022/03/15/1647311359589.html