← 返回 Kubernetes图解 目录
🏗️ K8s核心概念图解
Pod/Node/Namespace/Label/Annotation + 控制器体系 + 声明式API · SVG可视化
凌晨2:17, PagerDuty疯狂报警——生产环境所有API响应超时。
登上K8s集群一看:Node 1 内存 99%,一个订单服务的Pod吃掉了整台Node的内存,导致同Node上其他Pod全部被OOMKilled。
排查发现,这个Pod的YAML里没有设置 resources.limits,Java应用默认堆内存无限增长,最终把Node资源全部榨干。
根因:YAML缺少 resources.limits.memory,Pod可以无限制使用Node内存
修复:加上 limits.memory: "1Gi" + requests.memory: "512Mi",从此Pod被限制在1GB内
教训:K8s不会替你设资源上限,不设limits = 允许一个Pod炸掉整个Node
学习路线:先认架构 → 再明核心 → 最终会避坑实战
① K8s架构全景
K8s就是一个自动化港口(扩展Docker=集装箱的类比):
• Master节点 = 港口控制中心(大脑,决定哪个集装箱放哪)
• Node节点 = 码头(实际放集装箱的地方)
• Pod = 工位(最小工作单元,一个或几个员工一起工作)
• Controller = HR部门(管理员工的招聘/解雇/扩编)
• Service = 前台/总机(来访者不直接找员工,通过前台转接)
• kubectl = 你给控制中心打电话的方式
K8s架构:Master(控制中心)管理所有Node(码头),Node上运行Pod(工位)
✅ 搞懂了K8s整体架构——Master管全局,Node跑Pod
→ 接下来深入Pod:K8s最小调度单元到底长什么样?
② Pod深入
Pod就是工位——K8s最小的调度单元。一个工位上可以坐1个人(单容器Pod),也可以几个人一起协作(多容器Pod,共享同一张桌子/电话/网络)。工位上总有一个"桌长"(Pause容器),它负责维持工位的网络和存储空间,即使别人都下班了,桌长还在。
Pod核心特性
| 特性 | 类比 | 说明 |
| 最小调度单元 | 工位 | K8s不直接管理容器,而是管理Pod。Pod是创建/调度的最小单位 |
| 共享网络命名空间 | 同一张电话 | Pod内所有容器共享同一个IP和端口空间,可通过localhost互相访问 |
| 共享存储卷 | 同一张桌子 | Pod内容器可以共享Volume,实现文件交换 |
| Pause容器 | 桌长 | 每个Pod都有一个Pause容器,持有网络命名空间。其他容器加入Pause的网络 |
| 生命周期短暂 | 工位是临时分配的 | Pod不会自愈!被删除或Node宕机后,由Controller创建新Pod替代 |
pod.yaml — Pod定义
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
tier: frontend
spec:
containers: # ← Pod内的容器列表
- name: app # ← 主容器
image: my-app:v1.0
ports:
- containerPort: 8080
resources:
requests: # ← 保底资源
cpu: "100m" # 0.1核
memory: "128Mi"
limits: # ← 上限资源
cpu: "500m" # 0.5核
memory: "512Mi"
- name: sidecar # ← 边车容器(辅助)
image: log-collector:v1
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
volumes: # ← 共享存储
- name: shared-logs
emptyDir: {} # Pod级别临时存储
✅ 搞懂了Pod——最小调度单元,容器共享网络和存储
→ 接下来看控制器:谁在管理这些Pod?怎么保证Pod数量和健康?
③ 控制器体系
控制器就是不同类型的管理人员:
• Deployment = HR经理(管招聘和解雇,确保在岗人数正确)
• StatefulSet = 班主任(每个学生有固定学号,不能乱换位置)
• DaemonSet = 保安队长(每个楼层必须有一个保安,新开楼层自动派一个)
• Job = 项目外包(干完就撤,干不完就重试)
• CronJob = 定时外包(每天凌晨3点跑数据备份)
5种控制器各有分工:Deployment管无状态、StatefulSet管有状态、DaemonSet每节点一个、Job一次性、CronJob定时
✅ 搞懂了5种控制器——各管各的应用类型
→ 接下来看Namespace和Label:怎么给Pod分组和打标签?
④ Namespace与Label
Namespace = 办公区域/楼层——同一栋楼(集群)里有不同的楼层(Namespace),每个楼层有自己的工位(Pod)和前台(Service)。不同楼层互不干扰,但可以互相串门。
Label = 工牌——贴在Pod上的键值对标签,比如 app=order-service, tier=backend, env=prod。Service和Controller通过Label找到对应的Pod,就像通过工牌找人。
terminal — Namespace操作
# 查看所有namespace
$ kubectl get namespaces
NAME STATUS AGE
default Active 30d # ← 默认空间
kube-system Active 30d # ← K8s系统组件
kube-public Active 30d # ← 公共资源
prod Active 15d # ← 生产环境
staging Active 10d # ← 预发布环境
# 创建namespace
$ kubectl create namespace dev
# 在指定namespace部署
$ kubectl apply -f deployment.yaml -n dev
# 通过Label筛选Pod
$ kubectl get pods -l app=order-service
$ kubectl get pods -l tier=backend,env=prod
✅ 搞懂了Namespace和Label——分组隔离 + 标签筛选
→ 接下来看K8s的核心哲学:声明式API——为什么说"提需求"比"下命令"更好?
⑤ 声明式API
K8s是声明式的——你告诉HR"我需要3个Java开发"(期望状态),HR自动去招聘/解雇来保持3个人(实际状态)。你不需要说"去招2个人"或"裁掉1个人"——你只需要不断声明"我要3个",K8s自己算差异并执行。
声明式API:你声明期望状态,Controller自动把实际状态调到期望状态
✅ 搞懂了声明式API——你写期望,K8s自动调到期望
→ 接下来进入实战:K8s最常见的三大坑,每个都是血泪教训
⑥ K8s三大坑 ❌✅对比
开头的Pod OOMKilled事故不是个例。以下三个坑,几乎每个K8s新手都踩过。
坑1:没设 resources → Pod吃光Node内存 → OOMKilled
❌ bad-deployment.yaml — 没设资源限制
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: app
image: order-service:v1.0
ports:
- containerPort: 8080
# ⚠️ 没有 resources!Pod可以无限使用Node资源
# Java默认-Xmx=1/4物理内存,8GB Node → 2GB/Pod
# 3个Pod × 2GB = 6GB,再加其他Pod → Node内存耗尽
✅ good-deployment.yaml — 设置了资源限制
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: app
image: order-service:v1.0
ports:
- containerPort: 8080
# ✅ 设置 requests(保底)和 limits(上限)
resources:
requests:
cpu: "200m" # 保底0.2核
memory: "256Mi" # 保底256MB
limits:
cpu: "1000m" # 上限1核
memory: "1Gi" # 上限1GB → 超过就OOMKilled,但不影响Node
# ✅ 对Java应用,还需设置JVM堆参数
env:
- name: JAVA_OPTS
value: "-Xms256m -Xmx768m" # 堆不超过limits的75%
resources设置原则:requests=调度依据(Scheduler保证Pod至少能拿到这么多),limits=限制依据(超过CPU被限速,超过Memory被OOMKilled)。
QoS等级:requests=limits → Guaranteed 最高优先级;有requests没limits → Burstable;都没有 → BestEffort 第一个被杀。
Java应用特别注意:-Xmx 不要超过 limits.memory 的 75%,因为JVM还有堆外内存(native memory、线程栈等)。
坑2:没设健康检查 → 僵尸Pod一直存在 → 请求打到死Pod
❌ bad-healthcheck.yaml — 没有健康检查
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
spec:
containers:
- name: app
image: user-service:v1.0
ports:
- containerPort: 8080
# ⚠️ 没有 livenessProbe 和 readinessProbe
# 应用内部死锁了 → 进程还活着但无法处理请求
# K8s以为Pod健康 → Service继续把流量打过来 → 全是超时
# 这就是"僵尸Pod"——活着但已经废了
✅ good-healthcheck.yaml — 设置了存活+就绪探针
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
spec:
containers:
- name: app
image: user-service:v1.0
ports:
- containerPort: 8080
# ✅ livenessProbe:检查进程是否还活着
# 失败 → K8s自动重启Pod(杀掉旧容器,创建新容器)
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30 # 启动后等30s再检查
periodSeconds: 10 # 每10s检查一次
failureThreshold: 3 # 连续3次失败→重启
# ✅ readinessProbe:检查是否准备好接收流量
# 失败 → Service把Pod从Endpoints摘除,流量不再打来
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe vs readinessProbe 区别:
• livenessProbe 失败 → K8s重启Pod(杀容器+重建),适合检测死锁、线程耗尽
• readinessProbe 失败 → K8s摘除流量(Pod还在跑,但不接新请求),适合检测依赖服务不可用
⚠️ livenessProbe 不要用检查数据库连接的接口——DB短暂抖动会导致所有Pod被重启!
坑3:Deployment跑MySQL → 数据全部丢失
❌ bad-mysql.yaml — 用Deployment跑MySQL
apiVersion: apps/v1
kind: Deployment # ❌ 错误!Deployment是无状态的
metadata:
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "secret"
# ⚠️ 没有PVC!Pod重建后数据全部丢失
# ⚠️ Deployment Pod名随机(mysql-7d4f5b-xxx)
# ⚠️ 滚动更新时旧Pod先删,新Pod用空数据启动
# ⚠️ 如果replicas>1,多个MySQL实例互相不知道
✅ good-mysql.yaml — 用StatefulSet跑MySQL
apiVersion: apps/v1
kind: StatefulSet # ✅ StatefulSet:有状态应用专用
metadata:
name: mysql
spec:
serviceName: mysql-headless # ← Headless Service,每个Pod有固定DNS
replicas: 1
selector:
matchLabels:
app: mysql
template:
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef: # ← 密码从Secret读取
name: mysql-secret
key: password
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
# ✅ volumeClaimTemplates:每个Pod自动创建专属PVC
# Pod名固定:mysql-0, mysql-1... → DNS也固定
# 有序启动:mysql-0先启动→就绪→mysql-1再启动
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
选Controller的判断标准:
① 数据需要持久化吗?需要 → StatefulSet 不需要 → Deployment
② 每个Node都需要跑一份吗?需要 → DaemonSet
③ 跑完就退出吗?一次 → Job 定时 → CronJob
⚠️ 数据库、消息队列、ZooKeeper 等必须用 StatefulSet,绝不能用 Deployment!
K8s三大坑速查表
| 坑 | 现象 | 根因 | 修复 |
| 坑1 没设resources | Pod吃光Node内存,同Node其他Pod被OOMKilled | limits缺失,Pod可无限制使用资源 | 设 requests+limits,Java设 -Xmx |
| 坑2 没设健康检查 | 应用死锁但Pod还活着,流量打到死Pod全部超时 | 无livenessProbe/readinessProbe | 加 livenessProbe(重启) + readinessProbe(摘流量) |
| 坑3 选错Controller | MySQL用Deployment,Pod重建后数据丢失 | Deployment无固定Pod名/PVC | 有状态应用用 StatefulSet + PVC |
✅ 搞懂了三大坑——resources、健康检查、Controller选型
→ 最后用面试速答检验一下,看看是否真的理解了
⑦ 面试速答
Pod和容器的关系?为什么不直接管理容器?
Pod是K8s最小调度单元,一个Pod可以包含1个或多个容器。Pod内的容器共享网络命名空间(同一IP、可通过localhost互访)和存储卷。为什么不直接管容器:因为有些容器需要密切协作(主容器+Sidecar),它们需要共享网络和存储,Pod就是这种"协作组"的抽象。
Deployment和StatefulSet的区别?
Deployment:无状态应用,Pod可随时替换(没有固定名字、没有固定顺序、没有持久化存储绑定)。适合Web服务、API。
StatefulSet:有状态应用,每个Pod有固定编号(Pod-0, Pod-1),有序创建/删除,每个Pod有自己独立的PV。适合数据库、ZooKeeper、Kafka。
K8s的声明式API是什么意思?和命令式有什么区别?
声明式:你描述"我要什么"(期望状态),系统自动把它变成现实。比如 replicas: 3,K8s自动确保3个Pod在运行。
命令式:你告诉系统"做什么"(具体操作)。比如 扩容到3个、重启Pod。
声明式的好处:幂等(多次apply结果一样)、自愈(Pod挂了自动重建)、GitOps友好(YAML文件即真相)。
Pod的requests和limits有什么区别?
requests:保底资源(调度依据)。K8s保证Pod至少能用到这么多。Scheduler根据requests决定把Pod调度到哪个Node。
limits:上限资源(限制依据)。Pod最多只能用这么多,超过CPU会被限速(throttle),超过Memory会被OOMKilled。
QoS等级:requests=limits → Guaranteed(最高);有requests没limits → Burstable;都没有 → BestEffort(最低,Node内存不足时第一个被杀)。
Namespace的作用?什么时候需要多个Namespace?
Namespace是资源隔离的单位。同一Namespace内的资源名不能重复,不同Namespace可以重名。
使用场景:1.多环境隔离(dev/staging/prod) 2.多团队隔离(前端/后端/数据) 3.资源配额(ResourceQuota)限制每个NS的用量。
注意:Namespace不做网络隔离!不同NS的Pod默认可以互通。需要网络隔离用NetworkPolicy。
K8s组件API Server、etcd、Scheduler、Controller Manager各自的作用?
API Server:所有操作的入口,认证/授权/准入控制,RESTful API。
etcd:分布式KV存储,存所有集群状态数据(唯一的状态存储)。
Scheduler:根据资源需求/亲和性/污点容忍,把Pod分配到合适的Node。
Controller Manager:运行各种Controller(Deployment/ReplicaSet/Node等),持续调谐实际状态→期望状态。
livenessProbe和readinessProbe的区别?生产环境怎么配置?
livenessProbe(存活探针):检测容器进程是否还活着。失败→K8s重启容器(杀掉+重建)。适合检测:死锁、线程耗尽、OOM后僵死。
readinessProbe(就绪探针):检测容器是否准备好接收流量。失败→Service把Pod从Endpoints摘除(不重启,只是不发流量)。适合检测:依赖服务不可用、初始化未完成。
生产配置建议:
1. livenessProbe的initialDelaySeconds要设大(30s+),给应用启动时间
2. livenessProbe不要检查数据库连接——DB抖动会导致所有Pod被重启
3. readinessProbe用轻量接口(如/health/readiness),检查关键依赖即可
4. Spring Boot用/actuator/health/liveness和/actuator/health/readiness
Pod的重启策略(restartPolicy)有哪些?和Controller的重建有什么区别?
restartPolicy(Pod级别):只有3个值——Always(默认)、OnFailure、Never。
• Always:容器退出就重启(适合长期运行的服务)
• OnFailure:只有异常退出(非0)才重启(适合Job)
• Never:从不重启(适合一次性任务)
重启 vs 重建:
• restartPolicy触发的"重启"=在同一个Pod内重启容器(Pod IP不变,但容器ID变了)
• Controller触发的"重建"=删掉旧Pod,创建新Pod(新IP、新名字)
所以Pod被OOMKilled后,你看到的是"重启"(Pod名不变,RESTARTS+1);而Deployment滚动更新时,是"重建"(新Pod名)。
📌 K8s核心概念速记三原则
1. 架构三件套:Master(大脑) + Node(手脚) + kubectl(嘴)
2. 资源三件套:resources(必须设) + 健康检查(必须加) + Controller(必须选对)
3. 声明式核心:你写期望状态,K8s自动调到期望。YAML即真相,多次apply幂等