Kubernetes 核心概念
提出问题
Kubernetes 已经成为容器编排的事实标准,几乎每个微服务面试都会触及。面试官不只是想听你背出 Pod、Deployment、Service 的定义,而是考察你是否理解它们之间的协作关系:控制器模式如何保证声明式状态收敛、Service 如何将流量路由到 Pod、HPA 如何根据指标自动扩缩容。这些概念在面试中频频出现,更在实际生产排障中每天都要打交道。
分析问题
Pod、Deployment 与控制器模式
Pod 是 Kubernetes 的最小调度单元,一个 Pod 可包含一个或多个容器(共享网络栈和存储卷)。但你不会直接创建 Pod——Deployment 是管理 Pod 的声明式控制器,它通过 ReplicaSet 确保指定数量的 Pod 副本始终运行。
控制器模式是 Kubernetes 最核心的设计哲学,理解它比背 YAML 字段重要十倍。一个 Pod 从提交到运行的全流程时序如下:
用户 kubectl kube-apiserver etcd Deployment ReplicaSet Scheduler kubelet CRI/容器运行时
| | | | Controller Controller | | |
|--kubectl apply -f deploy.yaml->| | | | | | |
| | |--校验/鉴权------>| | | | | |
| | |<--写入成功-------| | | | | |
| |<--返回创建成功-----| | | | | | |
| | | | | | | | |
| | | |<--Informer 监听 Deployment 变化--------->| | |
| | | | 发现期望副本数=3,当前 ReplicaSet=0 | | |
| | | |----创建 ReplicaSet 对象---------------->| | |
| | | | | | | | |
| | | | |<--Informer 监听 ReplicaSet 变化--| | |
| | | | | 发现期望 Pod 数=3,当前 Pod=0 | | |
| | | | |-----创建 3 个 Pod 对象---------->| | |
| | | | | | | | |
| | | | | |<--Informer 监听未调度 Pod--| |
| | | | | | Predicates: 资源足够/端口不冲突/污点容忍 |
| | | | | | Priorities: 资源余量/亲和性/反亲和性 |
| | | | | |----绑定 Pod 到 Node A------------->| |
| | | | | | | | |
| | | | | | |<--Watch 分配到本节点的 Pod-----| |
| | | | | | | PodSpec → 准备容器环境 | |
| | | | | | |---拉取镜像---------------------->| |
| | | | | | |---创建容器(Sandbox)----------->| |
| | | | | | |---启动 Init 容器(如有)-------->| |
| | | | | | |---启动主容器------------------>| |
| | | | | | |---执行 postStart hook------->| |
| | | | | | | | |
| | |<--Pod 状态更新---|--------------|-------------|-------------|-----状态上报到 API Server----| |
| | | Running | | | | | |每层都在做同一件事:从 etcd 读当前状态,跟自己的期望对比,有差异就调谐。面试时能画出这个链路,比干巴巴说"控制器模式就是调谐循环"强很多。
Scheduler 的 predicates 和 priorities:调度不是随机选节点。Predicates 做硬过滤(节点资源够不够、端口是否冲突、节点是否被污点标记、Pod 是否容忍污点、Pod 的亲和性/反亲和性约束),过滤完后如果还有多个候选节点,Priorities 做打分(LeastRequestedPriority 按资源余量打分、NodeAffinity 按亲和性权重、TaintToleration 按容忍度)。分数最高的节点中标。如果 Predicates 过滤后一个节点都剩不下,Pod 一直 Pending,kubectl describe pod 的 Events 里会写明原因。
生产里踩过的坑:
- revisionHistoryLimit 不设导致 etcd 暴涨:默认保留 10 个 ReplicaSet 历史版本,如果你滚动更新 100 次,就有 100 个历史 ReplicaSet 对象躺在 etcd 里。etcd 默认 2GB 存储上限(
--quota-backend-bytes),你只跑 3 个 Deployment 不觉得,跑 200 个微服务、每个每两周更新一次,半年后 etcd 容量告警。建议显式设revisionHistoryLimit: 2。 - maxSurge/maxUnavailable 组合错了导致滚动更新卡死:
maxSurge: 0+maxUnavailable: 0是不合法的,因为滚动更新既不能超量也不能缺量,死锁。生产至少配maxSurge: 1, maxUnavailable: 0或maxSurge: 0, maxUnavailable: 1。 - Pod 资源 request/limit 配了 0 或不配:不配 request 的 Pod 会被调度到任何节点,但节点资源可能被其他 Pod 挤占。我们踩过 Java 应用的 Pod 不配 memory request,结果两个 Pod 被调度到同一台 4GB 的节点上,各自 JVM 堆 2GB,加上操作系统和 sidecar 直接 OOM。必须配 request 让 scheduler 做资源预留。
- livenessProbe 和 readinessProbe 配置不当:initialDelaySeconds 设得太短,容器还没启动就被 kill 重启循环。Spring Boot 应用启动要 30 秒,liveness 的 initialDelaySeconds 设了 5 秒,结果 Pod 永远起不来。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
revisionHistoryLimit: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 3
periodSeconds: 5Pod 网络模型与 CNI
Kubernetes 的网络模型要求:每个 Pod 都有自己的 IP,Pod 之间可以直接通信(不需要 NAT),Pod 与 Node 之间也可以直接通信。这个模型由 CNI(Container Network Interface)插件实现。
Pod 内通信:一个 Pod 里的多个容器共享同一个 network namespace(通过 pause 容器先创建 Sandbox),所以它们通过 localhost 互相访问,端口不能冲突。
跨节点 Pod 通信:CNI 插件负责跨节点网络连通。主流方案对比:
| 方案 | 数据面 | 封装方式 | 吞吐损耗 | 典型场景 |
|---|---|---|---|---|
| Flannel (VXLAN) | 内核 | UDP 封装 | ~5-10% | 小规模、快速上手 |
| Calico (BGP) | 内核 | 无封装,纯路由 | <1% | 大规模、性能敏感 |
| Cilium (eBPF) | 内核 eBPF | 无封装或 Geneve | <1% | 大规模 + 安全策略 + 可观测性 |
| Weave | 用户态/内核 | UDP 封装 | ~5-10% | 已边缘化,不推荐新项目 |
Cilium 为什么是趋势:Cilium 用 eBPF 直接在内核态处理网络包,绕过了 iptables 的 O(n) 开销。在 1000 个 Service 的集群里,iptables 规则可能有 1 万条,每条连接都要逐条匹配,CPU 飙升。Cilium 用 eBPF Map 做哈希查找,O(1) 复杂度,延迟和吞吐都稳定。我们实践迁移后,Service 间 P99 延迟从 8ms 降到了 2ms。
Service 与 Ingress 流量入口
Pod 的 IP 是不稳定的——每次重启或被调度到其他节点都会变化。Service 提供了一个稳定的虚拟 IP(ClusterIP)和 DNS 名称,通过标签选择器(Label Selector)匹配后端 Pod,实现负载均衡。
Service 的完整网络路径:
客户端请求 → Service ClusterIP:Port
↓
Node 内核收到包,目标 IP = ClusterIP
↓
kube-proxy 写入的 iptables/IPVS 规则匹配到 ClusterIP
↓
DNAT 将目标 IP 改为选中的 Pod IP(iptables 的 DNAT 或 IPVS 的 NAT 模式)
↓
包转发到具体的 Pod 所在 Node(跨节点时走 Node 路由)
↓
Pod 内的容器收到请求,回复包走反向 SNAT 回到客户端(iptables conntrack 记录原始连接)kube-proxy 模式对比:
| 模式 | 实现方式 | 路由复杂度 | 连接量 1000 下 CPU 开销 | 连接量 10000 下 CPU 开销 |
|---|---|---|---|---|
| userspace | 用户态代理 | 慢,已废弃 | 高 | 极高 |
| iptables | 内核 netfilter | O(n) 链式匹配 | 5% | 40%+ |
| IPVS | 内核 LVS | O(1) 哈希 | 2% | 5% |
| eBPF (Cilium) | 内核 eBPF | O(1) Map | 1% | 2% |
大集群(>500 Service)建议用 IPVS 或 Cilium eBPF 模式。iptables 模式在规则数超过 1000 条时,每次连接匹配的 CPU 开销会直线上升。
Service 的类型决定了暴露范围:
| 类型 | 访问方式 | 典型场景 | 坑 |
|---|---|---|---|
| ClusterIP | 集群内 DNS 解析 | 微服务间通信 | 默认模式,外部无法直接访问 |
| NodePort | NodeIP:NodePort | 测试环境、本地调试 | 端口范围 30000-32767,不能冲突 |
| LoadBalancer | 云厂商 LB | 生产环境对外暴露 | 每个 Service 一个 LB,成本高 |
Service 的 Session Affinity:如果你需要同一个客户端的请求始终打到同一个 Pod(比如 WebSocket 或本地缓存),设置 sessionAffinity: ClientIP,默认基于 iptables 的随机转发会导致请求漂移。生产上我们遇到过 WebSocket 连接频繁断开,排查发现是 kube-proxy 的随机转发把同一个客户端的不同请求发到了不同 Pod,而 Pod 上的 WebSocket 状态是本地内存存储的。
集群外部流量更常用 Ingress——一个七层路由规则,将 HTTP/HTTPS 请求按域名和路径转发到不同的 Service。Ingress 需要安装 Ingress Controller(如 Nginx Ingress、Traefik)才能真正生效。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2-service
port:
number: 8080Ingress 的坑:pathType: Prefix 匹配 /v1 会匹配到 /v1/user,但也可能匹配到 /v1abc,因为 Prefix 是按字符串前缀匹配,不是按路径段。如果你需要精确段匹配,用 pathType: Exact。Nginx Ingress Controller 默认用 / 后缀做路径段匹配,但标准 Kubernetes API 的 Prefix 行为就是按字符串前缀,这个差异踩过的人不少。
健康检查与自动扩缩容
Kubernetes 提供三种探针(Probe),面试时能说出区别和适用场景才算真懂:
| 探针 | 失败后果 | 典型使用场景 | 翻车案例 |
|---|---|---|---|
| livenessProbe | 重启容器 | 检测死锁、内存泄漏 | 配置不当导致 Pod 反复重启 |
| readinessProbe | 从 Service 端点移除 | 应用启动慢、依赖未就绪 | 数据库未就绪时接收流量,返回 500 |
| startupProbe | 禁用 liveness/readiness | 启动慢的 Java 应用 | 没有它,Spring Boot 启动 60s 期间被 liveness 杀死 |
启动慢的 Java 应用为什么需要 startupProbe:Spring Boot 应用启动要 30-60 秒,如果 livenessProbe 的 initialDelaySeconds 设 10 秒,容器启动 10 秒后开始检查,还没准备好就连续失败,kubelet 认为 Pod 不健康就重启,陷入死循环。startupProbe 在启动期间覆盖 liveness/readiness,等启动完成后再切换回正常探针。
# 适合 Spring Boot 的配置
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # 等 Spring Boot 启动完
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3HPA(Horizontal Pod Autoscaler)根据 CPU/内存使用率或自定义指标自动调整 Pod 副本数。HPA 通过 Metrics Server 获取指标,不断计算所需副本数:
desiredReplicas = ceil(currentReplicas × currentMetricValue / targetMetricValue)例如当前 2 个 Pod,CPU 平均使用率 90%,目标是 70%,则:
desiredReplicas = ceil(2 × 90 / 70) = ceil(2.57) = 3apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却窗口,防止流量抖动缩了又涨
scaleUp:
stabilizationWindowSeconds: 0 # 扩容不冷却,快速响应
policies:
- type: Pods
value: 4
periodSeconds: 15 # 15 秒内最多扩容 4 个 PodHPA 与 VPA 对比:
| 维度 | HPA | VPA |
|---|---|---|
| 调整对象 | Pod 副本数 | Pod 的 request/limit |
| 适用场景 | 无状态应用、流量波动大 | 有状态应用、资源利用率优化 |
| 缩容时间 | 5-15 分钟(冷却窗口) | 需要重建 Pod |
| 限制 | 与 VPA 不能同时作用于 CPU/内存 | 不能独立工作,需要配合 Cluster Autoscaler |
| 典型配合 | HPA + CA 全自动扩缩 | VPA + CA 控制资源浪费 |
HPA 生产踩坑:
- Metrics Server 未安装:HPA 不工作,查不到指标。很多新手搭集群只装 kubeadm 没装 Metrics Server,HPA 永远不扩缩。
kubectl top nodes能跑才能确认 Metrics Server 正常。 - 只配 CPU 不配内存:内存密集型应用(如 Java 堆内存 4GB),CPU 负载不高但内存快爆了,HPA 不会触发扩容。建议 CPU + 内存双指标,或对接 Prometheus 自定义指标。
- 扩缩容震荡:minReplicas=2, maxReplicas=100, CPU 目标 70%,流量波动 10 秒一个周期,HPA 每 15 秒评估一次,Pod 频繁创建销毁。解决方案:配
behavior.scaleDown.stabilizationWindowSeconds: 300缩容冷却窗口,防止流量抖动导致频繁缩容再扩容。我们一个电商业务在双十一前踩过:凌晨流量低谷缩到 2 个 Pod,早上 8 点流量突然涨了 5 倍,HPA 从 2 扩到 10 个 Pod 花了 5 分钟,期间大量请求超时。后来我们把 minReplicas 提到 5,配合 PDB 和 CA 预热,扩缩更平滑。
ConfigMap 与 Secret:配置管理
ConfigMap 存储非敏感配置(如环境变量、配置文件),Secret 存储敏感数据(如密码、Token)。两者都以 Volume 挂载或环境变量的方式注入到 Pod 中。
Secret 的误解:Secret 值只做 Base64 编码,不是加密。任何有 etcd 访问权限的人都可以直接解码看明文。生产环境必须配合:
- sealed-secrets:在 GitOps 流程中加密存储 Secret 的 YAML
- sops + age:在 Git 仓库里加密 secret 文件
- 云厂商 KMS(AWS KMS / GCP KMS):Kubernetes 1.24+ 支持 KMS v2 加密 etcd 中的 Secret
ConfigMap 的挂载行为:如果 ConfigMap 是 Volume 挂载,更新 ConfigMap 后 Pod 内的文件也会更新(几秒延迟),但不会触发 Pod 重启。很多用户以为改了 ConfigMap 应用会自动重载配置,实际上应用的 hot reload 机制需要自己实现。Spring Boot 可以用 spring-cloud-kubernetes 的 ConfigMap 刷新,或者用 Reloader 组件监听 ConfigMap 变更自动重启 Pod。
生产排障速查表
| 现象 | 排查思路 | 命令 |
|---|---|---|
| Pod 一直 Pending | 资源不足、PVC 未就绪、节点污点 | kubectl describe pod <pod> |
| Pod 反复 CrashLoopBackOff | 启动失败、探针失败 | kubectl logs <pod> --previous |
| Service 不通 | 标签选择器不匹配、端口不对 | kubectl describe svc <svc> | grep Endpoints |
| Ingress 返回 404 | 路径不匹配、Ingress Controller 未部署 | kubectl describe ingress <ingress> |
| HPA 不扩缩 | Metrics Server 未安装、指标未上报 | kubectl get hpa -o yaml 看 conditions |
| 节点 NotReady | kubelet 挂了、磁盘满、网络不通 | kubectl get nodes 看状态 |
| DNS 解析失败 | CoreDNS 未部署、Pod DNS 策略不对 | kubectl -n kube-system logs -l k8s-app=kube-dns |
总结
面试官面 k8s,最想听到的不是你背 Pod 的定义,而是你能说出控制器模式的全链路(用户 → API Server → etcd → Controller → Scheduler → kubelet → CRI),以及实践中踩过的坑(revisionHistoryLimit 撑爆 etcd、liveness 配置不当导致重启循环、HPA 只配 CPU 导致内存爆满、Service 的 IPVS vs iptables 性能差异)。Kubernetes 核心概念围绕声明式 API + 控制器模式展开:Pod 是最小单元,Deployment 保证副本数,Service 提供稳定网络入口,Ingress 处理七层路由,HPA 实现自动伸缩,CNI 插件解决跨节点网络。
参考
参考:Kubernetes 官方文档 - Concepts;《Kubernetes in Action》第二版;Kubernetes Design Proposals - Controller Pattern