Pod 配置详解
最小的 Pod 配置文件
apiVersion: v1 # 核心 API 组的版本。
kind: Pod # 告诉 K8s 这是一个 Pod,而不是 Deployment 或 Service。
metadata:
name: min-nginx # Pod 的名字(在同一个 Namespace 下,这个名字必须唯一)。
spec:
containers: # 这是一个列表(注意前面的短横线 -),意味着一个 Pod 里可以跑多个容器。
- name: nginx-c # 容器的名字
image: nginx:alpine # 使用的镜像,如果不写版本号,默认会去拉取 latest,在生产环境中强烈不建议这样做。- 其实也可以通过这个命令生成:
kubectl run my-pod --image=nginx:alpine --dry-run=client -o yaml > min-nginx.yaml
Pod 中运行多个容器(Sidecar 模式)
在一个 Pod 中运行多个容器(Sidecar 模式)是 K8s 的核心特性之一。这种模式下,所有容器共享同一个网络命名空间(IP 和端口)以及存储卷。
这里重点展示如何定义多个容器以及它们如何通过 volumes 共享数据:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
# 定义共享存储卷,这是多容器协作的基础
volumes:
- name: shared-data
emptyDir: {}
containers:
# 容器 1:主应用容器 (Main Container)
- name: app-container
image: nginx:alpine
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html # 挂载到 web 目录
# 容器 2:辅助容器 (Sidecar Container)
# 职责:例如每隔 5 秒生成一次数据,供主容器使用
- name: sidecar-container
image: alpine
volumeMounts:
- name: shared-data
mountPath: /data # 挂载到同一个卷
command: ["/bin/sh"]
args: ["-c", "while true; do date > /data/index.html; sleep 5; done"]这两个容器通过 共享卷(Shared Volume) 实现了一个典型的“生产者-消费者”模型:
sidecar-container(生产者):它运行一个简单的 Shell 循环,每隔 5 秒将当前的系统时间写入/data/index.html。app-container(消费者):它是一个标准的 Nginx。因为它把/usr/share/nginx/html挂载到了同一个卷上,所以它会直接读取并展示 Sidecar 生成的那个文件。
验证:
# 创建 Pod
kubectl apply -f multi-container-pod.yaml
# 查看 Pod 状态,应该看到 READY 列显示 2/2,这表示 Pod 中的两个容器都已正常启动并运行。
kubectl get pod multi-container-pod
# 验证实时更新,隔几秒执行一次,将发现输出的时间在变
kubectl exec multi-container-pod -c app-container -- curl localhost:80Sidecar 这种多容器模式通常用于:
- 日志采集:主容器写日志到共享卷,Sidecar 容器(如 Filebeat 或 Categraf)从卷里读日志并推送到远端。
- 配置更新:Sidecar 监听 Git 或配置中心的变化,更新共享卷里的配置文件,然后通知主容器重载。
- Proxy/Adapter:Sidecar 处理网络流量(如 Envoy),为主容器提供安全传输或协议转换。
镜像拉取策略
imagePullPolicy(镜像拉取策略)决定了节点上的 kubelet 何时尝试从镜像仓库下载镜像。
这是一个包含三种策略对比的 Pod 示例:
apiVersion: v1
kind: Pod
metadata:
name: image-policy-demo
spec:
containers:
# 1. Always:无论本地是否有镜像,每次启动都必须拉取
- name: always-policy
image: nginx:latest
imagePullPolicy: Always
# 2. IfNotPresent:如果本地没有才拉取(SRE 生产环境最推荐,节省带宽)
- name: if-not-present-policy
image: nginx:1.21
imagePullPolicy: IfNotPresent
# 3. Never:只使用本地镜像,绝不尝试从远程仓库拉取
- name: never-policy
image: my-local-image:v1
imagePullPolicy: Never| 策略 | 行为说明 | 适用场景 |
|---|---|---|
Always |
每次启动 Pod 都会去仓库检查一次镜像的 Hash 值是否变动。 | 标签为 latest 或 master 的镜像,或者测试环境频繁更新镜像时。 |
IfNotPresent |
只要节点本地已经有这个镜像了,就直接启动。 | 生产环境推荐。能有效降低镜像仓库并发压力,缩短 Pod 启动速度。 |
Never |
完全不联网。如果本地没有,Pod 会报 ErrImageNeverPull。 |
离线机房、预拉取好镜像的边缘计算环境,或调试本地构建的镜像。 |
注意事项”
- 关于
latest标签的默认行为:如果在 YAML 中没有写imagePullPolicy,但镜像标签是:latest,K8s 会默认将其设为Always。 - 版本固定(Best Practice):在生产环境中,强烈建议使用具体的版本号(如
v1.2.3)并配合IfNotPresent。这样既能保证一致性,又能获得最快的启动速度。 - 安全性:如果担心镜像被篡改,
Always可以确保即使本地有同名镜像,也会对比仓库里的最新校验和(Checksum)。
容器启动命令与参数
在 Kubernetes 中,command 和 args 对应 Docker 中的 ENTRYPOINT 和 CMD。作为 SRE,理解这两者的覆盖关系非常重要,因为很多时候我们需要在不重新构建镜像的情况下,临时修改启动参数(比如开启调试模式或修改配置文件路径)。
这是一个典型的在容器启动时执行 Shell 脚本并传入参数的例子:
apiVersion: v1
kind: Pod
metadata:
name: command-demo
spec:
containers:
- name: alpine-container
image: alpine:3.18
# 1. command:相当于 Docker 的 ENTRYPOINT
# 如果不写,则运行镜像默认的程序
command: ["/bin/sh", "-c"]
# 2. args:相当于 Docker 的 CMD
# 会作为参数传递给上面的 command
args:
- |
echo "Starting application..."
sleep 10
echo "Done."K8s 与 Docker 字段映射关系:
| K8s 字段 | Docker 字段 | 描述 |
|---|---|---|
command |
ENTRYPOINT |
容器启动时执行的可执行文件 |
args |
CMD |
传递给可执行文件的默认参数 |
覆盖逻辑规则:
- 如果只定义了
command,镜像默认的ENTRYPOINT和CMD都会被忽略。 - 如果只定义了
args,镜像默认的CMD会被覆盖,但会带上镜像默认的ENTRYPOINT运行。 - 如果两个都定义了,镜像默认的设置会全部失效,只运行定义的这两个。
实用技巧:
# 技巧 A:解决容器启动即退出的问题
# 有时候我们需要进入一个没有守护进程的容器进行调试,可以用这个“死循环”命令防止容器退出:
command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]
# 技巧 B:使用环境变量作为参数
# 可以通过 $(VAR_NAME) 语法在参数中引用容器内定义的环境变量:
env:
- name: APP_PORT
value: "8080"
containers:
- name: app
image: my-app:v1
command: ["/app/server"]
args: ["--port", "$(APP_PORT)"] # 动态注入变量容器环境变量
以下是一个涵盖了所有容器环境变量主流注入方式的综合示例:
apiVersion: v1
kind: Pod
metadata:
name: env-demo-pod
spec:
containers:
- name: app-container
image: nginx:alpine
env:
# 1. 直接定义键值对 (Static Value)
- name: APP_REGION
value: "china-east"
# 2. 从 ConfigMap 中读取某个 Key (ConfigMap Ref)
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config # ConfigMap 的名字
key: log_level # ConfigMap 里的 Key
# 3. 从 Secret 中读取加密信息 (Secret Ref)
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret # Secret 的名字
key: password # Secret 里的 Key
# 4. 读取 Pod 自身的元数据 (Downward API)
# 这在分布式系统中非常有用,比如应用需要知道自己的 Pod IP
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
# 5. 批量注入 (envFrom)
# 如果 ConfigMap 里有 50 个配置,不用一个个写,直接全部注入
envFrom:
- configMapRef:
name: global-env-config环境变量注入方式对比:
| 注入方式 | 适用场景 | SRE 建议 |
|---|---|---|
直接定义 (value) |
环境不敏感的常量(如时区) | 简单直观,但不灵活。 |
| ConfigMap | 业务配置、日志级别、连接字符串 | 推荐。实现“一次构建,到处运行”。 |
| Secret | 数据库密码、Token、证书内容 | 强制。严禁将明文密码直接写在 YAML 里。 |
| Downward API | 获取 Pod IP、Node 名、资源限制值 | 监控插件或服务发现必备。 |
| envFrom (批量) | 注入大量业务环境变量 | 保持 YAML 整洁,方便统一管理。 |
容器资源配额
在 SRE 的日常工作中,资源配额 是最重要的配置项之一。它直接决定了集群是否稳定,以及在资源紧张时谁会被“牺牲”。
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: heavy-app
image: nginx:alpine
resources:
# 1. Requests (需求量):调度依据
# 只有当 Node 的剩余资源大于这个值时,Pod 才能被调度上去
requests:
cpu: "250m" # 250m = 0.25 核
memory: "512Mi" # Mi 代表兆字节 (1024进制)
# 2. Limits (上限值):运行限制
# 限制容器在运行时能使用的资源最大值
limits:
cpu: "1000m" # 1000m = 1 核
memory: "1Gi" # Gi 代表吉字节 (1024进制)注意事项:
- CPU 是可压缩资源。当容器达到
limits时,K8s 会通过 CPU 周期节流(Throttling)来限制它,应用会变慢,但不会挂掉。 - 内存是不可压缩资源。一旦容器使用的内存超过
limits,系统会立即触发 OOM Kill 杀掉该容器。
Qos (服务质量) 与资源配额
在 K8s 中,QoS (Quality of Service,服务质量) 是系统用来管理容器资源分配和在资源紧张时进行“择优录取”或“驱逐”的一套机制。
简单来说,当集群资源不足(比如内存爆满)时,K8s 需要决定先“杀掉”哪个 Pod 来保全大局。QoS 等级就是它的参考标准。
K8s 根据 Pod 中容器对 CPU 和内存的 Requests (请求值) 和 Limits (限制值) 的配置情况,自动将 Pod 划分为以下三个等级,这三种等级在由于资源不足而驱逐 Pod 时,有不同的优先级(优先级从高到低):
-
Guaranteed (完全保障型):这是最高优先级的 Pod。系统会确保它获得所请求的资源,且除非系统本身崩溃,否则不会被轻易驱逐。
- 配置条件:Pod 中所有容器的所有资源(CPU 和内存)都必须设置了 requests 和 limits。且每个资源的 requests 必须等于 limits。
- 特点:极其稳定,就像“一等座”,只要火车还在开,你的位子就是你的。
-
Burstable (弹性伸缩型):这是最常见的类型。Pod 至少有一个最低资源保障,但在系统空闲时可以“超载”使用更多资源。
- 配置条件:
requests小于limits。 - 特点:像“二等座/无座”,有位子时能坐,没位子时可能得站着,甚至在资源极其匮乏时被请下车。
- 配置条件:
-
BestEffort (尽力型):完全不写
resources。- 配置条件:Pod 中任何一个容器都没有设置 requests 或 limits。
- 特点:像“挂票”,系统有富余资源就给你用,一旦资源稍微紧张,这类 Pod 首当其冲会被第一个杀掉。
总结对比表:
| QoS 级别 | 配置策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Guaranteed | Requests == Limits | 生产核心业务、数据库 | 极度稳定,不会被随机驱逐 | 资源利用率相对较低(存在浪费) |
| Burstable | Requests < Limits | 非核心业务、Web 应用 | 资源利用率高,支持波峰冲刺 | 可能因为宿主机压力大导致性能抖动 |
| BestEffort | 不写资源限制 | 禁止用于生产 | 编写简单 | 极度危险,可能导致整台服务器宕机 |
- 可以在 Namespace 上配置
LimitRange,强制要求所有 Pod 必须写resources,否则拒绝创建。
查看 Qos 等级
# 查看单个 Pod 的 Qos 等级
kubectl describe pod <pod-name>| grep QoS
# 查看所有 Namespace 中的 Pod Qos 等级
kubectl get pods -A -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,QOS:.status.qosClass"容器生命周期
从上到下表示时间轴从开始到结束:
- Init Containers
- 初始化容器,如果其中存在多个容器,则容器默认是串行启动的;初始化完成后会进入到 Terminated 状态之后会运行 containers
- 假设初始化容器中有两个容器,则先运行容器一,容器一运行完毕退出后,再运行容器二,最后运行正式容器)
- 初始化容器,如果其中存在多个容器,则容器默认是串行启动的;初始化完成后会进入到 Terminated 状态之后会运行 containers
以下属于 main container
- Containers
- 如果其中存在多个容器,则容器默认是并行启动的,即无法控制启动的先后顺序
- Post Start Hook
- 启动后钩子,一般在容器启动后做一些简单的初始化工作(只有启动后钩子执行成功 容器才会变成 running 状态
- Startup Probe
- 启动后执行一次
- Readiness Probe
- 周期性执行
- liveness Probe
- 周期性执行
- Pre Stop Hook
- 停止前钩子,一般在容器停止前做一些清理操作(只有停止前钩子执行成功 容器才会正常停止)
初始化容器
健康检查探针
三种探针的综合配置示例:
apiVersion: v1
kind: Pod
metadata:
name: probe-demo
spec:
containers:
- name: business-app
image: nginx:1.21
# -------------------------------------------------------
# 1. 启动探针 (Startup Probe)
# 作用:保护启动慢的应用。在它成功之前,其他探针都会被禁用。
# -------------------------------------------------------
startupProbe:
httpGet:
path: /api/startup
port: 8080
failureThreshold: 30 # 允许失败30次
periodSeconds: 10 # 每10秒检查一次,总共给应用300秒启动时间
# -------------------------------------------------------
# 2. 就绪探针 (Readiness Probe)
# 作用:决定流量是否接入。失败时,Pod 会从 Service 的 Endpoint 中移除。
# -------------------------------------------------------
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5 # 启动探针成功后,等待5秒开始检查
periodSeconds: 10 # 检查频率
successThreshold: 1 # 成功1次就算就绪
failureThreshold: 3 # 连续失败3次移除流量
# -------------------------------------------------------
# 3. 存活探针 (Liveness Probe)
# 作用:决定容器是否重启。失败时,K8s 会直接杀掉容器并按重启策略重启。
# -------------------------------------------------------
livenessProbe:
tcpSocket: # 也可以检查端口是否通
port: 8080
initialDelaySeconds: 15
periodSeconds: 20检查方式
HTTP GET,向容器发 HTTP 请求(状态码 200-399 算成功):
httpGet:
path: /healthz
port: 8080TCP Socket,只要端口能通就观测成功:
tcpSocket:
port: 8080Exec 命令,在容器内执行命令,退出码为 0 算成功:
exec:
command: ["cat", "/tmp/healthy"]检查顺序与成功失败动作
第一关:Startup Probe(一次性探测):
- 优先级最高,成功后才会执行其他探针(Readiness 和 Liveness),失败了会重启容器;
- 这保证了启动慢的应用(比如 Java)不会因为还没初始化完就被 liveness 给误杀重启。
第二关:Liveness & Readiness(并行执行 + 重复探测):
- Readiness Probe:成功后才会将 Pod 加入 Service Endpoint,失败了会将 Pod 从 Service Endpoint 中移除(停止发送流量)。确保用户不会访问到正在初始化或负载过高崩溃的页面。
- Liveness Probe:成功后才会认为容器“存活”,失败了会根据重启策略(如
restartPolicy)重启容器。用于解决应用死锁或无法自愈的僵死状态。
时间线模拟:
- T = 0s:容器启动。
- T = 5s:
startupProbe开始检查。 - T = 30s:
startupProbe终于成功(比如数据库连接初始化完成了)。 - T = 31s:
startupProbe功成身退。此时liveness和readiness同时启动。 - T = 35s:
readiness成功。流量开始进入 Pod。 - T = 60s:应用突然 OOM 导致死锁。
readiness失败:流量停止进入,用户看到 502(由其他副本处理)。liveness失败:K8s 发现死锁,直接杀掉进程,重启容器。
- T = 重启后:重新从第一关
startupProbe开始。
注意事项与最佳实践
- Readiness vs Liveness 区分:如果数据库连不上了,应用应该 Readiness 失败(断开流量,等会儿可能好),而不是 Liveness 失败(疯狂重启应用并不能修好数据库,反而可能引发雪崩)。
- 避免检查外部依赖:探针脚本里不要去
curl外部 API 或数据库。如果外部挂了,你集群里成百上千个 Pod 同时重启/掉线,后果不堪设想。探针只应检查容器“自身”的状态。 - 超时时间设置 (
timeoutSeconds):默认是 1 秒。如果你的接口响应较慢,一定要调大这个值,否则会因为超时导致误杀。 - 接口解耦:建议应用专门提供
/healthz、/ready等轻量级接口,不要直接检查业务逻辑复杂的接口。 - 探针的检查频率:
- 设置长周期的
startupProbe:针对启动慢的应用,给够失败次数(比如 30 次 * 10 秒),防止应用还没起来就被杀掉。 - 设置敏感的
readinessProbe:频率高一点(比如 5 秒一次),一旦应用卡顿立即切断流量。 - 设置保守的
livenessProbe:频率低一点(比如 20 秒一次),只要应用还能动,就不要轻易重启它。
- 设置长周期的
⚠️ 注意:如果没配置
startupProbe,那么liveness会在容器启动后的initialDelaySeconds秒后直接开始检查。如果应用启动时间超过了延迟时间,应用会陷入“启动 -> 被杀 -> 启动”的死循环。
启动后与停止前钩子
启动后与停止前钩子是实现“初始化配置”和“优雅停机”的终极武器。
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: app-container
image: nginx:1.21
lifecycle:
# 1. PostStart: 容器创建后立即执行
postStart:
exec:
command: ["/bin/sh", "-c", "echo 'Container started' > /var/log/start.log"]
# 2. PreStop: 容器终止前执行
preStop:
exec:
# 场景:通知 Nginx 优雅退出,并等待 10 秒让存量请求处理完
command: ["/bin/sh", "-c", "/usr/sbin/nginx -s quit; sleep 10"]PostStart 实现原理:
- 执行时机:容器启动后立即执行,并和容器主进程 (ENTRYPOINT/CMD) 同时触发。
- 如果执行成功,执行启动探针(如有)。
- 如果执行失败:容器会被杀掉并根据 restartPolicy 重启。
- 注意事项:
- 不要在里面跑太重的任务。如果它一直跑不完,容器状态会卡在 ContainerCreating。
- postStart 钩子和容器主进程 (ENTRYPOINT/CMD) 是并行执行的,因此无法确定哪个先执行。 PreStop 实现原理:
- 执行时机:当 Pod 收到删除指令(如 kubectl delete 或缩容)时,在向容器发送 SIGTERM 信号之前执行。
- 只有当 preStop 脚本跑完了,K8s 才会发送 SIGTERM 信号给主进程。
- 注意事项:整个 preStop 的执行时间受 terminationGracePeriodSeconds(默认 30s)限制。如果钩子跑了 40s,K8s 还是会直接杀掉容器。