Kubernetes:Pod 的生命周期与管理实战
大家好!在云原生的世界里,Kubernetes (K8s) 无疑是容器编排领域的王者。作为 K8s 中最核心、最基础的资源对象,Pod 的概念理解和管理能力是每一位 K8s 使用者的必备技能。今天,我们就来一起深入探讨 Pod 的奥秘,并通过实战演练掌握其创建、查看、交互和删除的全过程。
准备工作:规整你的资源清单
在 K8s 中,我们通常使用 YAML 文件来定义(声明)我们期望的资源状态,这些文件被称为“资源清单”(Manifests)。为了更好地管理这些文件,养成良好的习惯非常重要。
# 推荐创建一个专门存放 K8s 资源清单的目录结构
[root@master ~]# mkdir -pv /data/k8s-manifests/pods
[root@master ~]# cd /data/k8s-manifests/pods
- 说明:这里使用了
/data/k8s-manifests/作为示例,你可以根据自己的服务器规划选择合适的路径。清晰的目录结构有助于项目的维护和协作。
揭秘资源清单:YAML 文件的四大核心要素
一个标准的 K8s 资源清单(以 Pod 为例)通常包含以下几个关键字段:
apiVersion: 定义了创建该对象所使用的 Kubernetes API 的版本。不同的资源类型(Kind)可能属于不同的 API Group 和 Version。例如,核心资源如 Pod 通常使用v1。kind: 指定了要创建的资源类型。比如Pod,Deployment,Service,Namespace等。metadata: 包含了资源的元数据,用于唯一标识和组织资源。常见的子字段有:name: 资源的名称,在同一个 Namespace 下必须唯一。namespace(可选): 资源所属的命名空间。如果省略,则默认为default命名空间。labels(可选): 键值对标签,用于筛选和组织资源。annotations(可选): 键值对注解,用于存储非识别性的元数据,通常给工具或系统使用。
spec: 这部分是资源的核心,定义了你期望该资源达到的状态。对于 Pod 来说,spec中最重要的就是定义其包含的容器(containers)列表,以及其他运行策略(如调度约束、存储卷、网络设置等)。
还有一个重要的部分,虽然不由我们编写,但需要了解:
status: 描述了资源的实际运行状态。这部分由 Kubernetes 系统(主要是 Kubelet 和 Controller Manager)动态维护和更新,我们通常只读不写。
什么是 Pod?K8s 的原子调度单元
你可能听说过 Docker 容器,但在 Kubernetes 的世界里,Pod 是最小的可部署和可调度的计算单元。
可以将 Pod 想象成一个“豌豆荚”(Pod 的中文直译),而容器则是里面的“豌豆”。一个 Pod 可以包含一个或多个紧密关联的容器。
关键特性:
- 共享网络和存储:同一个 Pod 内的所有容器共享相同的网络命名空间(即同一个 IP 地址和端口空间)和存储卷(Volumes)。它们可以通过
localhost相互通信。 - 原子性:Pod 内的容器总是被一起调度到同一个节点(Node)上,并且“同生共死”。它们作为一个整体单元进行管理。
虽然可以直接创建 Pod,但在生产环境中,我们通常不直接管理 Pod,而是通过更高级别的控制器(如 Deployment, StatefulSet, DaemonSet)来管理 Pod 的生命周期,以实现应用的弹性伸缩、滚动更新和自愈能力。不过,理解 Pod 是掌握这些高级概念的基础。
实战一:创建并管理一个单容器 Pod
让我们来创建一个简单的 Pod,它只运行一个 Web 应用容器。
1. 编写 Pod 资源清单 (01-my-app-pod.yaml)
# 指定 API 版本
apiVersion: v1
# 指定资源类型为 Pod
kind: Pod
# 元数据信息
metadata:
# Pod 的名称
name: my-app-v1
# (可选) 添加标签,方便后续管理
labels:
app: my-app
version: v1
# 定义期望状态 (Specification)
spec:
# (注意) 显式指定 nodeName 通常仅用于测试或特殊场景
# 在生产中,应让 K8s 调度器自动选择合适的节点
# nodeName: worker232
# 定义 Pod 内的容器列表
containers:
# 第一个(也是唯一一个)容器
- name: my-app-container # 容器名称,在 Pod 内唯一
# 指定容器镜像
image: registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1 # 替换为你实际使用的镜像
# (可选) 定义容器端口,仅作信息声明,不影响网络
ports:
- containerPort: 80
- 注意:
- 上面 YAML 中的
nodeName: worker232字段会强制将 Pod 调度到名为worker232的节点上。在生产环境中,强烈建议不要使用nodeName,让 Kubernetes 调度器根据资源需求、亲和性/反亲和性规则等自动选择最佳节点。这里保留它是为了演示,但在实际应用中请注释掉或删除它。 metadata.labels是非常重要的,后续会用到它来选择和管理 Pod。spec.containers[].ports字段主要是信息性的,方便用户和其他工具了解容器暴露的端口。它并不直接影响容器的网络访问,实际的网络策略由 Service 或 Ingress 等资源控制。
- 上面 YAML 中的
2. 创建 Pod 资源
我们推荐使用 kubectl apply 命令来创建或更新资源。
[root@master pods]# kubectl apply -f 01-my-app-pod.yaml
pod/my-app-v1 created
# 再次执行 apply,如果资源未变化,则提示 unchanged,不会报错
[root@master pods]# kubectl apply -f 01-my-app-pod.yaml
pod/my-app-v1 unchanged
kubectl apply vs kubectl create
- 相同点: 如果资源不存在,两者都能创建资源。
- 不同点:
create: 如果资源已存在,执行会报错AlreadyExists。它更像是一次性的创建动作。apply: 如果资源已存在,它会尝试将现有资源的状态更新到 YAML 文件中定义的状态。如果无变化,则提示unchanged。apply是声明式的,支持幂等性操作,意味着你可以安全地重复执行同一个apply命令。
- 生产实践: 强烈推荐始终使用
kubectl apply来管理 K8s 资源。这使得 CI/CD 流程更简单、更健壮,无论是首次部署还是后续更新,都使用同一个命令。
3. 查看 Pod 状态
# 查看当前 namespace 下的所有 Pod 列表
[root@master pods]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-app-v1 1/1 Running 0 1m
# 查看更详细的信息,包括 Pod IP 和所在 Node
[root@master pods]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-v1 1/1 Running 0 90s 10.100.1.5 worker232 <none> <none>
字段解读:
NAME: Pod 的名称。READY:就绪的容器数量 / Pod 内总容器数量。1/1表示 Pod 内有 1 个容器,且这 1 个容器已经准备好对外提供服务(通过了 Readiness Probe,如果定义了的话)。STATUS: Pod 的当前生命周期阶段。常见状态有:Pending: Pod 已被 K8s 接受,但一个或多个容器尚未创建成功。通常在拉取镜像或等待调度。Running: Pod 已绑定到节点,所有容器都已创建。至少有一个容器仍在运行,或者正在启动/重启。Succeeded: Pod 中的所有容器都已成功终止,并且不会再重启。通常用于 Job 类型的 Pod。Failed: Pod 中的所有容器都已终止,但至少有一个容器是以非零状态(即错误)退出的。Unknown: 通常是由于无法与 Pod 所在的节点通信导致无法获取 Pod 状态。
RESTARTS: Pod 内所有容器的总重启次数。注意,这里的重启是指容器进程异常退出后,Kubelet 尝试重新启动它。这与docker restart不同。高重启次数通常意味着应用存在问题。AGE: Pod 从创建到现在所经过的时间。IP: Pod 的 IP 地址。这个 IP 地址是在集群内部可达的。NODE: Pod 当前运行在哪个工作节点上。
获取更深入的 Pod 信息
# 查看 Pod 的详细信息,包括事件、配置、状态等,非常适合排错
[root@master pods]# kubectl describe pod my-app-v1
# 查看 Pod 内特定容器的日志
[root@master pods]# kubectl logs my-app-v1
# 如果 Pod 内有多个容器,需要指定容器名:
# kubectl logs my-app-v1 -c my-app-container
4. 删除 Pod 资源
# 通过文件名删除
[root@master pods]# kubectl delete -f 01-my-app-pod.yaml
pod "my-app-v1" deleted
# 或者通过 Pod 名称直接删除
# kubectl delete pod my-app-v1
# 确认 Pod 已被删除
[root@master pods]# kubectl get pods
No resources found in default namespace.
实战二:部署一个多容器 Pod (Sidecar 模式)
一个 Pod 内运行多个容器是非常常见的模式,最典型的应用场景就是 Sidecar(边车)模式。例如,一个主应用容器,旁边跟一个负责日志收集、监控数据暴露、网络代理等的辅助容器。这些容器紧密协作,共享生命周期和网络环境。
1. 环境准备 (可选,仅作示例)
假设我们需要一个主应用 xiuxian:v2 和一个辅助的 alpine 容器。确保这些镜像在你的集群节点可以访问的镜像仓库中。如果使用私有仓库(如 Harbor),节点需要配置访问权限。
- (原始笔记中的镜像准备步骤是特定环境的操作,这里假设镜像已存在于
harbor.oldboyedu.com或其他你的集群可访问的仓库中)
2. 编写多容器 Pod 资源清单 (02-my-app-with-sidecar.yaml)
apiVersion: v1
kind: Pod
metadata:
name: my-app-v2
labels:
app: my-app
version: v2
spec:
# 同样,生产环境不建议指定 nodeName
# nodeName: worker232
containers:
# 第一个容器:主应用
- name: my-app-main # 主容器名称
image: harbor.oldboyedu.com/oldboyedu-web/xiuxian:v2 # 主应用镜像
ports:
- containerPort: 80
# 第二个容器:辅助容器 (Sidecar)
- name: my-sidecar # Sidecar 容器名称
image: harbor.oldboyedu.com/oldboyedu-linux/alpine:3.20.2 # 使用 Alpine 作为示例
# --- 让 Sidecar 容器保持运行的技巧 ---
# 方式一:使用标准输入保持阻塞 (如原始笔记)
# stdin: true
# tty: true # 通常 stdin 和 tty 一起使用
# 方式二:执行一个永远不会退出的命令 (更常用)
command: ["sh", "-c", "sleep infinity"]
- 关键点:
spec.containers是一个列表,可以定义多个容器。 - Sidecar 存活技巧:很多基础镜像(如 Alpine, BusyBox)默认启动后如果没有前台任务就会立即退出。为了让 Sidecar 容器持续运行,我们需要一些技巧:
- 方法一 (
stdin: true,tty: true): 分配一个伪终端并保持标准输入打开,这会让容器的入口进程(通常是 shell)保持在前台运行状态。 - 方法二 (
command: ["sleep", "infinity"]或类似命令):这是更推荐、更通用的方法。覆盖容器的默认启动命令,让它执行一个永远不会结束的命令(如sleep infinity),从而保持容器运行。
- 方法一 (
3. 创建并查看多容器 Pod
[root@master pods]# kubectl apply -f 02-my-app-with-sidecar.yaml
pod/my-app-v2 created
[root@master pods]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-v2 2/2 Running 0 30s 10.100.1.8 worker232 <none> <none>
注意 READY 字段变成了 2/2,表示 Pod 内的两个容器都已成功启动并处于就绪状态。
实战三:与 Pod 内的容器交互 (kubectl exec)
有时我们需要进入 Pod 内的某个容器执行命令,进行调试或检查。kubectl exec 就是为此而生的。
1. 在单容器 Pod 中执行命令
# 在 my-app-v1 Pod 中执行 ifconfig 命令
[root@master pods]# kubectl exec my-app-v1 -- ifconfig
# '--' 用于分隔 kubectl 命令和要在容器内执行的命令
eth0 Link encap:Ethernet HWaddr 0E:DD:73:C9:60:A7
inet addr:10.100.1.9 Bcast:10.100.1.255 Mask:255.255.255.0
...
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
...
2. 在多容器 Pod 中执行命令
当 Pod 内有多个容器时,你需要指定要操作哪个容器。
# 不指定容器名,默认在 Pod 的第一个容器 (my-app-main) 中执行
[root@master pods]# kubectl exec my-app-v2 -- ifconfig
Defaulted container "my-app-main" out of: my-app-main, my-sidecar # K8s 提示了它选择的默认容器
eth0 Link encap:Ethernet HWaddr 2A:A5:6C:D7:B4:E2
inet addr:10.100.1.8 Bcast:10.100.1.255 Mask:255.255.255.0
...
# 使用 -c 参数显式指定在 my-app-main 容器中执行
[root@master pods]# kubectl exec my-app-v2 -c my-app-main -- ifconfig
eth0 Link encap:Ethernet HWaddr 2A:A5:6C:D7:B4:E2
inet addr:10.100.1.8 Bcast:10.100.1.255 Mask:255.255.255.0
...
# 使用 -c 参数显式指定在 my-sidecar 容器中执行
[root@master pods]# kubectl exec my-app-v2 -c my-sidecar -- ifconfig
eth0 Link encap:Ethernet HWaddr 2A:A5:6C:D7:B4:E2
inet addr:10.100.1.8 Bcast:10.100.1.255 Mask:255.255.255.0
...
重要发现: 观察上面 my-app-main 和 my-sidecar 容器中 ifconfig 的输出,你会发现它们的IP 地址是完全相同的 (10.100.1.8)!这验证了我们之前提到的:同一个 Pod 内的所有容器共享同一个网络命名空间。它们可以使用 localhost 互相访问对方监听的端口。
3. 获取交互式 Shell (进入容器)
如果你想在容器内部执行多条命令,可以使用 -it 参数获取一个交互式的 TTY。
# 进入 my-app-v2 Pod 的 my-app-main 容器,启动 sh shell
[root@master pods]# kubectl exec -it my-app-v2 -c my-app-main -- sh
/ # hostname # 在容器内执行命令
my-app-v2
/ # ps -ef # 查看容器内的进程
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
...
/ # exit # 退出容器 shell
[root@master pods]#
# 进入 my-app-v2 Pod 的 my-sidecar 容器
[root@master pods]# kubectl exec -it my-app-v2 -c my-sidecar -- sh
/ # hostname
my-app-v2
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 sh -c sleep infinity # 看到我们定义的 sleep 命令
6 root 0:00 sh
12 root 0:00 ps -ef
/ # exit
[root@master pods]#
-i(stdin): 保持标准输入打开。-t(tty): 分配一个伪终端。-- sh(或/bin/bash): 指定要在容器内启动的 shell 程序。
生产环境核心建议 (非常重要!)
- 避免直接管理 Pod: 在生产环境中,几乎从不直接创建独立的 Pod。因为它们缺乏自愈、扩展和更新能力。一旦 Pod 所在的节点故障或 Pod 自身崩溃,它不会自动恢复。
- 拥抱控制器: 使用 Deployment (用于无状态应用)、StatefulSet (用于有状态应用)、DaemonSet (用于确保每个节点运行一个 Pod 副本) 等控制器来管理你的应用。这些控制器会维护你期望的 Pod 副本数量,处理滚动更新、回滚,并确保 Pod 在节点故障时能在其他节点重新调度。
- 定义资源请求和限制 (Resource Requests & Limits): 在 Pod 的
spec.containers[].resources字段中,为每个容器设置requests(K8s 调度器需要保证的最小资源) 和limits(容器能使用的最大资源)。这对于保证集群稳定性和资源公平使用至关重要。resources: requests: memory: "64Mi" # 请求 64 Mebibytes 内存 cpu: "250m" # 请求 0.25 CPU 核 (millicores) limits: memory: "128Mi" # 限制最多使用 128 Mebibytes 内存 cpu: "500m" # 限制最多使用 0.5 CPU 核 - 配置存活探针和就绪探针 (Liveness & Readiness Probes):
- Liveness Probe: Kubelet 使用它来检测容器是否还在运行。如果探测失败,Kubelet 会杀死该容器并根据 Pod 的重启策略尝试重启它。
- Readiness Probe: Kubelet 使用它来检测容器是否准备好处理请求。如果探测失败,该 Pod 的 IP 地址会从关联的 Service 的 Endpoints 列表中移除,使其暂时不接收流量,直到探测成功。
livenessProbe: httpGet: path: /healthz # 应用提供的健康检查接口 port: 80 initialDelaySeconds: 5 # Pod 启动后 5 秒开始探测 periodSeconds: 10 # 每 10 秒探测一次 readinessProbe: httpGet: path: /ready # 应用提供的就绪检查接口 port: 80 initialDelaySeconds: 5 periodSeconds: 10 - 利用标签 (Labels) 和选择器 (Selectors): 善用
metadata.labels来标记你的 Pod,然后通过 Service 或其他控制器使用 Label Selectors 来关联和管理这些 Pod。
总结
我们今天从 Pod 的基本概念出发,学习了如何编写 Pod 的 YAML 资源清单,并通过 kubectl apply、kubectl get、kubectl describe、kubectl logs、kubectl exec 和 kubectl delete 等命令,实战演练了单容器和多容器 Pod 的创建、查看、交互和删除。我们还特别强调了在生产环境中使用控制器、资源管理和健康检查的重要性。
掌握 Pod 是你深入 Kubernetes 世界的第一步。希望这篇结合了实战和生产建议的文章能帮助你更好地理解和运用 Pod,为后续学习更高级的 K8s 概念打下坚实的基础。
如果你觉得这篇文章对你有帮助,欢迎点赞、分享,并在评论区留下你的想法和问题!下次我们将继续探索 Kubernetes 的其他核心组件,敬请期待!
浙公网安备 33010602011771号