Skip to content

Commit

Permalink
fix typo
Browse files Browse the repository at this point in the history
  • Loading branch information
isno committed May 5, 2024
1 parent cb94323 commit 36ae143
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 147 deletions.
13 changes: 11 additions & 2 deletions .vuepress/config.js
Expand Up @@ -271,8 +271,17 @@ export default defineUserConfig({
children: [
'/container/borg-omega-k8s.md',
'/container/orchestration.md',
'/container/image.md',
'/container/runtime.md',
'/container/image.md', {
text: '7.3 容器运行时:从 Docker 到 CRI',
collapsable: false,
sidebarDepth: 1,
link: '/container/runtime.md',
children: [
"/container/Docker.md",
"/container/CRI.md"
]
},

"/container/container-network.md",
{
text: '7.5 资源模型与调度',
Expand Down
6 changes: 2 additions & 4 deletions ServiceMesh/ServiceMesh-and-Kubernetes.md
@@ -1,10 +1,8 @@
# 8.4 服务网格与 Kubernetes

以 Kubernetes 为基础构建起的云原生世界里,Sidecar 模式无疑是最经典的设计。
以 Kubernetes 为基础构建起的云原生世界里,Sidecar 模式无疑是最经典的设计。当需要为应用提供与自身逻辑无关的辅助功能时,在应用 Pod 内注入对应功能的 Sidecar 显然是最 Kubernetes Native 的方式。

当需要为应用提供与自身逻辑无关的辅助功能时,在应用 Pod 内注入对应功能的 Sidecar 显然是最 Kubernetes Native 的方式。服务网格通过在 Pod 内的 Sidecar Proxy 实现透明代理,通过更接近微服务应用层的抽象,实现服务间的流量、安全性和可观察性细粒度管理。

如下图所示,服务网格(Istio)最大化地利用 Kubernetes 这个基础设施,两者叠加形成一套从底层的负载部署运行到综合服务治理的完整基础设施。
服务网格通过在 Pod 内的 Sidecar Proxy 实现透明代理,通过更接近微服务应用层的抽象,实现服务间的流量、安全性和可观察性细粒度管理。如下图所示,服务网格(Istio)最大化地利用 Kubernetes 这个基础设施,两者叠加形成一套从底层的负载部署运行到综合服务治理的完整基础设施。

<div align="center">
<img src="../assets/ServiceMesh-and-Kubernetes.png" width = "450" align=center />
Expand Down
98 changes: 98 additions & 0 deletions container/CRI.md
@@ -0,0 +1,98 @@
# 7.3.2 CRI 运行时规范

早期 Kubernetes 完全依赖且绑定 Docker,并没有过多考虑够日后使用其他容器引擎的可能性。当时 kubernetes 管理容器的方式通过内部的 DockerManager 直接调用 Docker API 来创建和管理容器。

<div align="center">
<img src="../assets/k8s-runtime-v1.svg" width = "600" align=center />
</div>

Docker 盛行之后,CoreOS 推出了 rkt 运行时实现,Kubernetes 又实现了对 rkt 的支持,随着容器技术的蓬勃发展,越来越多运行时实现出现,如果还继续使用与 Docker 类似强绑定的方式,Kubernetes 的工作量将无比庞大。Kubernetes 要重新考虑对所有容器运行时的兼容适配问题了。

Kubernetes 从 1.5 版本开始,在遵循 OCI 基础上,将容器操作抽象为一个接口,该接口作为 Kubelet 与运行时实现对接的桥梁,Kubelet 通过发送接口请求对容器进行启动和管理,各个容器运行时只要实现这个接口就可以接入 Kubernetes,这便是 CRI(Container Runtime Interface,容器运行时接口)。

CRI 实现上是一套通过 Protocol Buffer 定义的 API,如下图:

<div align="center">
<img src="../assets/cri-arc.png" width = "450" align=center />
</div>

从上图可以看出:CRI 主要有 gRPC client、gRPC Server 和具体容器运行时实现三个组件。其中 Kubelet 作为 gRPC Client 调用 CRI 接口,CRI shim 作为 gRPC Server 来响应 CRI 请求,并负责将 CRI 请求内容转换为具体的运行时管理操作。因此,任何容器运行时实现想要接入 Kubernetes,都需要实现一个基于 CRI 接口规范的 CRI shim(gRPC Server)。

## CRI-O

2017 年,由 Google、RedHat、Intel、SUSE、IBM 联合发起的 CRI-O(Container Runtime Interface Orchestrator)项目发布了首个正式版本。从名字就可以看出,它非常纯粹, 就是兼容 CRI 和 OCI, 做一个 Kubernetes 专用的轻量运行时。

<div align="center">
<img src="../assets/k8s-cri-o.png" width = "440" align=center />
</div>

虽然 CRI-O 摆出了直接挖掉 Docker 根基手段,但此时 Docker 在容器引擎中的市场份额仍然占有绝对优势,对于普通用户来说,如果没有明确的收益,并没有什么动力要把 Docker 换成别的引擎。不过我们也能够想像此时 Docker 心中肯定充斥了难以言喻的危机感。

## containerd

不过 Docker 也没有“坐以待毙”,与其将来被人分离或者抛弃不用,不如主动革新。

于是 Docker 采取了“断臂求生”的策略推动自身的重构,把原本单体架构的 Docker Engine 拆分成了多个模块,其中的 Docker daemon 部分就捐献给了 CNCF,形成了 containerd 与 Kubernetes 深度绑定在一起。containerd 作为 CNCF 的托管项目,自然符合 CRI 标准的。但 Docker 出于自己诸多原因的考虑,它只是在 Docker Engine 里调用了 containerd,外部的接口仍然保持不变,也就是说还不与 CRI 兼容。

由于 Docker 的“固执己见”且 Docker 是当时容器技术主流存在,Kuberentes 虽然提出了 CRI 接口规范,仍然需要去适配 CRI 与 Docker 的对接,因此它需要一个中间层(shim,垫片)来对接 Kubelet 和 Docker 运行时实现。

此时,Kubernetes 里就出现了两种调用链:
1. CRI 接口调用 dockershim,然后 dockershim 调用 Docker,Docker 再走 containerd 去操作容器。
2. CRI 接口直接调用 containerd 去操作容器。

<div align="center">
<img src="../assets/k8s-runtime-v2.png" width = "500" align=center />
</div>

在这个阶段 **Kubelet 的代码和 dockershim 都是放在一个 Repo**。这也就意味着 dockershim 是由 Kubernetes 进行组织开发和维护!由于 Docker 的版本发布 Kubernetes 无法控制和管理,所以 Docker 每次发布新的 Release,Kubernetes 都要集中精力去快速地更新维护 dockershim。

同时 Docker 仅作为容器运行时也过于庞大,Kubernetes 弃用 dockershim 有了足够的理由和动力。

Kubernetes 在 v1.24 版本正式删除和弃用 dockershim,这件事情的本质是废弃了内置的 dockershim 功能转而直接对接 containerd。从上图可以看出在 containerd 1.0 中,对 CRI 的适配是通过一个单独的 CRI-Containerd 进程来完成的,这是因为最开始 containerd 还会去适配其他的系统(比如 swarm),所以没有直接实现 CRI,这个对接工作就交给 CRI-Containerd 这个 shim 了。

2018 年,由 Docker 捐献给 CNCF 的 containerd,在 CNCF 的精心孵化下发布了 1.1 版,1.1 版与 1.0 版的最大区别是此时它已完美地支持了 CRI 标准,这意味着原本用作 CRI 适配器的 cri-containerd 从此不再需要。
<div align="center">
<img src="../assets/k8s-runtime-v3.png" width = "500" align=center />
</div>

Kubernetes 从 1.10 版本宣布开始支持 containerd 1.1,在调用链中已经能够完全抹去 Docker Engine 的存在。此时,再观察 Kubernetes 到容器运行时的调用链,你会发现调用步骤会比通过 DockerShim、Docker Engine 与 containerd 交互的步骤要减少两步,用户只要愿意抛弃掉 Docker 情怀,在容器编排上便可至少省略一次调用,获得性能上的收益。

从 Kubernetes 角度看,选择 containerd 作为运行时的组件,调用链更短、更稳定、占用节点资源也更少。根据 Kubernetes 官方给出的测试数据[^1],containerd1.1 对比 Docker 18.03:Pod 的启动延迟降低了大约 20%;CPU 使用率降低了 68%;内存使用率降低了 12%,这是一个相当大的性能改善。

<div align="center">
<img src="../assets/k8s-runtime-v4.svg" width = "100%" align=center />
</div>

## 安全容器运行时

尽管容器有许多技术优势,然而传统以 runc 为代表基于共享内核技术进行的软隔离还是存在一定的风险性。如果某个恶意程序利用系统缺陷从容器中逃逸,就会对宿主机造成严重威胁,尤其是公有云环境,安全威胁很可能会波及到其他用户的数据和业务。

将虚拟机的安全优势与容器的高速及可管理性相结合,为用户提供标准化、安全、高性能的容器解决方案,于是就有了 Kata Containers 。

<div align="center">
<img src="../assets/kata-container.webp" width = "550" align=center />
<p>Kata Containers 与传统容器技术的对比[^1]</p>
</div>

Kata Containers 安全容器的诞生解决了许多普通容器场景无法解决的问题,譬如多租户安全保障、差异化 SLO混合部署、可信/不可信容器混合部署等等。在这些优势的基础上,Kata Containers 也在虚拟化上也追求极致的轻薄,从而让整体资源消耗和弹性能力接近 runc 容器方案,以此达到 Secure as VM、Fast as Container 的技术目标。

为了缩短容器的调用链、高效地和 Kubernetes CRI 集成,Kata-Container 直接将 containerd-shim 和 kata-shim 以及 kata-proxy 融合到一起。并做到了运行符合 OCI 规范,同时兼容 Kubernetes CRI(虚拟机级别的 Pod 实现)。

CRI 和 Kata Containers 的集成如下图所示:

<div align="center">
<img src="../assets/kata-container.png" width = "600" align=center />
</div>


今天,如下图所示,符合 CRI 规范的容器运行时已达十几种,要使用哪一种容器运行时取决于你安装 Kubernetes 时宿主机上的容器运行时环境,但对于云计算厂商来说,如果没有特殊的需求(譬如因为安全性要求必须隔离内核)采用的容器运行时普遍都已是 containerd,毕竟运行性能以及稳定对它们来说就是核心生产力和竞争力。

<div align="center">
<img src="../assets/runtime.png" width = "550" align=center />
</div>

[^1]: 参见 https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga/

[^2]: 图片来源 https://medium.com/kata-containers/inject-workloads-with-kata-containers-in-istio-4730a57b33fd
[^3]: 图片来源 https://github.com/kata-containers/documentation/blob/master/design/architecture.md

42 changes: 42 additions & 0 deletions container/Docker.md
@@ -0,0 +1,42 @@
# 7.3.1 Docker 的演变

笔者曾提及云原生早期阶段,将 Docker 作为容器技术代表实至名归。这句话还有一层背后的含义:

:::tip <a/>

容器技术不是 Docker,Docker 只是将容器技术推到了巅峰。

:::

早期的容器生态圈内,还有一个不能忽视的角色 CoreOS,它定位于容器设计的操作系统。作为 Docker 的互补,CoreOS+Docker 曾经也是容器部署的明星套餐。然而好景不长,与之前对 Docker 的期望是“一个简单的基础单元”不同,Docker 也在通过开发或收购逐步完善容器云平台的各种组件,准备打造自己的生态圈,而这与 CoreOS 的布局有直接竞争关系。

于是,2014 年底就有了 CoreOS 与 Docker 的分手事件,其导火索是 CoreOS 撇开 Docker,推出了与 Docker 对抗的开源容器引擎 Rocket(简称rkt),并联合一些知名的 IT 公司成立委员会试图主导容器技术的标准化。最终 CoreOS 在 2015 榜上 Google 公司,并联合推出基于 CoreOS + Rocket + Kubernetes 的商用容器平台 Tectonic。从此,容器江湖分为两大阵营,Google 派系和 Docker 派系。

这也让当时 Docker 阵营和粉丝们无比担心 Docker 的命运,不管最终鹿死谁手,容器技术的分裂对所有牵涉其中的人没有任何好处,于是 Linux 基金会出面调和,双方各退一步,最终结果是 Linux 基金会于 2015 年 6 月在 DockerCon 大会上宣布成立 OCI(Open Container Initiative,开放容器倡议)项目[^1]

OCI 的创始成员包括 Amazon、Microsoft、CoreOS、Docker、Intel、Mesosphere 和 Red Hat。Docker 在这个名单内只能算一个小角色,OCI 的成立最终结束了容器技术标准之争,Docker 公司也被迫放弃自己的独家控制权。作为回报,Docker 的容器格式被 OCI 采纳为新标准的基础,并且由 Docker 起草 OCI 草案规范的初稿。

当然这个“标准起草者” 也不是那么好当的,Docker 需要提交自己的容器引擎源码作为启动资源。首先是 Docker 最初使用的容器引擎 libcontainer,这是 Docker 在容器运行时方面的核心组件之一 ,用于实现容器的创建、管理和运行。Docker 将 libcontainer 捐赠给了OCI,成为 OCI Runtime Specification 的基础。在 OCI 的基础上,OCI Runtime Specification 进一步发展演进,并形成了一个名为"runtime-spec" 的项目,后来为了更好地推进容器运行时的标准化和互操作性,OCI runtime-spec 项目与 OCI 的其他相关项目合并,形成了 OCI Runtime Bundle 规范,并将容器运行时的核心组件命名为"runc"。

经过如上的驱动演进之后,OCI 有了三个主要的规范标准:

- **runtime-spec**(容器运行时标准):定义了容器运行的配置,环境和生命周期。即如何运行一个容器,如何管理容器的状态和生命周期,如何使用操作系统的底层特性(namespace,cgroup,pivot_root 等)。
- **image-spec**(容器镜像标准):定义了镜像的格式,配置(包括应用程序的参数,环境信息等),依赖的元数据格式等,简单来说就是对镜像的静态描述
- **distribution-spec**(镜像分发标准):即规定了镜像上传和下载的网络交互过程。

总的来说 OCI 的成立促进了社区的持续创新,同时可以防止行业由于竞争导致的碎片化,容器生态中的各方都能从中获益。


经过一系列的改造、拆分之后,从 docker v1.11 版本开始,docker 就不是简单通过 Docker Daemon 来启动了,而是通过集成 containerd、containerd-shim、runc 等多个组件共同完成。docker 架构流程图已如下所示:

<div align="center">
<img src="../assets/docker-arc.png" width = "550" align=center />
</div>

从 Docker 的拆分来看,容器运行时根据功能的不同分成了两类:
- 只关注如 namespace、cgroups、镜像拆包等基础的容器运行时实现被称为**低层运行时(low-level container runtime)**, 目前应用最广泛的低层运行时是 runc;
- 支持更多高级功能,例如镜像管理、CRI 实现的运行时被称为**高层运行时(high-level container runtime)**,目前应用最广泛高层运行时是 containerd。

这两类运行时按照各自的分工,共同协作完成容器整个生命周期的管理工作。

[^1]: 该项目最初的名字叫 OCP(Open Container Project),不过因为 OCP 总是容易和 Open Compute Project 混淆,后来在 OSCON 会议上宣布更名为开放容器计划(Open Container Initiative)。
4 changes: 3 additions & 1 deletion container/auto-scaling.md
Expand Up @@ -90,7 +90,9 @@ minReplicaCount 和 maxReplicaCount 分别定义了要伸缩的对象的最小

随着业务的发展,应用会逐渐增多,每个应用使用的资源也会增加,总会出现集群资源不足的情况。为了动态地应对这一状况,我们还需要 CLuster Auto Scaler,能够根据整个集群的资源使用情况来增减节点。

Cluster AutoScaler 是一个自动扩展和收缩 Kubernetes 集群 Node 的扩展。当集群容量不足时,它会自动去 Cloud Provider(支持绝大部分的云服务商 GCE、GKE、Azure、AKS、AWS 等等)创建新的 Node,而在 Node 长时间(超过 10 分钟)资源利用率很低时(低于 50%)自动 Pod 会自动调度到其他 Node 上面,并删除节点以节省开支。
Cluster AutoScaler 是一个自动扩展和收缩 Kubernetes 集群 Node 的扩展:
- 当集群容量不足时,它会自动去 Cloud Provider(支持绝大部分的云服务商 GCE、GKE、Azure、AKS、AWS 等等)创建新的 Node;
- 而在 Node 长时间(超过 10 分钟)资源利用率很低时(低于 50%)自动 Pod 会自动调度到其他 Node 上面,并删除节点以节省开支。

<div align="center">
<img src="../assets/Cluster-AutoScaler.png" width = "500" align=center />
Expand Down

0 comments on commit 36ae143

Please sign in to comment.