ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kubernetes实战:从控制器到HPA,掌握应用部署与弹性伸缩核心机制

Kubernetes实战:从控制器到HPA,掌握应用部署与弹性伸缩核心机制 k8s一聊了基础概念k8s二讲了集群怎么搭起来这篇系列第三篇我想集中回答一个我后台被问过无数次的问题集群装好了nodes 全部 Ready然后呢很多人的卡点就在这里。kubectl get nodes三个节点整整齐齐 Ready但真到部署应用的时候面对 Deployment、Service、Ingress、HPA 这一堆概念东一榔头西一棒子地照着网上教程敲命令结果一个应用折腾两天也起不来或者起来了访问不通排错也无从下手。这篇文章我换个讲法不按官方文档的顺序罗列概念而是按照一个应用从“部署进去”到“稳定运行”再到“能被监控、能扛流量”的完整链路把 k8s 真正核心的机制讲透。如果你手头有《Kubernetes 权威指南》第五版可以对照着相关的控制器和网络章节一起看比单纯翻文档要更容易串起来。1. 控制器的调谐循环k8s 的“自动驾驶”是怎么实现的1.1 期望状态与现实状态之差控制器唯一关心的事理解 k8s 的控制器机制是我认为从入门到进阶最关键的一道坎。很多人用 k8s 停留在“照着 YAML 跑应用”的层面不知道为什么这个系统能自动恢复故障、为什么 Deployment 能滚动更新、为什么 Pod 挂了会自动拉起一个新 Pod。这些能力全部来自同一个底层设计控制器模式Controller Pattern。这个模式的核心思想特别朴素用户告诉集群一个“期望状态”比如“我要 3 个 Nginx 副本”然后控制器负责不断地把“当前状态”往“期望状态”上靠拢。当前状态是 2 个副本控制器就去创建一个 Pod当前状态是 4 个副本控制器就杀掉一个某个 Pod 所在节点宕机了当前状态变成了 1 个副本控制器就立即在其他可用节点上补一个新的。这个“不断对比、不断修正”的过程叫调谐循环Reconcile Loop。我在给团队做内部培训时经常用一个类比这就像空调的温控器。你设定 26 度是期望状态温度传感器是 API Server压缩机是控制器。温度高于 26 度压缩机启动制冷温度低于 26 度压缩机停止。整个过程周而复始不需要人工干预。k8s 里的控制器本质上就是这么个东西只不过它控制的对象不是温度而是 Pod、Service、节点这些资源。每个控制器都有一个固定的调谐循环不停地从 API Server 获取状态对比期望状态和当前状态然后执行操作让它们趋近一致。1.2 Deployment、ReplicaSet、Pod 之间的三层嵌套很多人对 Pod、ReplicaSet、Deployment 这三个概念之间的关系搞不清楚。我之前遇到一个读者直接在 YAML 里写一个独立的 ReplicaSet 来管理 Pod结果发现更新镜像版本的时候根本没反应因为 ReplicaSet 这个资源本身不支持滚动更新。这就要说到 k8s 里控制器的“套娃”关系了。实际使用中绝大多数场景你只需要操作 Deployment它在底层会自动帮你构建 ReplicaSet而 ReplicaSet 再负责创建和管理 Pod。层级关系是这样的Deployment负责声明“我要什么”包括镜像版本、副本数、更新策略。它不直接创建 Pod而是创建 ReplicaSet。ReplicaSet负责维持固定数量的 Pod 副本。它也不直接创建 Pod而是通过 API Server 创建 Pod 对象。Pod最小的调度单元里面可以有一个或多个容器。当你执行kubectl scale deployment nginx --replicas5的时候Deployment 控制器接收到这个指令会去调整底层 ReplicaSet 的副本数期望值RS 控制器再创建或删除 Pod。理论上 Deploy - RS - Pod 是三层结构但实际调试中需要反向看Pod 出问题了先看它属于哪个 ReplicaSet再看这个 RS 属于哪个 Deployment层级关系一目了然。我排错时的第一条命令永远是kubectl get deploy,rs,pod -o wide一眼就能看出哪一层出了问题。1.3 控制器崩溃了会怎样一次真实的故障复盘我自己踩过一次很典型的控制器问题当时是在一个测试环境里有人误操作把kube-controller-manager这个核心组件停掉了。这个组件是干嘛的呢它是所有内置控制器的集合Deployment 控制器、RS 控制器、Namespace 控制器都在这里面跑。它一停集群不会立刻瘫痪。因为已经运行着的 Pod 不会消失kubelet 还在节点上正常干活。但等你杀掉一个 Pod或者某个节点宕机你就会发现Pod 一直处于 Terminating 或者 Pending 状态就是没人管它。已经存在的资源能被请求访问但任何“变化”都不会发生。那次排查的链路我记得很清楚测试环境反馈新发版本一直起不来Pod 卡在 ContainerCreating。看 kubelet 日志发现它尝试调用 API Server 创建容器但是在等待调度器调度。再看 scheduler 是正常的怀疑 controller-manager 出了问题。一查进程发现kube-controller-manager确实挂了。重启之后所有积压的 Pod 请求在一分钟内全部处理完毕。这个经历给我的教训很直接控制器组件的健康状态比 Pod 本身的状态更能决定集群的可用性。监控 k8s 集群时不要让监控焦点全放在业务 Pod 上controller-manager、scheduler、etcd 这些控制面组件的存活状态才是首先要盯住的。2. 工作负载选型一个应用该用哪种控制器跑2.1 无状态应用首选 Deployment但别忽略 replicas 的合理值理解了控制器机制之后面临的第一个实际问题就是我的应用该用哪种工作负载类型跑这个决策直接影响应用的可用性和维护成本。我在帮别人评审部署方案时见过最典型的问题就是不管什么应用一律 Deployment包括数据库。这是不对的。先说 Deployment。它最适配的是无状态应用API 服务、Web 前端、消息消费者这类任何一个副本都能独立处理请求不需要特殊网络标识数据不存本地或者数据可以走外部存储。无状态应用部署上有几个容易忽略的点副本数不要拍脑袋定。很多人在测试环境直接 replicas: 1然后忘了改就上生产。应用只有一个副本节点一挂服务全挂这和有状态应用没有区别。生产环境至少 2 个副本起步这是最基本的容错保障。更新策略要明确配置。Deployment 默认是滚动更新maxUnavailable默认 25%maxSurge默认 25%。但这个默认值对很多应用来说并不合适。比如某些应用启动时需要做数据迁移或加载大量缓存启动时间超过 60 秒滚动更新时就会出现新旧副本数量加起来不足的情况。我一般喜欢显式配置strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1这样保证最多只有一个副本短暂不可用也不会同时起来太多新副本把集群资源打爆。2.2 StatefulSet数据库进了 k8s网络标识和存储怎么保住有状态应用的情况就复杂多了。StatefulSet 存在的意义是给 Pod 提供稳定的网络标识和稳定的存储。稳定网络标识是什么意思Deployment 创建的 Pod 名字是随机后缀比如nginx-7c9b5f54b6-abcde每次重建名字都会变。而 StatefulSet 创建的 Pod 名字是有序的、固定的比如mysql-0、mysql-1、mysql-2并且每个 Pod 会获得一个固定的 DNS 名称。这个固定 DNS 名称在数据库场景下太关键了。举个例子我帮人把一个微服务项目迁移到 k8s 时用的是 MySQL 一主一从。从库要能稳定地连接主库进行主从同步如果主库 Pod 名字老是变从库的配置文件就没法写。StatefulSet 给的解决办法是主库 Pod 的 DNS 名称固定为mysql-0.mysql.default.svc.cluster.local不管 Pod 重启多少次、被调度到哪个节点这个名称都能解析到它。存储方面StatefulSet 配合 PersistentVolumeClaim 模板使用每个 Pod 挂载独立的 PVC。Pod 被重新调度后由于 Pod 名字不变PVC 绑定关系也能保持数据不会丢。一个需要特别提醒的点StatefulSet 的滚动更新是有序的。默认是从mysql-0开始一个一个更新如果没有特殊配置这个串行更新在大规模集群里会非常慢。我见过一个 10 节点的数据库集群做一次版本升级花了将近两小时。如果有信心保证兼容性可以把更新策略设置成:updateStrategy: type: RollingUpdate rollingUpdate: partition: 0同时把 podManagementPolicy 改成Parallel让 Pod 可以并行启停。不过再强调一句k8s 上跑数据库类应用必须对存储方案、数据备份策略、故障恢复流程有完整的预案否则在生产环境里玩早晚出事故。除非你用的是 Operator 这类带专业管理能力的方案你只是用 StatefulSet 裸跑数据库我在实践中发现它不是一个轻易能驾驭的活。2.3 DaemonSet 和 Job/CronJob被低估的两种工作负载Deployment 和 StatefulSet 之外还有两种工作负载我建议每个 k8s 使用者在生产环境里认真对待它们经常被忽略但用好了能解决很多基础设施问题。DaemonSet的语义是“每个节点跑一个 Pod”。它适合日志采集、节点监控、网络插件这类和节点强相关的组件。比如Filebeat/Fluentd采集每个节点上的容器日志。Node Exporter采集宿主机监控指标。kube-proxy 本身也就是个 DaemonSet。它的好处是你不需要关心节点数量集群扩容后新加入的节点会自动补上对应的 Pod。迁移后应用可以正常工作而你只需要关注业务层的运行状态。Job 和 CronJob用来处理一次性任务和定时任务。数据迁移、批量导入、临时清理这些任务推荐用 Job 而不是手动kubectl exec到 Pod 里执行。一个常见的反面教材是有人直接在某个正在运行服务的容器里挂 cron 来跑定时任务结果任务依赖的库和主服务冲突或者任务占用资源导致主服务变慢。用 CronJob 跑定时任务的好处是任务运行在独立 Pod 里资源和生命周期完全隔离不会污染主服务。CronJob 在 k8s 里的写法是标准的 cron 表达式但注意它有自己的时间解释逻辑默认时区是 UTC。如果你有个任务需要在每天凌晨 2 点跑而服务器时区是东八区直接写0 2 * * *会在 UTC 凌晨 2 点也就是北京时间上午 10 点执行。这个坑我踩过一次后来统一在 CronJob 里加了时区配置才解决。2.4 我的建议先画一张应用架构图再选工作负载每次在选型上犹豫不决时我的做法是先画一张应用架构图标注出每个组件的状态属性。画图的时候问三个问题这个组件能不能被无差别替代任何一个副本挂掉另一个副本能否无缝接管如果能用 Deployment。这个组件是否需要固定的网络标识或独立的持久化存储如果需要用 StatefulSet。这个组件是不是每个节点都需要如果是用 DaemonSet。举个例子我之前帮人把若依这类单体微服务项目部署到单节点 k8s 上整套环境包括了网关、认证服务、业务服务、MySQL、Redis、Nginx。画完架构图之后方案非常清晰无状态的网关和业务服务用 DeploymentMySQL 用 StatefulSet 挂块存储Redis 如果只是缓存且能容忍丢失用 Deployment 就够了Nginx 用 Deployment 暴露端口即可。只花了一个下午就把整套环境搭好了后续扩容也只是改个 replicas 数量的事。3. 从 Pod 到外部流量Service、Endpoints、Ingress 的完整链路3.1 为什么 Pod IP 不能直接拿来用重建即失效的痛点应用部署进去了下一步是让它被访问到。这个环节是很多人踩坑最密集的地方核心原因是没搞清楚 Service 在这条链路里的位置。直接访问 Pod IP想法很朴素但不可行原因有两个第一Pod IP 是临时的。Pod 被删除、节点故障、应用崩溃重启都会导致 Pod IP 变化。如果你在代码里写死了某个服务的 Pod IP这个服务一重启你的代码就找不到它了。第二Pod IP 是集群内网地址从集群外部根本不可达。所以 k8s 引入了 Service 来做一层抽象。Service 有三个关键作用提供一个稳定的虚拟 IPClusterIP后端 Pod 挂了Service 会自动摘除它的 IP。提供负载均衡能力把请求分发到后端多个 Pod。给 Pod 提供稳定的 DNS 名称应用之间可以通过服务名互相访问。3.2 ClusterIP、NodePort、LoadBalancer三种 Service 类型怎么选Service 有几种类型选型逻辑和前面选择工作负载类似先看场景。Service 类型适用场景访问方式底层实现ClusterIP集群内部应用间互相调用集群内可通过 Service 名称或 ClusterIP 访问kube-proxyNodePort需要从集群外部访问但没有现成负载均衡通过任意节点 IP 指定端口访问kube-proxy 宿主机端口LoadBalancer云环境或已有外部负载均衡器通过云平台分配的负载均衡 IP 访问云负载均衡器 NodePort绝大多数互联网应用集群内部互相调用都用 ClusterIP外部访问走 Ingress 或 LoadBalancer。NodePort 常用于测试环境临时访问或者做一步转发。这里有一个很容易被绕晕的技术点Service 的负载均衡是怎么实现的Service 本身不处理请求它是一个抽象对象。真正做流量转发的是每个节点上的kube-proxy组件。kube-proxy 通过 iptables 或 IPVS 规则把目标为 Service IP 的流量转发到后端某个具体的 Pod IP。有一个排错技巧值得分享当你发现 Service 访问不通但 Pod 是正常的第一时间检查 Service 的 Endpoints 是否为空。kubectl get endpoints service-name如果 Endpoints 列表是空的说明 Service 的 selector 没有匹配到任何 Pod。这种问题 90% 是因为 YAML 里的 label 写错了。其次再检查 kube-proxy 是否正常运行因为如果节点上 iptables 规则没生成Service IP 就是不通的。3.3 Ingress 是七层入口不是另一种 Service很多人分不清 Ingress 和 Service 的区别以为 Ingress 是 Service 的一种其实两者是完全不同的抽象层级。Service 是 L4 层传输层的负载均衡它不理解 HTTP 路径和域名只负责把 TCP/UDP 流量转发到后端 Pod。而 Ingress 是 L7 层应用层的入口它理解 HTTP 协议可以做到根据域名把请求转发到不同的 Service。根据 URL 路径把请求转发到同一个 Service 的不同路径。配置 TLS 证书实现 HTTPS 终止。注意Ingress 只是一个 API 对象它本身不干活。真正干活的是 Ingress Controller比如 Nginx Ingress Controller、Traefik。Ingress Controller 本身也是一个 Deployment跑在你的集群里。它读取 Ingress 资源定义的规则动态生成 Nginx 配置文件并完成实际的流量转发。我遇到过不少人在集群里创建了 Ingress 资源但访问域名的 IP 依然打不开最后发现集群里根本没装 Ingress Controller。这就好比你在墙上画了一个开关面板但后面根本没有布线灯当然不会亮。3.4 一次 Service 调试实录DNS 解析、kube-proxy、iptables 的配合分享一次我实际碰到并排查完整的 Service 问题这条链路涵盖了 DNS、Service、kube-proxy 的配合过程排查思路值得记录。当时有一个应用 A 要调用应用 BA 容器里访问http://b-service:8080但一直报Connection Refused。B 的 Pod 明明是 RunningService 也建了。我的排查步骤先进 A 所在的 Pod用nslookup b-service看 DNS 解析。结果解析出了正确的 ClusterIP说明 CoreDNS 没问题。在 A 的 Pod 里直接 curl ClusterIP:8080发现也连不上说明问题不在 DNS而在 Service 到 Pod 的转发链路上。kubectl get endpoints b-service一看Endpoints 里居然没有记录后端的 Pod IP。再看 B 的 Service YAML发现 selector 写的是app: b-service而 Pod 的 label 是app: bapp完全不匹配。改好 label 之后Endpoints 立刻有了 Pod IPA 访问 B 恢复正常。这个案例说明一个道理k8s 网络排错要按层来。先看 DNS 解析再看 Service 到 Endpoints最后再看 kube-proxy 规则。如果你从下往上乱试可能调了半天发现是配置拼写错误白白浪费时间。4. 高并发场景的支撑HPA 弹性伸缩实战4.1 从 metrics-server 到 HPA指标怎么流转应用能通了下一个绕不开的话题就是来突发流量了怎么办k8s 里处理高并发最核心的组件就是HPAHorizontal Pod Autoscaler水平 Pod 自动伸缩器。HPA 做的事情很直接持续监控 Pod 的资源使用率当指标超过阈值时自动增加副本数低于阈值一段时间后自动减少副本数。但 HPA 本身是收集不到指标的它从哪拿数据这就要提到一个底层的组件metrics-server。它负责从每个节点的 kubelet 上采集 CPU 和内存使用数据然后通过 API Server 提供给 HPA 使用。整个链条是metrics-server 从 kubelet 收集节点和 Pod 的 CPU、内存使用量。HPA 定期向 API Server 查询 metrics API 接口。HPA 计算当前资源使用率和设定阈值的比例。根据比例调整 Deployment 的 replicas 数量。底层的 Deployment 控制器创建或删除 Pod。如果你没有安装 metrics-server执行kubectl top nodes都会报错更别说配置 HPA 了。4.2 CPU/内存之外自定义指标扩缩容的扩展思路CPU 使用率是最常见的 HPA 指标但生产环境里很多场景不适合用 CPU 来判断。比如消息消费者这类应用它的瓶颈往往是消息积压数量而不是 CPU 占用率。这时候就需要自定义指标。自定义指标实现方案有很多我只说最常用的那套。在 k8s 生态里自定义指标通常通过 Prometheus Adapter 实现。它的逻辑是Prometheus 采集应用暴露的业务指标Prometheus Adapter 定期从 Prometheus 查询指标值然后以自定义指标 API 的形式暴露给 HPA。比如你想实现“当消息队列积压超过 1000 条时消费者副本数扩到 10”流程是应用暴露mq_consume_lag指标给 Prometheus。Prometheus Adapter 配置一个规则把mq_consume_lag暴露为可用的自定义指标。HPA 配置type: Object指标名指向mq_consume_lag。这个配置稍微有点复杂但理解了整条数据链路后排查问题就很顺畅。我遇到过的最常见的问题是 Prometheus 里有指标但 HPA 拿不到最后发现是 Adapter 的规则里没有正确写指标名称的匹配规则。4.3 压测验证Replicas 从 2 弹到 10 的实测过程为了让你对 HPA 的实际表现有个直观概念我给你还原一次我做的压测实验。环境是三节点 k8s 集群部署了一个简单的 Nginx 服务HPA 配置如下apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50初始状态是 2 个副本CPU 使用率大概在 15% 左右。我用压测工具模拟了 1000 个并发请求持续 5 分钟。压测刚开始的 30 秒HPA 还没有任何反应因为指标采集和计算本身有延迟。大约 1 分钟后kubectl get hpa看到 CPU 使用率弹到了 85%副本数开始增加。你猜实际扩了多久整个过程大约花了两分钟副本数从 2 个涨到了 10 个。期间新 Pod 的创建是分批进行的HPA 每次按计算出的副本数增量扩但又受限于扩容速率不会一次性全建出来。压测停止后副本数没有立刻降下来而是保持了大约 5 分钟的观察期。因为 HPA 默认有一个缩放策略副本数减少需要等指标持续低于阈值一段时间防止出现抖动导致频繁扩缩容。4.4 弹性伸缩不是越快越好min/max 值的经验取值HPA 的 minReplicas 和 maxReplicas 取值看起来简单实际上最有讲究。minReplicas 如果太小像刚才的例子突发流量一来扩容期间的大量请求会直接打到那 2 个副本上可能直接把服务打崩。反过来minReplicas 如果太大比如直接设成 8 个副本平时流量低时很多 Pod 空转浪费资源。我给团队定的经验值是生产环境关键服务minReplicas 设 2-3 个保证单节点故障时仍能提供服务maxReplicas 根据高峰期流量预留 50% 的余量。周期性明显的业务比如白天流量高、晚上流量低的系统HPA 的冷却时间参数要仔细调避免晚上流量下降时频繁缩容第二天早上又频繁扩容。另一个很多人忽略的问题HPA 只负责“水平扩缩容”也就是增减副本数它不管单副本的资源请求量。如果每个副本的 resources.requests 设置得过小导致单个 Pod 能处理的流量很少HPA 会疯狂扩容直到触发 maxReplicas 上限。所以配置 HPA 之前先要保证容器里的 resources.requests 和 limits 是合理的否则 HPA 等于在错误的基准线上做放大。5. ConfigMap 与 Secret把配置从镜像里“拆”出来5.1 为什么不要在镜像里写死配置前面提到单节点 k8s 上跑一整套微服务环境的案例当时有一个很深的感触配置文件如果不外置微服务迁移的成本会翻好几倍。在物理机时代我们把配置写在 application.yml 里跟着服务一起部署。到了 k8s 时代如果你还这么干每次改配置都要重新构建镜像非常浪费时间。而且不同环境测试环境、预发环境、生产环境的配置本来就不一样如果写死在镜像里就得为每个环境各打一个镜像。k8s 给出的解药是ConfigMap把配置数据和容器镜像解耦。镜像里不写配置配置通过 ConfigMap 单独管理容器启动时动态注入。5.2 挂载 vs 环境变量两种注入方式怎么选ConfigMap 注入容器有几种方式最常见的是环境变量和文件挂载。环境变量方式适合配置项少、且都是简单键值对的场景尤其是数据库连接地址、开关标志这类。文件挂载方式适合配置项多、多行文本、格式复杂的场景比如 Nginx 的 nginx.conf、Java 应用的 application.yml。我个人的经验是能挂文件就不要用环境变量。原因有三环境变量在容器启动时注入改了 ConfigMap 不会自动生效需要重启 Pod。环境变量太多时排错很难受docker exec 进容器一看几百个环境变量找半天。文件挂载可以把整个配置文件替换掉内容一目了然而且 ConfigMap 更新后挂载的文件会同步更新。配置挂载的 YAML 写法很好理解比如把app-config这个 ConfigMap 挂到容器的/etc/app/config目录volumes: - name: app-config-volume configMap: name: app-config containers: - name: app volumeMounts: - name: app-config-volume mountPath: /etc/app/config5.3 Secret 不等于安全etcd 加密与 RBAC 才是重点ConfigMap 之外还有个 Secret用来管理密码、私钥、Token 这类敏感信息。Secret 和 ConfigMap 的用法高度相似都可以挂载、都可以作为环境变量注入。但这里有一个普遍的误区我必须说清楚Secret 并不是加密存储它只是对数据做了 Base64 编码。Base64 不是加密只是编码任何人拿到 Secret 的 YAML拖到终端里echo xxx | base64 -d就能还原出明文。所以 Secret 的真正安全性不是靠它本身的存储机制而是靠你如何管理它的访问权限控制谁有权读取 Secret这靠 RBAC 配置精细到命名空间级别。对 etcd 里的敏感数据启用加密这是 k8s 1.13 版本开始支持的能力但很多集群没有开启。可以检查 API Server 的启动参数里有没有--encryption-provider-config。不要把生产环境的 Secret 提交到 Git 仓库这一点看着老生常谈但我真的见过不止一次。Secret 还有一个坑挂载到容器里的 Secret 更新后文件内容会同步更新但如果应用缓存了旧配置不会自动生效。我在实际项目里遇到过应用一直报密码错误查了半天发现 Secret 已经改了Pod 也已经重新挂载了但 Java 应用的内存里还缓存的旧密码。解决办法只能是重启应用或者写一个配置热加载的逻辑。6. 监控体系落地Prometheus Grafana 从零部署6.1 需要盯住的三类指标节点、Pod、事件集群里应用跑起来了弹性伸缩也配置了但一个生产集群如果没有监控等于开着一辆没有仪表盘的车。这不是夸张我以前就在没监控的时候吃过亏半夜里一个节点的磁盘被日志写满这个节点上的所有 Pod 都开始异常第二天早上才发现业务已经影响了好几个小时。在 k8s 里做监控首先要明确监控对象。我把它们分成三类节点层指标CPU、内存、磁盘、网络进出流量、节点负载。这些能帮你判断集群的资源水位是否健康预见性地扩容。Pod 层指标Pod 的 CPU、内存使用率、重启次数、容器 crash 情况。这类指标帮你快速定位是哪个具体服务出了问题。事件类信息kubectl get events能看到的事件比如 Pod 被驱逐、镜像拉取失败、探针失败。这类信息是动态的需要有一个系统把事件统一收集起来否则等你想查的时候早被新的时间冲掉了。6.2 kube-prometheus-stack 一键部署与目录拆解Prometheus 生态在 k8s 里最主流的落地方式是kube-prometheus-stack。它不是一个单独的组件而是一整套监控方案包含了 Prometheus、Alertmanager、Grafana、Node Exporter、kube-state-metrics 等组件并且预设了很多 k8s 相关的告警规则。用 Helm 安装的时候网上最常见的就是三行命令helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring但是我要提醒你这套默认配置直接装上去有些生产环境不适合。默认情况下它会创建一个 LoadBalancer 类型的 Service 来暴露 Prometheus 和 Grafana有些集群里没有外部负载均衡器支持这一步会卡住。我建议装的时候直接指定所有 Service 类型为 ClusterIP然后用 Ingress 按域名暴露。还有一个资源占用的预期问题。kube-prometheus-stack 装完默认会采集很多的指标包括每一个容器的每个指标一个三节点的集群光监控本身的资源占用就可能达到 4-6 个 CPU 和 10GB 以上的内存。如果你的集群本身资源就紧装完发现经常线程告警那就要裁剪采集配置只保留核心指标。6.3 几个必配的告警规则配置告警是监控系统的灵魂。没有告警的监控它的价值就少了一半。我总结过几个刚上 k8s 时最应该配的告警规则而不是一上来就搞一堆花里胡哨的指标节点状态异常Node 状态不是 Ready 超过 5 分钟。节点是承载一切的基础节点挂了上面的 Pod 再健康也是空中楼阁。Pod 频繁重启单个 Pod 的 restart 次数在 10 分钟内超过 5 次。这说明应用可能在不停崩溃循环需要立即介入。磁盘使用率超过 80%k8s 节点磁盘写满是个很隐蔽的故障一旦发生整个节点上的所有容器都会被标记为异常。Controller-manager 或 scheduler 不可用这是控制面组件的存活告警因为这两个组件一旦挂掉整个集群的调度和运维能力就瘫痪了。这些告警规则在 kube-prometheus-stack 里默认部分已经自带建议在部署后检查一遍规则的严重级别和阈值是否符合你的业务预期。6.4 Grafana 看板推荐与使用心得Grafana 是 Prometheus 生态里最常用的可视化组件。如果你是 k8s 新手不推荐自己手搓看板直接用社区成熟的模板效率最高。我常用的两个看板是Kubernetes ClusterID 315覆盖集群整体视角节点 CPU、内存、网络、Pod 状态分布都有。Kubernetes Pod Monitoring从 Pod 维度展示资源使用率、重启次数、网络流量排错时很实用。导入方式是在 Grafana 界面里选 Import Dashboard输入 ID 即可。用了一段时间之后我的体会是看板不是越多越好监控的核心目标是快速定位问题而不是堆砌数据。我最后固定下来一看板只放三块内容节点资源总览、活跃 Pod 的异常状态重启次数、Pending 数量、关键服务的请求延迟。排错时先看这三块能解决 80% 的问题。7. 长期运维绕不开的硬骨头证书续期、GPU 调度与高可用7.1 集群证书过期一年一度的“定时炸弹”k8s 集群里各种组件之间的通信都依赖 TLS 证书而这些证书默认有效期是一年。很多集群搭完就不怎么管了等到某个早上突然发现所有 kubectl 命令都报错提示证书过期这才是真正的大坑。证书过期的报错一般是x509: certificate has expired or is not yet valid很多人第一次遇到时以为集群挂了其实只是证书过期集群本身可能还在运行但所有需要通过证书认证的交互都不可用了。解决办法是手动续期针对 kubeadm 部署的集群正确的姿势是kubeadm certs renew all然后在每个节点上更新 kubeconfig 配置kubeadm init phase kubeconfig all --config /etc/kubernetes/kubeadm-config.yaml最后重启 kubelet 使配置生效。但更省心的方法是直接组件一个自动续期的定时任务。在控制面上的/etc/kubernetes/pki目录里证书快到期时可以提前跑续期命令配合 crontab 每个月跑一次就很稳。如果你用的是 Kubekey 这类工具部署的集群它也提供了证书更新的命令查一下对应文档就行。7.2 GPU 怎么让 k8s 调度nvidia-device-plugin另一个我最近接触比较多的场景是 GPU 调度。很多 AI 团队开始把训练任务往 k8s 上迁移但 GPU 不是 CPU不能用普通的资源方式直接暴露给容器。k8s 本身不感知 GPU 设备要让集群能调度 GPU需要装一个组件NVIDIA Device Plugin。它做的事情就是把宿主机上的 NVIDIA 显卡以“设备资源”的形式注册给 kubelet这样你在 Pod 的 YAML 里可以申请resources: limits: nvidia.com/gpu: 1装 NVIDIA Device Plugin 之前有个前提节点上必须已经装好 NVIDIA 驱动和nvidia-container-toolkit否则插件能发现显卡但容器里根本起不来。安装步骤不复杂kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/main/nvidia-device-plugin.yml装完验证一下kubectl get node -o json | jq .items[].status.allocatable如果输出里能看到nvidia.com/gpu说明插件工作正常了。实际使用中有个非常容易踩的坑如果你只设置了 limits 里的 nvidia.com/gpu而 requests 里没有对应配置k8s 的调度器会把 requests 当作 0 来处理可能会导致多个 GPU 请求被调度到同一个节点但节点显存不够。所以 GPU 的 requests 和 limits 一定要保持一致。7.3 三个 Master 节点高可用Kubekey 与外部负载均衡的配合之前看到很多热搜词在问三台 Master 怎么保证高可用我就知道这个确实是 k8s 生产化绕不开的痛点。单 Master 集群一旦控制面挂了整个集群的管理功能就瘫痪了业务无大碍但你想做任何变更都做不了。多 Master 高可用的思路是多个 Master 节点运行控制面组件通过 etcd 的分布式一致性选出一个“活跃”的 leader其他节点作为备份节点。问题在于kubelet、kube-proxy、kubectl 这些客户端访问 API Server 时需要知道该连哪个地址。这个地址必须是一个固定的虚拟 IP 或者负载均衡器的地址不能是某一个 Master 节点的 IP否则那个节点挂了客户端还连的是旧地址。用 Kubekey 部署高可用集群时它的配置里会要求你先提供一个负载均衡地址这就是高可用的关键。我之前用 Kubekey 在 Ubuntu 上部署过一套三 Master 集群步骤是准备两台负载均衡可以是 Keepalived 加 Nginx 或 HAProxy跑一个虚拟 IP。准备三台 Master 节点和若干 Worker 节点。在 KK 配置文件中指定 lb 的地址为虚拟 IP。执行部署命令。部署完成后随便停掉一个 Master 节点上的 kubelet集群的kubectl操作依然正常。因为负载均衡自动把流量转发到了其他健康的 Master 节点上。7.4 管理工具现状为什么我最终还是回到 kubectl最后聊一聊管理工具。k8s 官方的管理页面Dashboard很形象但它有一个很尴尬的问题它需要单独的 Service Account 授权才能访问这个授权过程对新用户来说也比较绕。你可以创建一个管理员权限的 Service Account然后把 Token 复制出来登录每次 Token 过期还需要重新生成。后来我试过 k9s 这类终端 UI 工具说实话体验很好它把 Pod、日志、事件集中在了一个界面里排错效率比反复敲 kubectl get 高不少尤其适合在 SSH 终端里远程管理集群。用习惯之后我现在排查问题第一件事就是 k9s 打开对应命名空间先看有没有 Pending 或 CrashLoopBackOff 的 Pod。但要说最根本的我还是建议你老老实实把 kubectl 的常用命令练熟。因为在自动化、CI/CD 集成、以及一些紧急在线排障的时候kubectl 是唯一不会掉链子的手段。我自己整理了一套日常排错命令的组合在这里顺便分享给你# 看整体状态 kubectl get nodes -o wide kubectl get pods -A | grep -v Running # 看某资源详情最常用排错第一步 kubectl describe pod pod-name # 看业务日志 kubectl logs -f pod-name --tail100 # 查事件 kubectl get events --sort-by.lastTimestamp | tail -20这套组合能解决日常 90% 的排错场景。我的经验是先把这五条练熟然后再去折腾其他工具底层逻辑通了工具只是提高效率的辅助。关于 k8s 的进阶内容还有很多比如多集群管理、服务网格、Operator 开发、网络策略、安全加固这些都是可以继续深挖的方向。这篇系列第三篇主要解决的是“从会用集群到能扛业务”这一段的问题。如果你在实践过程中遇到了一些有意思的坑或解决思路欢迎多交流毕竟这种东西自己踩过的坑比什么文档都记得牢。
返回列表