在上一篇文章中,我详细介绍了 Kubernetes 项目中最重要的概念 Pod。在今天的文章中,我将分享更多关于 Pod 对象的细节。
Pod 与虚拟机
到目前为止,你应该非常清楚,Pod 而不是容器才是 Kubernetes 项目中最小的编排单元。这个设计体现在 API 对象中,容器只是 Pod 属性中的一个普通字段。自然而然地,问题就来了:哪些属性属于 Pod 对象,哪些属于容器?
Pod 在传统部署环境中扮演着“虚拟机”的角色。这样设计的目的是为了使从传统环境(虚拟机环境)到 Kubernetes(容器环境)的过渡更加平滑。
如果把 Pod 看作传统环境中的“机器”,把容器看作运行在这台“机器”上的“用户程序”,那么 Pod 对象设计的许多方面就变得容易理解了。
例如,与调度、网络、存储和安全相关的属性本质上都是 Pod 级别的。这些属性的共同特点是它们描述的是整个“机器”,而不是内部运行的“程序”。
比如,配置“机器”的网卡(即 Pod 的网络定义)、配置“机器”的磁盘(即 Pod 的存储定义)以及配置“机器”的防火墙(即 Pod 的安全定义)。更不用说这台“机器”运行在哪台服务器上(即 Pod 的调度)。
Kubernetes YAML
在 Kubernetes 中,我们通过声明式文件来定义 Pod 资源。
NodeSelector:这是一个允许用户将 Pod 绑定到特定 Node 的字段,如下所示:
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
这样的配置意味着这个 Pod 只能运行在具有 disktype: ssd 标签的节点上,否则调度会失败。
NodeName:一旦这个 Pod 的字段被赋值,Kubernetes 项目就会认为这个 Pod 已经被调度了,并且调度结果就是所赋值的节点名称。因此,这个字段通常由调度器设置,但用户也可以手动设置以“欺骗”调度器,不过这种做法通常只用于测试或调试。
HostAliases:定义 Pod 的 hosts 文件内容(例如 /etc/hosts),如下所示:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
需要注意的是,在 Kubernetes 项目中,如果你想设置 hosts 文件的内容,必须通过这种方法。否则,如果你直接修改 hosts 文件,kubelet 会在 Pod 被删除并重新创建后自动覆盖修改的内容。
除了上述与“机器”相关的配置,你可能还会发现,任何与容器的 Linux Namespace 相关的属性也都是 Pod 级别的。这也容易理解:Pod 的设计是为了让其中的容器尽可能共享更多的 Linux Namespace,只保留必要的隔离和限制能力。这样,Pod 模拟出来的效果就非常类似于虚拟机的程序之间的关系。
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
这是来自 Kubernetes 官方文档的一个 Pod YAML 文件。它实际上非常简单,只是定义了一个使用 nginx 镜像的容器。但是,在这个 YAML 文件的 containers 部分中,你会看到这个容器设置了 postStart 和 preStop 参数。
这意味着什么呢?
先从 postStart 说起,它指的是容器启动后立即执行的一个操作。需要明确的是,postStart 定义的操作是在 Docker 容器的 ENTRYPOINT 之后执行的,但并没有严格的顺序保证。也就是说,当 postStart 启动时,ENTRYPOINT 可能还没有结束。
当然,如果 postStart 执行超时或出错,Kubernetes 会在该 Pod 的 Events 中报一个容器启动失败的错误信息,导致 Pod 也处于失败状态。
类似地,preStop 的执行时机是在容器被终止之前(例如,当它收到 SIGKILL 信号时)。需要说明的是,preStop 操作是同步执行的。因此,它会阻塞当前的容器终止进程,直到定义的操作完成,这与 postStart 有所不同。
所以,在这个例子中,容器成功启动后,会向 /usr/share/message 写入一条“问候消息”(即 postStart 定义的操作)。而在容器被删除之前,我们先调用 nginx 的退出命令(即 preStop 定义的操作),从而实现容器的“优雅关闭”。
熟悉了 Pod 及其容器部分的主要字段之后,我将继续分享 Kubernetes 中 Pod 对象的生命周期。
Pod 生命周期的变化主要体现在 Pod API 对象的 Status 部分,这是除了 Metadata 和 Spec 之外的第三个重要字段。其中,pod.status.phase 代表 Pod 的当前状态,可能的情况如下:
-
Pending。此状态意味着 Pod 的 YAML 文件已经提交给 Kubernetes,API 对象已经创建并保存在 Etcd 中。但是,Pod 中的某些容器由于某种原因无法顺利创建。例如,调度失败。
-
Running。在此状态下,Pod 已成功调度并绑定到特定的节点。它所包含的所有容器都已成功创建,并且至少有一个正在运行。
-
Succeeded。此状态表示 Pod 中的所有容器都已运行完成并退出。这种情况最常出现在运行一次性任务时。
-
Failed。在此状态下,Pod 中至少有一个容器以异常状态退出(非零返回码)。出现此状态意味着你需要弄清楚如何调试容器中的应用程序,例如检查 Pod 的 Events 和日志。
-
Unknown。这是一种异常状态,表示 kubelet 无法持续向 kube-apiserver 报告 Pod 的状态,这很可能是 master 和 kubelet 之间的通信问题。
此外,Pod 对象的 Status 字段可以进一步分解为一组 Conditions。这些详细的状态值包括:PodScheduled、Ready、Initialized 和 Unschedulable。它们主要用于描述当前 Status 的具体原因。
例如,如果 Pod 的当前状态是 Pending,并且对应的 Condition 是 Unschedulable,则说明其调度存在问题。
其中,Ready 子状态特别值得我们关注:它意味着 Pod 不仅正常启动(处于 Running 状态),而且已经准备好对外提供服务。这两者(Running 和 Ready)是有区别的,所以你可能需要仔细思考一下。
Pod 的这些状态信息是我们判断应用程序运行状况的重要依据,特别是当 Pod 进入非“Running”状态时,你必须能够快速反应,并基于所呈现的异常情况开始跟踪和定位,而不是慌慌张张地查阅文档。
总结
在今天的文章中,我详细解释了 Pod API 对象,介绍了 Pod 的核心使用方法,并从字段的角度分析了 Pod 和 Container 的异同。希望这些解释能帮助你更好地理解和记住 Pod YAML 中的核心字段及其精确含义。
实际上,Pod API 对象是整个 Kubernetes 系统中最核心的概念,也是我之后将要讲解的控制器中会使用的对象。
