讲讲k8s的一些高级用法
延续我们前面聊的内容,这次讲一些更进阶的 Kubernetes 用法,大体上可以分成几类:更复杂的工作负载类型、精细化的调度控制、自动化扩展能力、流量治理、以及围绕 Kubernetes 生态发展出来的一整套自动化运维体系。
更复杂的工作负载类型
前面举的例子一直是 Deployment,它假设 Pod 之间是无状态、可以随意替换、顺序不重要的,但现实里很多场景不满足这个假设。StatefulSet 就是为有状态应用设计的,比如数据库集群(MySQL 主从、Elasticsearch、Kafka 这类)。它和 Deployment 最大的区别是:每个 Pod 会拿到一个稳定不变的编号和网络标识(比如 mysql-0、mysql-1、mysql-2),即使 Pod 被删除重建,新 Pod 还是叫这个名字、还是挂载原来那块存储卷,不会像 Deployment 那样随机分配一个新名字、丢失原来绑定的存储;而且 StatefulSet 创建和删除 Pod 是严格按顺序来的(先 0 后 1 再 2),这对很多需要按顺序初始化的集群化软件是必需的。
DaemonSet 解决的是另一类问题:某些程序需要在集群里每一台节点上都跑一份,而不是跑几个副本随便分布到哪。典型场景是日志采集agent(比如 Fluentd)、监控 agent(比如 node-exporter)、网络插件本身,这些都得在每台机器上都有一个实例才能采集到完整数据,DaemonSet 会自动确保集群里每加入一个新节点,就自动在上面调度一份对应的 Pod,节点被移除时也自动清理掉。
Job 和 CronJob 处理的是”跑完就结束”的一次性或周期性任务,和前面几种”要一直运行、维持副本数”的负载逻辑不同。Job 保证某个任务运行到成功完成为止(可以配置失败重试次数),适合批处理、数据迁移这类一次性任务;CronJob 则是在 Job 基础上加了定时调度能力,写法和 Linux 的 crontab 表达式一样,适合定期备份、定期清理这类周期性工作。
调度控制:让 Pod 落到你想要的位置
前面 kubectl describe pod 的输出里其实已经埋了两个线索——Tolerations(容忍度)和 Node-Selectors,这两个都属于调度控制的范畴,进阶用法比那次看到的默认配置要丰富得多。亲和性(Affinity)分节点亲和性(Node Affinity)和 Pod 亲和性/反亲和性(Pod Affinity/Anti-Affinity)两类:节点亲和性是”我要求 Pod 只能调度到带有某些标签的节点上”,比如只调度到 GPU 节点;Pod 反亲和性是”我要求这个 Pod 不要和另一批特定的 Pod 挤在同一个节点上”,最常见的用法是让同一个应用的多个副本强制分散到不同节点甚至不同可用区,这样一台机器宕机不会把这个应用的所有副本一锅端。污点(Taint)和容忍度(Toleration)是反过来的思路:污点是打在节点上的排斥标记,意思是”默认情况下不允许 Pod 调度到我这里”,容忍度是打在 Pod 上的”我能接受这个污点”,两者配合可以实现专用节点池(比如给某类污点标记的节点只给特定团队用)、或者像我们之前看到的默认容忍那样,允许 Pod 在节点短暂失联时先不着急被驱逐。更极端的情况下,如果内置调度器的调度逻辑满足不了需求,还可以写自定义调度器(Custom Scheduler),替换掉默认的调度决策逻辑,大规模互联网公司在超大规模集群、有复杂资源画像的场景下会这么做。
自动化扩展能力
前面深入聊过 HPA(按 CPU/内存等指标横向扩缩 Pod 数量)和 Cluster Autoscaler(按节点资源压力增减 VM),这里再补一个 VPA(Vertical Pod Autoscaler,垂直扩缩容),它解决的是另一个问题:HPA 调整的是副本数量,VPA 调整的是单个 Pod 该分配多少 CPU 和内存这个 requests/limits 的具体数值。VPA 会持续观察容器实际的资源使用情况,发现你一开始配置的 requests: cpu: 100m 明显不够用,就自动帮你把这个数值调高(需要重建 Pod 才能生效),避免了人工凭经验猜资源配置、猜少了导致被限流、猜多了导致浪费的问题。HPA 和 VPA 通常不建议对同一个指标同时生效(比如都用 CPU 做判断依据),容易互相打架,实践中要规划好谁管哪个维度。
流量治理:服务网格
Service 只解决了”怎么找到后端 Pod”这个基础问题,但更复杂的流量场景,比如按比例灰度发布(1% 流量先切新版本)、服务间调用的自动重试和熔断、双向 TLS 加密、调用链路追踪,靠 Service 本身是做不到的。服务网格(Service Mesh,最常见的实现是 Istio、Linkerd)的做法是给每个 Pod 自动注入一个叫 Sidecar 的额外容器(通常是个轻量代理,比如 Envoy),所有进出这个 Pod 的流量都先经过这个 Sidecar,应用代码本身完全无感知。因为所有流量都被这层代理拦截了,就可以在这一层统一实现流量切分、失败重试、超时控制、加密、监控,而不需要每个业务团队自己在代码里造轮子。这也是”Sidecar 模式”这个词的由来,前面聊 EA 数据平台用 Airflow 时提到的 Sidecar 同步 DAG 文件,本质上是同一种设计模式的不同应用场景。
Operator 和 CRD:把运维知识写成代码
前面聊 EA 的 KubeCon 演讲时提过这个概念,这里补充展开。CRD 让你可以往 Kubernetes 里注册全新的资源类型,Operator 则是监听这些自定义资源、并据此自动执行一整套运维动作的控制器。这套模式最初是社区为了解决”怎么用 Kubernetes 优雅地管理数据库这类有状态、有复杂运维流程的软件”而发展出来的:比如装一个 Postgres Operator 后,你只需要提交一个 apiVersion: postgres.io/v1, kind: PostgresCluster 的自定义资源,声明”我要一个 3 节点的 Postgres 集群、带自动备份”,Operator 就会自动完成建 StatefulSet、配置主从复制、设置定时备份任务这些原本需要 DBA 手动操作的复杂流程。可以理解成 Operator 是把某个领域专家的运维经验(怎么做故障转移、怎么做备份恢复)写成了自动化代码,这也是为什么现在几乎所有复杂中间件(Kafka、Redis、Elasticsearch、各类数据库)都有对应的 Operator。
GitOps:用 Git 驱动集群状态
前面提到 EA 的框架里用了 GitOps,这里稍微展开。核心理念是把集群该有的状态(用哪个镜像版本、多少副本、什么配置)全部写成 YAML 存在 Git 仓库里,Git 仓库变成”唯一真相来源”。集群里跑着一个专门的控制器(常见的是 ArgoCD 或者 Flux),持续对比 Git 仓库里声明的状态和集群实际状态是否一致,一旦有人往 Git 提交了新的镜像版本号,这个控制器会自动检测到差异,把变更同步应用到集群上,不需要人登进集群手动执行 kubectl apply。好处是每一次变更都有 Git 的提交记录,天然可审计、可追溯、出问题了可以直接 git revert 回滚;而且因为唯一的变更入口是 Git,杜绝了有人手动登录集群”临时改一下”结果没人知道改了什么这种漂移问题。
灰度发布:比滚动更新更精细的发布策略
原生的滚动更新(RollingUpdate)只能做到”逐步替换,新旧版本混跑一段时间”,但没法做”先放 5% 流量到新版本观察半小时,没问题再逐步加到 100%”这种更精细的灰度策略,也没法在发现问题时按流量比例自动回滚。社区在这个基础上发展出了 Argo Rollouts、Flagger 这类工具,通常要配合服务网格或者 Ingress 控制器一起用,可以定义”金丝雀发布”(Canary,小比例导流观察指标,逐步放量)或者”蓝绿发布”(Blue-Green,新旧两个环境完全并存,验证通过后一次性切流量、旧环境保留一段时间方便快速回退)这类更贴近大公司实际发布流程的策略,还能接入 Prometheus 这类监控系统,让”新版本错误率超过阈值就自动回滚”变成完全自动化的动作,不需要人工盯着仪表盘做决策。
准入控制:在资源创建之前就拦下不合规的配置
前面聊的 RBAC 解决的是”谁能做什么操作”的问题,但没解决”即使你有权限提交,这个配置本身是不是符合规范”的问题,比如公司规定所有容器不能用 latest 这种不确定的镜像标签、不能以 root 用户运行,这类规则没法用 RBAC 表达。准入控制器(Admission Webhook)提供了一个钩子,在资源真正被写入 etcd 之前,先把这个请求转发给一个自定义的校验服务,由这个服务决定”放行”还是”拒绝并报错”。OPA Gatekeeper 是这个领域最常用的开源工具之一,允许用一种叫 Rego 的策略语言写各种校验规则,统一拦截所有不合规的提交,是大公司做安全合规、成本治理(比如强制要求所有 Pod 必须写明资源 requests)的常见手段。
这些进阶能力不是每个团队都需要一次性全上,通常是随着集群规模变大、团队变多之后,遇到具体痛点(状态管理、流量精细控制、发布风险、合规审计)才逐一引入的。如果你对某一块特别感兴趣,比如想深入某个具体工具(Istio、ArgoCD、Operator 怎么写),我可以针对性展开。
