weight: 13
title: Pod 解析
date: “2022-05-21T00:00:00+08:00”
type: book
Pod 是Kubernetes 中可以创建的最小部署单元,也是Kubernetes REST API 中的顶级资源类型。
在Kuberentes V1 core API 版本中的Pod 的数据结构如下图所示:
Pod Cheatsheet
什么是Pod ?
Pod 就像是豌豆荚一样,它由一个或者多个容器组成(例如Docker 容器) ,它们共享容器存储、网络和容器运行配置项。Pod 中的容器总是被同时调度,有共同的运行环境。你可以把单个Pod 想象成是运行独立应用的 “逻辑主机”—— 其中运行着一个或者多个紧密耦合的应用容器 —— 在有容器之前,这些应用都是运行在几个相同的物理机或者虚拟机上。
尽管kubernetes 支持多种容器运行时,但是Docker 依然是最常用的运行时环境,我们可以使用Docker 的术语和规则来定义Pod 。
Pod 中共享的环境包括Linux 的namespace 、cgroup 和其他可能的隔绝环境,这一点跟Docker 容器一致。在Pod 的环境中,每个容器中可能还有更小的子隔离环境。
Pod 中的容器共享IP 地址和端口号,它们之间可以通过 localhost
互相发现。它们之间可以通过进程间通信,例如 SystemV 信号或者POSIX 共享内存。不同Pod 之间的容器具有不同的IP 地址,不能直接通过IPC 通信。
Pod 中的容器也有访问共享volume 的权限,这些volume 会被定义成pod 的一部分并挂载到应用容器的文件系统中。
根据Docker 的结构,Pod 中的容器共享namespace 和volume ,不支持共享PID 的namespace 。
就像每个应用容器,pod 被认为是临时(非持久的)实体。在Pod 的生命周期中讨论过,pod 被创建后,被分配一个唯一的ID (UID) ,调度到节点上,并一致维持期望的状态直到被终结(根据重启策略)或者被删除。如果node 死掉了,分配到了这个node 上的pod ,在经过一个超时时间后会被重新调度到其他node 节点上。一个给定的pod (如UID 定义的)不会被 “重新调度” 到新的节点上,而是被一个同样的pod 取代,如果期望的话甚至可以是相同的名字,但是会有一个新的UID 。
临时卷的生命周期跟pod 相同,当Pod 因为某种原因被删除或者被新创建的相同的pod 取代时,pod 的附属物(例如volume )也会被销毁和重新创建。Kubernetes 中提供了众多的卷类型,关于卷(Volume)的详细介绍请参考 Kubernetes 文档 。
Pod 示意图
说明:一个多容器Pod ,包含文件提取程序和Web 服务器,该服务器使用持久卷在容器之间共享存储。
Pod 的动机
管理
Pod 是一个服务的多个进程的聚合单位,pod 提供这种模型能够简化应用部署管理,通过提供一个更高级别的抽象的方式。Pod 作为一个独立的部署单位,支持横向扩展和复制。共生(协同调度) ,命运共同体(例如被终结) ,协同复制,资源共享,依赖管理,pod 都会自动的为容器处理这些问题。
资源共享和通信
Pod 中的应用可以共享网络空间(IP 地址和端口) ,因此可以通过 localhost
互相发现。因此,pod 中的应用必须协调端口占用。每个pod 都有一个唯一的IP 地址,跟物理机和其他pod 都处于一个扁平的网络空间中,它们之间可以直接连通。
Pod 中应用容器的hostname 被设置成Pod 的名字。
Pod 中的应用容器可以共享卷。持久化卷能够保证pod 重启时使用的数据不丢失。
Pod 的使用
Pod 也可以用于垂直应用栈(例如LAMP ) ,这样使用的主要动机是为了支持共同调度和协调管理应用程序,例如:
内容管理系统、文件和数据加载器、本地换群管理器等。
日志和检查点备份、压缩、旋转、快照等。
数据变更观察者、日志和监控适配器、活动发布者等。
代理、桥接和适配器等。
控制器、管理器、配置器、更新器等。
通常单个pod 中不会同时运行一个应用的多个实例。
详细说明请看: The Distributed System ToolKit: Patterns for Composite Containers 。
其他替代选择
为什么不直接在一个容器中运行多个应用程序呢?
透明。让pod 中的容器对基础设施可见,以便基础设施能够为这些容器提供服务,例如进程管理和资源监控。这可以为用户带来极大的便利。
解耦软件依赖。每个容器都可以进行版本管理,独立的编译和发布。未来kubernetes 甚至可能支持单个容器的在线升级。
使用方便。用户不必运行自己的进程管理器,还要担心错误信号传播等。
效率。因为由基础架构提供更多的职责,所以容器可以变得更加轻量级。
为什么不支持容器的亲和性的协同调度?
这种方法可以提供容器的协同定位,能够根据容器的亲和性进行调度,但是无法实现使用pod 带来的大部分好处,例如资源共享,IPC,保持状态一致性和简化管理等。
Pod 的持久性(或者说缺乏持久性)
Pod 在设计支持就不是作为持久化实体的。在调度失败、节点故障、缺少资源或者节点维护的状态下都会死掉会被驱逐。
通常,用户不需要手动直接创建Pod ,而是应该使用controller (例如 Deployments ) ,即使是在创建单个Pod 的情况下。Controller 可以提供集群级别的自愈功能、复制和升级管理。
使用集合API 作为主要的面向用户的原语在集群调度系统中相对常见,包括 Borg 、Marathon 、Aurora 和 Tupperware 。
Pod 原语有利于:
调度程序和控制器可插拔性
支持pod 级操作,无需通过控制器API “代理” 它们
将pod 生命周期与控制器生命周期分离,例如用于自举(bootstrap)
控制器和服务的分离 —— 端点控制器只是监视pod
将集群级功能与Kubelet 级功能的清晰组合 ——Kubelet 实际上是 “pod 控制器”
高可用性应用程序,它们可以在终止之前及在删除之前更换pod ,例如在计划驱逐、镜像预拉取或实时pod 迁移的情况下,详见Issue #3949 。
StatefulSet 控制器支持有状态的Pod 。在1.4 版本中被称为PetSet 。在kubernetes 之前的版本中创建有状态pod 的最佳方式是创建一个replica 为1 的replication controller 。
Pod 的终止
因为pod 作为在集群的节点上运行的进程,所以在不再需要的时候能够优雅的终止掉是十分必要的(比起使用发送KILL 信号这种暴力的方式) 。用户需要能够发起一个删除Pod 的请求,并且知道它们何时会被终止,是否被正确的删除。用户想终止程序时发送删除pod 的请求,在pod 可以被强制删除前会有一个宽限期,会发送一个TERM 请求到每个容器的主进程。一旦超时,将向主进程发送KILL 信号并从API server 中删除。如果kubelet 或者container manager 在等待进程终止的过程中重启,在重启后仍然会重试完整的宽限期。
示例流程如下:
用户发送删除pod 的命令,默认宽限期是30 秒;
在Pod 超过该宽限期后API server 就会更新Pod 的状态为 “dead”;
在客户端命令行上显示的Pod 状态为 “terminating”;
跟第三步同时,当kubelet 发现pod 被标记为 “terminating” 状态时,开始停止pod 进程:
如果在pod 中定义了preStop hook ,在停止pod 前会被调用。如果在宽限期过后,preStop hook 依然在运行,第二步会再增加2 秒的宽限期;
向Pod 中的进程发送TERM 信号;
跟第三步同时,该Pod 将从该service 的端点列表中删除,不再是replication controller 的一部分。关闭的慢的pod 将继续处理load balancer 转发的流量;
过了宽限期后,将向Pod 中依然运行的进程发送SIGKILL 信号而杀掉进程。
Kubelet 会在API server 中完成Pod 的的删除,通过将优雅周期设置为0 (立即删除) 。Pod 在API 中消失,并且在客户端也不可见。
删除宽限期默认是30 秒。kubectl delete
命令支持 —grace-period=<seconds>
选项,允许用户设置自己的宽限期。如果设置为0 将强制删除pod 。在kubectl>=1.5 版本的命令中,你必须同时使用 --force
和 --grace-period=0
来强制删除pod 。在yaml 文件中可以通过 {{ .spec.spec.terminationGracePeriodSeconds }}
来修改此值。
强制删除Pod
Pod 的强制删除是通过在集群和etcd 中将其定义为删除状态。当执行强制删除命令时,API server 不会等待该pod 所运行在节点上的kubelet 确认,就会立即将该pod 从API server 中移除,这时就可以创建跟原pod 同名的pod 了。这时,在节点上的pod 会被立即设置为terminating 状态,不过在被强制删除之前依然有一小段优雅删除周期。
强制删除对于某些pod 具有潜在危险性,请谨慎使用。使用StatefulSet pod 的情况下,请参考删除StatefulSet 中的pod 文章。
Pod 中容器的特权模式
从Kubernetes1.1 版本开始,pod 中的容器就可以开启privileged 模式,在容器定义文件的 SecurityContext
下使用privileged
flag 。这在使用Linux 的网络操作和访问设备的能力时是很有用的。容器内进程可获得近乎等同于容器外进程的权限。在不需要修改和重新编译kubelet 的情况下就可以使用pod 来开发节点的网络和存储插件。
如果master 节点运行的是kuberentes1.1 或更高版本,而node 节点的版本低于1.1 版本,则API server 将也可以接受新的特权模式的pod ,但是无法启动,pod 将处于pending 状态。
执行 kubectl describe pod FooPodName
,可以看到为什么pod 处于pending 状态。输出的event 列表中将显示: Error validating pod "FooPodName"."FooPodNamespace" from api, ignoring: spec.containers[0].securityContext.privileged: forbidden '<*>(0xc2089d3248)true'
如果master 节点的版本低于1.1 ,无法创建特权模式的pod 。如果你仍然试图去创建的话,你得到如下错误:
The Pod "FooPodName" is invalid. spec.containers[0 ].securityContext.privileged: forbidden '<*>(0 xc20 b222 db0 )true'