云原生

Kubernetes 核心概念:Pod、Deployment、Service 的关系

把 Kubernetes 拆开看只有三层:声明式 API 描述期望状态,控制器通过控制循环把实际状态逼近期望状态,调度器与 kubelet 负责把 Pod 真正落到节点上跑起来。理解这条主线后,Pod、Deployment、Service 就不是零散名词,而是"被管理的对象、管理副本的对象、给副本提供稳定入口的对象"。

声明式 API 与控制循环

命令式是你告诉系统"做什么":启动 3 个容器。声明式是你告诉系统"要什么":任何时候都要有 3 个带标签 app=web 的 Pod 在运行。Kubernetes 走的是后者——对象存进 etcd,控制器持续比对期望状态与实际状态,有偏差就修正。所以删掉一个 Pod,Deployment 会自动补一个;节点宕机,副本会被调度到其他节点。

这套机制带来一个重要心智转换:不要手工去修被 Deployment 管理的 Pod,改了也会被重建覆盖。要变更就改对象定义(镜像版本、副本数、资源请求),让控制器去执行。同理,排查问题看的是 kubectl describe 里的 Events 和对象状态,而不是登录节点 ps aux。

Pod:最小调度单元

Pod 是 Kubernetes 里能被创建、调度、销毁的最小单位,不是容器。一个 Pod 内可以有一个或多个容器,它们共享同一个网络命名空间和同一组卷——即同一个 IP、同一个端口空间,可以用 localhost 互访,也可以通过 emptyDir 交换文件。

  • 单容器 Pod 是最常见形态,业务容器直接放里面。
  • 多容器 Pod 用于强耦合场景:sidecar(日志采集、代理)、init 容器(初始化配置、等待依赖就绪)、ambassador(出站代理)。
  • 同一 Pod 内容器生命周期绑定:主容器退出、Pod 就结束,不适合放"必须独立扩缩"的组件。

Pod 是临时且易逝的:IP 会变、名字会变、所在节点会变。所以任何依赖 Pod IP 的通信方式都是脆弱的,这也是 Service 存在的理由。

Deployment 与 ReplicaSet:副本与滚动更新

三层关系要分清:

对象 职责 是否直接创建
Pod 运行一个或多个容器,最小调度单元 可以但不推荐,裸 Pod 不会自愈
ReplicaSet 保证某个 Pod 模板的副本数恒定 通常由 Deployment 自动创建,不手写
Deployment 管理 ReplicaSet 版本,负责滚动更新与回滚 是,无状态应用的默认入口
StatefulSet 提供稳定网络标识与有序部署,配合 PVC 使用 是,用于数据库、消息队列等有状态服务
DaemonSet 每个(或指定)节点跑一个副本 是,用于日志代理、监控采集、CNI 组件
Job / CronJob 跑一次或按计划跑的任务 是,用于批处理、备份、报表

滚动更新的工作原理是:Deployment 创建新的 ReplicaSet,新 RS 扩容、旧 RS 缩容,交替推进,直到新版本副本全部就绪。控制节奏的两个参数是 maxSurge(最多超出期望副本多少个)与 maxUnavailable(最多允许多少个不可用)。默认 25%/25% 意味着滚动过程中容量会有波动,对流量敏感的服务应把 maxUnavailable 设为 0。

滚动更新是否安全,取决于 readinessProbe 是否配对了。没有就绪探针,Pod 一启动就被判为可用,流量会在应用还没初始化完成时打进来;没有 livenessProbe,进程假死也不会被重启。

Service 与 kube-proxy

Service 解决的正是"Pod IP 不稳定"的问题。它给一组 Pod 一个固定的集群虚拟 IP(ClusterIP)和 DNS 名,通过标签选择器动态维护后端 Endpoints。当 Pod 被重建、扩缩、迁移,Endpoints 自动更新,调用方无需感知。

实现上,Service 的虚拟 IP 并不是真实绑在某个网卡上的地址。kube-proxy 在每个节点写 iptables 或 IPVS 规则,把发往 ClusterIP 的流量按概率或轮询转发到某个后端 Pod。所以 ClusterIP 只能在集群内部访问,外部访问要用 NodePort、LoadBalancer 或 Ingress。

类型 暴露方式 适用场景
ClusterIP 仅集群内虚拟 IP + DNS 服务间内部调用,默认类型
NodePort 每个节点的固定高位端口 测试环境、云上无 LB 时的临时暴露
LoadBalancer 向云厂商申请外部负载均衡 对外服务,每个 Service 一个 LB,成本较高
Headless(clusterIP: None) 不做负载均衡,DNS 直接返回 Pod IP 列表 有状态集群的成员发现,如 StatefulSet
Ingress 七层路由,按域名/路径分发到多个 Service 统一入口、共享证书、灰度与限流

一次请求的完整流转

把对象串起来看:用户从浏览器访问 https://api.example.com/orders。

  1. DNS 解析到 Ingress 控制器(或云负载均衡)的外部 IP。
  2. Ingress 按 host 与 path 规则匹配到对应的 Service。
  3. Service 依据 Endpoints 列表选中一个就绪的 Pod,流量经 kube-proxy 规则转发到该 Pod IP 与容器端口。
  4. Pod 内的容器处理请求;如果它需要调用数据库或其他服务,会用对方 Service 的 DNS 名再走一遍同样的过程。
  5. 配置通过 ConfigMap 挂载为文件或环境变量、敏感信息通过 Secret 注入,业务代码不感知环境差异;日志与指标由 sidecar 或节点级 DaemonSet 采集,形成可观测性闭环。

ConfigMap 与 Secret

ConfigMap 存非敏感配置,Secret 存密码、Token、证书。二者都可以通过环境变量或卷挂载注入,但实践上更推荐卷挂载——以卷形式挂载的 ConfigMap 在更新后,容器内的文件会在几十秒内自动刷新(前提是应用支持热读取),环境变量则必须重启 Pod 才生效。Secret 默认只是 base64 编码,并不是加密,真正加密需要开启 etcd 静态加密(EncryptionConfiguration)并配合 RBAC 收紧读取权限。

上手顺序建议

不要一上来就啃全部 API。推荐按这个顺序建立肌肉记忆:先 kubectl get/describe/logs/exec 四个排查命令,再写一个 Deployment + Service 跑通无状态应用,第三步加 Ingress 与 ConfigMap,最后才碰 StatefulSet、HPA、NetworkPolicy。托管集群能省掉控制面运维,但节点规格、网络规划、权限模型仍然要自己定,可参考 云服务器选购指南;容器本身的概念如果还不熟,先看 Docker 入门。

常见问题

Pod 和容器有什么区别,为什么不直接调度容器?

容器是运行形态,Pod 是调度与生命周期单位。Pod 让多个容器共享网络与卷,可以作为一个整体被调度到同一节点、一起扩缩、一起销毁。Kubernetes 之所以不直接调度容器,是因为现实中存在强耦合的辅助进程(日志采集、代理、初始化),必须和主容器同生共死。

Deployment 和 StatefulSet 该怎么选?

看实例之间是否等价。无状态 Web 服务每个副本一样、随时可替换,用 Deployment;数据库、ZooKeeper、Kafka 这类需要稳定主机名、稳定存储、按序启动的,用 StatefulSet 配合 headless Service 与 PVC。判断标准是"这个 Pod 换到别的节点、换个名字,还能不能正常工作"。

Service 的 ClusterIP 为什么 ping 不通,是不是坏了?

ClusterIP 是 kube-proxy 用 iptables 或 IPVS 规则实现的虚拟地址,并没有真实网卡持有它,ICMP 通常不响应,所以 ping 不通是正常现象。验证服务是否可用应该用 curl 访问具体端口,或 kubectl get endpoints 检查后端 Pod 是否已被正确关联。

滚动更新时流量为什么会短暂报错?

多数情况是就绪探针缺失或过于宽松,新 Pod 还没真正能处理请求就被加入 Endpoints;也可能是应用没处理 SIGTERM,旧 Pod 被摘除后仍在处理或直接中断连接。解决办法是配置正确的 readinessProbe、在应用里实现优雅退出,并给 Pod 设置合理的 terminationGracePeriodSeconds 与 preStop 钩子。

延伸阅读