先说核心:Pod是什么
Pod是Kubernetes中最小的可部署单元,你可以把它理解成"一个或多个容器的组合",它们共享网络、存储和运行环境。实际生产中最常见的场景是单容器Pod------一个Pod里只跑一个容器,占了Kubernetes Pod使用场景的主流。
Pod的API版本是v1,Kind是Pod。定义Pod的YAML有四个顶级字段:apiVersion、kind、metadata、spec。其中spec是Pod的核心规格说明,containers字段是必需的,Pod中至少得有一个容器。
⚠️ 注意:Pod创建后,spec.containers无法更新。想改容器配置?删了重建。
最小的Pod:单容器配置
先上一个最干净的单容器Pod YAML,跑的是nginx(这里用了轻量化的alpine镜像,生产环境请根据安全策略选择具体版本):
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
把它存成pod.yaml,然后用kubectl apply创建:
kubectl apply -f pod.yaml
# 预期输出:pod/nginx created
创建完可以用kubectl get pods看一眼状态:
kubectl get pods
# 预期输出:
# NAME READY STATUS RESTARTS AGE
# nginx 1/1 Running 0 40s
READY 1/1表示Pod里有一个容器,且该容器处于就绪状态。
Init Container:主容器启动前的"守门员"
先讲Init Container,是因为它在生产环境中的出场率极高,而且往往决定主容器能否正常启动。
Init Container在主容器之前 运行,并且必须全部成功退出 后,主容器才会被启动。如果某个Init Container失败了,Pod会一直重启它(取决于restartPolicy),直到成功为止。
典型应用场景:等待数据库/中间件就绪、初始化数据表结构、下载依赖文件、修改挂载目录权限。
看一个实战例子------等待MySQL完全启动后再跑主程序:
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
- name: wait-for-mysql
image: busybox:1.36
command: ['sh', '-c',
'until nslookup mysql-service; do echo waiting for mysql; sleep 2; done;']
containers:
- name: main-app
image: myapp:latest
注意 :Init Container的镜像建议优先用busybox或alpine这种轻量级工具镜像,别把重量级应用塞进去,否则会拖长启动时间。
怎么看Pod和容器的状态
kubectl get pods只能看个大概,真正排查问题要靠kubectl describe pod。
kubectl describe pod nginx
这个命令会输出一大堆信息,我一般重点关注这几个部分:
- Node:Pod被调度到了哪个节点
- IP地址:Pod的内网IP
- Events:最底部的Event列表,所有调度、拉镜像、启动失败的日志都在这
彩蛋 :80%的部署问题靠kubectl describe pod加kubectl logs --previous就能搞定。
想看容器日志用这个:
kubectl logs <pod-name>
# 如果容器重启过,看上一次的日志
kubectl logs <pod-name> --previous
自定义容器启动命令和参数
容器镜像本身有ENTRYPOINT和CMD,Kubernetes允许你用command和args覆盖它们。
对应关系是:
command→ 覆盖镜像的ENTRYPOINTargs→ 覆盖镜像的CMD
来个例子,跑一个debian容器并执行printenv HOSTNAME KUBERNETES_PORT:
apiVersion: v1
kind: Pod
metadata:
name: command-demo
spec:
containers:
- name: command-demo-container
image: debian:12-slim
command: ["printenv"]
args: ["HOSTNAME", "KUBERNETES_PORT"]
restartPolicy: OnFailure
[终审微调] 由于restartPolicy: OnFailure且容器以exit 0正常退出,Pod的生命周期阶段(Phase)会变为Succeeded(在kubectl get pods的STATUS列中通常显示为Completed)。想看输出请立即执行kubectl logs command-demo,否则集群可能会在回收策略触发时清理掉该Pod。
kubectl logs command-demo
# 预期输出类似:
# command-demo
# tcp://10.3.240.1:443
我踩过的坑:如果想把命令放到shell里执行(比如要管道操作),这样写:
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10; done"]
容器环境变量:配置注入的四种方式
环境变量在Pod启动时设定,不会动态更新。如果ConfigMap变了,需要重启Pod才能生效。
YAML里用env字段定义环境变量,支持四种来源:
1. 直接写死(Literal)
env:
- name: APP_ENV
value: "production"
2. 从ConfigMap取
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: database_host
3. 从Secret取(敏感信息)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
4. 从Downward API取(Pod自身信息)
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MEMORY_LIMIT
valueFrom:
resourceFieldRef:
containerName: app
resource: limits.memory
如果想把ConfigMap或Secret里的所有键 都变成环境变量,用envFrom。注意 optional必须缩进在 configMapRef或 secretRef内部:
envFrom:
- configMapRef:
name: app-config
optional: true
- secretRef:
name: app-secrets
optional: true
注意:env和envFrom同时存在时,env的优先级更高,相同key会覆盖envFrom的值。
健康探针:保命配置(必配)
很多初学者只定义了容器镜像就跑上了生产,结果流量一上来就频繁重启。探针是生产环境的强制性标准配置,没有之一。
Kubernetes提供了三种探针:
- livenessProbe(存活探针):检测容器是否还活着,失败则重启容器。
- readinessProbe(就绪探针):检测容器是否准备好接收流量,失败则从Service摘除。
- startupProbe(启动探针):用于保护启动慢的容器,成功后其他探针才会介入(1.28+已是稳定功能)。
下面是一份同时配置了liveness和readiness的YAML示例,直接抄作业就行:
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: app
image: nginx:1.27-alpine
ports:
- containerPort: 80
# 存活探针:检查 /healthz,初始等待30秒(给足启动时间),每10秒检查一次
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 30
periodSeconds: 10
# 就绪探针:检查 /ready,初始等待5秒,每5秒检查一次
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
如果你的应用没有HTTP接口,可以用tcpSocket检查端口是否监听,或用exec执行脚本命令。
Pod的一生:创建与删除过程
生命周期阶段
Pod从创建到结束会经历这几个阶段:
|---------------|-----------------------------------|
| 阶段 | 说明 |
| Pending | 已提交给API,但还没被调度到节点,或者还在拉镜像、跑Init容器 |
| Running | 至少一个容器在运行,探针开始工作 |
| Succeeded | 所有容器正常退出(exit 0),一般用于Job类Pod |
| Failed | 至少一个容器异常退出(非0)或被系统杀掉 |
| Unknown | 跟节点失联了,状态未知 |
Pending最常见的原因:节点资源不足、nodeSelector/亲和性不匹配、PVC没绑定、有污点没容忍。
删除过程(这地方坑最多)
执行kubectl delete pod后,Kubernetes不会立刻干掉容器,而是走一套优雅终止流程:
- Pod标记为Terminating,同时从Service的Endpoints中移除(API Server并发发起这两个动作)。
- 如果定义了
preStop钩子,执行它(必须在宽限期内完成)。 - 向容器PID 1发送SIGTERM信号。
- 开始宽限期倒计时(默认30秒)。
- 宽限期结束还没退出?发SIGKILL强制干掉。
这里有个经典坑 :[终审微调] Endpoints的更新通过API Server发起,但kube-proxy将新规则同步到iptables/IPVS需要一定时间(通常数百毫秒到几秒)。如果容器收到SIGTERM后立即退出,控制面组件(如kube-proxy)可能尚未完成规则刷新,仍有流量被转发到正在关闭的Pod上,导致5xx错误。因此 preStop sleep是为了"等待规则同步",而不是单纯为了拖时间。
解决方案 :加个preStop sleep,给网络规则同步留出缓冲:
lifecycle:
preStop:
exec:
command: ["sleep", "5"]
完整优雅终止示例(结合Nginx):
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
terminationGracePeriodSeconds: 60 # 宽限期改成60秒
containers:
- name: web
image: nginx:1.27-alpine
lifecycle:
preStop:
exec:
command:
- sh
- -c
- |
sleep 5
nginx -s quit
while [ -f /var/run/nginx.pid ]; do sleep 1; done
while循环确保Nginx完全退出后才给kubelet返回,代码非常严谨,你可以直接拿去用。
资源配额与QoS:影响Pod生死的关键
resources字段定义容器的CPU和内存配额:
-
requests:调度器用它来决定把Pod放在哪个节点,保证容器至少能拿到这些资源。
-
limits:容器能使用的上限,防止单个容器把节点资源吃光。
resources:
requests:
cpu: 100m # 0.1核
memory: 200Mi
limits:
cpu: "1" # 1核
memory: 500Mi
重点来了:QoS等级(Quality of Service)
Kubernetes根据requests和limits的配置组合,自动将Pod划分为三个QoS等级。当节点内存资源紧张时,kubelet会按照 BestEffort > Burstable > Guaranteed 的顺序驱逐Pod。
|----------------|------------------------------------------------|-------------|
| QoS等级 | 判定条件 | 驱逐优先级 |
| Guaranteed | Pod中所有容器 都同时设置了requests和limits,且两者值相等 | 最高保障,最不易被驱逐 |
| Burstable | Pod中至少有一个容器设置了requests或limits,但不满足Guaranteed条件 | 中等风险 |
| BestEffort | Pod中所有容器均未设置任何requests和limits | 最先被驱逐 |
我推荐这样设置:
- 生产环境必须设置requests和limits,避免被归为BestEffort。
- 核心服务尽量让每个容器的
requests == limits,享受Guaranteed待遇。 - requests不要设太高,否则节点资源碎片化,调度不上去。
调度策略:让Pod去它该去的地方
生产环境中经常需要把Pod固定到特定节点(比如SSD机型、专有硬件),或者避开某些节点。最基础的是用nodeSelector:
spec:
nodeSelector:
disktype: ssd
containers:
- name: app
image: nginx
如果情况更复杂,比如需要软性偏好或反亲和性(避免同应用Pod扎堆),可以用nodeAffinity和podAntiAffinity。由于配置较复杂,建议初级SRE先掌握nodeSelector和tolerations(容忍节点污点),后续再深入学习亲和性。
常见故障:三板斧搞定
不管遇到啥Pod问题,先执行这三条:
kubectl get pods # 看状态
kubectl describe pod <pod-name> # 看Events
kubectl logs <pod-name> --previous # 看崩溃前的日志
ImagePullBackOff / ErrImagePull
典型现象 :kubectl get pods 显示 ImagePullBackOff
NAME READY STATUS RESTARTS AGE
api-server 0/1 ImagePullBackOff 0 5m
原因:镜像不存在、tag写错了、私有仓库没配凭证、或者Docker Hub拉取限额到了
解决办法:
-
检查镜像名和tag对不对。
-
私有仓库要创建
imagePullSecret并在Pod里引用。kubectl create secret docker-registry regcred
--docker-server=registry.example.com
--docker-username=user
--docker-password=passspec:
imagePullSecrets:
- name: regcred
ImagePullBackOff不是bug,是kubelet在告诉你"镜像拉不下来,我正在重试"。
CrashLoopBackOff
典型现象 :kubectl get pods 显示 CrashLoopBackOff
NAME READY STATUS RESTARTS AGE
api-server 0/1 CrashLoopBackOff 5 10m
原因:容器启动后立刻崩溃,Kubernetes不断重启,且重启间隔指数增长
解决办法:
kubectl logs <pod-name> --previous看崩溃原因。- 检查存活探针是不是太激进(比如应用启动要30秒,
initialDelaySeconds只给了3秒),参考上文探针配置调整initialDelaySeconds。 - 检查应用依赖(数据库连不上、配置文件不对等)。
CrashLoopBackOff是生产环境最常见的故障之一。
OOMKilled
典型现象 :容器状态显示 OOMKilled
原因 :容器内存使用超过了limits.memory,被内核杀了
解决办法 :调大limits.memory,或者排查应用的内存泄漏。同时检查该Pod的QoS等级,若为BestEffort,节点资源紧张时它会优先被驱逐。
总结与互动
回顾一下核心要点:
- Pod是Kubernetes的最小部署单元 ,
apiVersion: v1、kind: Pod,spec.containers是必需的。 - Init Container在主容器之前运行,常用于等待依赖就绪,全部成功退出主容器才会启动。
- 存活探针和就绪探针是生产环境标配 ,务必配置
initialDelaySeconds和periodSeconds,否则滚动更新必踩坑。 - QoS等级决定Pod被驱逐的优先级 ,核心服务务必让每个容器都满足
requests == limits(Guaranteed)。 - Pod删除走优雅终止 :SIGTERM → 等待宽限期 → SIGKILL;务必加上
preStop sleep规避网络规则同步延迟。 - 排查三板斧 :
kubectl get pods→kubectl describe pod→kubectl logs --previous。
你在生产环境遇到过什么诡异的Pod问题?欢迎在评论区分享,咱们一起排排坑。
如果觉得有用,欢迎分享给更多小伙伴。
参考来源: