一句话总结
Kubernetes 是一个声明式容器编排平台:你用 YAML 声明"期望状态"(要跑几个副本、什么镜像、暴露什么服务),集群里的控制器不断调谐(Reconcile)把实际状态拉回期望状态——这就是"自愈、扩缩容、滚动更新"的统一原理。架构上分控制面(etcd 存状态、api-server 唯一入口、scheduler 调度、controller-manager 执行调谐)和工作节点(kubelet 管 Pod、kube-proxy 管转发、容器运行时跑容器)。面试主线:架构组件 → 工作负载与网络 → 存储与调度 → 排障。
# 一句话串起核心对象:
# Pod 是最小调度单位(一组共享网络存储的容器);
# Deployment 管无状态副本与滚动更新,StatefulSet 管有稳定身份的有状态服务;
# Service 给漂移的 Pod 一个稳定虚拟 IP + DNS 名;
# ConfigMap/Secret 管配置,PV/PVC 管存储,RBAC 管权限。
初级理解
集群架构组件
| 层面 | 组件 | 职责 |
| 控制面 | etcd | 唯一数据源,存全部集群状态(KV) |
| 控制面 | apiserver | 唯一入口:认证授权、准入、REST 语义,其余组件都经它读写 |
| 控制面 | scheduler | 给未调度 Pod 选节点(过滤 + 打分) |
| 控制面 | controller-manager | 一堆控制器(Deployment/ReplicaSet/Node…)执行调谐逻辑 |
| 工作节点 | kubelet | 按 PodSpec 管理本节点 Pod 生命周期、上报状态 |
| 工作节点 | kube-proxy | 维护 Service 的转发规则(iptables/IPVS) |
| 工作节点 | 容器运行时 | containerd / CRI-O,通过 CRI 接口拉起容器 |
Pod:最小调度单位
# Pod = 一组"绑死"在一起的容器:
# - 共享网络命名空间:同 IP、同 localhost、端口不能冲突
# - 共享存储卷:容器间可通过 volume 交换文件
# - 同生共死:一起调度到某节点,一起重启(同一 pause 容器兜底网络)
# 为什么不是一个容器包打天下?——亲密性:主容器旁挂日志收集/网格 sidecar,
# 它们必须与主容器同机同网络,但生命周期/资源可以独立管理
# Pod 是原子的、可抛弃的:节点挂了 Pod 会在别处重建,IP 会变——
# 所以绝不能直接依赖 Pod IP,这就是 Service 存在的意义
工作负载类型怎么选
| 类型 | 适用 | 关键特征 |
| Deployment | 无状态服务(Web/API) | 通过 ReplicaSet 管副本,支持滚动更新/回滚 |
| StatefulSet | 有状态(数据库/MQ/Kafka) | 稳定网络标识 app-0/1/2、稳定存储、有序部署扩缩 |
| DaemonSet | 每节点一个(日志/监控 agent) | 新节点加入自动铺一个 |
| CronJob | 定时任务 | 按 cron 表达式创建 Job |
| Job | 一次性任务 | 跑完成功即止,可配 backoffLimit 重试 |
Service 四种类型
# ClusterIP(默认):集群内虚拟 IP,只能集群内访问
# NodePort:在每节点开一个端口(30000-32767)对外暴露,经节点 IP 转到 Service
# LoadBalancer:云厂商 LB + NodePort(要花钱),生产对外服务的标配
# ExternalName:CNAME 指向外部域名,把外部服务"引入"集群
# Service 本质:一组 Pod 的稳定访问入口。kube-proxy 为每个 Service 维护
# iptables/IPVS 规则 + Endpoints 列表(就绪 Pod 的 IP 集合),
# 访问 ClusterIP:Port 的流量被 DNAT 转发到某个就绪 Pod(默认随机负载均衡)
中级深入
三种探针与 Pod 生命周期
livenessProbe: # 活着吗?失败 → 重启容器(K8s 自愈的边界在哪靠它)
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
failureThreshold: 3
readinessProbe: # 就绪吗?失败 → 从 Service Endpoints 摘除(不重启,只是不给流量)
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
startupProbe: # 启动好了吗?给慢启动应用"护航",成功前不跑 liveness
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 10
提示:三者分工是高频考点:liveness 管"要不要重启"、readiness 管"要不要给流量"、startup 管"给慢启动应用缓冲期"。liveness 探得太急(依赖外部 DB)会造成雪崩重启,readiness 不通则流量全打到健康实例上。
优雅终止与资源 QoS
# 删除 Pod 时的流程(优雅终止):
# 1. Pod 从 Endpoints 摘除(不再进新流量)
# 2. 执行 preStop 钩子(给应用"收尾"时间,比如等在途请求结束)
# 3. 发 SIGTERM,应用自行清理退出
# 4. 等 terminationGracePeriodSeconds(默认 30s)耗尽仍未退 → SIGKILL 强杀
# requests/limits 与 QoS 等级(决定节点资源紧张时谁先被驱逐):
# requests = 调度依据(scheduler 按它选节点)+ 保底配额
# limits = 上限:CPU 超了被限流(throttle),内存超了 OOMKilled
# Guaranteed:requests==limits 全配齐 → 最后才被驱逐
# Burstable :requests < limits → 中等
# BestEffort:都没配 → 内存紧张第一个被驱逐
调度流程与亲和性
# 调度 = 过滤(Filter)→ 打分(Score)→ 绑定
# 过滤:资源不够、端口冲突、nodeSelector/Affinity 不匹配、Taint 不能容忍 → 剔除节点
# 打分:资源均衡度、亲和命中、数据局部性等维度加权选最优
# nodeSelector:最简单的按标签选节点
nodeSelector: { disk: ssd }
# Affinity:支持"软偏好(preferred)"与复杂表达式
affinity:
podAntiAffinity: # 反亲和:同类 Pod 分散到不同节点/可用区,防单点
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels: { app: web }
# Taint/Toleration:节点"拒绝"某些 Pod(专用节点/预留资源):
# kubectl taint nodes n1 dedicated=db:NoSchedule
# Pod 需声明 toleration 才能调度上去;NoExecute 影响已在节点上的 Pod
配置与存储
# ConfigMap:非敏感配置(可挂文件或注入环境变量)
# Secret:敏感信息(base64 编码,可配合加密静态数据 etcd encryption),类型:
# Opaque / kubernetes.io/tls / kubernetes.io/dockerconfigjson
# 注意:Secret 也只是 base64,真正安全要靠 RBAC 限读 + 静态加密 + 不进 Git
# 存储三件套:
# PV(集群资源,运维预供)←PVC(用户申请,"我要 10Gi 的 gp3")→ Pod 挂载
# StorageClass:动态供给——PVC 创建时由 provisioner 自动创建 PV(云盘/NFS),
# 省掉运维手工建 PV;accessMode:ReadWriteOnce / ReadOnlyMany / ReadWriteMany
高级进阶
集群网络模型
# K8s 网络三大铁律(CNI 插件必须实现):
# 1. 每个 Pod 一个全局唯一 IP,Pod 之间可不经过 NAT 直接通信
# 2. 节点上的 agent(kubelet)可与该节点上所有 Pod 通信
# 3. Pod 看到的自己的 IP = 别人看到的它的 IP
# Service 访问链路(iptables 模式):
# Client Pod → ClusterIP:Port
# → iptables KUBE-SVC 链按概率选中某个 KUBE-SEP(就绪 Pod 的 endpoint)
# → DNAT 改写目标 IP 为 Pod IP → 节点路由/CNI 转发到目标 Pod
# IPVS 模式:规则在 conntrack/哈希表里,大规模 Service 下性能远优于 iptables 链式规则
# DNS:同 namespace 访问 mysvc,跨 namespace 用 mysvc.namespace.svc.cluster.local
# Service 由 kube-dns/CoreDNS 提供 A 记录(指向 ClusterIP),headless(clusterIP: None)返回 Pod IP 列表
# Ingress:七层路由(按域名/路径分发到不同 Service),本身只是规则声明,
# 由 ingress-nginx/envoy 等 controller 实现,可配 TLS 终止 —— 对外暴露的主流方案
滚动更新与回滚
strategy:
rollingUpdate:
maxSurge: 25% # 更新期间最多多出 25% 的 Pod(先起新的)
maxUnavailable: 25% # 最多允许 25% 不可用(再删旧的)
# Deployment 每次更新产生新的 ReplicaSet,旧 RS 保留(默认留 10 个历史)
# 回滚 = rollout undo 指回旧 RS:
# kubectl rollout undo deployment/web
# kubectl rollout undo deployment/web --to-revision=3
# 配合 readinessProbe:新 Pod 没 ready 前不进流量 → 真正的"零中断"发布
# HPA(水平扩缩):按指标(CPU/内存/自定义/对象)调整 replicas
# metrics-server 提供资源指标;缩容有 stabilizationWindow 防抖,扩容立即生效
RBAC 与故障排查方法论
# RBAC:Role/ClusterRole(能做什么)+ RoleBinding/ClusterRoleBinding(绑给谁)
# 最小权限原则:应用只拿自己 namespace 的读写;集群级监控才给 ClusterRole
# 排查方法论(自上而下):
# 1. kubectl get pods -o wide —— 看状态(Running? CrashLoopBackOff? Pending?)
# 2. kubectl describe pod x —— 看 Events:调度失败/拉镜像失败/探针失败一目了然
# 3. kubectl logs x [-c 容器名] --previous —— 看崩溃容器日志(--previous 是上次崩溃的)
# 4. kubectl top pod —— 看资源实际用量(需 metrics-server)
# 常见状态速查:
# Pending → 资源不足 / nodeSelector 无匹配节点 / 有 PV 绑不上
# ImagePullBackOff → 镜像名错/私有仓库没配 imagePullSecret/仓库不通
# CrashLoopBackOff → 容器启动即退出:看 logs --previous,多半是启动参数/配置错
# OOMKilled → 内存超 limits:调 limits 或查内存泄漏
# Error → 主进程非 0 退出:看退出码与日志
实战场景
一个生产可用的 Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata: { name: web, namespace: prod }
spec:
replicas: 3
selector: { matchLabels: { app: web } }
template:
metadata: { labels: { app: web } }
spec:
containers:
- name: web
image: registry.example.com/web:v1.2.3 # 生产必须锁定版本号,忌 latest
ports: [{ containerPort: 8080 }]
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "1", memory: "512Mi" }
livenessProbe: { httpGet: { path: /healthz, port: 8080 }, initialDelaySeconds: 10 }
readinessProbe: { httpGet: { path: /ready, port: 8080 }, periodSeconds: 5 }
envFrom: [{ configMapRef: { name: web-config } }]
---
apiVersion: v1
kind: Service
metadata: { name: web, namespace: prod }
spec:
selector: { app: web } # 与 Pod labels 匹配,自动维护 Endpoints
ports: [{ port: 80, targetPort: 8080 }]
发布后异常的排查顺序(实战模板)
# 1. kubectl get rs | grep web —— 新 ReplicaSet 的 desired/ready 是否到 replicas
# 2. kubectl rollout status deploy/web —— 卡在哪一步(新 Pod 起不来 / 老 Pod 不退)
# 3. kubectl describe pod web-xxx —— Events 里找探针失败/调度失败/存储挂载失败
# 4. kubectl logs web-xxx --previous —— 崩溃现场
# 5. 排除项:镜像 tag 是否被覆盖(latest)、configMap 改名没同步、secret 没重建、
# 资源 limits 太小被 OOM、就绪探针路径升级后 404 导致永远不进 Endpoints
# 6. 兜底:kubectl rollout undo deploy/web 回滚止血,再复盘
注意:ConfigMap/Secret 以文件挂载时,kubelet 更新后容器内不会瞬时生效(同步有延迟),且应用若不监听文件变化需要主动重载;改配置后确认应用层是否需要重启——"改了配置没生效"是高频事故。
面试模拟
面试官:Pod 到 Service 的请求是怎么到达容器的?
你:Service 只是抽象,背后是 kube-proxy 维护的转发规则。客户端 Pod 访问 Service 的 ClusterIP:Port,出口流量先到节点,被 iptables(或 IPVS)规则 DNAT——按概率选中一个就绪 Pod 的 Endpoint(IP:Port),改写目标后经 CNI 网络转发到目标 Pod 的容器。ClusterIP 本身并不真的"存在"于网卡上,它只是规则里的虚拟地址;负载均衡在 iptables 概率规则或 IPVS 调度算法里完成。同 namespace 可以直接用服务名,DNS(CoreDNS)把名字解析成 ClusterIP。
面试官:liveness 和 readiness 探针的区别?如果 liveness 探测写成查数据库会怎样?
你:liveness 决定容器要不要重启,readiness 决定 Pod 要不要接流量、不触发重启。如果 liveness 去查数据库,DB 抖动时所有实例会同时探活失败→全部重启,而重启并不能修复 DB 问题,只会雪上加霜——数据库依赖应该放在 readiness 或应用内部降级里。liveness 只应探测"进程自身是否健康"这种重启能解决的问题。慢启动应用再加 startupProbe 给缓冲期,避免启动阶段就被 liveness 杀掉。
面试官:Deployment 和 StatefulSet 的区别?什么时候用哪个?
你:Deployment 面向无状态:副本是可互换的,名字随机、存储临时、随便扩缩和替换,更新走滚动替换 ReplicaSet。StatefulSet 面向有状态:每个副本有稳定网络标识(web-0、web-1)和稳定的持久存储(PVC 按序号绑定,重建后还挂原来的盘),部署/扩缩/更新都按序进行——适合数据库、Kafka 这类"节点身份有意义"的系统。判断标准就一条:副本之间是否可互换、是否需要稳定的持久化身份。