☰
Kubernetes 实战指南:从安装到弹性伸缩的容器编排实践
2026/10/17 6:42:16 网站建设 项目流程

1. 先搞清楚:K8s 在真实项目里到底解决什么问题

聊 K8s 之前,我想先说一段自己的真实经历。早些年我维护过一个微服务项目,服务数量从几个快速膨胀到二十多个。每次发版都要手动登录服务器、拉镜像、停容器、再起容器,遇到流量高峰还要半夜爬起来扩容。最痛苦的不是操作本身,而是"漏了一台机器"这种低级错误——明明每个步骤都有文档,但人总有走神的时候。后来我们把服务迁到 Kubernetes(以下简称 K8s)上,发版变成了一个命令,扩容变成了一个数字,机器宕了业务自动恢复。那个阶段我才真正体会到,K8s 的价值不在"跑容器",而在"让几十上百个容器像一台机器一样被管理"。

所以这篇教程的思路也很明确:不堆概念,而是从一个使用者的角度,告诉你 K8s 是什么、怎么装、怎么用、踩过哪些坑。适合刚接触容器编排的开发者、想给团队引入 K8s 的运维,以及所有"听过很多次但没真正跑通"的朋友。

K8s 本质上是一个开源的容器编排平台,解决的核心问题可以概括成三句话:自动调度(容器该跑在哪台机器上)、自动恢复(容器挂了如何拉起新的)、自动伸缩(流量涨了如何扩展实例)。它对外暴露的是一套资源描述方式——你想让系统变成什么样,就声明什么样,剩下的交给控制面去协调。

为了避免一开始就被术语吓住,我先把 K8s 的架构压缩成一个简单的图景:

  • 控制面(Control Plane):集群的"大脑",负责决策。里面有 API 服务(所有操作的入口)、调度器(决定 Pod 放哪台节点)、控制器管理器(确保实际状态向期望状态收敛),以及存储集群状态的数据库。
  • 工作节点(Worker Node):集群的"手脚",真正跑容器的地方。每台节点上有容器运行时、负责执行指令的代理进程 kubelet,以及负责网络规则的小组件。

从这个图景出发,后面所有的操作都能对上号:你提交一份描述文件给 API 服务,调度器决定在哪台节点上启动,kubelet 收到指令后拉起容器,控制器时刻检查"现状 vs 期望",发现偏差就纠正。理解了这条链路,K8s 就没有那么玄乎了。

2. 环境选型:本地学习、单机生产、多节点集群该走哪条路

动手之前先解决"装在哪里"的问题。很多新手一上来就跟着网上的教程装 kubeadm,结果卡在初始化、网络插件、镜像拉取这些环节,折腾两天还没跑起来一个 Pod,信心全没了。这是很典型的"选型错误导致的挫败"。

我的建议是分场景选择部署方式,不要一套方案打天下。

使用场景推荐方案优势注意点
本地学习、快速体验Minikube一条命令启动单节点集群,功能完整需要能运行虚拟机的环境,消耗资源适中
隔离环境练习多节点排障Kind(K8s in Docker)用 Docker 模拟多节点,启动快、易销毁适合学习,不适合生产
生产或长期使用kubeadm 部署官方推荐的生产级工具,可控性强网络、证书、升级都要自己维护
在已有基础设施上快速建集群各家云厂商的托管 K8s免运维控制面,按量付费有厂商绑定倾向,需要注意

我不想做工具的广告,但可以给一个很实在的选型建议:如果你是第一次接触 K8s,直接选 Minikube 或者 Kind,别碰 kubeadm。原因很简单——本地学习的目标是尽快把核心对象跑通,而不是在安装环节就耗尽耐心。等你掌握了 Pod、Deployment、Service 这些概念,再回头用 kubeadm 搭一个正经的多节点集群,那时候你对每个组件的作用已经有直观感受,排障也更有方向感。

另外,很多同学会纠结"K8s 是不是必须要 Docker"。这里要澄清一个长期存在的误解:K8s 要求的是符合容器运行时接口(CRI)的运行时,Docker 只是其中一种。现在更多团队默认使用 containerd 这类轻量运行时,直接由 kubelet 调用。用 Docker 作为开发环境的运行时完全没有问题,但不要把"Docker = K8s 唯一的运行时"当成铁律。

以 kubeadm 为例,多节点集群的搭建大致是这么几步:

  1. 准备两台以上 Linux 服务器(我习惯用某 Linux 发行版,内核版本不要太老),配置好主机名和/etc/hosts。
  2. 在所有节点上安装容器运行时并启用内核网络模块。这部分最常出的问题有两个:一个是忘记加载模块导致网络插件起不来;另一个是运行时和 kubelet 的 cgroup 驱动不一致,后面会有专门的排障段落。
  3. 在控制面节点执行kubeadm init,初始化集群。初始化成功后,kubectl 的配置文件会被生成到$HOME/.kube/config,这一步很多人忘了设置,导致执行 kubectl 命令报 "connection refused"。
  4. 安装网络插件。初始化完成后集群处于 NotReady 状态,原因是节点之间没有网络方案。用kubectl apply安装一份网络插件清单即可。
  5. 将工作节点通过kubeadm join加入集群,带上初始化输出的 token 和证书哈希。

我第一次搭集群时,在第四步卡了将近三个小时——Pod 一直处于 ContainerCreating,事件里反复出现网络错误。后来查了很久才发现是安装网络插件前忘了确认节点之间的网络连通性,防火墙把某些端口挡了。所以这里强烈建议:集群节点之间的端口放行一定要先确认清楚,尤其是控制面 API 端口和节点间通信端口。

3. 核心资源对象:Pod、Deployment、Service 的职责边界

很多人学 K8s 的第一步就是背概念,但背完之后依然不会写配置。我想换个讲法:先把资源对象当成你向集群"声明期望状态"的不同粒度,再看它们之间怎么配合。

3.1 Pod:最小的"逻辑主机"

Pod 是 K8s 中最小的调度和运行单位。一个 Pod 里可以放一个容器,也可以放多个紧密协作的容器,这些容器共享同一个网络命名空间和存储卷。

关于 Pod,我最想强调的一点是:不要把 Pod 当成跑单个容器的普通进程来看,而要把它当成一台"逻辑主机"。容器只是这台主机里的进程,而主机的生命周期和里面的进程是绑定在一起的。Pod 里面的容器会被调度到同一台物理节点上,所以它们之间用 localhost 就能通信。业务上需要"同生共死"的容器(比如日志采集 sidecar 和应用主容器),就应该放在一个 Pod 里。

但在实际生产中,你很少直接创建 Pod,原因很简单:Pod 一旦被删除或被重新调度,名字和 IP 都会变化。你需要一种能保持稳定身份、能扩容缩容、能保证存活数量的机制,这就是 Deployment。

3.2 Deployment:声明式的副本管理

Deployment 是你在生产环境最常打交道的一个对象。它管的事情很清晰:

  • 保证副本数:你声明replicas: 3,它就保证系统里始终有 3 个 Pod 在运行。
  • 滚动更新:修改镜像版本后,它会逐个替换旧 Pod,而不是一次性全部杀掉,避免服务中断。
  • 回滚:如果新版本有问题,可以通过kubectl rollout undo回退到上一个版本。

写 Deployment 的 yaml 时,有几个字段我建议每个新手都认真理解:

apiVersion: apps/v1 kind: Deployment metadata: name: demo-web labels: app: demo-web spec: replicas: 3 selector: matchLabels: app: demo-web template: metadata: labels: app: demo-web spec: containers: - name: web image: nginx:1.26 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi

selector.matchLabels这个字段很关键,它决定了这个 Deployment 管理哪些 Pod。一旦 Pod 的标签不匹配,Deployment 就会认为这些 Pod 不是自己管辖的,从而创建新的 Pod 去补足数量。一个很常见的误操作是手动修改了 Pod 的标签,导致 Deployment 反复重建 Pod——这在生产环境会造成不小的混乱。

3.3 Service:把变化的 Pod IP 变成稳定的访问入口

Deployment 保证了 Pod 的数量和状态,但 Pod 的 IP 是随时会变的。Service 就是那个扛把子——它提供一组 Pod 的稳定访问入口。

Service 的工作机制可以简单理解成:给一组 Pod 打上标签选择器,然后分配一个稳定的虚拟 IP(ClusterIP)。访问这个虚拟 IP 时,流量会被转发到后面实际的 Pod 上。

Service 有几种常见类型:

  • ClusterIP:默认类型,只在集群内部可以访问,适用于服务间调用。
  • NodePort:在每个节点的固定端口上暴露服务,外部通过节点IP:NodePort访问。
  • LoadBalancer:在云环境里,通过云负载均衡器暴露服务,是生产环境比较直接的方式。

从使用感受上说,Deployment 管"数量",Service 管"访问",两者配合起来,你就能在完全不知道 Pod 实际 IP 的情况下,稳定地访问到一组 Pod。

3.4 ConfigMap 与 Secret:让环境配置和敏感信息脱离镜像

还有一个很容易被忽略但非常实用的对象是 ConfigMap 和 Secret。它们解决的核心问题是:同一个镜像要在不同环境(开发、测试、生产)里用不同的配置,总不能把配置写死在镜像里。

ConfigMap 存的是明文配置,Secret 存的是敏感信息(比如数据库密码、密钥)。两者都可以挂载成文件或者注入成环境变量。我见过不少项目把配置写进镜像,每次改配置都要重新构建镜像并重新发布,这其实完全没有必要——把配置存成 ConfigMap,一个kubectl set或直接修改 Deployment 引用版本,就能完成配置更新。

4. 从零部署一个带配置和存储的 Web 服务

概念讲得再多,不亲手跑一个完整应用等于白学。这一节我就用最常见的场景——一个 web 服务加上配置文件和持久化存储——带你走一遍完整的部署链路。

4.1 准备好镜像和配置

假设我有一个简单的 web 应用,它需要在启动时读取一个配置文件,并且要把日志写到磁盘上,以便后续排查问题。我先准备两个文件:

  • demo-config.yaml:ConfigMap,用来存放配置文件内容。
  • demo-deployment.yaml:Deployment,引用这个 ConfigMap,并声明一个持久化存储卷。

先来看 ConfigMap 的写法:

apiVersion: v1 kind: ConfigMap metadata: name: demo-config data: app-config.yml: | server: port: 8080 log: level: info

这里把app-config.yml整个文件内容塞进 ConfigMap 的 data 字段里,后续挂载到容器里就会生成一个以此为文件名的文件。

4.2 部署的体会:先从 kubectl 命令说起

很多教程一上来就是让你写一大段 yaml,然后 apply,却不说清 kubectl 的基本用法。实际上 kubectl 的常用命令非常集中,我平时用得最多的就这几条:

  • kubectl apply -f <file>:创建或更新资源。
  • kubectl get <resource>:查看资源列表。
  • kubectl describe <resource> <name>:查看资源的详细事件和状态。
  • kubectl logs <pod-name>:查看 Pod 日志。
  • kubectl exec -it <pod-name> -- <command>:进入 Pod 内执行命令。
  • kubectl delete -f <file>:删除资源。

刚开始用的时候,我建议遇到问题先get看状态,再describe看事件,最后用logs看应用日志。这个排查顺序后面会专门展开。

4.3 挂载配置和存储的完整 yaml

接下来是核心部分。我把一个完整但不过度复杂的 Deployment 写出来,再逐段解释。

apiVersion: apps/v1 kind: Deployment metadata: name: demo-app namespace: default spec: replicas: 2 selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app spec: containers: - name: demo-app image: registry.example.com/demo-app:v1.0.0 ports: - containerPort: 8080 env: - name: APP_ENV value: "production" volumeMounts: - name: config-volume mountPath: /etc/demo - name: log-volume mountPath: /var/log/demo resources: requests: cpu: 200m memory: 256Mi limits: cpu: 1000m memory: 512Mi volumes: - name: config-volume configMap: name: demo-config - name: log-volume emptyDir: {}

我先说几个设计点,这些是我在实际项目中总结出来的习惯,不一定每个都绝对正确,但至少踩过几次坑之后证明好用:

  • 把配置文件和日志目录分开挂载。配置文件来自 ConfigMap,日志目录用emptyDir。emptyDir表示卷的生命周期跟 Pod 一致,Pod 删除后数据就没了。这里只是让你先跑通流程;如果日志要长期保留,应该换成持久卷(PersistentVolume)。
  • 资源 requests 和 limits 一定要写。不写的话,调度器不知道你的应用需要多少资源;线上如果某个节点被一个没限上限的容器打爆,整个节点都会进入不稳定状态。我遇到过不止一次"某个 Pod 把节点内存耗尽,其余 Pod 被驱逐"的事故,根因就是没写 limits。
  • 端口映射写在containerPort这里,它更像一个声明信息,真正的访问入口是通过 Service 暴露。

4.4 让外部能访问到:创建 Service

Deployment 只负责把 Pod 建起来,外部的访问还需要 Service。这里我用 NodePort 类型来演示,方便本地测试:

apiVersion: v1 kind: Service metadata: name: demo-service spec: type: NodePort selector: app: demo-app ports: - port: 8080 targetPort: 8080 nodePort: 30080

这样配置之后,通过任意节点的30080端口就能访问到 demo-app 了。selector里的app: demo-app要和 Deployment 里的 Pod 标签保持一致,这是 Service 能找到后端 Pod 的关键。

我整理一个常见的问题排查表格,对照着看会更快定位:

现象可能原因排查方法
Service 访问不通selector 和后端 Pod 标签不一致kubectl describe service看 Endpoints 是否有 IP
Endpoints 为空后端的 Pod 没起来或标签不匹配kubectl get pods --show-labels核对标签
Pod 一直 Pending节点资源不足或调度约束不满足kubectl describe pod看事件
Pod 一直 CrashLoopBackOff启动命令或配置有问题kubectl logs查看应用日志
镜像拉取失败镜像名写错或私有仓库未配置密钥kubectl describe pod的 Events 中会给出具体原因

4.5 存储升级:日志要持久化怎么办

上面用了emptyDir,Pod 一删日志就没了。如果日志需要留底,就要用到 PersistentVolumeClaim(PVC)和 StorageClass。在大多数集群里,StorageClass 已经由管理员预先创建,应用只需要声明 PVC:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: demo-log-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi

然后在 Deployment 的 volumes 和 volumeMounts 里换成 PVC:

volumes: - name: log-volume persistentVolumeClaim: claimName: demo-log-pvc

这样日志就会写到持久卷里,Pod 无论怎么重建,数据都能保留。这块的坑在于:accessModes 要和存储类型匹配。很多本地测试环境只支持 ReadWriteOnce;如果多个副本需要同时读写同一份数据,就要用 ReadWriteMany,但这个能力非常依赖底层存储实现,要先确认再使用。

5. 探针、滚动更新与优雅停机:保障发布的稳定性

学会了部署,下一步就是让发布过程更稳。这一节讲三个直接影响线上稳定性的机制:探针、滚动更新、优雅停机。

5.1 探针:别让流量打到"没准备好"的 Pod

探针(Probe)是 K8s 用来判断容器是否健康的机制,主要分三种:

  • 存活探针(livenessProbe):探测容器是否还活着。如果失败,kubelet 会杀掉容器并重启。
  • 就绪探针(readinessProbe):探测容器是否能够接收流量。如果失败,该 Pod 会从 Service 的 Endpoints 中摘除,不接收新流量。
  • 启动探针(startupProbe):给启动较慢的应用提供一个缓冲,避免存活探针在启动阶段误杀。

我见过一个很经典的事故:某应用启动需要 15 秒,但存活探针配置的initialDelaySeconds: 5、periodSeconds: 5,导致容器才启动 5 秒就被判定不健康,杀掉重启,重启后再被杀,陷入 CrashLoopBackOff。这其实是探针参数和业务实际启动时间不匹配造成的。

合理的配置思路是:先按经验估算启动时间,然后用启动探针给足缓冲,再用就绪探针决定何时接入流量。下面是一个示例:

livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 3 startupProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 30

failureThreshold: 30配合每 5 秒一次,能容忍最长 150 秒的启动时间,对大多数应用来说已经足够。探针的路径一定要是应用真正能反映健康状态的接口,而不是一个写死的 200 页面,否则探针形同虚设。

5.2 滚动更新:让发版不中断业务

Deployment 默认就支持滚动更新,但很多人没有理解它的更新策略,导致发布过程中出现短暂不可用。

默认策略是RollingUpdate,参数包括maxUnavailable和maxSurge。前者表示更新过程中最多允许有多少个 Pod 不可用;后者表示最多允许超出期望副本数多少个 Pod。默认值是 25% 左右,也就是说如果你的副本数是 4,更新时会先拉起 1 个新 Pod,等它就绪后再下线 1 个旧 Pod,依次类推。

我自己实践的推荐配置是:

strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1

对于副本数不多的小服务,这个配置简单可控:最多同时有 1 个旧 Pod 停掉和 1 个新 Pod 启动,不会出现大规模不可用。副本数很大时,用百分比扩张会更方便。

需要注意:滚动更新的前提是新 Pod 能正常变成 Ready。如果新版本镜像启动就崩溃,Deployment 会一直卡在更新中,不会把旧的全部替换掉。配合 ready 探针,能大大提高发布安全性。

5.3 优雅停机:让请求不中断

很多人在容器里直接忽略了 SIGTERM 信号的处理。K8s 删除 Pod 时,会先发送 SIGTERM 信号,然后等待宽限期(默认 30 秒)后再发送 SIGKILL 强制杀死。如果你的应用不处理 SIGTERM,它可能直接退出,导致正在处理的请求被中断。

正确的做法是让应用监听 SIGTERM,先停止接收新请求、处理完存量请求,再退出。对于 Web 应用来说,这通常意味着从负载均衡摘除、等待存活请求完成、最后关闭连接。K8s 还提供了terminationGracePeriodSeconds字段,可以按业务需要调整宽限期长度。

spec: terminationGracePeriodSeconds: 30

如果业务里有长轮询或者比较慢的请求处理,这个值可以适当调大。但调大不等于无脑调——如果 60 秒内还处理不完,要么设计上有问题,要么宽限期再长也只是推迟问题。

6. 线上踩坑实录:六个高频故障和排查思路

这一节可能是全篇最实用的部分。我把自己在真实环境里踩过的坑、帮别人排查过的问题,挑出六个高频场景,按"现象-排查-根因-规避"的方式写出来。

6.1 镜像拉取失败:从 ImagePullBackOff 说起

现象:Pod 状态是ImagePullBackOff,事件里显示拉取镜像失败。

排查链路:

  1. kubectl describe pod <pod-name>,查看 Events。
  2. 区分是镜像名不存在、私有仓库未认证,还是网络原因。

我在内网环境遇到过一种情况:镜像在私有仓库里,Pod 一直拉取失败,原因是部署 Pod 所在的节点上没有配置仓库的登录凭证。解决方式是在命名空间里创建 Secret,然后在 Deployment 的imagePullSecrets里引用:

spec: imagePullSecrets: - name: registry-secret

另外,如果用了latest标签,要考虑拉取策略。默认的拉取策略是IfNotPresent,即本地存在就不拉。但某些场景下你需要强制每次拉取,可以改成Always。一个合理的习惯是:开发测试用latest方便迭代,生产环境固定版本号,避免不可控的漂移。

6.2 Pod 一直 Pending:节点资源不够还是调度约束不对

现象:kubectl get pods看到 Pod 一直 Pending,很长时间不变化。

排查思路:

  1. kubectl describe pod看 Events,通常能看到"0/3 nodes are available"以及具体原因。
  2. 常见原因包括:节点 CPU/内存不足、污点(Taint)不匹配、节点选择器(nodeSelector)没有节点匹配、PVC 未绑定。

一个让新手很困惑的场景是:明明每个节点都有空闲资源,Pod 还是 Pending。这时候要检查 CPU 和内存的 requests,因为调度器判断的是 requests,而不是实际使用量。如果你把 requests 设置得特别高(比如给一个小工具设 2 核 4G),节点上其实已经放不下这个"规格"的 Pod。这是 requests 设计不合理导致的调度失败。

6.3 CrashLoopBackOff:启动即崩溃的循环

现象:Pod 反复处于CrashLoopBackOff,说明容器启动后崩溃,然后被重启,再次崩溃。

这类问题要先分清是"启动后立刻退出"还是"启动一段时间后 OOMKilled":

  • 立刻退出:多半是应用配置错误,比如连不上数据库、读取配置文件失败。用kubectl logs --previous查看上一次崩溃的日志。
  • OOMKilled:容器内存超过 limits,被内核杀掉。kubectl describe pod里会显示OOMKilled状态。这种情况要审视是不是 limits 设置过低,或者应用存在内存泄漏。

我第一次写 requests 和 limits 时,把 limits 压得太紧,结果应用在流量高峰直接被 OOM 杀掉,一个"健康"的 Pod 完全没有抗压能力。后来我把内存 limits 调高,并且加了就绪探针,流量高峰才稳定下来。这里不是让你无限调高 limits,而是说limits 应该基于压测数据来设置,而不是拍脑袋。

6.4 Service 有了但访问不了:Endpoints 是排查的钥匙

现象:Service 存在,Pod 也 Running,但外部访问不通。

排查顺序:

  1. kubectl get endpoints,看这个 Service 有没有关联到 Pod IP。
  2. 如果 Endpoints 为空,检查 Service 的 selector 与 Pod 的标签是否匹配。
  3. 如果有 Endpoints,但访问还是不通,检查 NodePort 是否被防火墙挡了,或者网络策略是否拦截。

这里有一个很多人踩过的细节:Service 的targetPort要和容器实际监听的端口一致。如果容器监听 8080,而 Service 的 targetPort 写成了 80,流量会被转发到一个不存在的地方,Pod 虽然健康,但请求全部失败。这类问题单看get不好发现,一定要用describe或者实际请求去验证。

6.5 更新之后 Pod 没变:镜像拉取策略的默认陷阱

现象:我改了镜像 tag,apply之后 Pod 并没有拉取新镜像。

原因:如果你没改镜像 tag(比如一直用latest),且镜像在节点本地已存在,Pod 不会重建。Deployment 根本不知道镜像是否发生了变化。

此时有两个选择:

  • 显式把imagePullPolicy设为Always。
  • 或者修改镜像 tag 指向新版本,触发滚动更新。

我更推荐第二种,因为固定 tag 会让版本可追溯,出问题时能快速定位"线上跑的是哪一版镜像"。latest看起来方便,但在团队协作中很容易造成"我以为线上跑的是这个版本,其实是另一个"的尴尬。

6.6 节点 NotReady:别慌,先看 kubelet

现象:kubectl get nodes显示某个节点 NotReady。

排查链路:

  1. 登录到该节点,检查 kubelet 是否运行正常。
  2. 查看 kubelet 日志,常见问题是证书过期、资源耗尽、容器运行时沟通失败。
  3. 如果控制面正常,可以考虑重启 kubelet 服务,让节点重新注册。

这里我不想给一个"万能修复命令",因为 NotReady 的根因差异很大。但我可以给一个非常重要的原则:任何时候都不要直接kubectl delete node去解决 NotReady。这个操作会把节点从集群里删掉,而节点上如果有状态型应用,恢复成本会很高。正确的做法是排查根因,恢复节点,让 kubelet 重新上报状态。

7. 按规模迭代:从 10 个服务到 100 个服务,配置思路的转变

很多团队在 K8s 上跑通第一个服务之后,会陷入"能用就行"的状态。但集群规模和业务复杂度上来之后,很多早期"图省事"的做法会变成定时炸弹。

7.1 命名空间的划分

刚开始把所有服务丢在 default 命名空间,20 个服务以内问题不大。服务多之后,你会开始头疼这几个问题:

  • 同名资源冲突。
  • 权限难以划分。
  • 日志和监控不好归类。

我建议在服务数量变多的初期就按环境(dev、prod)或者按业务域(order、payment、user)划分命名空间。命名空间的隔离成本很低,但它带来的组织收益很高。配合 RBAC(基于角色的访问控制),你还能做到不同团队只能操作自己的命名空间。

7.2 标签是长期运维的地基

标签(Labels)是一个被严重低估的功能。它本质上是资源的元数据,但用好了,可以非常灵活地选择和管理资源:

  • 按环境:env=production、env=test
  • 按业务域:app=order-service
  • 按版本:version=v2.0.1

一条kubectl get pods -l app=order-service,env=production就能精确筛出生产环境订单服务的所有 Pod。配合换标签,在 A/B 测试和流量灰度上也有发挥空间。

7.3 从逐个管理到声明式治理

在规模变大后,我最推荐的一个习惯变化是:不要让 kubectl 变成日常手工操作的首选。YAML 文件放进版本库,用 CI/CD 管道执行 apply,结合代码评审,让每一次集群变更都可追踪。

我见过不少团队用kubectl edit deployment在线改配置,改完之后没有任何记录,出问题完全不知道谁在什么时候改了什么。这是非常危险的。把配置当成代码来管理,是 K8s 运维走向正规化的必由之路。

7.4 成本与治理:宁可"不够用",不要"不敢动"

最后说一个有点反直觉的经验:在资源治理上,我犯过的最大的错误不是资源不够,而是资源 requests 设置过高导致资源被白白浪费。

举个例子,一个服务实际只用了 256Mi 内存,但 requests 写在 1Gi,集群能调度的 Pod 数量就减少了一大截,更多的计算资源被打上"已分配"的标记却没有被真正使用。正确做法是:

  1. 先用监控数据计算服务的真实用量。
  2. requests 设为略高于平时的峰值,limits 设为应对突发流量的上限。
  3. 定期复查,用水平 Pod 伸缩(HPA)按 CPU 或自定义指标自动调节副本数,而不是手动加副本。

HPA 的配置不算复杂,核心是定义目标指标和副本范围:

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-web-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-web minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60

这段配置的含义是:让 demo-web 的 CPU 平均使用率维持在 60% 左右,副本数在 2 到 10 之间自动伸缩。HPA 让"高峰期自动扩容、低谷期自动缩容"变成现实,但前提是 Pod 的 requests 有真实参考意义——如果 requests 明显高于实际,HPA 看到的利用率会普遍偏低,扩容策略也就不准了。

最后再分享一点个人体会

说实话,从我在真实项目里维护 K8s 的体会来说,这套系统最难的从来不是某个 yaml 怎么写,而是你要从"盯着单个容器"的思维转换到"管理一组容器"的思维。你的重心不再是怎么启动某个进程,而是描述清楚期望状态、定义好健康标准、设置合理资源边界,然后让平台去收敛。每次 Pod 被重新调度、应用自动恢复、新版本平滑发布,我都会有那么一点感慨——这种"程序托底、人只做决策"的方式,才是 K8s 真正值钱的地方。

建议你先在小集群里把本文提到的 Deployment、Service、ConfigMap、PVC、探针、HPA 逐个跑一遍,遇到问题就按"看事件、看日志、查 Endpoints"的顺序去排查。等这些对象都跑顺了,再回头看它在整个研发流程里的位置,你会对容器编排这件事有完全不一样的理解。K8s 的功能远不止这些,但你已经把最核心的地基打牢了,后面无论是做有状态服务、可观测性还是多集群管理,都不会太慌。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询