1. 为什么K8S要引入Deployment这个东西
1.1 Pod的局限性与Deployment的定位
先聊个基本问题:K8S里最小调度单位是Pod,但你在生产环境里几乎不会直接创建Pod。为啥?因为Pod太“脆”了。它是有生命周期的,节点挂了Pod就没了,节点上的容器不会自动在其他机器上重启,Pod被删除就是真的被删了。你手动创建一个Pod,它挂了就是挂了,没人管你。
Deployment就是来解决这个问题的。它的核心定位是一个控制器,负责声明“我想要多少个Pod副本”,然后由K8S的控制器循环(Control Loop)去保证实际状态无限逼近你的期望状态。比如你写replicas: 3,Deployment就会确保集群里始终有3个Pod在跑,少了一个它马上重新拉起一个,多了一个它会帮你回收掉。
我早期刚接触K8S的时候,总觉得Deployment就是个“批量建Pod的工具”,后来才明白这个想法错得离谱。Deployment的本质是一套“状态管理机制”,它和Pod的关系,有点像Kubernetes里的“项目经理”和“一线员工”——员工干活,经理负责盯着人数、盯版本、盯健康度,员工栽了立刻补位,版本不对了安排滚动替换。
也常有朋友问:“K8S和Docker到底啥区别?”其实两者不是一个层面的东西。Docker解决的是“单个容器怎么打包、怎么跑”,K8S解决的是“成百上千个容器怎么编排、怎么调度、怎么自愈”。Deployment就是这一整套调度体系里最常用的一张牌。
1.2 Deployment能干什么:副本、滚动更新、自愈
Deployment的能力可以拆成三块来看。
第一块是副本管理。你声明replicas: 5,它就把Pod数量维持到5。这里的“维持”不是创建完就完事了,而是一直盯着的。有Pod被Evicted(驱离)、被误删、所在节点宕机,Deployment都会在别的可用节点上重建,直到数量回到5。
第二块是滚动更新(Rolling Update)。你要升级镜像版本,不需要手动一个个删Pod。只需要修改镜像标签,apply一下,Deployment就会按照你设定的策略一批一批替换旧Pod,期间服务不中断。这就是线上应用发布的基础能力。
第三块是自愈与故障转移。Pod里的容器如果一直崩溃(CrashLoopBackOff),Deployment会按照restartPolicy配合kubelet做重启尝试;节点故障时,Pod会被重新调度到健康节点。把这三块能力展开来讲,其实就是Deployment作为“无状态应用部署标准方案”的全部底气。
1.3 Deployment和StatefulSet、DaemonSet怎么选
很多人看K8S的控制器列表会眼花。Deployment之外还有StatefulSet、DaemonSet、Job、CronJob,它们到底怎么分工?这里分享一个最简单的判断逻辑:
- 应用无状态(谁都能替代谁,数据不落在本地盘):用Deployment。
- 应用有状态(每个实例有固定身份、需要稳定网络标识、数据要持久化):用StatefulSet。
- 需要在每台节点上都跑一个实例(比如日志采集、监控Agent):用DaemonSet。
- 跑一次性任务或定时任务:用Job或CronJob。
我在实际项目里见过不少把MySQL这类有状态应用塞进Deployment的,结果Pod漂移后数据全乱套。这种场景应该用StatefulSet配合持久化存储,或者干脆用托管数据库。Deployment不是万能的,它的设计前提是“副本之间完全对等,谁走了都能立刻补位”。
也就是说,Deployment是K8S里部署无状态应用的默认选项。你写微服务、Web应用、API网关,第一反应就应该是Deployment。这句话值得刻在工位上。
2. 动手编写一个Deployment:从命名到字段拆解
2.1 清单文件结构:apiVersion、kind、metadata、spec
K8S所有资源都是声明式的,写YAML就是和集群“签合同”。一个标准的Deployment清单长这样:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy namespace: production labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80四个顶级字段,一个都不能想当然:
apiVersion:Deployment在apps/v1这个稳定版本里。早期K8S用过apps/v1beta1和apps/v1beta2,新集群不要再写老版本。kind:资源类型,这里就是Deployment。metadata:名字、命名空间、标签。注意name必须符合DNS子域名规范,只能用小写字母、数字和中划线。spec:核心期望状态,下面展开。
2.2 核心字段逐项解读:replicas、selector、template
spec里的三个核心配置,是Deployment的灵魂。
replicas,副本数。它表示期望运行的Pod数量。但这里有个很多人一开始容易忽略的点:Deployment管理Pod,不是直接对着Pod下手的,而是通过一个中间层——ReplicaSet。你每次修改Pod模板(比如换镜像),Deployment都会创建一个新的ReplicaSet,然后控制新旧ReplicaSet的Pod数量此消彼长。所以你kubectl get rs时能看到一串历史ReplicaSet,这是回滚的“存档点”。
selector,这是Deployment识别自己管辖Pod的规则。它用matchLabels去匹配Pod模板里的标签。这里我必须强调:selector一旦创建就不可修改,想改只能删掉重建Deployment。而且selector的标签必须和template里Pod的标签对得上,否则Deployment会报错。我见过同事手滑改了个标签,结果Deployment找不到Pod,傻等半天。
template,这是Pod的“蓝本”。Deployment创建Pod,完全是按照template里定义的来。包括Pod的标签、容器镜像、端口、环境变量、资源限制、探针等等。你的容器配置基本都写在template下面的spec.containers里。
2.3 副本数怎么定:从N+1到HPA
replicas不是拍脑袋定的。它决定了你的服务能扛多大流量、能容忍几台机器同时宕机。这里分享我自己的推导方法。
先考虑冗余能力。比如你单副本能扛1000 QPS,线上业务高峰期大概2000 QPS,那至少2个副本。但如果要考虑单节点故障,假设集群里3台节点同时只能坏1台,而副本分布在节点上是不均衡的(K8S默认调度并不保证均匀分布),那最极端情况下2个副本可能都在同一台节点上,节点挂了服务就全挂。所以更稳妥的是N+1冗余,即满足流量所需副本数再加1。
再考虑滚动更新期间的资源占用。默认滚动策略maxSurge=25%,意味着更新时最多会额外创建25%的Pod。如果你replicas: 4,更新时集群里最多可能有5个Pod同时运行,这些额外Pod也要占节点资源。所以给副本数留点余量,不只是给流量留余量,也是给发布过程留余量。
如果业务流量波动大,建议直接上HPA(Horizontal Pod Autoscaler)。比如生产环境一个Deployment副本数是5,但配置了HPA根据CPU使用率在2到10之间自动伸缩。这种弹性能力是K8S相比传统运维方式最大的魅力。
2.4 给Deployment起中文名字?劝你算了吧
我之前看到社区里有个挺有意思的讨论:“如果给K8S Deployment一个中文名字,会是什么?”顺着这个问题我去翻了K8S的源码和文档,结论很明确:Deployment的name字段必须符合DNS子域名规范,只支持小写字母、数字、中划线,不支持中文。这是硬性限制,不是配置问题。
所以如果你是做国际化项目,Deployment的名字最好和业务域、环境、组件挂钩,比如order-service-prod、payment-api-staging这样。我踩过最深的坑是命名太随意,比如test、aaa,到了排查问题的时候根本不知道哪个Deployment是哪个服务,还要一个个去describe翻镜像地址,效率极低。
一个可参考的命名规范是:
<应用名>-<环境>,比如gateway-prod。
如果同一个应用有多个版本共存,可以再加版本标识,比如gateway-prod-v2。然后配合labels标注更细的维度,比如app: gateway、env: prod、version: v2。这样后续做灰度发布、标签路由都非常方便。
3. 部署实操盘点:从apply到状态确认
3.1 先准备什么:Namespace和镜像
部署一个Deployment之前,有三件事我建议先确认好。
第一,Namespace。如果集群规模不大,用default当然也行。但一旦团队人多、项目多,建议每个项目或每条产品线一个独立的Namespace,用ResourceQuota控制资源上限,用NetworkPolicy做隔离。我见过最乱的情况是所有人都在default里折腾,某天一个测试服务抢占大量CPU,把生产服务给拖死了。没有隔离,事故只是时间问题。
第二,镜像。镜像必须能被集群里的节点拉取到。私有镜像仓库需要提前配置imagePullSecrets,或者把镜像推到K8S集群各节点能访问的仓库里。镜像标签强烈建议用具体版本或commit哈希,不要用latest。理由很简单:latest是动态的,你昨天部署的是A版本,今天集群按需扩容新Pod时拉到的可能是B版本,同一Deployment下新旧Pod镜像不一致,出问题很难查。
第三,资源配额。在template里给容器设置requests和limits,这是生产环境的基本素养。没有资源限制的容器,就像不设预算的项目,跑着跑着就开始抢占节点内存,直到把整台机器搞挂。Kubelet发现节点内存不足时会开始Evict Pod,往往先杀掉的是那些没设limits的应用。
3.2 kubectl apply与kubectl create,别混着用
部署命令一般就两种:kubectl create和kubectl apply。这两个命令的区别,对刚接触K8S的人来说容易混淆。
kubectl create是“创建”语义。它要求目标资源不存在,存在就报错。它也不保留你上一次apply的配置记录。
kubectl apply是“声明式更新”语义。它会做三方合并(上次配置、当前配置、线上配置),资源不存在就创建,存在就按需更新。我强烈建议一律使用kubectl apply,这样才能配合kubectl rollout等命令做版本管理。
实际部署命令:
kubectl apply -f deployment.yaml -n production如果配置文件在远程URL上也可以直接apply,但生产环境我更倾向把YAML放进Git仓库,配合CI/CD流水线执行。这样每一次部署都有记录,出问题可以回溯。
3.3 怎么确认Deployment真的部署好了
apply命令执行完,输出显示deployment.apps/nginx-deploy created,这时候远没结束。需要从三个层级确认状态。
第一层:Deployment状态。
kubectl get deployment nginx-deploy -n production输出里的READY列显示类似3/3,表示3个副本中3个可用。如果显示1/3,说明还有副本没就绪,需要进一步排查。UP-TO-DATE列表示有几个副本已经更新到最新模板,AVAILABLE表示几个副本可供服务。
第二层:ReplicaSet状态。
kubectl get rs -l app=nginx -n production这里能看到当前以及历史的ReplicaSet列表,DESIRED和CURRENT应该一致。
第三层:Pod状态。
kubectl get pods -l app=nginx -n production重点看STATUS列,正常是Running。如果是Pending、ContainerCreating、CrashLoopBackOff、ImagePullBackOff,都是异常状态,需要kubectl describe pod <pod-name> -n production看详细事件。
3.4 Deployment状态值里藏着的门道
kubectl get deployment输出的状态列,看起来只是几个数字,但背后的判断逻辑很关键。K8S对Deployment的可用性有明确定义:Deployment处于可用状态,需要同时满足三个条件——最新ReplicaSet已经达到期望副本数、最新ReplicaSet已经完成滚动更新且新旧ReplicaSet副本数符合预期、Deployment的Progressing状态没有超时。
有个很常见的坑:Deployment的AVAILABLE显示3/3,但滚动更新后老ReplicaSet的Pod迟迟不缩容,比如kubectl get rs看到新旧两个ReplicaSet都有Pod在跑,那说明滚动更新可能卡在了终止Pod的阶段。这时候要看Pod是不是卡在Terminating,通常是PreStop钩子执行太久,或者节点资源不够导致新Pod无法被调度。
想快速看Deployment的整体状态,可以用:
kubectl rollout status deployment/nginx-deploy -n production这条命令会持续输出滚动更新的过程,直到全部完成或者报错。发布脚本里加上它,比干等yaml文件apply完要靠谱得多。
4. 发布与回滚:滚动更新机制拆解
4.1 滚动更新策略:maxSurge和maxUnavailable怎么配
Deployment最爽的功能之一,就是无损发布。这依赖的是strategy字段。默认策略是RollingUpdate,还有一种是Recreate(先把旧Pod全删了再建新的,会导致短暂停机,适合不能新旧共存的场景)。
RollingUpdate有两个关键参数:
strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25%maxSurge:更新时最多允许超出期望副本数的Pod数量,可以是绝对数值或百分比。默认25%,副本数为4时,更新期间最多5个Pod。maxUnavailable:更新时最多允许不可用的Pod数量。默认25%,副本数为4时,最多允许1个Pod短暂不可用。
这两个参数一起决定了发布的速度和风险。我的建议是:
- 核心业务、流量敏感的:
maxUnavailable: 0,maxSurge: 1。先多起一个副本,确认就绪后再杀旧的,保证整个发布过程没有容量缺口,代价是发布慢一点、资源占用多一点。 - 资源紧张、发布频次高:
maxSurge: 25%,maxUnavailable: 25%。发布快,但高峰期可能造成短暂容量下降。
4.2 更新触发与Pod重建的完整流程
更新一个Deployment很简单,改镜像版本后apply即可:
kubectl set image deployment/nginx-deploy nginx=nginx:1.26 -n production也可以用直接修改YAML的方式,改完apply。触发更新后,Deployment的完整动作是这样的:
- 生成一个新的ReplicaSet,期望副本数先按照
maxSurge比例增加。 - 新ReplicaSet的Pod启动并等待
readinessProbe通过。 - 新Pod就绪后,旧ReplicaSet的Pod开始按
maxUnavailable比例缩容。 - 重复上面步骤,直到新ReplicaSet达到期望副本数,旧ReplicaSet缩容到0。
这里我强烈建议给容器配置就绪探针(readinessProbe),尤其是HTTP服务:
readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 5没有就绪探针,K8S默认Pod里容器启动就算就绪,新Pod还没准备好接收流量就被拉进Service端点列表,线上会出现发布瞬间大量5xx的情况。我早期踩过这个坑,后来所有生产Deployment都强制加readinessProbe。
4.3 回滚操作:rollout undo与修订版本管理
发布出问题怎么办?Deployment的另一个优势就是回滚速度极快。K8S会把每次Deployment的模板变化记录为一次“修订版本”(revision),可以随时回滚到任意历史版本。
查看历史版本:
kubectl rollout history deployment/nginx-deploy -n production回滚到上一个版本:
kubectl rollout undo deployment/nginx-deploy -n production回滚到指定版本:
kubectl rollout undo deployment/nginx-deploy -n production --to-revision=2回滚的操作本质上是触发一次新的滚动更新,把Pod模板改回历史版本。所以回滚过程也是无损的,不会停机。需要说明的是,回滚也会生成新的revision,也就是说回滚后,你会有一份对应旧模板的新“存档”。
rollout history里每个revision对应一次模板变化,但如果你只是改了副本数(没有改template),这不会产生新revision。这个细节搞清楚,回滚时就不会错乱。
我还习惯在每次发布前手动给Deployment打一个注解标记发布原因:
kubectl annotate deployment/nginx-deploy kubernetes.io/change-cause="upgrade nginx to 1.26 for CVE fixes" -n production这样rollout history输出里就可以看到每次发布的原因,多人协作时特别有用。
5. 日常运维排坑实录
5.1 ImagePullBackOff:镜像拉不下来
kubectl get pods看到ImagePullBackOff,最直接的原因是节点拉取镜像失败。排查步骤:
kubectl describe pod <pod-name> -n productionEvents里一般会写明原因,常见几类:
401 Unauthorized:私有仓库认证失败,检查imagePullSecrets有没有配置。manifest unknown:镜像标签不存在,去仓库确认这个tag有没有推送。connection refused:节点访问不到仓库,需要排查网络和防火墙规则。- 没有
imagePullSecrets:Pod启动用的是默认的docker config,如果集群和仓库不在同一环境,需要额外配置。
我作为一个习惯,写YAML镜像地址时一定写全地址,比如registry.example.com/library/nginx:1.26,不要写nginx:1.26这种简写,省得在不同环境里产生歧义。
5.2 CrashLoopBackOff:容器起来就挂
CrashLoopBackOff表示容器启动后立刻崩溃,kubelet反复重启,间隔时间指数退避(10秒、20秒、40秒、80秒...)。排查思路:
kubectl logs <pod-name> -n production --tail=100 --previous--previous能看容器上一次崩溃前的日志,这个很关键,因为当前容器可能已经退出,直接kubectl logs只能拿到空输出或者只拿到崩溃后的启动日志。
常见原因有:启动命令依赖的配置不存在、环境变量缺失、数据库连接失败、端口冲突、启动参数错误。还有一个容易被忽视的问题:容器镜像的entrypoint在变更工作目录后需要重新调整,否则容器一起就退出。
如果日志看着没问题但还是一直重启,可以进容器手动执行启动命令:
kubectl exec -it <pod-name> -n production -- /bin/sh手动启动进程,看它报什么错。比对着日志猜要快得多。
5.3 Pending状态:Pod卡在排队
Pod一直Pending,说明调度器还没把它分配到节点上。描述Pod后常见事件是:
0/3 nodes are available: 1 Insufficient memory, 2 Insufficient cpu:节点资源不够,要么扩容节点,要么给Deployment的requests调低。0/3 nodes are available: 1 node(s) had untolerated taint:节点有污点(taint),Pod没有对应的容忍(toleration)。比如Master节点通常有node-role.kubernetes.io/master污点,普通Pod默认不会调度上去。0/3 nodes are available: 1 node(s) had volume node affinity conflict:使用了节点亲和性或者存储卷绑定了特定节点,但调度条件不满足。
Pending还有一个隐蔽原因:节点上有大量已终止的Pod和堆积的镜像,导致磁盘压力。可以用kubectl describe node <node-name>看Allocated resources和Conditions,如果DiskPressure为True,先清理节点上的无用镜像和日志文件。
5.4 证书过期这回事,别等报警了才处理
群里总有人问K8S集群的证书过期怎么办。这块虽然不是Deployment本身的问题,但集群证书过期直接会导致kube-apiserver不可用,所有Deployment操作全部失效。
如果集群是用kubeadm搭建的,证书有效期默认只有一年。kubeadm提供了自动续签命令:
kubeadm certs renew all如果每年自动续签太麻烦,有两条路:一是用kubeadm init时指定--certificate-ttl参数,默认是8760小时(一年),你可以改成--certificate-ttl=87600h让有效期变成十年;也可以把它接入cron等定时任务定期执行续签。如果你用的是托管K8S,这部分完全由云厂商负责,基本不用操心。
我自己踩过一次证书过期的坑,那真是“整个集群什么都干不了”,kubectl命令全线超时。后来就在监控里专门加了证书到期时间检查项,提前30天提醒续签。这个检查比部署本身重要得多,集群没了,Deployment再多也是白搭。
6. 高可用与资源管理:生产环境必须考虑的事
6.1 多Master集群高可用和Deployment的关系
很多朋友问过:“K8S三台Master怎么保证高可用?”
Master节点跑的是kube-apiserver、etcd、kube-controller-manager、kube-scheduler这几个核心组件。高可用的关键在etcd和数据一致性,一般会用奇数台(比如3台)Master组成etcd集群,通过Raft协议保证多数派可用。只要不是同时挂掉2台以上,集群的控制面就能持续工作。
那么,Deployment在这个架构里扮演什么角色?它跑在Worker节点上,其可用性由集群整体健康度决定。如果Master的kube-controller-manager挂了,Deployment的“期望状态不断校正”也就停了——已经跑着的Pod不会立刻挂掉,但副本数的调整、滚动更新、故障Pod重建都会停摆。所以Master高可用是Deployment可用性的前提。
如果你用kubekey这类工具搭建高可用集群,建议保持3个Master。这样哪怕规整维护一台,集群仍能正常调度,Deployment的滚动更新也不会中断。
6.2 资源限制与HPA:别让Deployment把集群吃垮
给Deployment里的容器设置resources,这是生产环境的基础操作。我自己常用的写法:
resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mirequests给调度器提供参考,limits限制容器使用上限。这里有个细节值得提醒:limits的CPU可以超卖但memory不能超卖。CPU是可压缩资源,超了只是被限流;内存是不可压缩资源,超了内核会触发OOM Kill,直接把容器干掉。所以内存的limits一定要根据容器实际使用情况来设,千万不要拍脑袋写个大数值。
副本数不想固定在3个,可以配合HPA自动伸缩:
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-deploy-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deploy minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60这个HPA会监控Deployment所有Pod的CPU平均使用率,超过60%就扩容,低于60%并且持续一段时间就缩容。注意:HPA调的是replicas,但不改YAML里的replicas数值,这是两套逻辑,别混在一起看。
6.3 K8S常用命令速查表,存一份少踩坑
Deployment相关的日常操作命令,整理成一张速查表:
| 操作 | 命令 |
|---|---|
| 创建/更新Deployment | kubectl apply -f deployment.yaml |
| 查看Deployment列表 | kubectl get deployment -A |
| 查看Deployment详情 | kubectl describe deployment <name> -n <ns> |
| 查看滚动更新状态 | kubectl rollout status deployment/<name> -n <ns> |
| 修改镜像版本 | kubectl set image deployment/<name> <container>=<image:tag> -n <ns> |
| 查看修订历史 | kubectl rollout history deployment/<name> -n <ns> |
| 回滚到上一版 | kubectl rollout undo deployment/<name> -n <ns> |
| 回滚到指定版本 | kubectl rollout undo deployment/<name> --to-revision=<ver> -n <ns> |
| 查看Pod详情 | kubectl describe pod <pod-name> -n <ns> |
| 实时查看Pod日志 | kubectl logs -f <pod-name> -n <ns> |
| 进入Pod执行命令 | kubectl exec -it <pod-name> -n <ns> -- /bin/sh |
| 临时扩容 | kubectl scale deployment/<name> --replicas=5 -n <ns> |
这些命令覆盖了Deployment生命周期里90%的操作需求。我不建议死记硬背,把这份表存到自己的笔记里,用到哪查哪,比强行记命令更高效。
我自己平时还有个习惯,部署前先跑一句kubectl diff -f deployment.yaml,它会输出本次改动和集群现状的差异。这条命令不用真的改动集群,非常适合在apply之前自查一遍,我靠着它避免过好几次误改镜像版本的事故。