Kubernetes 之浅析 Pod

一. 为什么需要 Pod

在一个 Linux 操作系统中,进程是以进程组的方式组织在一起的,即一个或多个进程的集合,一方面是一些进程之前存在密切的关系,更重要的一点是方便管理。

而容器的本质实际上就是进程,经过了隔离,限制等一系列操作之后形成了一个独立的容器,那么 K8s 则类似于操作系统。因此同样地,K8s 也采用了 Linux 中进程组的思想,去组织其中的进程——即容器。Pod 的出现就是为了处理那些存在着密切协作关系的,需要部署在同一台机器上的应用的运维问题。

Pod 是 K8s 中的原子调度单位,所有 K8s 在对容器进行节点调度的时候,看的其实是一整个 Pod 而非单个容器。这也就理所当然地解决了调度的时候的资源问题,因为 K8s 会保证同属一个 Pod 的容器必定会在同一个节点当中,保证了应用直接的协作关系。

二. Pod 实现原理

很多地方把容器与虚拟机作对比,但其实对于容器进程和 K8s 来说,Pod 更像是运行在节点机器上的虚拟机,因为它才是管控其中进程的关键。但实际上,K8s 并没有真正地创建一个 Pod 实体出来,Pod 一直都只是一个逻辑的概念。对于 K8s 来说,Pod 其实就是一组容器,只不过这组容器之间存在着资源共享的关系。

实际上,Pod 里面所有的容器都共享了同一个 Network Namespace,同时也可以共享同一个数据卷。为了达到这样的目的,K8s 会首先创建一个 pause 容器,又叫 infra 容器,既然是首先创建的,那么很显然,它会作为一个先导的作用,引导其他容器的加入,在 pause 容器被创建以后,其他容器则可以通过 Join Network Namespace 的方式,与它关联在一起。 数据卷的话,只需要把所有的数据卷定义在 Pod 层级即可。

因为 pause 容器的作用特殊,所以它占用的资源非常少,使用的镜像是 k8s.gcr.io/pause,解压后的大小只有 100~200 KB。而且从名字可以看出来,它是一个永远处于暂停状态的容器。值得注意的是,Pod 生命周期只与 pause 容器有关。

因此我们可以得知,一个 Pod 只会有一个 IP 地址,即 Network Namespace 对应的 IP 地址,里面的容器看到的网络设备,网络资源也都是相同的。也因为有 pause 容器的存在,容器的网络配置只能跟随 pause 容器的脚步,即是跟随 Pod 的脚步,而不能让容器网络自定义为我们自己想要的网络配置,因为必须保证 Pod 里面所有容器的网络的统一性。

三. Pod 属性

由上述可知,我们可以把 Pod 看做一台机器,而容器就是运行在这个机器里面的程序。所以理所当然地,与机器相关的属性,网络,存储,安全等属性都是基于 Pod 级别的。而这些属性,我们都可以通过编写 YAML 文件进行自定义。

调度

调度是 K8s 中至关重要的一环,调度器会经过调度策略将 Pod 调度到理想的节点中。在 YAML 文件中,有 NodeSelector 和 NodeName 等属性与调度息息相关。

NodeSelector 顾名思义是一种节点选择器,经过设置可以指定该 Pod 必须调度到携带了规定的 Label 的节点当中。

NodeName 即节点名,当调度器设置了 NodeName,则表示该 Pod 已经完成了调度。而我们使用者也可以通过设置该属性来让调度器误以为其已经被调度过,适用于测试和调试的场景。

配置

在 YAML 文件中我们可以完成 Pod 层级的配置工作,比如我们通常会使用 HostAliases 属性来设置 host,而不是直接去修改 hosts 文件,因为这样不利于 host 的持久化,当 Pod 删除重建以后,K8s 会覆盖掉之前直接在文件中修改的内容。

Namespace

Namespace 是与隔离密不可分的属性,Namespace 包含了很多方面,包括网络相关的 Network Namespace,进程相关的 PID Namespace,文件系统相关的 Mount Namespace 等。

在 K8s 中,凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的。以便在保留必要的隔离和限制能力的同时,让里面的容器尽可能多地共享 Linux Namespace。例如 shareProcessNamespace 属性可以设置该 Pod 是否共享 PID Namespace,除此之外还有 hostNetwork,hostIPC 等也是同理。

容器

Pod 里面最重要的属性固然是容器了,在 YAML 中,我们可以对 Pod 中的容器进行全方位的配置,包括Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口)、volumeMounts(数据卷)等等。除此以外,还有诸如如 Init Containers 可以设置为 init 容器,它的创建会先于其他所有的容器,ImagePullPolicy 定义了每次创建是否都需要重新拉取一次镜像等字段

四. Pod 生命周期

Pod 的生命周期,即 status 属性,包含以下:

  • Pending:代表 Pod API 对象已创建完成,但其中的容器还未被顺利创建。
  • Running:表示调度完成且其中的容器至少有一个在运行中
  • Succeeded:所有容器正常运行完毕且已经退出,如一次性任务
  • Failed:其中的容器至少有一个以不正常状态退出
  • Unknown:异常状态,意味着 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver,这很有可能是主从节点间的通信出现了问题

五. 配置管理

在 K8s 中,有专门的 Projected Volume 用于配置的工作,这些 Volume 是为容器预先定义好的数据,可以理解为用于配置的数据卷,通过在容器中进行挂载,来访问其中的信息。这些信息都存放于 K8s 的 Etcd 之中,因此一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的内容,同样也会被更新。

分为以下两种:

Secret

以键值对形式存放加密数据,如数据库的 Credential 信息,数据需要经过 Base64 转码,而在真正的生产环境中,通常还需要开启 Secret 的加密插件,以增强数据的安全性。除此之外还可以指定可用的命名空间。

ConfigMap

存放键值对形式的配置信息,无需加密

推荐阅读更多精彩内容