← 返回 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
🗺️ 学习路线图:从"认识港口"到"避坑实战" ① 认架构 Master=控制中心 Node=码头 kubectl=打电话 ② 明核心 Pod=工位(最小单元) 5种Controller=5种管理 声明式API=提需求 ③ 会避坑 resources必须设 健康检查不能少 选对Controller类型 📌 三个层次,逐步深入 第①层搞懂"K8s是什么" → 第②层搞懂"核心组件怎么协作" → 第③层搞懂"怎么避免踩坑" 很多线上事故都出在第③层——YAML写错一个字段就炸
学习路线:先认架构 → 再明核心 → 最终会避坑实战

① K8s架构全景

K8s就是一个自动化港口(扩展Docker=集装箱的类比):
Master节点 = 港口控制中心(大脑,决定哪个集装箱放哪)
Node节点 = 码头(实际放集装箱的地方)
Pod = 工位(最小工作单元,一个或几个员工一起工作)
Controller = HR部门(管理员工的招聘/解雇/扩编)
Service = 前台/总机(来访者不直接找员工,通过前台转接)
kubectl = 你给控制中心打电话的方式
Kubernetes 架构全景图 🏢 Master节点(控制中心) API Server 前台接待 etcd 档案室 Scheduler 调度员 Ctrl Mgr HR部门 所有操作的入口 存所有状态数据 决定Pod放哪个Node 监控+维护期望状态 kubectl → API Server → etcd(存状态) + Scheduler(调度) + Controller(维护) 📍 Node 1(码头A) kubelet 码头主管 kube-proxy 电话总机 容器运行时 Pod 容器A 容器B Pause容器(网络) Pod 容器C Sidecar Pause容器(网络) 📍 Node 2(码头B) kubelet kube-proxy 容器运行时 Pod 容器D Pause容器 可用空间 Scheduler会 把新Pod放这
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定义
📁 k8s/
pod.yaml
deployment.yaml
service.yaml
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 👔 HR经理 Pod1 Pod2 Pod3 无状态/可替换/扩缩容 StatefulSet 🧑‍🏫 班主任 Pod-0 Pod-1 Pod-2 有序/固定名/持久存储 DaemonSet 💂 保安队长 Node1🟢 Node2🟢 Node3🟢 每Node一个/日志/监控 Job 📋 外包项目 跑完→Completed 一次性/失败重试 backoffLimit: 3 CronJob ⏰ 定时外包 0 3 * * * → 创建Job Cron表达式/定时 5种控制器对比 Deployment 无状态应用 Web/Api/微服务 Pod可随时替换 StatefulSet 有状态应用 MySQL/ZK/Kafka 有序启动/固定名 DaemonSet 每Node一个 日志/监控/网络 新Node自动部署 Job 一次性任务 数据迁移/计算 跑完就退出 CronJob 定时任务 备份/报表/清理 Cron表达式触发
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操作
📁 k8s/
namespace.yaml
terminal
# 查看所有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:期望状态 vs 实际状态 期望状态 replicas: 3 image: my-app:v2 (你在YAML里写的) 🔄 控制循环 不断对比 期望 vs 实际 差异→执行动作 实际状态 running: 2 image: my-app:v1 (etcd里存的) 差异=3个replicas但只有2个running → Controller创建1个新Pod + 滚动更新image到v2
声明式API:你声明期望状态,Controller自动把实际状态调到期望状态
✅ 搞懂了声明式API——你写期望,K8s自动调到期望
→ 接下来进入实战:K8s最常见的三大坑,每个都是血泪教训

⑥ K8s三大坑 ❌✅对比

开头的Pod OOMKilled事故不是个例。以下三个坑,几乎每个K8s新手都踩过。

坑1:没设 resources → Pod吃光Node内存 → OOMKilled

❌ bad-deployment.yaml — 没设资源限制
📁 k8s/
bad-deployment.yaml
good-deployment.yaml
service.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 — 设置了资源限制
📁 k8s/
bad-deployment.yaml
good-deployment.yaml
service.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 — 没有健康检查
📁 k8s/
bad-healthcheck.yaml
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 和 readinessProbe # 应用内部死锁了 → 进程还活着但无法处理请求 # K8s以为Pod健康 → Service继续把流量打过来 → 全是超时 # 这就是"僵尸Pod"——活着但已经废了
✅ good-healthcheck.yaml — 设置了存活+就绪探针
📁 k8s/
bad-healthcheck.yaml
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
📁 k8s/
📁 mysql/
bad-mysql.yaml
good-mysql.yaml
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
📁 k8s/
📁 mysql/
bad-mysql.yaml
good-mysql.yaml
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 没设resourcesPod吃光Node内存,同Node其他Pod被OOMKilledlimits缺失,Pod可无限制使用资源设 requests+limits,Java设 -Xmx
坑2 没设健康检查应用死锁但Pod还活着,流量打到死Pod全部超时无livenessProbe/readinessProbe加 livenessProbe(重启) + readinessProbe(摘流量)
坑3 选错ControllerMySQL用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幂等

下一篇:K8s网络与服务图解 →