前言

本文主要会介绍笔者在学习Kubernetes的容器设计理念时所总结的知识点,其中会涉及到Kubernetes对于容器的设计思路以及相关的实现方式等方面的相关内容。 笔者也会将自己的理解在文中进行阐述,这也算是在和大家交流心得的一个过程。若文中有错误的理解和概念,请大家及时纠正;吸纳大家的建议,对于我来说也是很重要的学习过程之一。


(目录)


1.设计思想

在一台宿主机上往往不只是运行一个进程,而是有很多进程同时运行在其中;从而使得宿主机可以对外提供各式各样的服务或应用。这些进程通常不全是“单打独斗”,而是会联合在一起合作运转,也就是以进程组的方式组合在一起。

sessions.png

容器的本质实际就是运行在宿主机上的一个用户进程。同样,一台宿主机上往往会运行着多个容器对外提供服务。此时如果将一个容器看作为一个进程的话,那么宿主机上的多个容器在共同合作完成任务时,是否可以像类似于进程组的方式组合在一起工作呢?Kubernetes认为是可行的,并以此为依据提出了一种”容器组“的设计思想。Kubernetes认为当一些容器经常会发生直接的文件交换、或是使用 localhost 或者 Socket 文件进行本地通信、或是需要共享某些 Linux Namespace、以及非常频繁的进行相互远程调用时,那么就可以考虑将这些容器划分到一个”容器组“中。在这个”容器组“内的容器能够相互共享很多底层资源,这种共享资源的方式使得这些容器在相互配合工作时更加方便和高效。


2.实现方式

2.1 Pod

Kubernetes的Pod对象实际上就是第一章所提到的设计理念的一种实现方式。之所以抽象出Pod这个概念,就是为了要让在同一个Pod中的容器尽可能多地共享Linux Namespace并且仅保留必要的隔离和限制能力。 这里要注意的是,Pod只是一个逻辑概念。Kubernetes真正要操作的还是宿主机操作系统上Linux的Namespace和Cgroups,而并不存在一个所谓的Pod的边界或者隔离环境。

2.1.1 Infra容器

Pod本质上就是一组共享了某些资源的容器。Pod里的所有容器共享一个Linux Network Namespace,并且可以声明共享同一个Volume。而上述这个理念,实际上是通过Infra的容器来实现的。常见的pause容器即为infra容器,全称为infrastructure container。

1070925-20201203173127767-1397727648.png

Infra容器永远都是第一个被创建的容器,而其他用户定义的容器则通过Join Namespace 的方式与 Infra 容器关联在一起。用户容器通过加入到Infra容器的Network namespace后,就可以获得向外界以及和同一个pod内部的其他容器使用网络通信的能力。同理,当用户容器加入到Infra容器的Mount namespace后,就可以访问相应的Volume,并且此时多个用户容器可以共享这些Volume。

Tips: 实际上同一个Pod中共享的是Infra容器的Linux Namespace。因此Pod以及Pod内容器的存活就与Infra容器息息相关了。一旦Infra容器被重建,则整个Pod就会无法正常工作。

Infra容器主要为Pod中的主容器提供以下功能:

  1. 建立PID namespace 使Pod中的不同应用程序可以看到其他应用程序的进程ID。

  2. 建立Network namespace 使Pod中的多个容器能够通过同一网络栈进行通信。

  3. 建立IPC namespace 使Pod中的多个容器能够使用SystemV IPC或POSIX消息队列进行通信。

  4. 建立UTS namespace 使Pod中的多个容器共享一个主机名。

  5. 建立Mount namespace 使Pod中的各个容器可以访问同一数据存储。

2.1.2 资源限制

Pod中容器的资源限制是使用Linux Cgroups实现的。Kubernetes会为Pod中的每一个容器都会创建相应的CPU cgroup, Memory cgroup以及其他cgroup,容器中所有进程都会被加入到这些cgroup中。基于这种实现方式,Kubernetes就可以做到针对Pod中的每一个容器进行资源限制,即对每一个容器所属的cgroups进行配置。

注意:虽然Pod中的容器可能会共享相关的Namespace,但是cgroup并不能共享,每一个容器都有其单独的cgroup。

2.1.3 Pod的组建原则

Pod这个概念提供的是一种编排思想而不是具体的技术方案

pod.svg

这里笔者提供几个组建思路供大家参考:

  1. 可以将Pod比作是一台虚拟机,而容器看作是这台虚拟机中里运行的用户进程/程序/应用。基于上述思路,在组建Pod时就可以将相关的用户程序/应用制作为多个独立的容器,并可以使用Init Container来定义这些容器之间的启动顺序/关系。即将原本运行在同一台虚拟机中的多个进程变更为运行在一个Pod中的多个容器。这一思路比较适用于从传统架构转换为微服务架构的需求场景中。

  2. 将经常发生网络通信的用户进程/程序/应用放入到一个Pod中。这样可以充分利用在同一个Pod中的容器共享同一个Linux Network Namespace的特点,以此来提升这些程序之间的通信效率。

  3. 将需要经常通过文件(本地)交互的用户进程/程序/应用放入到一个Pod中。同理,这样可以充分利用在同一个Pod中的容器共享同一个Linux Mount Namespace的特点,加快容器间传输文件以及读写文件的效率。

Tips: 容器中虽然能同时运行多个进程,但不能同时对多个进程进行管理。如果出现了一个容器中运行着多个进程来提供服务,那么就应该考虑是否可以把这些进程都放入到一个单独的容器中,并通过让这些容器相互配合来对外提供相同的服务。即容器只能管理一个进程,但Pod可以管理多个容器/进程

2.1.4 Sidecar模式

Sidecar指是可以在一个Pod中启动一个辅助容器来完成一些独立于主进程(主容器)之外的工作。例如对容器内应用程序的日志采集,或是进行一些Pod内关于网络的相关配置和管理操作。

u=3347434780,2738446378&fm=253&fmt=auto&app=138&f=PNG.webp

2.1.4.1 与Init容器的区别

Sidecar与Init container的区别在于Sidecar是在主应用容器之前启动并持续运行的容器,而Init container是在 Pod 初始化期间完成运行的容器。

2.1.4.2 定义Sidecar

在Kubernetes v1.28版本后官方支持了Sidecar,即可通过initContainers属性来进行定义。方式为在initContainers属性内定义的容器中加上restartPolicy属性,就可以表示该容器是一个Sidecar类型的容器。

Tips: 官方支持的Sidecar实现,其生命周期是与Pod中运行的主应用容器生命周期是相互独立的。即当修改编排对象中的Sidecar定义部分后,Kubernetes只会重新部署Sidecar容器,而不会重新部署编排中涉及到的所有容器。

在Kubernetes v1.28版本之前若需要定义Sidecar,则需要通过在pod中添加相应的容器来实现Sidecar功能。以这种方式实现的Sidecar容器,实际上是与pod中运行的主应用容器生命周期是相同的。即如果需要变更Sidecar容器,则会影响到主应用容器的正常运行。