Skip to content

第7章 配置与密钥管理:ConfigMap与Secret ​

前面我们讲了存储——持久化的数据存在哪里。这一章我们讲另一种"数据"——应用的配置。

配置跟普通数据不一样——它不是业务数据,而是应用运行需要的参数、环境变量、配置文件等等。

配置管理是个老问题了——怎么管理不同环境的配置?怎么安全地管理密码、密钥这些敏感信息?怎么热更新配置?

K8s给我们提供了两个专门的资源——ConfigMap和Secret,用来管理配置和敏感信息。

7.1 为什么要把配置跟镜像分开? ​

在讲ConfigMap之前,我们先想想:为什么不直接把配置文件打到镜像里?为什么要单独管理配置?

7.1.1 十二要素应用方法论 ​

这个问题,"十二要素应用"(The Twelve-Factor App)里早就讲过了——配置要严格地和代码分离。

什么是十二要素应用?它是一套现代应用(特别是SaaS应用)的最佳实践,总结了应用开发应该遵循的12条原则。其中第三条就是"配置"——配置存储在环境变量里,跟代码分开。

为什么?

  1. 不同环境的配置不一样——开发、测试、生产环境的数据库地址、密码、各种参数都不一样。如果配置在镜像里,那每个环境都要打不同的镜像——太麻烦了。

  2. 配置会变,代码不会经常变——改个配置还要重新构建镜像、重新部署?那也太重了。

  3. 敏感信息不能进代码库——密码、密钥这些东西,不能打到镜像里,更不能提交到Git里——太危险了。

  4. 同一个镜像要能在多个环境运行——build once, run anywhere。同一个镜像,在开发、测试、生产都能跑,只是配置不一样。

所以,配置和代码分离,这是最佳实践。

7.1.2 传统配置管理的问题 ​

传统的配置管理有哪些方式?

  • 配置文件跟代码一起放Git里——敏感信息不安全,改配置要重新发版
  • 环境变量——简单,但多了不好管理,而且不能热更新
  • 配置中心(Apollo、Nacos等)——功能强大,但要额外部署,增加复杂度
  • 直接把配置文件挂进去——麻烦,不好管理

K8s的ConfigMap和Secret,提供了一种原生的、声明式的配置管理方式——配置是K8s的资源对象,可以用YAML管理,可以跟应用一起部署。

7.2 ConfigMap:普通配置的管家 ​

7.2.1 ConfigMap是什么? ​

ConfigMap是什么?简单说,它就是K8s里的一个资源对象,用来存普通的配置数据——不敏感的、可以明文存的配置。

ConfigMap里存的是键值对——key是配置名,value是配置内容。value可以是一个字符串,也可以是整个配置文件的内容。

ConfigMap是命名空间级别的——每个命名空间里的ConfigMap只能在这个命名空间里用。

7.2.2 ConfigMap的四种使用方式 ​

ConfigMap怎么用?有四种方式:

1. 环境变量 把ConfigMap里的值作为环境变量注入到容器里。

比如ConfigMap里有个key叫LOG_LEVEL,值是info——你可以把它变成容器里的环境变量LOG_LEVEL=info,应用直接读环境变量就行。

适合:简单的配置项、少量的配置。

2. 命令行参数 把ConfigMap里的值作为容器启动命令的参数。

跟环境变量类似,只是用在命令行参数里。

3. Volume挂载(整个文件) 把ConfigMap里的每个key-value,变成挂载目录里的一个文件——key是文件名,value是文件内容。

比如ConfigMap里有个key叫nginx.conf,value是Nginx配置文件的内容——挂载之后,容器里就有一个nginx.conf文件,内容就是配置。

适合:配置文件、多个配置项、需要用文件的应用。

4. subPath(单个文件) 上面那种方式是把整个ConfigMap都挂载成一个目录,目录里有多个文件。

如果你只想挂载其中一个文件,不想覆盖整个目录——就用subPath。

比如容器里有个/etc/nginx/conf.d/目录,里面已经有一些文件了,你只想加一个my.conf进去,不想把整个目录覆盖掉——就用subPath,只挂载这一个文件。

这是个很实用的技巧——很多人刚开始用的时候不知道subPath,一挂载把整个目录覆盖了,原来的文件都没了,应用就挂了。

7.2.3 配置热更新?——看情况 ​

很多人问:ConfigMap改了,应用里的配置会自动更新吗?

答案是:看你怎么用的。

  • 如果是Volume挂载的:会自动更新。ConfigMap改了,过一会儿(大概几十秒),挂载的文件内容就会更新。

    • 但是!应用会不会重新加载配置?那要看应用自己——大部分应用不会自动重新加载配置文件,得重启或者发信号让它 reload。
    • 所以ConfigMap更新了,文件内容是更新了,但应用可能还用的旧配置——除非应用支持热加载。
  • 如果是环境变量或者命令行参数:不会自动更新。环境变量是容器启动的时候注入的,启动之后就不会变了。要更新的话,得重启Pod。

这是个很常见的坑——很多人以为改了ConfigMap应用就自动用新配置了,结果不是。

所以:

  • 如果你需要配置热更新,要么用Volume挂载 + 支持热加载的应用,要么用专门的配置中心
  • 如果用环境变量,改了ConfigMap之后要重启Pod才能生效

7.3 Secret:敏感信息的保险箱 ​

ConfigMap是存普通配置的,那敏感信息呢?比如密码、密钥、证书这些——不能明文存的东西。

那就用Secret。

7.3.1 Secret是什么? ​

Secret跟ConfigMap很像,也是键值对,也是命名空间级别的,也可以用环境变量或者Volume挂载。

区别是:

  • Secret是专门用来存敏感信息的——密码、token、密钥、证书等等
  • Secret的内容是Base64编码的(注意:不是加密!)
  • Secret有专门的类型,不同类型用途不一样
  • K8s对Secret有一些特殊的处理——比如不会随便输出到日志里

等等,Base64编码?那不是跟明文差不多吗?Base64解码一下就看到了。

没错!Secret默认只是Base64编码,不是加密的。很多人有这个误解,以为Secret是加密的,很安全——其实不是。

为什么叫Secret?因为它是"敏感信息",K8s会尽量避免把它泄露出去——比如不会在日志里随便打,不会在kubectl describe里直接显示内容——但它本质上还是明文存在etcd里的。

所以,不要以为用了Secret就绝对安全了。它只是比ConfigMap稍微安全一点,但不是加密存储。

7.3.2 Secret的类型 ​

Secret有好几种类型,不同类型用途不一样:

1. Opaque(默认类型)

  • 普通的Secret,存任意的敏感数据
  • 最常用的类型

2. kubernetes.io/dockerconfigjson

  • 用来存私有镜像仓库的认证信息
  • 拉私有镜像的时候用
  • Pod的imagePullSecrets字段指定这个Secret,就能拉私有仓库的镜像了

3. kubernetes.io/tls

  • 用来存TLS证书和私钥
  • Ingress做HTTPS的时候用的就是这种Secret

4. kubernetes.io/service-account-token

  • ServiceAccount的token
  • 这个是K8s自动创建的,用来给Pod访问apiserver用的
  • 我们后面讲安全的时候会详细讲

还有一些其他类型,用得不多。

7.3.3 Secret的使用方式 ​

Secret的使用方式跟ConfigMap差不多:

  • 环境变量注入
  • Volume挂载
  • 镜像拉取认证(dockerconfigjson类型)
  • Ingress TLS(tls类型)

用法跟ConfigMap几乎一样,只是资源类型不一样。

7.3.4 Secret真的安全吗? ​

这是个很重要的问题——Secret到底安不安全?

我们来分析一下:

默认情况下,Secret的安全性:

  • 存在etcd里——是明文的(Base64,跟明文差不多)
  • 有权限的人(比如集群管理员)可以看到所有Secret
  • 节点上——kubelet会把Secret的内容存在节点上(挂载的话就是文件,环境变量的话就在进程环境里)
  • 如果etcd没加密,那etcd备份泄露了,所有Secret就都泄露了

所以,默认的Secret不是强安全的,只是"比明文好一点"。

那怎么加强Secret的安全性?

  1. etcd加密:开启etcd的静态加密——Secret存在etcd里是加密的,就算etcd备份泄露了,没有密钥也解不开
  2. RBAC权限控制:严格控制谁能访问Secret——不是所有人都能看Secret的,用RBAC限制权限
  3. Pod Security:限制特权容器,防止有人从容器里偷Secret
  4. 外部密钥管理:用专门的密钥管理服务——比如HashiCorp Vault、云厂商的KMS、AWS Secrets Manager
  5. Sealed Secrets:加密的Secret,可以安全地存在Git里

生产环境,如果对安全要求高,建议用外部的密钥管理服务,不要只用默认的Secret。

7.4 配置管理最佳实践 ​

讲完了ConfigMap和Secret,我们来总结一下配置管理的最佳实践。

7.4.1 什么配置放哪里? ​

  • 普通配置、非敏感的 → ConfigMap
  • 敏感配置、密码、密钥、证书 → Secret
  • 非常敏感、对安全要求极高的 → 外部密钥管理服务(Vault、KMS等)
  • 跟应用代码一起的、不会变的 → 可以打在镜像里(但不推荐,最好还是分离)

7.4.2 配置与代码分离 ​

严格遵循十二要素应用原则——配置和代码分离:

  • 不要把配置写在代码里
  • 不要把配置打到镜像里
  • 配置用ConfigMap/Secret管理,跟应用分开部署
  • 同一个镜像可以在不同环境用不同配置运行

7.4.3 敏感信息不要进Git ​

密码、密钥这些敏感信息,绝对不能提交到Git里——不管是公开仓库还是私有仓库。

  • 公共仓库就不用说了,谁都能看到
  • 私有仓库也不安全——仓库可能泄露,历史记录里有,离职员工可能带走

那GitOps怎么办?配置都要存在Git里啊——用Sealed Secrets或者External Secrets,加密之后再存Git里。

7.4.4 配置热更新的问题 ​

前面说了,ConfigMap/Secret更新了,应用不一定会自动更新配置。

如果需要热更新,有几种方案:

  1. 应用自己支持热加载——比如Nginx可以reload,Java应用可以用Spring Cloud Config之类的
  2. 用配置中心——Apollo、Nacos、Consul等,专门做配置管理,支持热更新
  3. Reloader之类的工具——监听ConfigMap/Secret变化,自动滚动更新Pod(相当于重启应用)

根据你的需求选。

7.4.5 配置的版本管理 ​

配置也要做版本管理——改了什么、什么时候改的、谁改的,都要有记录。

  • 用GitOps的话,配置存在Git里,天然有版本历史
  • 不用GitOps的话,也要有变更记录——不然出了问题都不知道改了什么

7.5 外部配置中心与K8s的集成 ​

ConfigMap和Secret虽然好用,但功能还是比较基础——没有界面、没有版本管理、没有灰度发布、没有权限管理……

如果你的配置管理需求比较复杂,可能还是需要专门的配置中心——比如Apollo、Nacos、Consul、Spring Cloud Config等。

那这些配置中心怎么跟K8s集成?

7.5.1 集成方式 ​

主要有几种集成方式:

1. 应用直接集成

  • 应用直接连配置中心,拉配置
  • 跟K8s没关系,应用该怎么用怎么用
  • 优点:简单,跟以前一样
  • 缺点:每个应用都要集成SDK,有语言绑定

2. Sidecar模式

  • 给Pod加一个Sidecar容器,Sidecar负责跟配置中心通信,把配置拉下来写到文件里
  • 主应用直接读文件就行,不用改代码
  • 优点:应用不用改,无侵入
  • 缺点:多了个Sidecar,增加资源开销

3. 同步到ConfigMap/Secret

  • 有个工具把配置中心的配置,自动同步成K8s的ConfigMap/Secret
  • 应用还是用ConfigMap/Secret,跟普通用法一样
  • 优点:应用不用改,用K8s原生方式
  • 缺点:多了一层同步,有延迟

根据你的情况选合适的方式。

7.5.2 什么时候用配置中心,什么时候用ConfigMap? ​

  • 简单场景、配置不多 → 用ConfigMap/Secret就够了,简单,K8s原生,不用额外部署
  • 复杂场景、配置很多、需要热更新、需要权限管理、需要灰度发布 → 用配置中心,功能更强大
  • 微服务多、多语言、配置管理需求复杂 → 配置中心更合适

没有最好的,只有最合适的——根据你的需求来。

7.6 扩展知识:信息安全基础与加密算法简介 ​

讲到Secret和敏感信息,我们扩展一下信息安全的基础知识——了解了这些,你对安全的理解会更深。

7.6.1 对称加密 vs 非对称加密 ​

加密算法分两大类:

对称加密:

  • 加密和解密用同一个密钥
  • 例子:AES、DES、3DES
  • 优点:速度快,适合加密大量数据
  • 缺点:密钥分发困难——怎么把密钥安全地传给对方?

非对称加密(公钥加密):

  • 有一对密钥:公钥和私钥
  • 公钥加密,私钥解密;或者私钥签名,公钥验签
  • 例子:RSA、ECC
  • 优点:不用分发密钥,公钥可以公开
  • 缺点:速度慢,不适合加密大量数据

实际应用中,一般是两者结合:

  • 用非对称加密交换对称密钥
  • 然后用对称密钥加密数据

比如HTTPS就是这样的——握手的时候用非对称加密交换密钥,之后用对称密钥加密通信内容。

7.6.2 哈希算法 ​

哈希算法是什么?它是把任意长度的数据,转换成固定长度的字符串(哈希值)。

特点:

  • 单向的——只能从数据算哈希值,不能从哈希值反推出数据
  • 雪崩效应——输入改一点点,输出就完全不一样
  • 碰撞概率低——很难找到两个不同的数据有相同的哈希值

例子:MD5、SHA-1、SHA-256、SHA-512

用途:

  • 数据完整性校验——下载文件后算哈希,跟官方的对比,看文件有没有被篡改
  • 密码存储——存密码的哈希值,不存明文(但要加盐,防止彩虹表攻击)
  • 数字签名——签名的是数据的哈希值

注意:MD5和SHA-1已经不安全了,现在推荐用SHA-256及以上的。

7.6.3 数字证书与PKI ​

数字证书是什么?它是用来证明"这个公钥确实属于某个人/某个组织"的。

为什么需要证书?因为公钥是公开的,你怎么知道你拿到的公钥真的是对方的,而不是中间人伪造的?

这就需要第三方权威机构(CA,证书颁发机构)来做担保——CA用自己的私钥给你的公钥签名,签出来的就是证书。

你拿到证书,用CA的公钥验证签名——验证通过,就说明这个公钥确实是证书里那个人的。

这就是PKI(公钥基础设施)——一套基于证书的信任体系。

我们平时用的HTTPS,就是基于PKI的——网站有证书,浏览器验证证书,确认网站身份,然后建立加密连接。

K8s里也大量用到证书——apiserver、etcd、kubelet、kube-proxy……所有组件之间的通信,都是用TLS加密的,都需要证书。

7.6.4 安全的基本原则 ​

最后,讲几个安全的基本原则:

  1. 最小权限原则:每个用户、每个服务、每个程序,只给它需要的最小权限——能不给的就不给
  2. 纵深防御:多层防护——一层被攻破了还有下一层,不要把所有鸡蛋放在一个篮子里
  3. 默认安全:默认就是安全的配置,不安全的功能默认关闭——要用户主动开才能开
  4. 不要自己造轮子:加密、安全这些东西,不要自己实现,用成熟的、经过验证的方案
  5. 安全是持续的:不是做一次就完了,要持续监控、持续更新、持续改进

安全是个系统工程,不是靠某一个工具就能解决的。Secret只是其中一环,还要配合RBAC、网络策略、运行时安全等等,才能真正做好安全。

7.7 本章小结 ​

这一章我们讲了配置与密钥管理——ConfigMap和Secret。

总结一下重点:

  • 配置要跟代码分离——十二要素应用原则,build once run anywhere
  • ConfigMap:存普通配置,支持环境变量、命令行参数、Volume挂载、subPath四种用法
  • Secret:存敏感信息,用法跟ConfigMap类似,但只是Base64编码,不是加密的
  • ConfigMap/Secret更新后,Volume挂载的会自动更新文件,但应用不一定会重新加载;环境变量的不会自动更新
  • Secret的安全性:默认只是Base64,要加强安全可以用etcd加密、RBAC、外部密钥管理服务等
  • 配置中心:复杂场景可以用Apollo/Nacos等配置中心,跟K8s有多种集成方式
  • 安全基础:对称加密、非对称加密、哈希、证书、PKI

配置管理看起来简单,但其实水很深——怎么管理不同环境的配置、怎么安全管理敏感信息、怎么做热更新、怎么做版本管理……都是学问。理解了这些工具的原理和特点,你才能选对方案、用好工具。

下一章,我们来讲调度器——Pod到底是怎么被放到节点上的?


第8章 调度器深度解析:Pod去哪儿了 ​

你有没有想过:当你创建一个Pod的时候,它是怎么被放到某个节点上的?是谁决定的?根据什么决定的?

这就是**调度器(Scheduler)**干的事儿。调度器是K8s的"大脑"之一——它负责给Pod找一个合适的家(节点)。

这一章我们就深入讲讲调度器——它是怎么工作的?有哪些调度策略?怎么影响调度决策?

8.1 调度器是干什么的? ​

8.1.1 调度的本质 ​

调度的本质是什么?就是给每个Pod找一个最合适的节点。

说起来简单,但做起来难——因为"最合适"的定义很复杂:

  • 节点资源够不够?
  • 节点有没有污点?
  • Pod有没有节点选择器?
  • Pod跟其他Pod的亲和性/反亲和性满足吗?
  • 怎么平衡各个节点的负载?
  • 怎么提高资源利用率?
  • 怎么保证高可用?

这是一个经典的装箱问题(Bin Packing)——把不同大小的物品(Pod)放进不同容量的箱子(节点)里,目标是用最少的箱子装下所有物品,同时还要满足各种约束条件。

装箱问题是NP难的——没有完美的最优解,只能找尽量好的近似解。

K8s的调度器,就是在各种约束条件下,给每个Pod找一个尽量好的节点。

8.1.2 调度器的位置 ​

调度器在K8s架构里是什么位置?

  • 调度器是控制平面的组件之一,叫kube-scheduler
  • 它是独立的进程,跑在Master节点上
  • 它通过apiserver Watch新创建的、还没分配节点的Pod
  • 它给每个Pod选一个最合适的节点,然后告诉apiserver(绑定)
  • 然后对应节点的kubelet就会在那台节点上启动Pod

调度器只做一件事——给Pod分配节点。它不负责启动Pod,也不负责监控Pod,它只负责调度决策。

8.2 调度流程:Filter → Score → Bind ​

调度的过程分三步:预选(Filter)→ 优选(Score)→ 绑定(Bind)。

8.2.1 第一步:预选(Filter) ​

预选是什么?就是先把所有不符合条件的节点过滤掉。

比如:

  • 节点资源不够的?去掉
  • 节点有污点,Pod不能容忍的?去掉
  • 节点不满足Pod的nodeSelector的?去掉
  • 端口冲突的?去掉
  • 节点不满足Pod亲和性/反亲和性的?去掉
  • 节点磁盘不够的?去掉
  • 节点网络不可用的?去掉
  • ……

预选之后,剩下的节点就是"有资格"运行这个Pod的节点。

如果预选之后一个节点都没剩下——那Pod就一直处于Pending状态,调度失败。

8.2.2 第二步:优选(Score) ​

优选是什么?就是在剩下的合格节点里,给每个节点打分,选出分数最高的。

打分的维度有很多:

  • 资源利用率怎么样?——资源越均衡分数越高(或者说,剩下的资源越多分数越高?取决于调度策略)
  • 节点有没有跟Pod匹配的亲和性?——匹配的话加分
  • 节点上有没有同应用的Pod?——如果是反亲和的话减分
  • 节点是不是已经有很多Pod了?——负载高的减分
  • 数据局部性怎么样?——Pod需要的数据在这台节点上,加分
  • ……

每个维度打一个分,然后加权加起来,就是这个节点的总分。

分数最高的节点,就是最终的选择——如果有多个节点分数一样,就随机选一个。

8.2.3 第三步:绑定(Bind) ​

绑定是什么?就是调度器告诉apiserver:"这个Pod就放这台节点了"。

具体来说,调度器调用apiserver的API,把Pod的nodeName字段设成选中的节点名——这就叫绑定。

绑定之后,Pod就有了节点归属,对应节点的kubelet就会开始创建Pod。

8.2.4 为什么分两步? ​

你可能会问:为什么要分预选和优选两步?直接给所有节点打分不行吗?

不行,因为节点可能很多——几百上千个节点,如果每个都要算一遍优选的分数,那太慢了。

预选的作用就是先快速过滤掉大部分不合格的节点,剩下少数几个合格的,再仔细算优选分数——这样效率高多了。

这就像找工作——先看学历、工作经验这些硬条件,不符合的直接pass(预选);剩下符合条件的,再仔细面试、比较,选最好的(优选)。

8.3 影响调度的因素:怎么控制Pod调度到哪? ​

默认的调度策略是通用的,但很多时候我们想自己控制Pod的调度——比如:

  • 我想让Pod跑到有GPU的节点上
  • 我想让同一个应用的Pod分散到不同节点,提高可用性
  • 我想让两个紧密协作的Pod放在同一个节点,提高性能
  • 我想把某些节点专门给特定的应用用
  • ……

K8s提供了很多方式来影响调度决策,我们一个个来讲。

8.3.1 nodeSelector:最简单的节点选择 ​

nodeSelector是最简单的方式——你给Pod指定一个nodeSelector,就是一组标签。Pod只能调度到有这些标签的节点上。

比如:

  • 你给某些节点打上标签disktype=ssd
  • 然后Pod的nodeSelector设成disktype: ssd
  • 那这个Pod就只能调度到有SSD的节点上

简单粗暴,容易理解。但功能有限——只能做"等于"的匹配,不能做复杂的逻辑。

8.3.2 节点亲和性(Node Affinity) ​

节点亲和性(Node Affinity)是nodeSelector的升级版——功能更强大,支持更复杂的规则。

节点亲和性支持两种:

  • requiredDuringSchedulingIgnoredDuringExecution:必须满足,不满足就调度不了——跟nodeSelector类似,但规则更丰富
  • preferredDuringSchedulingIgnoredDuringExecution:尽量满足,不满足也没关系——软限制,优先选满足的节点

名字很长,什么意思?

  • required:必须的,硬限制
  • preferred:偏好的,软限制
  • DuringScheduling:调度的时候生效
  • IgnoredDuringExecution:运行期间不生效——就是说Pod已经跑起来了,节点标签变了,Pod也不会被赶跑

节点亲和性支持哪些操作?

  • In:标签值在列表里
  • NotIn:标签值不在列表里
  • Exists:标签存在就行,不管值是什么
  • DoesNotExist:标签不存在
  • Gt:大于
  • Lt:小于

比nodeSelector强大太多了——你可以写很复杂的规则。

8.3.3 Pod亲和性与反亲和性(Pod Affinity / Anti-Affinity) ​

节点亲和性是Pod跟节点的关系,那Pod跟Pod之间的关系呢?

比如:

  • 我想让这两个Pod尽量放在同一个节点——因为它们通信很多,放一起网络快
  • 我想让这几个Pod尽量分散在不同节点——提高可用性,一个节点挂了还有别的

这就是Pod亲和性(Pod Affinity)和Pod反亲和性(Pod Anti-Affinity)。

跟节点亲和性类似,也分硬限制和软限制:

  • requiredDuringSchedulingIgnoredDuringExecution:必须满足
  • preferredDuringSchedulingIgnoredDuringExecution:尽量满足

Pod亲和性:这个Pod跟哪些Pod要放一起——比如前端Pod跟缓存Pod放一起,减少网络延迟。

Pod反亲和性:这个Pod跟哪些Pod不要放一起——比如同一个应用的多个副本,分散到不同节点,提高可用性;或者不同租户的Pod不要放一起,安全隔离。

Pod亲和性/反亲和性是通过标签选择器来匹配的——匹配哪些Pod,是通过标签来选的。

这是个非常有用的功能——特别是反亲和性,生产环境常用它来提高应用的可用性,避免单点故障。

8.3.4 污点与容忍(Taint & Toleration) ​

前面讲的都是Pod选节点——Pod说"我要去什么样的节点"。

那反过来呢?节点能不能选Pod?——节点说"我不想要什么样的Pod"。

可以!这就是污点(Taint)和容忍(Toleration)。

**污点(Taint)**是打在节点上的——节点有了污点,普通的Pod就调度不上去了。就像节点上有"臭味",普通Pod受不了,不愿意来。

**容忍(Toleration)**是Pod上的——Pod如果能容忍这个污点,就能调度到有这个污点的节点上。

打个比方:

  • 节点上的污点就像"隔离区"——普通人不能进
  • Pod的容忍就像"通行证"——有通行证的才能进

污点有什么用?

  • 专用节点:把某些节点专门给特定的应用用——给节点打上污点,只有对应的应用有容忍,才能调度上去
  • 特殊硬件节点:比如GPU节点、高内存节点——打上污点,只有需要这些硬件的Pod才能调度上去,不浪费资源
  • 节点维护:要维护节点了,给节点打上污点,把Pod都赶出去(当然还要配合cordon和drain)
  • 节点问题:节点有问题,先打上污点,不让新的Pod调度上来

污点有三种效果(Effect):

  • NoSchedule:不能调度上去——硬限制,已经在上面的Pod不受影响
  • PreferNoSchedule:尽量不要调度上去——软限制
  • NoExecute:不仅不能调度上去,已经在上面的Pod,如果不能容忍,也会被赶出去

NoExecute比较狠——节点打上NoExecute的污点,上面所有不能容忍这个污点的Pod都会被立刻驱逐。

污点和容忍是个很强大的机制——用来做节点隔离、专用节点、特殊硬件节点,非常方便。

8.3.5 节点名称(nodeName) ​

最简单粗暴的方式——直接指定Pod的nodeName,说"我就要跑在这个节点上"。

调度器直接跳过,Pod就直接跑在指定节点上。

但这种方式不推荐用——太硬了,节点挂了Pod就跑不了了。一般只有特殊场景才用。

8.3.6 优先级与抢占(Priority & Preemption) ​

前面讲的都是"怎么选节点",那如果节点资源不够了呢?——所有节点都满了,新的Pod调度不上去怎么办?

这时候就有了优先级(Priority)和抢占(Preemption)。

优先级:Pod有优先级——优先级高的Pod比优先级低的重要。

抢占:如果一个高优先级的Pod调度不上去——所有节点都满了,那调度器会把某个节点上低优先级的Pod删掉(抢占),腾出资源来跑高优先级的Pod。

就像飞机升舱——你是白金卡会员,经济舱满了,把普通乘客赶下去,给你腾位置?(当然现实中不会这样,但K8s里会)

什么时候用优先级和抢占?

  • 核心业务Pod设高优先级——资源不够的时候,先保证核心业务跑起来
  • 非核心、批处理任务设低优先级——资源不够的时候,先把这些停掉,给核心业务腾地方
  • 系统组件设最高优先级——比如kube-proxy、CNI插件这些,绝对不能被抢占

生产环境建议给不同的Pod设置不同的优先级——保证重要的应用在资源紧张的时候能优先运行。

8.4 调度器框架:可扩展的调度器 ​

K8s的调度器不是一个黑盒——它是高度可扩展的。这就是调度器框架(Scheduler Framework)。

8.4.1 扩展点 ​

调度器框架定义了很多扩展点——在调度流程的不同阶段,你可以插入自己的逻辑。

主要的扩展点有:

  1. PreFilter:预选之前的预处理——比如做一些计算、缓存一些数据
  2. Filter:预选——过滤掉不合格的节点
  3. PreScore:优选之前的预处理
  4. Score:优选——给节点打分
  5. Reserve:预留资源——选中节点后,先预留资源
  6. Permit:批准——批准还是拒绝这个调度
  7. PreBind:绑定之前的操作——比如挂载存储什么的
  8. Bind:绑定——把Pod绑定到节点
  9. PostBind:绑定之后的操作
  10. Reserve失败的时候:Unreserve——释放预留的资源

每个扩展点,你都可以写自己的插件,实现自定义的调度逻辑。

8.4.2 怎么扩展调度器? ​

扩展调度器有几种方式:

  1. 写调度插件:用调度器框架,写自己的插件,编译进调度器——功能最强大,但要改调度器代码
  2. 调度器配置文件:通过配置文件,启用/禁用某些插件,调整插件的参数——不用改代码,配置就行
  3. 多个调度器:跑多个调度器,不同的Pod用不同的调度器——比如默认调度器管普通Pod,你自己写的调度器管特殊Pod
  4. 调度器扩展器(Scheduler Extender):老的扩展方式,通过HTTP接口扩展——现在推荐用调度器框架

K8s的调度器设计得非常灵活——你几乎可以自定义任何调度逻辑。

8.4.3 常用的调度插件 ​

K8s自带了很多调度插件,常用的有:

  • NodeResourcesFit:检查节点资源够不够
  • NodeName:检查nodeName匹配
  • NodeSelector:检查nodeSelector匹配
  • NodeAffinity:检查节点亲和性
  • PodAffinity:检查Pod亲和性
  • PodAntiAffinity:检查Pod反亲和性
  • TaintToleration:检查污点容忍
  • NodeResourcesBalancedAllocation:优选的时候,让节点资源更均衡
  • ImageLocality:优选的时候,节点上已经有镜像的加分
  • InterPodAffinity:Pod亲和性优选
  • ……

大部分调度逻辑,都是通过这些插件实现的。

8.5 调度策略:LeastRequested vs MostRequested ​

优选阶段,给节点打分的时候,有个很重要的策略——怎么算资源分数?

有两种常见的策略:

8.5.1 LeastRequested(最少请求) ​

LeastRequested策略——剩余资源越多的节点,分数越高。

什么意思?就是优先把Pod放到资源比较空闲的节点上——让各个节点的负载都比较均衡,不要有的节点忙死、有的闲死。

这是默认的策略吗?以前是,现在默认是BalancedAllocation——更均衡的分配。

这种策略的好处:

  • 节点负载均衡
  • 每个节点都有较多剩余资源,突发流量的时候扛得住
  • 单个节点故障影响小

缺点:

  • 资源利用率低——很多节点都有剩余资源,但加起来很多,浪费了
  • 需要更多的节点

8.5.2 MostRequested(最多请求)/ BinPacking ​

MostRequested策略——资源用得越多的节点,分数越高。

什么意思?就是尽量把Pod往已经用了很多资源的节点上塞——尽量把节点填满,减少需要的节点数。

这就是**装箱问题(Bin Packing)**的目标——用最少的箱子装下所有物品。

这种策略的好处:

  • 资源利用率高——尽量把节点填满,用更少的节点,省钱
  • 节点数量少,管理成本低

缺点:

  • 节点负载不均衡——有的节点满了,有的可能很空
  • 突发流量的时候可能扛不住——节点已经满了,没余量
  • 单个节点故障影响大——一个节点满了,挂了影响的Pod多

8.5.3 怎么选? ​

两种策略各有优劣,怎么选?

  • 对可用性要求高、有突发流量 → LeastRequested / BalancedAllocation——留有余量,更安全
  • 对成本敏感、流量平稳 → MostRequested / BinPacking——提高利用率,省钱
  • 生产环境一般用BalancedAllocation——平衡型的,既不太浪费,也留有余量

这是个权衡——成本和可用性之间的权衡。根据你的业务特点来选。

8.6 扩展知识:装箱问题与资源利用率优化 ​

讲到调度,就不得不提装箱问题(Bin Packing Problem)——这是调度的核心问题。我们扩展讲讲。

8.6.1 什么是装箱问题? ​

装箱问题是经典的组合优化问题:

  • 有若干个容量相同的箱子(节点)
  • 有若干个不同大小的物品(Pod)
  • 目标:用最少的箱子装下所有物品

这个问题看起来简单,但其实是NP难问题——没有多项式时间的最优解,物品多了只能用启发式算法找近似解。

什么是NP难?就是说,物品数量多了,你根本算不出最优解——因为可能的组合太多了,算到天荒地老也算不完。只能用一些启发式的算法,找一个"尽量好"的解。

8.6.2 常见的装箱算法 ​

常见的启发式装箱算法有:

1. 首次适应(First Fit)

  • 物品按顺序来,找到第一个能放进去的箱子就放进去
  • 优点:简单,快
  • 缺点:结果不是最优的,可能浪费空间

2. 最佳适应(Best Fit)

  • 物品按顺序来,找到能放进去的、剩余空间最小的箱子放进去
  • 尽量把箱子填满
  • 比首次适应好一点,但也不是最优的

3. 首次适应递减(First Fit Decreasing)

  • 先把物品按从大到小排序
  • 然后用首次适应算法放
  • 大的先放,小的见缝插针
  • 效果比前两个好很多

4. 最佳适应递减(Best Fit Decreasing)

  • 先排序,再最佳适应
  • 效果最好的启发式算法之一

K8s的调度器,本质上就是一个在线的装箱算法——Pod一个一个来,给每个找个合适的节点。

8.6.3 资源利用率优化 ​

调度的最终目标之一,就是提高资源利用率——资源是花钱买的,利用率高了就能省钱。

怎么提高资源利用率?

  1. 合理设置requests:很多人把requests设得很大,怕不够用——结果大部分时间资源都闲着,浪费钱。合理设置requests,根据实际用量调,能省很多钱。

  2. 用BinPacking调度策略:尽量把节点填满,减少需要的节点数。

  3. 超卖(Overcommit):limits比requests大——平时用不了那么多,高峰的时候可以用。但要注意风险,超卖太多容易出问题。

  4. 自动伸缩:节点自动伸缩(Cluster Autoscaler)——节点不够了自动加,多了自动减。不用一直留着很多节点待命。

  5. 混部:在线业务和离线业务混部——在线业务白天忙,离线业务晚上跑,错峰使用资源,提高利用率。

但要注意:资源利用率不是越高越好——利用率太高了,就没有余量了,出点问题就扛不住。要在成本和可用性之间找平衡。

一般来说,集群整体CPU利用率在60-70%左右是比较健康的——既不浪费,又留有余量。

8.7 本章小结 ​

这一章我们深入讲了K8s的调度器。

总结一下重点:

  • 调度器负责给Pod分配节点,是K8s的"大脑"之一
  • 调度流程分三步:预选(Filter)→ 优选(Score)→ 绑定(Bind)
  • 影响调度的因素:nodeSelector、节点亲和性、Pod亲和性/反亲和性、污点与容忍、优先级与抢占
  • 调度器框架是高度可扩展的——有很多扩展点,可以写自定义插件
  • 调度策略:LeastRequested(均衡)vs MostRequested(装箱),各有优劣
  • 装箱问题是调度的核心问题,是NP难的,只能用启发式算法
  • 资源利用率优化是调度的重要目标,但要跟可用性做平衡

调度是K8s里比较深入的一块——大部分时候你不用管它,默认的调度策略就够用了。但当你有特殊需求的时候,知道怎么影响调度决策、怎么扩展调度器,就很有用了。

下一章,我们来讲K8s的网络模型——这是K8s里最复杂、也最容易出问题的一块。


第9章 K8s网络模型:容器网络的黑盒揭秘 ​

如果说K8s里哪一块最复杂、最容易出问题、最让新手头疼,那一定是网络。

Pod之间怎么通信?Pod的IP是哪来的?Service的ClusterIP是怎么回事?网络插件是干什么的?为什么有这么多种CNI?

这一章我们就来揭开K8s网络的黑盒——从基础原理到主流方案,彻底搞懂K8s网络。

9.1 K8s网络模型:四大基本要求 ​

在讲具体实现之前,我们先搞清楚:K8s对网络有什么要求?

K8s的网络模型有四个基本要求——这是K8s的网络"宪法",所有网络方案都必须满足:

9.1.1 要求一:Pod之间可以直接通信,不用NAT ​

同一个集群里,所有Pod之间都可以直接用IP通信,不需要做网络地址转换(NAT)。

什么意思?就是说:

  • Pod A访问Pod B,Pod B看到的源IP就是Pod A的真实IP
  • 中间不会被改源地址、改目的地址
  • 不管Pod在哪个节点上,都是这样

这是K8s网络最基本的要求——Pod的IP是端到端的,中间不做NAT。

为什么要有这个要求?因为很多应用需要知道客户端的真实IP——比如日志、审计、安全策略,都需要真实的源IP。如果中间做了NAT,源IP就丢了。

9.1.2 要求二:节点上的组件可以跟Pod通信 ​

节点本身(比如kubelet、kube-proxy)可以跟所有Pod直接通信。

这个很自然——节点上的组件要管理Pod,肯定要能跟Pod通信。

9.1.3 要求三:每个Pod有自己的IP ​

每个Pod都有一个独立的IP地址,Pod里的所有容器共享这个IP。

前面我们讲过,Pod里的容器共享网络命名空间——所以它们的IP是一样的,端口空间也是一样的。

这就意味着,在K8s的世界里,Pod就像一台虚拟机(或者物理机)——有自己的IP,有自己的端口空间,跟其他Pod是平等的。

这就是K8s网络的核心思想——IP-per-Pod:每个Pod一个IP,Pod之间平等通信,就像物理机之间通信一样。

9.1.4 要求四:不指定怎么实现 ​

K8s只规定了网络应该是什么样的(上面三条),但不规定怎么实现。

具体怎么实现这个网络模型?K8s不管——你用什么方案都行,只要满足这几个要求就行。

这就是为什么有这么多网络插件——Calico、Flannel、Cilium、Weave……都是实现K8s网络模型的不同方案。

这种设计的好处是——灵活,你可以根据自己的需求选合适的网络方案。坏处是——方案太多了,新手不知道怎么选。

9.2 容器网络基础:Docker网络模型 ​

在讲K8s网络之前,我们先回顾一下Docker的网络模型——K8s的网络是在容器网络基础上发展来的。

9.2.1 Docker的默认网络:bridge模式 ​

Docker默认的网络模式是bridge模式:

  • 宿主机上有一个docker0的网桥(虚拟交换机)
  • 每个容器创建的时候,会创建一对veth设备(虚拟网卡对)——一端在容器里(eth0),一端连到docker0网桥上
  • 所有容器都连到docker0上,它们之间可以通过docker0互相通信
  • 容器访问外部网络的时候,通过docker0到宿主机的网卡,然后做SNAT(源地址转换)出去

这种模式下,容器的IP是docker0网段的内部IP——外部网络看不到,容器之间可以通信,但容器访问外部要做NAT。

9.2.2 Docker网络的问题 ​

Docker的默认网络有什么问题?

  1. 跨主机容器不能直接通信——不同宿主机上的容器,IP是各自docker0网段的,互相不通。要通信得通过宿主机端口映射(-p),做NAT。
  2. NAT带来的问题——源IP丢失,性能损耗,配置复杂。

这在单机的时候没问题,但在集群里就不行了——K8s是集群,Pod分布在很多节点上,必须跨节点也能直接通信。

所以K8s的网络,必须解决跨节点Pod通信的问题。

9.3 CNI:容器网络接口标准 ​

前面说了,K8s不规定网络怎么实现,只要满足模型就行。那这么多网络方案,怎么跟K8s对接?

答案是——CNI(Container Network Interface,容器网络接口)。

9.3.1 什么是CNI? ​

CNI是什么?它是一个标准接口——定义了容器网络应该怎么配置、怎么调用。

就像CSI是存储的标准接口一样,CNI是网络的标准接口——只要你的网络方案实现了CNI接口,就能跟K8s对接。

CNI定义了什么?

  • 容器网络的配置格式
  • 插件的调用方式(命令行参数、输入输出)
  • 插件应该实现的操作:ADD(添加网络)、DEL(删除网络)、CHECK(检查)……

K8s的kubelet在创建Pod的时候,会调用CNI插件——CNI插件负责给Pod配置网络、分配IP、设置路由等等。

这样,K8s本身不用管网络具体怎么实现——只要是CNI插件,就能用。

9.3.2 CNI插件的分类 ​

CNI插件有很多种,大致可以分几类:

1. 基础插件

  • 最基础的网络功能,比如bridge、vlan、macvlan、ipvlan……
  • 这些是构成复杂网络方案的基础组件

2. 元插件(Meta Plugin)

  • 调用其他插件的插件,比如flannel、calico这些
  • 或者是一些辅助插件,比如portmap(端口映射)、bandwidth(限流)、firewall(防火墙)……

3. IPAM插件

  • 负责IP地址管理的插件,比如host-local(本地分配)、dhcp、calico-ipam……

一般来说,你用的网络方案(比如Calico、Flannel),都会自带自己的CNI插件——你安装了网络插件,CNI就配置好了,不用自己管。

9.4 跨节点通信的两种思路 ​

跨节点的Pod怎么通信?主要有两种思路:Overlay网络和Underlay网络。

9.4.1 Overlay网络:隧道封装 ​

Overlay网络是什么?就是在现有网络之上,再建一层虚拟网络。

怎么建?用隧道封装——把Pod的网络包,封装在宿主机的网络包里,然后通过宿主机的网络传过去,到了对端再解封装,把里面的Pod包拿出来,发给Pod。

就像寄快递——你要寄一个东西(Pod的包),你把它装在快递盒里(封装),写上收件人地址(宿主机地址),然后通过快递网络(宿主机网络)寄过去,对方收到了拆开快递盒(解封装),拿出里面的东西。

常用的封装协议:

  • VXLAN:最常用的,把二层帧封装在UDP包里
  • IPIP:把IP包封装在IP包里,简单,性能比VXLAN好一点,但只能传IP包
  • Geneve:比较新的,跟VXLAN类似,更灵活

Overlay网络的优点:

  • 对底层网络没有要求——只要宿主机之间能通就行,不管底层是什么网络
  • 部署简单——不用改底层网络配置
  • 隔离性好——虚拟网络跟物理网络分开

缺点:

  • 有封装解封装的开销——性能损失,大概10-20%左右
  • 排错困难——包是封装的,抓包看到的都是封装后的包,不好排查问题
  • MTU问题——封装之后包变大了,要调小MTU,不然会分片或者丢包

代表方案:Flannel(VXLAN模式)、Calico(IPIP模式)、Weave

9.4.2 Underlay网络:路由打通 ​

Underlay网络是什么?就是不做封装,直接通过路由打通——Pod的IP在物理网络里是可路由的,直接通过路由转发。

怎么实现?

  • 每个节点分配一个Pod网段
  • 在物理网络的路由器上,加路由——某个Pod网段,下一跳是某个节点
  • 这样Pod的包直接通过路由转发到目标节点,不用封装

或者用BGP协议——每个节点跟物理路由器建立BGP邻居,把自己的Pod网段宣告出去,路由器自动学习路由。

Underlay网络的优点:

  • 性能好——没有封装开销,跟物理网络差不多
  • 排错简单——就是普通的IP路由,跟物理网络一样
  • 没有MTU问题

缺点:

  • 对底层网络有要求——需要能加路由,或者支持BGP
  • 部署复杂——要配置物理网络设备
  • IP地址消耗大——Pod IP要占用物理网络的地址空间
  • 隔离性差——Pod网络跟物理网络是通的

代表方案:Calico(BGP模式)、Macvlan、SR-IOV

9.4.3 怎么选? ​

两种方案各有优劣,怎么选?

  • 简单、快速部署、对底层网络没要求 → Overlay(Flannel VXLAN、Calico IPIP)
  • 对性能要求高、底层网络可控 → Underlay(Calico BGP)
  • 公有云环境 → 一般用Overlay,或者云厂商的CNI(比如AWS VPC CNI、阿里云Terway)
  • 一般场景 → Calico就挺好,功能全,性能也不错

没有最好的,只有最合适的——根据你的环境和需求来。

9.5 主流网络插件对比 ​

CNI插件太多了,我们来对比几个主流的。

9.5.1 Flannel ​

Flannel是CoreOS开发的,是最早、最简单的CNI插件之一。

特点:

  • 简单,容易部署
  • 功能单一——只管网络连通,不管别的
  • 支持多种后端:VXLAN、host-gw、UDP
  • 没有网络策略(NetworkPolicy)支持

适用场景:

  • 测试环境、简单场景
  • 只需要网络通,不需要高级功能

优点:简单,轻量。 缺点:功能少,性能一般,没有网络策略。

9.5.2 Calico ​

Calico是现在最流行的CNI插件之一,功能非常强大。

特点:

  • 支持多种模式:IPIP(Overlay)、BGP(Underlay)、VXLAN
  • 支持网络策略(NetworkPolicy)——而且比K8s原生的功能还强
  • 性能好——特别是BGP模式,几乎没有性能损失
  • 支持大规模集群
  • 功能丰富:网络策略、BGP、IPAM、服务网格集成……

适用场景:

  • 生产环境
  • 需要网络策略的场景
  • 对性能要求高的场景
  • 大规模集群

优点:功能强大,性能好,社区活跃。 缺点:稍微复杂一点,学习成本高一点。

9.5.3 Cilium ​

Cilium是比较新的CNI插件,基于eBPF技术,最近非常火。

特点:

  • 基于eBPF——内核级的可编程网络
  • 性能非常好——eBPF在内核里处理,比iptables快多了
  • 功能强大:网络策略、负载均衡、可观测性、服务网格(Cilium Service Mesh)……
  • 支持 Hubble——网络可观测性工具,能看到所有网络流量
  • 可以替代kube-proxy——用eBPF做Service负载均衡,比iptables/IPVS还快

适用场景:

  • 对性能要求极高的场景
  • 需要高级网络可观测性的场景
  • 大规模集群
  • 想用eBPF技术的

优点:性能极高,功能强大,可观测性好,未来方向。 缺点:比较新,社区和生态不如Calico成熟,对内核版本有要求(需要比较新的内核支持eBPF)。

9.5.4 其他方案 ​

还有一些其他方案:

  • Weave:简单易用,支持加密,但是性能一般,用的人越来越少了
  • Macvlan:直接用物理网卡的MAC地址,性能好,但是需要底层网络支持,用得不多
  • SR-IOV:硬件级的网络虚拟化,性能极高,但是需要特殊网卡,一般用于高性能计算场景
  • 云厂商CNI:比如AWS VPC CNI、阿里云Terway、Azure CNI——跟云厂商的VPC集成,Pod IP就是VPC里的IP,性能好,跟云服务集成好

9.5.5 选型建议 ​

怎么选CNI插件?我的建议:

  • 新手、测试环境 → Flannel,简单,容易上手
  • 生产环境、通用场景 → Calico,功能全,成熟稳定,社区好
  • 追求极致性能、想用eBPF → Cilium,未来的方向
  • 公有云环境 → 优先用云厂商的CNI,跟云集成更好

现在Calico是事实标准,大部分生产环境都用Calico——功能全,性能好,成熟稳定。

Cilium是后起之秀,发展很快——特别是eBPF火了之后,Cilium越来越受欢迎。如果你的环境比较新,内核版本够高,可以试试Cilium。

9.6 Service的实现:kube-proxy的工作原理 ​

前面我们讲Service的时候提到了kube-proxy——它负责实现Service的负载均衡。这一节我们深入讲讲它是怎么工作的。

9.6.1 ClusterIP是怎么回事? ​

首先,ClusterIP是什么?它是一个虚拟IP——不是真实存在的网卡上的IP,是iptables/IPVS里的规则。

你在节点上ping ClusterIP是ping不通的——因为它不是一个真实的地址,只是iptables里的一个规则,匹配到了就做DNAT。

9.6.2 iptables模式的工作原理 ​

kube-proxy的iptables模式,是怎么实现Service的?

简单说:

  1. kube-proxy Watch Service和Endpoint的变化
  2. 为每个Service,在iptables里加一系列规则
  3. 当访问Service的ClusterIP + Port的时候,iptables匹配到规则
  4. 做DNAT——把目的IP改成某个后端Pod的IP,目的端口改成Pod的端口
  5. 包就发给了那个Pod

返回的包呢?做SNAT——把源IP改成节点的IP,这样Pod返回的包能回到节点,再由iptables改回去,发给客户端。

整个过程是完全在Netfilter(内核的网络过滤框架)里做的——不需要经过用户态的kube-proxy进程,所以性能还不错。

但是iptables有个问题——Service多了,iptables规则会非常多,而且iptables是线性匹配的——规则多了性能会下降。

9.6.3 IPVS模式的工作原理 ​

IPVS模式跟iptables模式类似,也是内核里做负载均衡。

区别是:

  • IPVS是内核里专门做负载均衡的模块,性能更好
  • 支持多种负载均衡算法:轮询、加权轮询、最小连接、源地址哈希……
  • 规则更新是增量的,比iptables快
  • 大规模下性能稳定,不会像iptables那样规则多了就慢

所以大规模集群,推荐用IPVS模式。

9.6.4 eBPF模式:Cilium的做法 ​

前面说了Cilium——它可以不用kube-proxy,直接用eBPF来实现Service的负载均衡。

eBPF是什么?它是Linux内核里的一个虚拟机——可以在内核里运行用户写的小程序,做各种事情(网络、监控、安全……)。

Cilium用eBPF在内核里做负载均衡——比iptables和IPVS都快,因为更灵活,不需要遍历规则。

这是未来的方向——用eBPF替代iptables/IPVS,性能更好,功能更强。

9.7 网络策略:NetworkPolicy ​

前面讲了网络连通性——Pod之间怎么通信。但有时候我们不想让所有Pod之间都能通信——我们想做网络隔离,只有允许的才能通。

这就是NetworkPolicy(网络策略)。

9.7.1 什么是NetworkPolicy? ​

NetworkPolicy是什么?它是K8s的一个资源对象,用来定义Pod之间的网络访问规则——就像防火墙规则。

比如:

  • 只有前端Pod能访问后端Pod的8080端口
  • 只有某个命名空间的Pod能访问数据库
  • 某个Pod只能访问特定的外部地址

默认情况下,K8s的网络是全通的——所有Pod之间都能互相访问。配置了NetworkPolicy之后,匹配的Pod就会被隔离——只有规则允许的流量才能通过。

9.7.2 NetworkPolicy的类型 ​

NetworkPolicy有两种类型的规则:

  • 入站规则(Ingress):控制哪些流量能进来——哪些源能访问这个Pod的哪些端口
  • 出站规则(Egress):控制哪些流量能出去——这个Pod能访问哪些目的地址、哪些端口

可以分别配置,也可以都配置。

9.7.3 怎么选择Pod? ​

NetworkPolicy是作用在哪些Pod上的?通过标签选择器——跟Service一样,选择匹配某个标签的Pod。

然后规则里的源/目的,也可以用标签选择器选Pod,或者选命名空间,或者指定IP段。

非常灵活——你可以精确控制谁能访问谁。

9.7.4 注意:需要网络插件支持 ​

重要提醒:NetworkPolicy不是K8s默认就支持的,需要网络插件支持。

也就是说,不是你装了K8s就能用NetworkPolicy——你的CNI插件得支持NetworkPolicy才行。

哪些支持?

  • Calico:支持,而且功能比原生的还强
  • Cilium:支持,基于eBPF,性能好
  • Weave:支持
  • Flannel:不支持!Flannel只管连通,不管网络策略

这也是为什么生产环境一般不用Flannel——功能太少了,连网络策略都没有。

9.8 扩展知识:Linux网络虚拟化基础 ​

K8s的网络,本质上是Linux网络虚拟化的应用——veth、网桥、路由、iptables、eBPF……都是Linux内核的特性。

我们扩展讲讲这些基础概念,理解了这些,K8s网络就不神秘了。

9.8.1 veth:虚拟网卡对 ​

veth(virtual ethernet)是什么?它是一对虚拟网卡——两个网卡是连在一起的,从一个进去的包,会从另一个出来。

就像一根虚拟的网线——两头各有一个网卡,插在不同的地方。

veth有什么用?用来连接不同的网络命名空间——比如容器的网络命名空间和宿主机的网络命名空间,用一对veth连起来,这样容器就能跟宿主机通信了。

Pod里的eth0,就是veth的一端——另一端在宿主机上,连到网桥上。

9.8.2 网桥(Bridge):虚拟交换机 ​

网桥(Bridge)是什么?它是Linux内核里的虚拟交换机——可以把多个网络接口连起来,它们之间可以互相转发数据包。

就像物理交换机一样——把多台机器插在交换机上,它们之间就能通信了。

Docker的docker0、CNI的cni0,都是网桥——所有容器的veth都连到网桥上,容器之间就能通过网桥通信了。

9.8.3 网络命名空间(Network Namespace) ​

前面讲过网络命名空间——隔离网络栈。每个网络命名空间有自己的网络设备、IP地址、路由表、iptables规则。

容器的网络隔离,就是靠网络命名空间实现的——每个容器(或者说每个Pod)有自己的网络命名空间,跟其他容器隔离开。

9.8.4 iptables / Netfilter ​

Netfilter是什么?它是Linux内核里的网络过滤框架——可以在网络包处理的各个阶段,做过滤、修改、NAT等操作。

iptables是什么?它是用户态的工具,用来配置Netfilter的规则。

我们常说的iptables,其实指的是Netfilter + iptables工具。

K8s里的Service、SNAT、网络策略(部分实现),都是通过iptables实现的。

9.8.5 eBPF:内核可编程 ​

eBPF(extended Berkeley Packet Filter)是什么?它是Linux内核里的一个虚拟机——可以在内核里安全地运行用户写的小程序。

eBPF能做什么?

  • 网络:包过滤、负载均衡、流量控制……
  • 可观测性:跟踪内核函数、统计性能指标……
  • 安全:系统调用过滤、安全审计……

eBPF的好处是——不用改内核代码,不用加载内核模块,就能在内核里实现各种功能,而且性能很好。

Cilium就是基于eBPF的——用eBPF做网络、负载均衡、网络策略、可观测性,比传统的iptables/IPVS方案性能更好、功能更强。

eBPF是现在云原生领域最火的技术之一——被称为"Linux的JavaScript",因为它让内核变得可编程了。未来很多网络、可观测性、安全的功能,都会用eBPF来实现。

9.9 本章小结 ​

这一章我们深入讲了K8s的网络模型。

总结一下重点:

  • K8s网络模型的四个基本要求:Pod之间直接通信不用NAT、节点跟Pod能通信、每个Pod有自己的IP、不指定实现方式
  • CNI:容器网络接口标准,所有网络方案都通过CNI跟K8s对接
  • 跨节点通信的两种思路:Overlay(隧道封装,简单但有性能损失)和Underlay(路由打通,性能好但要求高)
  • 主流网络插件对比:Flannel(简单)、Calico(功能强大,生产首选)、Cilium(基于eBPF,未来方向)
  • kube-proxy的工作原理:iptables模式、IPVS模式、eBPF模式
  • NetworkPolicy:网络策略,做Pod之间的网络隔离,需要网络插件支持
  • Linux网络虚拟化基础:veth、网桥、网络命名空间、iptables、eBPF

网络是K8s里最复杂的一块,也是生产环境最容易出问题的一块。理解了网络的基本原理和各种方案的特点,你才能选对网络插件,出了问题也能排查。

下一章,我们来讲安全与权限——RBAC、NetworkPolicy、Pod Security。


第10章 安全与权限:RBAC、ServiceAccount与Pod Security ​

K8s很强大,但也很复杂——复杂就意味着安全风险。如果配置不当,K8s集群可能会成为安全重灾区。

这一章我们就来讲讲K8s的安全体系——怎么控制谁能访问集群、能做什么、Pod能有什么权限、网络怎么隔离。

10.1 K8s安全的四层防线 ​

K8s的安全,可以分成四层,从外到内:

  1. 认证(Authentication):你是谁?——验证用户的身份
  2. 授权(Authorization):你能做什么?——验证用户有没有权限做某个操作
  3. 准入控制(Admission Control):这个请求合不合规?——在请求生效之前做检查
  4. 运行时安全:Pod运行的时候安全吗?——Pod的权限、网络隔离、运行时防护

我们一层层来讲。

10.2 认证:你是谁? ​

10.2.1 K8s的用户类型 ​

首先,K8s里有两种"用户":

1. 普通用户(Human User)

  • 真实的人,比如运维工程师、开发人员
  • K8s本身不管理用户——没有用户数据库,没有用户表
  • 用户是在外部管理的——比如证书、LDAP、OIDC、云厂商的IAM

2. 服务账号(ServiceAccount)

  • 给Pod用的账号——Pod里的进程访问apiserver的时候用的身份
  • K8s管理的资源——你可以创建ServiceAccount,给它分配权限
  • 每个命名空间都有一个默认的ServiceAccount

为什么分两种?因为人和程序的认证方式不一样——人用用户名密码、证书、SSO,程序用token、证书。

10.2.2 认证方式 ​

K8s支持很多种认证方式:

1. 客户端证书(X509 Client Certs)

  • 最常用的方式——用SSL证书认证
  • apiserver配置CA证书,客户端用CA签发的证书来认证
  • 证书里的CN字段就是用户名,O字段是用户组
  • kubeadm部署的集群,默认就是这种方式

2. 静态Token文件

  • 一个静态的CSV文件,里面存着token、用户名、用户组
  • 简单,但不安全——token是静态的,不能过期,改了要重启apiserver
  • 现在很少用了

3. ServiceAccount Token

  • ServiceAccount的token,给Pod用的
  • Pod里自动挂载,进程可以用这个token访问apiserver

4. OIDC(OpenID Connect)

  • 基于OAuth2的认证方式,支持SSO
  • 可以对接企业的身份提供商,比如Google、Azure AD、Keycloak
  • 企业级环境常用

5. Webhook Token

  • 调用外部的webhook来认证token
  • 灵活,可以对接自己的认证系统

6. 匿名认证(Anonymous)

  • 没有认证信息的请求,当作匿名用户
  • 默认是开启的——但匿名用户默认没什么权限
  • 生产环境建议关掉,或者严格限制匿名用户的权限

还有其他一些方式,比如Bootstrap Token、Keystone Password……用得不多。

一般来说:

  • 小集群、个人用 → 客户端证书就行
  • 企业级、多用户 → OIDC对接企业身份系统
  • Pod用 → ServiceAccount

10.3 授权:你能做什么?——RBAC ​

认证解决了"你是谁"的问题,接下来是"你能做什么"——授权。

K8s的授权有好几种模式:ABAC、RBAC、Webhook、Node……现在最常用的是RBAC(Role-Based Access Control,基于角色的访问控制)。

10.3.1 什么是RBAC? ​

RBAC是什么?简单说:

  • 定义角色(Role)——角色有一组权限
  • 把角色绑定给用户/ServiceAccount——用户就有了这个角色的权限

就像公司里的岗位:

  • "运维工程师"这个岗位,有服务器的权限
  • "开发工程师"这个岗位,有代码仓库的权限
  • 你是运维工程师,你就有运维的权限

这种方式的好处是——灵活、可复用、好管理。用户多了,不用一个个给权限,只要给角色,然后把人放到角色里就行。

10.3.2 Role和ClusterRole ​

RBAC里有两种角色:

1. Role(角色)

  • 命名空间级别的——只能管某个命名空间里的资源
  • 比如你在default命名空间里创建一个Role,它只能管default里的资源

2. ClusterRole(集群角色)

  • 集群级别的——管整个集群的资源
  • 比如节点、PV、所有命名空间的资源……

为什么分两种?因为权限的范围不一样——有些权限是整个集群的,有些是某个命名空间的。

10.3.3 RoleBinding和ClusterRoleBinding ​

角色定义好了,怎么跟用户绑定?用Binding:

1. RoleBinding

  • 把Role绑定给用户/ServiceAccount
  • 绑定的是命名空间里的Role,权限只在这个命名空间生效
  • 也可以绑定ClusterRole——但权限只在这个命名空间生效(相当于把ClusterRole的权限限定在某个命名空间)

2. ClusterRoleBinding

  • 把ClusterRole绑定给用户/ServiceAccount
  • 权限在整个集群生效

举个例子:

  • 你想让张三在default命名空间里有Pod的查看权限 → 建一个Role(看Pod),然后RoleBinding绑给张三,在default命名空间
  • 你想让李四在整个集群里都有Pod的查看权限 → 建一个ClusterRole(看Pod),然后ClusterRoleBinding绑给李四

10.3.4 权限怎么定义? ​

角色里的权限是怎么定义的?用规则(Rules):

每条规则包含:

  • apiGroups:API组——比如apps、batch、extensions……核心资源的API组是空字符串
  • resources:资源类型——比如pods、deployments、services……
  • verbs:操作——get、list、watch、create、update、patch、delete、deletecollection……
  • (可选)resourceNames:具体的资源名——限制只能操作某个具体的资源
  • (可选)nonResourceURLs:非资源URL——比如/healthz、/version……

比如:

apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]

这条规则的意思是——可以查看(get/list/watch)核心API组里的pods资源。

非常灵活——你可以精确控制谁能对什么资源做什么操作。

10.3.5 ServiceAccount:Pod的身份 ​

前面提到了ServiceAccount——它是给Pod用的账号。

ServiceAccount是什么?

  • 它是K8s的一个资源对象,命名空间级别的
  • 每个ServiceAccount对应一个Secret——里面存着token
  • Pod可以指定用哪个ServiceAccount
  • Pod里的进程,可以用这个ServiceAccount的token访问apiserver
  • apiserver通过RBAC判断这个ServiceAccount有没有权限

默认情况下,每个命名空间都有一个叫default的ServiceAccount——如果你不指定,Pod就用这个默认的。

默认的ServiceAccount有什么权限?——基本没有,只能访问一些公开的API。

生产环境最佳实践:

  • 每个应用用自己的ServiceAccount,不要用默认的
  • 给ServiceAccount分配最小权限——需要什么权限给什么,不要多给
  • 不要给ServiceAccount集群管理员权限——太危险了

10.3.6 RBAC最佳实践 ​

RBAC的最佳实践:

  1. 最小权限原则:只给需要的权限,能不给的就不给
  2. 角色复用:尽量用角色,不要给用户直接加权限——好管理
  3. 用组:用户加到组里,给组授权——比给单个用户授权好管理
  4. 定期审计:定期检查权限,清理不用的权限和账号
  5. 不要用cluster-admin随便给人——集群管理员权限太大了,慎用
  6. ServiceAccount也要管:不要给Pod的ServiceAccount太多权限

10.4 准入控制:请求合不合规? ​

认证和授权之后,还有一关——准入控制(Admission Control)。

10.4.1 什么是准入控制? ​

准入控制是什么?它是apiserver里的一些插件——在请求被认证授权之后、对象被保存到etcd之前,对请求进行检查和修改。

如果准入控制插件拒绝了这个请求,整个请求就失败了,返回错误。

打个比方:

  • 认证:查身份证,看你是不是本人
  • 授权:看你有没有权限进这个楼
  • 准入控制:安检——看你带的东西合不合规,有没有违禁品

准入控制分两种:

  • Mutating Admission:修改型的——可以修改请求的内容,比如给Pod加默认的资源限制
  • Validating Admission:验证型的——只能检查,不能修改,不合规就拒绝

先执行Mutating的,再执行Validating的——先改,再检查。

10.4.2 常用的准入控制插件 ​

K8s有很多内置的准入控制插件,常用的有:

  • NamespaceLifecycle:防止删除正在使用的命名空间,防止在不存在的命名空间里创建资源
  • LimitRanger:给Pod设置默认的资源限制——如果Pod没设requests/limits,自动加上
  • ResourceQuota:限制命名空间的资源配额——总资源不能超过多少
  • ServiceAccount:自动给Pod挂载ServiceAccount
  • DefaultStorageClass:给没指定StorageClass的PVC自动加上默认的
  • PodSecurity:Pod安全策略——检查Pod的安全配置
  • AlwaysPullImages:总是拉取镜像——防止用本地缓存的镜像
  • ……还有很多

apiserver有个参数--enable-admission-plugins,可以配置启用哪些插件。

10.4.3 动态准入控制:Admission Webhook ​

内置的插件不够用?没关系,K8s支持动态准入控制——你可以自己写webhook,apiserver在处理请求的时候调用你的webhook,你来决定接受还是拒绝,或者修改请求。

这就是Admission Webhook——也分Mutating和Validating两种。

Admission Webhook非常强大——你可以实现各种自定义的策略:

  • 镜像必须来自指定的镜像仓库
  • Pod必须设置资源限制
  • 必须有某些标签
  • 不能用特权容器
  • 自动注入Sidecar(比如Istio就是这么干的)
  • ……

很多高级功能都是通过Admission Webhook实现的——Istio、Knative、各种Operator……

10.5 Pod安全:Pod能做什么? ​

前面讲的是访问apiserver的权限,那Pod本身的权限呢?Pod里的进程能做什么?能不能访问宿主机的资源?

这就是Pod安全——控制Pod的权限和行为。

10.5.1 Pod Security Standards:Pod安全标准 ​

K8s官方定义了Pod Security Standards(PSS,Pod安全标准)——三个安全等级:

1. Privileged(特权级)

  • 没有限制,Pod想干嘛干嘛
  • 跟直接跑在宿主机上差不多
  • 风险最高
  • 适用场景:系统组件、需要特权的应用(比如网络插件、存储插件)

2. Baseline(基线级)

  • 基本的安全限制,防止特权提升
  • 禁止特权容器、禁止HostNetwork、禁止HostPID、禁止HostIPC……
  • 大部分普通应用都能满足
  • 风险中等
  • 适用场景:大部分普通应用

3. Restricted(严格限制级)

  • 最严格的安全限制
  • 除了Baseline的限制,还要求:必须以非root用户运行、不能提权、只读根文件系统、不能加危险的capabilities……
  • 最安全,但有些应用跑不起来
  • 适用场景:安全要求高的应用、不可信的应用

这三个等级是递进的——Privileged最松,Baseline中等,Restricted最严。

10.5.2 Pod Security Admission:内置的Pod安全准入 ​

K8s 1.25之后,内置了Pod Security Admission(PSA)——一个准入控制插件,用来强制实施Pod安全标准。

怎么用?给命名空间打标签——指定这个命名空间用哪个安全等级:

  • pod-security.kubernetes.io/enforce: baseline —— 强制实施baseline标准,不符合的Pod会被拒绝
  • pod-security.kubernetes.io/audit: restricted —— 审计模式,不符合的会记日志,但不拒绝
  • pod-security.kubernetes.io/warn: restricted —— 警告模式,不符合的会给用户警告,但不拒绝

非常简单——给命名空间打个标签就行了,不用装额外的组件。

10.5.3 已废弃的PodSecurityPolicy(PSP) ​

以前K8s有个东西叫PodSecurityPolicy(PSP)——也是做Pod安全策略的。

但PSP有很多问题:

  • 难用,配置复杂
  • 容易踩坑——默认开启的话可能把所有Pod都拦了
  • 权限模型有问题

所以K8s 1.21开始废弃PSP,1.25正式移除了——取而代之的是Pod Security Standards + Pod Security Admission。

如果你还在用老版本的K8s,注意PSP已经废弃了,新的用PSA。

10.5.4 第三方方案:OPA/Gatekeeper、Kyverno ​

内置的PSA比较简单,如果需要更复杂的策略怎么办?

可以用第三方的策略引擎——比如OPA Gatekeeper和Kyverno。

OPA Gatekeeper:

  • 基于OPA(Open Policy Agent),用Rego语言写策略
  • 功能非常强大,非常灵活
  • 学习曲线比较陡——Rego语言需要学
  • CNCF毕业项目,社区活跃

Kyverno:

  • 专门为K8s设计的策略引擎
  • 策略用YAML写,跟K8s的风格一致
  • 容易上手,不用学新语言
  • 功能也很强大
  • CNCF孵化项目

这两个都是通过Admission Webhook实现的——比内置的PSA功能强大多了,可以实现各种复杂的自定义策略。

生产环境如果对安全要求高,建议用OPA Gatekeeper或者Kyverno。

10.5.5 Pod安全最佳实践 ​

Pod安全的最佳实践:

  1. 不要用特权容器——除非万不得已,特权容器跟root没区别,太危险了
  2. 以非root用户运行——容器里的进程不要用root用户,用普通用户
  3. 只读根文件系统——容器的根文件系统设为只读,防止篡改
  4. 不要用HostNetwork/HostPID/HostIPC——除非必须,不要共享宿主机的命名空间
  5. drop所有capabilities,只加需要的——Linux capabilities,默认的太多了,能去掉的都去掉
  6. 设置seccomp/AppArmor/SELinux——限制系统调用,减少攻击面
  7. 不要挂载宿主机的敏感目录——比如/var/run/docker.sock、/proc、/sys这些

10.6 网络安全:NetworkPolicy ​

前面讲网络的时候提到了NetworkPolicy——网络策略,用来做Pod之间的网络隔离。

这也是安全的重要一环——就算攻击者攻破了一个Pod,也不能随便访问其他Pod。

10.6.1 默认拒绝,最小开放 ​

网络安全的最佳实践是——默认拒绝,只开放需要的。

就是说,默认情况下,所有流量都拒绝——然后一条条加规则,只允许需要的流量通过。

这样最安全——就算有漏洞,攻击面也小。

很多人图省事,网络全通——这是很危险的。生产环境建议开启NetworkPolicy,做网络隔离。

10.6.2 分段隔离 ​

怎么设计网络策略?可以按应用、按环境、按租户分段隔离:

  • 不同环境(开发、测试、生产)之间隔离
  • 不同租户之间隔离
  • 不同应用之间隔离
  • 前端、后端、数据库之间隔离——只有前端能访问后端,只有后端能访问数据库

分层隔离,一层一层的,就算一层被攻破了,还有下一层。

10.7 镜像安全 ​

还有一块安全——镜像安全。镜像是应用的载体,如果镜像本身有问题,那跑起来的Pod也不安全。

10.7.1 镜像安全要点 ​

镜像安全要注意什么:

  1. 用基础镜像要小心:不要用不知名的基础镜像,可能有后门、有漏洞。用官方的、可信的基础镜像。
  2. 最小化镜像:镜像里只放需要的东西——不要装一堆没用的工具,减少攻击面。用Distroless、Alpine之类的精简镜像。
  3. 扫描镜像漏洞:用镜像扫描工具(比如Trivy、Clair、Aqua)扫描镜像里的漏洞,及时修复。
  4. 镜像签名:对镜像进行签名,确保镜像没有被篡改——比如用Cosign。
  5. 只允许可信镜像仓库:只允许从指定的镜像仓库拉镜像,防止拉到恶意镜像。
  6. 不要用latest标签:latest标签不稳定,不知道拉的是什么版本。用具体的版本号。
  7. 不要在镜像里放敏感信息:密码、密钥这些不要打到镜像里,用Secret。

10.7.2 供应链安全 ​

最近软件供应链安全很火——SolarWinds事件、Log4j事件……供应链攻击越来越多。

K8s生态里的供应链安全也很重要——你用的镜像、用的Helm Chart、用的Operator,是不是可信的?有没有被篡改?

CNCF有个项目叫Sigstore——专门做软件供应链安全,给软件签名、验证。还有SLSA——供应链安全等级框架。

这块是现在的热点,越来越受重视。

10.8 扩展知识:零信任安全模型 ​

讲到安全,我们扩展讲讲现在很火的**零信任(Zero Trust)**安全模型。

10.8.1 什么是零信任? ​

传统的安全模型是边界安全——假设内网是安全的,外网是危险的。在边界建防火墙,把坏人挡在外面,里面的都是好人。

但现在这个模型不行了——

  • 云原生时代,没有明确的边界了
  • 攻击者一旦攻破边界,内网里横着走
  • 内部威胁也越来越多——内鬼、账号泄露

零信任的理念是——从不信任,始终验证(Never trust, always verify)。

什么意思?就是:

  • 不管你是从内网来的还是外网来的,都不信任
  • 每次访问都要验证身份、验证权限
  • 没有"内网就是安全的"这种说法
  • 最小权限,只给需要的
  • 持续验证,不是一次验证就完了

10.8.2 零信任在K8s里的体现 ​

零信任在K8s里怎么落地?

  1. 身份认证:所有访问都要认证——人要认证,服务也要认证(mTLS)
  2. RBAC最小权限:只给需要的权限
  3. NetworkPolicy网络隔离:Pod之间默认不通,只开放需要的
  4. Pod安全:限制Pod的权限,减少攻击面
  5. mTLS:服务之间通信用mTLS加密,双向认证——服务网格(Istio、Cilium)可以做这个
  6. 持续监控:持续监控所有访问,异常行为告警

零信任不是一个产品,而是一种理念、一套体系——需要从多个层面一起做,才能实现真正的零信任。

云原生时代,零信任是必然的趋势——因为传统的边界安全模型已经不适用了。

10.9 本章小结 ​

这一章我们讲了K8s的安全体系。

总结一下重点:

  • K8s安全四层防线:认证 → 授权 → 准入控制 → 运行时安全
  • 认证:验证身份,支持证书、Token、OIDC等多种方式
  • RBAC:基于角色的访问控制,Role/ClusterRole + RoleBinding/ClusterRoleBinding
  • ServiceAccount:Pod的身份,Pod用它访问apiserver
  • 准入控制:在请求生效前做检查和修改,支持内置插件和动态Webhook
  • Pod安全:Pod Security Standards三个等级(Privileged/Baseline/Restricted),Pod Security Admission强制实施
  • 第三方策略引擎:OPA Gatekeeper、Kyverno,实现更复杂的策略
  • 网络安全:NetworkPolicy做网络隔离,默认拒绝最小开放
  • 镜像安全:镜像扫描、镜像签名、可信镜像仓库
  • 零信任安全模型:从不信任,始终验证,云原生安全的趋势

安全是个系统工程,不是靠某一个工具就能解决的——需要从认证、授权、网络、运行时、供应链等多个层面一起做,层层防护,才能真正做好安全。

下一章,我们来讲可观测性——日志、监控、告警,怎么知道集群里发生了什么。


第11章 可观测性三支柱:日志、监控与告警 ​

K8s集群跑起来了,应用也部署了——但你怎么知道集群运行得好不好?应用有没有问题?出了问题怎么排查?

这就是**可观测性(Observability)**要解决的问题。

可观测性是什么?简单说——通过系统的外部输出,就能理解系统的内部状态。不用进系统里面看,看日志、指标、链路追踪这些外部输出,就能知道系统里面发生了什么。

可观测性有三大支柱——日志(Logging)、指标(Metrics)、链路追踪(Tracing)。这一章我们就来讲讲K8s里的可观测性体系。

11.1 可观测性三支柱概述 ​

先简单介绍一下三支柱:

11.1.1 日志(Logging) ​

日志是什么?就是应用或者系统输出的、记录发生了什么事情的文本信息。

比如:

  • 应用打印的日志:"用户登录成功"、"数据库连接失败"
  • 系统日志:内核日志、kubelet日志、apiserver日志
  • 访问日志:Nginx的access log

日志的特点:

  • 记录具体的事件——发生了什么事,什么时候发生的,详情是什么
  • 是离散的、非结构化的(或者半结构化的)
  • 数据量大——尤其是高并发应用,日志量非常大
  • 适合排查具体问题——出了什么错,错误信息是什么

11.1.2 指标(Metrics) ​

指标是什么?就是可度量的、数值型的数据,按时间序列存储。

比如:

  • CPU使用率、内存使用率
  • QPS(每秒请求数)、延迟、错误率
  • Pod数量、节点数量

指标的特点:

  • 是数值型的、结构化的
  • 按时间序列存储——时间 + 数值 + 标签
  • 数据量相对小——因为是聚合过的
  • 适合监控趋势、告警——看系统整体状态怎么样,有没有异常

11.1.3 链路追踪(Tracing) ​

链路追踪是什么?就是追踪一个请求经过了哪些服务,每个服务花了多长时间。

比如一个用户请求,经过了网关 → 前端 → 后端 → 数据库——链路追踪能把整个链路串起来,告诉你每个环节花了多久,哪里慢了。

链路追踪的特点:

  • 记录请求的完整链路
  • 适合排查性能问题、分布式系统的问题
  • 微服务架构下特别有用——服务多了,不知道请求卡在哪了

11.1.4 三者的关系 ​

三者是什么关系?怎么配合用?

一般排查问题的流程是:

  1. 先看指标——发现异常,比如错误率升高了、延迟变大了
  2. 再看链路追踪——找到是哪个服务、哪个环节出了问题
  3. 最后看日志——看具体的错误信息,定位根因

指标告诉你"有没有问题",链路追踪告诉你"哪里有问题",日志告诉你"为什么有问题"。

三者配合,才能完整地观测系统。

11.2 监控体系:Prometheus + Grafana ​

K8s生态里,监控的事实标准是Prometheus + Grafana——Prometheus负责采集和存储指标,Grafana负责可视化展示。

11.2.1 Prometheus是什么? ​

Prometheus是什么?它是一个开源的监控系统和时序数据库——专门用来存指标数据的。

Prometheus是CNCF的毕业项目,现在是云原生监控的事实标准。

Prometheus的特点:

  • 多维数据模型:时序数据由指标名和一组键值对标签组成——非常灵活
  • PromQL查询语言:强大的查询语言,可以做各种聚合、计算
  • 拉模型(Pull):Prometheus主动去拉指标,不是客户端推过来——这样Prometheus可以控制拉取频率,也可以做服务发现
  • 服务发现:自动发现监控目标——在K8s里,自动发现Pod、Service、节点,不用手动配置
  • 强大的告警:Alertmanager,支持灵活的告警规则和通知

11.2.2 Prometheus的工作原理 ​

Prometheus是怎么工作的?

  1. 服务发现:Prometheus通过服务发现机制,找到要监控的目标(target)
  2. 拉取指标:定期去拉取每个目标的/metrics接口,获取指标数据
  3. 存储:把拉到的指标数据存在时序数据库里
  4. 查询:用户或者Grafana通过PromQL查询数据
  5. 告警:根据告警规则计算,触发的告警发给Alertmanager
  6. 通知:Alertmanager把告警发给用户(邮件、钉钉、企业微信……)

整个过程是拉模型——Prometheus主动去拉,不是客户端推。

为什么用拉模型?

  • Prometheus可以控制拉取频率
  • 可以做服务发现——自动发现目标
  • 方便调试——你可以手动curl目标的metrics接口,看对不对
  • Prometheus挂了不影响应用——应用不用管监控的事儿

11.2.3 监控什么?——指标分类 ​

在K8s里,我们一般监控哪些东西?

1. 基础设施层

  • 节点的CPU、内存、磁盘、网络——用Node Exporter采集
  • 节点的状态、数量

2. K8s组件层

  • apiserver、scheduler、controller-manager、etcd、kubelet、kube-proxy这些组件的指标
  • K8s资源的状态——Pod数量、Deployment状态、PVC状态……——用kube-state-metrics采集

3. 容器/ Pod层

  • 每个Pod/容器的CPU、内存、网络、磁盘——用cAdvisor采集(kubelet自带)
  • Pod的状态、重启次数

4. 应用层

  • 应用自己的业务指标——QPS、延迟、错误率、业务量……
  • 需要应用自己暴露metrics接口,Prometheus去拉

5. 黑盒监控

  • 从外部探测服务是否可用——比如探测网站能不能访问、端口通不通
  • 用Blackbox Exporter

一层层下来,从基础设施到应用,全覆盖。

11.2.4 Grafana:可视化 ​

Prometheus存了数据,怎么展示?用Grafana。

Grafana是什么?它是一个开源的可视化工具——可以接各种数据源(Prometheus、MySQL、Elasticsearch……),然后做漂亮的Dashboard。

Grafana的特点:

  • 支持多种数据源
  • 丰富的图表类型——折线图、柱状图、饼图、表格、热力图……
  • 灵活的Dashboard——可以自己拼,也可以用别人做好的
  • 变量、模板——Dashboard可以复用
  • 也支持告警

一般的用法是:Prometheus存数据,Grafana做Dashboard展示——运维人员平时看Grafana大盘,知道系统运行得怎么样。

K8s相关的Dashboard,Grafana官网上有很多现成的——节点监控、Pod监控、Deployment监控……直接导入就能用,不用自己从零做。

11.2.5 Alertmanager:告警 ​

监控了,发现异常怎么办?告警啊!

Prometheus的告警是通过Alertmanager实现的:

  • Prometheus负责计算告警规则——满足条件就触发告警
  • Alertmanager负责管理告警——去重、分组、抑制、静默、通知

Alertmanager的功能:

  • 分组:把相关的告警分到一组,一起发——避免告警风暴
  • 抑制:某个告警触发了,抑制其他相关的告警——比如节点挂了,节点上的Pod告警就不用发了
  • 静默:临时屏蔽某些告警——比如维护的时候
  • 多种通知方式:邮件、钉钉、企业微信、Slack、PagerDuty……

告警是监控最重要的功能之一——光有Dashboard没用,你不可能24小时盯着大盘看。有问题要自动通知你。

11.3 日志体系:EFK / PLG ​

监控指标告诉你"出问题了",但要知道"为什么出问题",还得看日志。

K8s里的日志体系,主流的有两套:EFK和PLG。

11.3.1 EFK:Elasticsearch + Fluentd + Kibana ​

EFK是传统的ELK(Elasticsearch + Logstash + Kibana)的K8s版本——把Logstash换成了Fluentd。

为什么换?因为Fluentd更轻量,更适合容器环境——Logstash比较重,资源消耗大。

EFK各组件的作用:

  • Fluentd:日志采集器——每个节点上跑一个(DaemonSet),收集节点上所有容器的日志,处理之后发给Elasticsearch
  • Elasticsearch:日志存储和搜索引擎——存日志,支持全文检索
  • Kibana:日志可视化和查询界面——查日志、做Dashboard

EFK是比较成熟的方案,用的人很多。但Elasticsearch比较重——资源消耗大,运维复杂,成本高。

11.3.2 PLG:Promtail + Loki + Grafana ​

PLG是Grafana Labs推出的轻量级日志方案——也叫Grafana Loki。

PLG各组件的作用:

  • Promtail:日志采集器——跟Fluentd类似,收集日志,发给Loki
  • Loki:日志存储——但它跟Elasticsearch不一样,它不索引日志内容,只索引标签
  • Grafana:可视化——在Grafana里查日志,跟指标、链路放在一起看

Loki的设计理念是——日志就像日志,不要像搜索引擎。

什么意思?Elasticsearch把日志内容都索引了,所以能全文检索,但代价是存储和计算成本高。

Loki的思路是——大部分时候你查日志,都是先知道哪个服务、哪个Pod有问题,然后去查那个Pod的日志。所以Loki只索引标签(比如Pod名、命名空间、容器名),日志内容存在对象存储里,不索引。

这样成本就低多了——存储成本只有Elasticsearch的几分之一。而且跟Grafana集成得很好,跟指标、链路追踪在同一个界面里看,非常方便。

Loki是后起之秀,发展很快——如果你的日志量很大,想省成本,或者已经在用Grafana了,可以试试Loki。

11.3.3 日志采集的方式 ​

K8s里的日志怎么采集?主要有几种方式:

1. DaemonSet方式(最常用)

  • 每个节点上跑一个日志采集Agent(Fluentd、Promtail、Filebeat……)
  • Agent收集节点上所有容器的日志
  • 优点:部署简单,每个节点一个,资源开销可控
  • 缺点:只能收集标准输出的日志——应用要把日志打到stdout/stderr

2. Sidecar方式

  • 每个Pod里加一个Sidecar容器,负责收集日志
  • 应用把日志写到文件,Sidecar收集文件里的日志
  • 优点:可以收集文件日志,应用不用改
  • 缺点:每个Pod都有Sidecar,资源开销大

3. 应用直接发

  • 应用直接把日志发给日志系统
  • 优点:不用Agent
  • 缺点:应用要改代码,耦合,而且应用挂了日志可能丢

最常用的是第一种——DaemonSet方式,应用把日志打到标准输出,节点上的Agent统一收集。这也是K8s推荐的方式。

11.3.4 结构化日志 ​

日志最好是结构化的——比如JSON格式,每个字段都有名字。

为什么?因为结构化日志好查询、好分析——你可以按字段过滤、聚合,比如查某个用户的所有日志,查错误码的分布……

如果是纯文本的非结构化日志,就很难做这些分析——只能全文搜索,而且不准。

所以最佳实践是——应用输出结构化日志(JSON格式),日志系统直接解析字段,方便查询和分析。

11.4 链路追踪:Jaeger / Zipkin / Tempo ​

微服务架构下,一个请求会经过很多个服务——出了问题,或者变慢了,你不知道是哪个服务的问题。

这时候就需要链路追踪(Distributed Tracing)。

11.4.1 链路追踪的基本概念 ​

链路追踪的几个基本概念:

  • Trace:一次完整的请求链路——从请求开始到结束,经过的所有服务
  • Span:链路中的一个单元——一个服务里的处理过程,或者一次RPC调用
  • 每个Span有:开始时间、结束时间、名称、标签、父Span ID
  • 整个Trace就是一棵Span树——父Span下面有子Span

通过Trace,你可以看到:

  • 请求经过了哪些服务
  • 每个服务花了多长时间
  • 哪个服务最慢
  • 有没有错误

11.4.2 主流的链路追踪工具 ​

主流的链路追踪工具有:

1. Jaeger

  • Uber开源的,CNCF毕业项目
  • 功能强大,UI好看
  • 支持多种存储:Elasticsearch、Cassandra、Kafka……
  • 用的人很多

2. Zipkin

  • Twitter开源的,比较老牌
  • 简单,轻量
  • 社区也挺活跃

3. Grafana Tempo

  • Grafana Labs的,跟Loki、Prometheus一套的
  • 只存Trace,不索引——成本低
  • 跟Grafana集成好

现在Jaeger用得比较多——功能全,社区好,CNCF毕业。

11.4.3 怎么实现链路追踪? ​

链路追踪不是自动就有的——需要应用配合:

  1. 注入Trace ID:请求进来的时候,生成一个Trace ID,然后在服务之间传递——HTTP的话放在Header里,RPC的话放在上下文里
  2. 生成Span:每个服务处理请求的时候,生成Span,记录开始结束时间、标签
  3. 上报Span:把Span数据发给链路追踪系统

这个传递Trace ID和生成Span的工作,一般用OpenTelemetry来做。

11.4.4 OpenTelemetry:统一标准 ​

以前链路追踪的标准很乱——Zipkin有Zipkin的格式,Jaeger有Jaeger的格式,OpenTracing和OpenCensus各搞各的。

现在好了,有了OpenTelemetry(OTel)——CNCF的项目,把OpenTracing和OpenCensus合并了,成为统一的标准。

OpenTelemetry是什么?它是一套可观测性数据的标准和工具集——指标、日志、链路追踪,三者统一。

特点:

  • 统一的API和SDK——多种语言都有
  • 统一的数据格式——OTLP协议
  • 支持指标、日志、链路追踪三支柱
  • 厂商中立——你可以把数据发给任何支持OTLP的后端

现在新的项目,都推荐用OpenTelemetry来做可观测性的埋点——一次埋点,三支柱的数据都有了,而且后端可以随便换,不用改代码。

这是未来的方向——统一的可观测性标准。

11.5 告警最佳实践 ​

监控和告警是分不开的。但告警不是越多越好——告警太多了,人就麻木了,真正重要的告警反而被淹没了。

11.5.1 告警的原则 ​

好的告警应该遵循什么原则?

  1. 可行动性:每个告警都应该是需要人去处理的——如果一个告警触发了,不需要人管,那这个告警就不该存在。
  2. 严重性分级:告警要有级别——紧急的、重要的、一般的。不同级别不同的通知方式。
  3. 减少噪音:避免告警风暴——相关的告警合并,重复的告警抑制。
  4. 精准:告警要准确——不要误报,也不要漏报。误报多了大家就不信了。
  5. 有上下文:告警信息里要有足够的上下文——哪个集群、哪个服务、什么问题、相关的Dashboard链接……收到告警就能知道怎么回事,不用再去查半天。

11.5.2 告警分级 ​

一般告警分几级:

  • P0 / 紧急:系统不可用,影响大量用户——需要立刻处理,24小时oncall
  • P1 / 高:重要功能不可用,影响部分用户——工作时间尽快处理
  • P2 / 中:一般问题,不影响核心功能——可以排期处理
  • P3 / 低:提示性的,不影响使用——看看就行

不同级别不同的通知方式:P0打电话、发短信;P1发钉钉/企业微信@所有人;P2发群消息;P3记下来就行。

11.5.3 常见的告警指标 ​

K8s里常见的告警有哪些?

基础设施层:

  • 节点宕机
  • 节点CPU/内存/磁盘使用率过高
  • 节点磁盘满了
  • 节点网络问题

K8s层:

  • Pod CrashLoopBackOff
  • Pod一直Pending
  • Deployment不可用副本数
  • PVC挂载失败
  • 组件不正常(apiserver、etcd等)

应用层:

  • 错误率过高
  • 延迟过高
  • QPS异常
  • 进程挂了

根据你的实际情况配置——不要太多,但关键的一定要有。

11.6 扩展知识:可观测性 vs 监控 ​

很多人搞不清"可观测性"和"监控"的区别——不就是一回事吗?

其实不是。我们来辨析一下。

11.6.1 监控是什么? ​

**监控(Monitoring)**是什么?它是——你知道你要关注什么,你去监控它。

比如:

  • 你知道CPU使用率很重要,所以你监控CPU使用率
  • 你知道错误率很重要,所以你监控错误率
  • 你设置阈值,超过了就告警

监控的前提是——你知道你要找什么。你知道哪些指标重要,哪些问题会发生。

11.6.2 可观测性是什么? ​

**可观测性(Observability)**是什么?它是——你不知道会出什么问题,但你能通过系统的输出,搞清楚发生了什么。

系统是复杂的,你不可能提前想到所有可能出问题的地方。可观测性的目标是——不管出什么你没预料到的问题,你都能通过日志、指标、链路追踪这些数据,快速定位和理解问题。

所以可观测性比监控的范围更大——监控是可观测性的一部分。监控是"已知的未知"——你知道你要监控什么;可观测性还包括"未知的未知"——你不知道会出什么问题,但你能排查出来。

11.6.3 为什么现在可观测性这么火? ​

因为系统越来越复杂了——微服务、云原生、分布式系统……系统太复杂了,你不可能预料到所有问题。

以前单体应用的时候,系统简单,监控几个关键指标就够了——出了问题也容易排查。

现在微服务,几十个上百个服务,请求链路很长——出了问题,你都不知道是哪个服务的问题。这时候就需要可观测性——通过三支柱的数据,快速定位问题。

这就是为什么可观测性在云原生时代这么重要——系统越复杂,越需要可观测性。

11.7 本章小结 ​

这一章我们讲了可观测性三支柱——日志、监控、告警。

总结一下重点:

  • 可观测性三支柱:日志(Logging)、指标(Metrics)、链路追踪(Tracing)——指标告诉你有没有问题,链路追踪告诉你哪里有问题,日志告诉你为什么有问题
  • 监控体系:Prometheus + Grafana + Alertmanager——Prometheus采集存储指标,Grafana可视化,Alertmanager告警
  • 日志体系:EFK(Elasticsearch + Fluentd + Kibana)和PLG(Promtail + Loki + Grafana)——前者成熟功能强,后者轻量成本低
  • 链路追踪:Jaeger、Zipkin、Tempo——微服务下排查性能问题必备
  • OpenTelemetry:统一的可观测性标准,未来的方向
  • 告警最佳实践:可行动、分级、减少噪音、精准
  • 可观测性 vs 监控:监控是已知的未知,可观测性还包括未知的未知

可观测性是生产环境必不可少的——没有可观测性,出了问题两眼一抹黑,根本不知道怎么回事。建好可观测性体系,是运维K8s最重要的工作之一。

下一章,我们来讲应用生命周期管理——升级、回滚、自愈。


第12章 应用生命周期管理:升级、回滚与自愈 ​

应用部署上去不是就完事儿了——你还要升级、回滚、故障自愈、扩缩容……这些都是应用生命周期管理的内容。

K8s在这方面非常强大——很多以前需要人工做的事情,现在K8s自动帮你做了。

这一章我们就来讲讲应用的全生命周期管理——从部署到升级到回滚到自愈,K8s是怎么帮你搞定的。

12.1 部署策略:怎么发布新版本? ​

发布新版本是最常见的运维操作之一。怎么发布?有很多种策略,各有优劣。

12.1.1 重建发布(Recreate) ​

重建发布:先把旧版本全停了,再启动新版本。

  • 优点:简单,不会有新旧版本同时存在的问题
  • 缺点:有停机时间——旧的停了新的还没起来,中间服务不可用
  • 适用场景:停机不影响的、测试环境、不重要的服务

这是最简单的方式,但生产环境一般不用——因为有停机时间。

12.1.2 滚动发布(Rolling Update) ​

滚动发布:逐步替换旧版本——启动几个新版本,停掉几个旧版本,慢慢全部替换完。

  • 优点:没有停机时间,服务不中断
  • 缺点:发布过程中新旧版本同时存在,要考虑兼容性
  • 适用场景:大部分无状态应用,生产环境最常用

这是K8s Deployment的默认发布方式——也是最常用的。

12.1.3 蓝绿发布(Blue/Green) ​

蓝绿发布:准备两套完全一样的环境——蓝环境跑旧版本,绿环境跑新版本。新版本测试没问题了,把流量切到绿环境。

  • 优点:切换快,回滚也快——切流量就行;不会有新旧版本同时存在的问题
  • 缺点:需要两套资源,成本高
  • 适用场景:对发布质量要求高、资源充足的场景

K8s里怎么实现蓝绿?可以用两个Deployment,一个蓝一个绿,然后Service切换selector——把selector从蓝的标签改成绿的标签,流量就切过去了。

12.1.4 金丝雀发布(Canary) ​

金丝雀发布:先让一小部分流量走新版本,观察没问题了再逐步扩大比例,最后全量。

为什么叫金丝雀?以前矿工下井,会带一只金丝雀——如果有毒气,金丝雀先死,矿工就知道有危险,赶紧撤。金丝雀发布就是这个意思——先小范围试,没问题再全量。

  • 优点:风险小——出问题只影响一小部分用户;可以观察新版本的表现
  • 缺点:发布过程慢;需要流量控制
  • 适用场景:重要的服务、大版本更新、对稳定性要求高的

K8s原生的Deployment不支持金丝雀——Deployment只能控制滚动更新的速度,不能按比例控制流量。要做金丝雀发布,需要用Ingress Controller、服务网格(Istio)、或者专门的发布工具(Argo Rollouts、Flagger)。

12.1.5 A/B测试 ​

A/B测试:跟金丝雀类似,也是一部分用户走A版本,一部分走B版本。但目的不一样——金丝雀是为了验证稳定性,A/B测试是为了验证产品效果(比如转化率、用户体验)。

A/B测试一般是按用户特征分流——比如新用户走B版本,老用户走A版本;或者按地域、按设备分流。

A/B测试需要更复杂的流量控制——一般用服务网格或者专门的A/B测试平台来做。

12.1.6 怎么选? ​

发布策略怎么选?

  • 简单、不重要的服务 → 滚动发布就够了
  • 对稳定性要求高 → 金丝雀发布
  • 资源充足,要求快速回滚 → 蓝绿发布
  • 产品验证 → A/B测试

大部分场景,滚动发布就够了——简单,够用。重要的服务可以用金丝雀,更稳妥。

12.2 Deployment的滚动更新与回滚 ​

前面讲Deployment的时候提到了滚动更新,这一节我们再深入讲讲。

12.2.1 滚动更新的参数 ​

Deployment的滚动更新有两个关键参数:

  • maxSurge:更新过程中最多可以比期望副本数多几个
  • maxUnavailable:更新过程中最多可以有几个不可用

这两个参数决定了滚动更新的速度和安全性:

  • maxSurge越大,更新越快——同时启动更多新版本的Pod
  • maxUnavailable越小,可用性越高——不会有太多Pod不可用
  • 两个都大,更新最快,但可用性差
  • 两个都小,更新最慢,但可用性高

根据你的需求调整——对可用性要求高,就把maxUnavailable设小一点;想更新快,就把maxSurge设大一点。

12.2.2 回滚机制 ​

Deployment的回滚非常方便——因为旧的ReplicaSet还在。

回滚的时候:

  • 把旧的ReplicaSet的副本数加起来
  • 把新的ReplicaSet的副本数减下去
  • 就回到旧版本了

跟滚动更新一样,也是逐步替换的——不会一下子全换回去,保证服务不中断。

你可以查看历史版本,回滚到任意一个版本——Deployment默认保留10个历史版本,可以通过revisionHistoryLimit配置。

12.2.3 发布暂停与继续 ​

Deployment还支持暂停和继续——你可以在发布过程中暂停,观察一下,没问题再继续。

这个功能很实用——比如你想做手动的金丝雀发布,先更新一个Pod,看看没问题,再继续更新剩下的。

12.2.4 就绪探针的重要性 ​

滚动更新的时候,就绪探针(Readiness Probe)非常重要。

为什么?因为新版本的Pod启动了,不代表它就准备好了——可能还在初始化、加载数据、预热缓存……这时候还不能接流量。

如果没有就绪探针,Pod一启动就加入Service的后端列表,开始接流量——这时候请求就会失败,导致发布过程中有5xx错误。

有了就绪探针,Pod启动后,就绪探针成功了,才会加入Service后端,才开始接流量——这样发布过程就是平滑的,用户无感知。

生产环境一定要配置就绪探针——不然发布的时候会有抖动。

12.3 自愈能力:K8s怎么帮你"修"应用 ​

K8s最强大的功能之一就是自愈——应用出问题了,K8s自动帮你恢复,不用人工干预。

K8s的自愈能力体现在哪些方面?

12.3.1 进程级自愈:容器重启 ​

应用进程挂了怎么办?——kubelet自动重启容器。

这是最基础的自愈——容器退出了,根据重启策略(RestartPolicy),自动重启。

重启策略有三种:

  • Always:总是重启——不管什么原因退出都重启(默认)
  • OnFailure:只有失败的时候才重启——正常退出(退出码0)不重启
  • Never:从不重启

大部分长期运行的服务,用Always就对了——挂了就重启。

但注意:重启不是无限的——重启次数多了,会有指数退避——第一次重启等10秒,第二次20秒,第三次40秒……最长等5分钟。防止崩溃循环一直重启,把资源耗光。

12.3.2 应用级自愈:存活探针 ​

进程还在,但应用死锁了、挂死了、不响应了——怎么办?进程没退出,所以不会自动重启。

这时候就需要存活探针(Liveness Probe)——定期检查应用是不是健康的,不健康就杀掉容器,重启。

前面讲过存活探针——它的作用就是应用级的自愈。

有了存活探针,就算应用没崩溃,但已经不工作了,K8s也能发现,然后重启它——让应用恢复正常。

12.3.3 Pod级自愈:控制器重建Pod ​

Pod挂了、节点挂了——怎么办?控制器自动重建Pod。

比如Deployment管理的Pod——如果某个Pod挂了,ReplicaSet发现副本数不够了,就自动创建一个新的Pod补上。

如果节点挂了呢?——节点上的所有Pod都没了。控制器发现副本数不够了,就会在其他节点上重建这些Pod。

这就是Pod级的自愈——Pod没了,自动重建,保证副本数始终是你期望的数量。

12.3.4 节点级自愈:节点问题怎么办? ​

节点出问题了——比如节点宕机了、节点网络断了、节点NotReady了——上面的Pod怎么办?

K8s的处理是:

  • 节点NotReady之后,等待一段时间(默认5分钟)
  • 如果节点还是没恢复,就把这个节点上的Pod标记为删除
  • 然后控制器在其他节点上重建这些Pod

为什么要等5分钟?因为可能只是临时的网络波动——等一会儿节点就恢复了,这时候Pod还在节点上,不用重建。如果一发现NotReady就立刻重建,可能会造成重复的Pod。

这个等待时间可以通过pod-eviction-timeout参数配置。

12.3.5 集群级自愈:节点自动修复 ​

节点本身出问题了——比如系统挂了、磁盘坏了——怎么办?

K8s本身不会修节点——它只会把Pod调度到别的健康节点上。

但如果你在云上,配合云厂商的功能,可以做到节点级自愈:

  • 节点不健康了,自动把这个节点删掉
  • 自动创建一个新的节点加入集群
  • 这就是集群自动伸缩(Cluster Autoscaler) + 云厂商的自动修复功能

这样从应用到节点到集群,都有自愈能力——大部分故障,K8s都能自动恢复,不用人工干预。

这就是K8s的强大之处——自愈能力,大大减少了运维工作量。

12.4 自动伸缩:HPA、VPA、CA ​

前面讲的自愈是"出了问题自动恢复",那负载变化了呢?——流量涨了,自动扩容;流量降了,自动缩容。

这就是自动伸缩(Auto Scaling)。

K8s里有三种自动伸缩:

12.4.1 HPA:水平Pod自动伸缩 ​

HPA(Horizontal Pod Autoscaler):水平Pod自动伸缩——根据Pod的CPU使用率(或者其他指标),自动增加或减少Pod的副本数。

比如:

  • 平均CPU使用率超过80%,就自动扩容,增加Pod数量
  • 平均CPU使用率低于30%,就自动缩容,减少Pod数量

HPA是最常用的自动伸缩方式——大部分无状态应用都能用HPA。

HPA能基于哪些指标伸缩?

  • 资源指标:CPU、内存使用率——最常用的
  • 自定义指标:应用自己的指标,比如QPS、队列长度——通过Prometheus Adapter之类的工具接入
  • 外部指标:外部系统的指标,比如消息队列的消息堆积数

HPA的工作原理:

  • 定期采集指标
  • 跟目标值对比
  • 计算需要的副本数
  • 修改Deployment/ReplicaSet的replicas
  • 然后控制器去调整Pod数量

HPA本身不直接创建或删除Pod——它只是改replicas,然后控制器去做具体的调整。

12.4.2 VPA:垂直Pod自动伸缩 ​

VPA(Vertical Pod Autoscaler):垂直Pod自动伸缩——自动调整Pod的资源requests和limits。

比如:

  • Pod的CPU使用率一直很高,说明给的CPU不够——VPA自动把CPU requests调大
  • Pod的内存使用率一直很低,说明给多了——VPA自动把内存requests调小

VPA有什么用?

  • 自动优化资源配置——不用人工去调requests/limits
  • 提高资源利用率——不会给太多浪费,也不会给太少OOM
  • 适合不知道该设多少资源的应用

但VPA有个问题——调整资源需要重启Pod——因为Pod的资源是创建的时候指定的,运行中不能改(至少现在还不行)。所以VPA调整资源的时候,会重启Pod。

所以VPA一般用在:

  • 不知道该设多少资源的应用
  • 对重启不敏感的应用
  • 跟HPA配合——VPA调资源大小,HPA调副本数量

VPA现在还是beta阶段,生产环境慎用。

12.4.3 CA:集群自动伸缩 ​

CA(Cluster Autoscaler):集群自动伸缩——自动增加或减少节点数量。

什么时候扩容节点?——Pod调度不上去了,所有节点都满了,Pending了,CA就自动加节点。

什么时候缩容节点?——某个节点上的Pod很少,这些Pod都能调度到其他节点上,CA就把这个节点删掉。

CA的工作原理:

  • 定期检查有没有Pending的Pod——有就扩容
  • 定期检查节点是不是利用率太低——是就缩容,把Pod调度走,然后删掉节点

CA需要跟云厂商配合——因为要创建和删除节点,所以需要调用云厂商的API。如果是自建集群,就需要自己实现节点的增减。

12.4.4 三层伸缩的配合 ​

这三种伸缩,是配合工作的:

  1. 流量涨了 → HPA先增加Pod副本数
  2. Pod多了,节点资源不够了 → 有些Pod Pending → CA增加节点
  3. 流量降了 → HPA减少Pod副本数
  4. Pod少了,节点利用率太低 → CA减少节点

从Pod到节点,三层自动伸缩——完全自动化,不用人工干预。

这就是云原生的弹性——用多少拿多少,按需付费,省钱又省心。

12.5 优雅终止:怎么让应用优雅地退出? ​

前面讲Pod生命周期的时候提到了优雅终止——这一节我们再深入讲讲。

12.5.1 为什么需要优雅终止? ​

为什么不能直接杀进程?因为直接杀的话:

  • 正在处理的请求会中断——用户体验差
  • 数据可能没保存——比如写文件写到一半,数据库事务没提交
  • 资源可能没释放——比如连接没断开,临时文件没删

所以我们希望应用能优雅地退出——收到退出信号后,做完该做的事情,再退出。

12.5.2 优雅终止的过程 ​

K8s删除Pod的时候,优雅终止的过程是这样的:

  1. Pod被标记为Terminating状态
  2. Service摘掉这个Pod——不再把新流量发给它
  3. 执行preStop钩子——如果配置了preStop,先执行
  4. 发送SIGTERM信号——给容器里的进程发SIGTERM
  5. 等待优雅终止时间——等terminationGracePeriodSeconds这么久,默认30秒
  6. 发送SIGKILL信号——如果到时间还没退出,就强杀
  7. 删除Pod

整个过程,应用有最多30秒的时间来做清理工作——处理完正在处理的请求、保存数据、关闭连接、释放资源……然后正常退出。

12.5.3 preStop钩子 ​

preStop钩子是什么?它是容器终止前执行的一个操作——可以是执行一个命令,也可以是发一个HTTP请求。

preStop有什么用?

  • 让应用优雅退出——比如给Nginx发-s quit,让它优雅退出
  • 等待一下,等Service把这个Pod摘掉——因为Service更新后端列表有延迟,preStop等几秒,确保没有新流量进来了再退出
  • 做一些清理工作

很多人忽略了preStop——结果删除Pod的时候,还有请求发过来,就失败了。因为Service更新iptables有延迟,Pod已经收到SIGTERM开始退出了,但Service还没把它摘掉,还有流量过来。

最佳实践:配置一个preStop,等个几秒(比如5-10秒),再开始退出——确保Service已经把这个Pod摘掉了,没有新流量了,再优雅退出。

12.5.4 应用要支持优雅退出 ​

当然,光K8s支持没用——你的应用也要支持优雅退出:

  • 能正确处理SIGTERM信号
  • 收到信号后,停止接收新请求
  • 把正在处理的请求处理完
  • 保存数据、释放资源
  • 然后正常退出

很多应用默认就支持——比如Nginx、Tomcat、Go的HTTP服务……但有些可能需要自己配置。

生产环境一定要配置优雅终止——不然发布、缩容、节点维护的时候,都会有请求失败,用户体验差。

12.6 扩展知识:DevOps与GitOps ​

讲到应用生命周期管理,就不得不提DevOps和GitOps——这是现在应用交付的主流理念。

12.6.1 DevOps是什么? ​

DevOps是什么?它是一种理念——开发(Dev)和运维(Ops)紧密合作,通过自动化工具,实现快速、可靠的软件交付。

DevOps的核心是——自动化和持续交付。

  • 代码提交 → 自动构建 → 自动测试 → 自动部署
  • 整个流程自动化,减少人工操作,提高效率和可靠性

K8s是DevOps的好帮手——声明式API、自动化部署、自愈、自动伸缩……这些都跟DevOps的理念完美契合。

12.6.2 GitOps是什么? ​

GitOps是DevOps的进一步发展——以Git为唯一真相来源(Single Source of Truth)。

什么意思?就是:

  • 所有的基础设施和应用配置,都存在Git仓库里
  • 所有的变更都通过Git提交——PR、review、合并
  • 有个工具自动把Git里的状态同步到集群里
  • 集群的实际状态,跟Git里的声明保持一致

GitOps的好处:

  • 版本控制:所有变更都有历史,谁改了什么、什么时候改的,一清二楚
  • 可审计:所有变更都有记录,符合合规要求
  • 可回滚:出了问题,Git回滚一下,集群就回滚了
  • 一致的环境:开发、测试、生产环境的配置都在Git里,保持一致
  • 声明式:跟K8s的声明式API完美契合

GitOps的工具,主流的有两个:

  • Argo CD:CNCF毕业项目,功能强大,UI好用,用的人多
  • Flux:CNCF孵化项目,轻量,跟GitOps的理念更贴近

GitOps是现在云原生应用交付的主流方式——越来越多的团队在用。如果你还在用手动kubectl apply,或者用CI脚本直接部署,建议试试GitOps——体验好太多了。

12.7 本章小结 ​

这一章我们讲了应用生命周期管理。

总结一下重点:

  • 发布策略:重建、滚动、蓝绿、金丝雀、A/B测试——各有优劣,根据场景选
  • Deployment的滚动更新与回滚:maxSurge和maxUnavailable控制速度和可用性,回滚方便
  • 自愈能力:进程级(容器重启)、应用级(存活探针)、Pod级(控制器重建)、节点级(迁移到其他节点)——层层自愈
  • 自动伸缩:HPA(水平Pod伸缩)、VPA(垂直Pod伸缩)、CA(集群节点伸缩)——三层配合,全自动弹性
  • 优雅终止:preStop钩子 + SIGTERM + 优雅终止时间——让应用平滑退出,不影响用户
  • DevOps与GitOps:Git为真相来源,自动化交付,是云原生应用交付的主流方式

K8s把应用生命周期的大部分事情都自动化了——部署、升级、回滚、自愈、伸缩……运维人员不用再做这些重复劳动,可以把精力放在更有价值的事情上。

下一章,我们来讲生产环境实战——集群部署与高可用。


第13章 生产环境实战:集群部署与高可用 ​

前面讲了很多K8s的概念和原理,这一章我们来讲实战——生产环境怎么部署K8s集群?怎么做高可用?有哪些坑?

部署K8s说难不难,说简单也不简单——测试环境随便搭一个很容易,但生产环境的高可用集群,要考虑的事情就多了。

13.1 集群部署方式:选哪种? ​

部署K8s有很多种方式,怎么选?

13.1.1 托管K8s(云上) ​

如果你在云上,最简单的方式就是用云厂商的托管K8s——比如:

  • 阿里云 ACK
  • 腾讯云 TKE
  • 华为云 CCE
  • AWS EKS
  • Google GKE
  • Azure AKS

什么是托管K8s?就是——控制平面(Master节点)云厂商帮你管,你只管Worker节点,管你上面的应用。

优点:

  • 省心——控制平面不用你管,高可用、升级、备份,云厂商都搞定了
  • 稳定——云厂商有专业团队维护,比你自己搭的靠谱
  • 跟云服务集成好——负载均衡、存储、网络、监控,都跟云原生集成
  • 不用自己维护Master节点,省钱——Master节点不用你出钱

缺点:

  • 贵——比自己搭的贵
  • 不自由——有些配置不能改,受云厂商限制
  • 绑定云厂商——迁移成本高

生产环境,如果在云上,优先用托管K8s——省心,稳定,出了问题找云厂商。自己搭控制平面,出了问题你得自己扛,不值得。

13.1.2 自动化部署工具(自建) ​

如果要自建集群,用什么工具?

主流的工具有:

1. kubeadm

  • 官方推荐的集群部署工具
  • 简单、易用、符合最佳实践
  • 适合大多数场景
  • 但只负责部署,不负责运维——升级、备份这些还要自己搞

2. kops

  • Kubernetes Operations,专门用来在云上部署生产级K8s集群
  • 功能强大,支持高可用、升级、备份
  • 主要支持AWS,其他云支持一般
  • 国外用得多,国内用得少

3. Kubespray

  • 基于Ansible的部署工具
  • 支持多种部署方式,可定制性强
  • 支持裸金属、各种云
  • 比较复杂,学习成本高

4. Rancher

  • 不仅是部署工具,还是K8s管理平台
  • 可以部署和管理多个K8s集群
  • 有UI,操作简单
  • 功能丰富,适合管理多集群

5. 二进制部署

  • 手动下载二进制文件,一个个组件部署
  • 最麻烦,最灵活
  • 适合想深入理解K8s的,或者有特殊需求的

怎么选?

  • 简单快速 → kubeadm
  • 管理多集群 → Rancher
  • 高度定制 → Kubespray
  • 学习研究 → 二进制部署

大部分自建场景,kubeadm就够了——官方推荐,简单可靠。

13.1.3 轻量级发行版 ​

还有一些轻量级的K8s发行版,适合资源受限的场景:

  • k3s:Rancher出的轻量级K8s,二进制才不到100MB,资源占用小,适合边缘计算、IoT、开发测试
  • minikube:单节点K8s,主要用来本地开发测试
  • kind:Kubernetes IN Docker,把K8s跑在Docker里,适合本地开发、CI测试

这些不适合生产环境(k3s可以用于生产边缘场景),但开发测试用很方便。

13.2 高可用架构:怎么做到不宕机? ​

生产环境最重要的是什么?——高可用。不能有单点故障。

K8s集群的高可用,主要是控制平面的高可用——Master节点不能挂。

13.2.1 控制平面高可用 ​

控制平面的组件:apiserver、scheduler、controller-manager、etcd。

怎么做到高可用?

etcd高可用:

  • etcd本身就是分布式的,部署3个或者5个节点,组成集群
  • Raft共识算法,只要半数以上节点活着,就能正常工作
  • 3个节点可以容忍1个故障,5个节点可以容忍2个故障
  • 生产环境至少3个etcd节点,重要的用5个

apiserver高可用:

  • apiserver是无状态的,可以部署多个实例
  • 前面放一个负载均衡(或者VIP),把请求分发到多个apiserver
  • apiserver挂了一个没关系,还有别的

scheduler和controller-manager高可用:

  • 这两个是有状态的——不能同时有多个在工作,不然会冲突
  • 用选主(Leader Election)机制——多个实例,只有一个Leader在工作,其他的待命
  • Leader挂了,其他的自动选一个新的Leader顶上
  • 所以部署多个实例就行,自动选主

总结一下,控制平面高可用架构:

  • 3个或5个Master节点
  • etcd集群部署在Master节点上(或者独立部署)
  • apiserver多实例,前面负载均衡
  • scheduler和controller-manager多实例,自动选主

这样,任何一个Master节点挂了,都不影响集群正常工作。

13.2.2 负载均衡:apiserver前面的入口 ​

多个apiserver,前面需要一个负载均衡——把请求分发到健康的apiserver上。

负载均衡怎么搞?

  • 云上的话,用云厂商的负载均衡(SLB/ELB)
  • 自建的话,可以用HAProxy、Nginx、Keepalived + VIP
  • 也可以用硬件负载均衡

负载均衡本身也要高可用——不能负载均衡自己成了单点。所以一般是两个负载均衡节点,做HA,用Keepalived飘VIP,或者用云厂商的托管LB。

13.2.3 Worker节点高可用 ​

Worker节点呢?——Worker节点本身就是多的,一个挂了,Pod会调度到其他节点上。

但要注意:

  • 节点数量要足够——不能只有一两个,挂一个就影响很大
  • 应用要多副本——单副本的话,节点挂了应用就挂了
  • 用反亲和性——把同一个应用的Pod分散到不同节点,避免单点故障
  • 跨可用区部署——如果在云上,把节点分布在不同可用区,一个可用区挂了还有别的

13.2.4 集群级高可用的最佳实践 ​

生产环境高可用最佳实践:

  1. 控制平面3节点起步:至少3个Master节点,etcd也3节点
  2. etcd独立部署(可选):如果集群很大,etcd可以独立部署在专门的节点上,不跟其他控制平面组件混部,性能更好,更稳定
  3. apiserver前面有负载均衡:多apiserver实例 + LB
  4. Worker节点跨可用区:分布在不同AZ,提高可用性
  5. 关键应用多副本 + 反亲和:避免单点故障
  6. 存储高可用:用分布式存储,不要用本地存储——本地存储节点挂了数据就没了
  7. 备份:定期备份etcd——这是K8s的"数据库",丢了就全完了

13.3 集群规划:多大的集群?多少节点? ​

部署集群之前,要做规划——集群多大?多少节点?节点什么配置?

13.3.1 集群规模 ​

首先,集群规模多大?——几个节点?几十个?几百个?几千个?

K8s官方说,单个集群最多支持:

  • 5000个节点
  • 15万个Pod
  • 30万个容器
  • 每个节点最多110个Pod

但这是理论最大值——实际生产环境,不建议搞这么大的单集群——太大了管理复杂,出问题影响面大。

一般建议:

  • 小集群:几个到几十个节点——没问题
  • 中等集群:几百个节点——没问题
  • 大集群:上千个节点——要做性能调优,而且建议按业务、按环境分集群
  • 超大集群:几千个——建议用联邦(Federation)或者多集群管理

不要把所有东西都塞到一个集群里——按环境(开发/测试/生产)、按业务线、按重要性分集群,隔离故障域。

13.3.2 节点配置 ​

节点用什么配置?——CPU、内存、磁盘多大?

这取决于你的应用——不同应用需求不一样。

一般来说:

  • Master节点:不需要太高配置——4核8G起步,大集群8核16G、16核32G
  • Worker节点:看应用——一般8核16G、16核32G、32核64G比较常见
  • etcd节点:对磁盘IO要求高——一定要用SSD,最好NVMe,etcd对延迟很敏感

节点不是越大越好——大节点的话,一个节点挂了影响大;小节点的话,节点数量多,管理成本高。根据你的情况选合适的。

13.3.3 网络规划 ​

网络也要规划:

  • Pod CIDR:Pod的IP段——选一个大的私网段,比如10.244.0.0/16
  • Service CIDR:Service的IP段——另一个私网段,比如10.96.0.0/12
  • 节点IP段:跟你现有网络规划一致

注意:这几个网段不能重叠,也不能跟你现有网络的网段重叠——不然会有路由冲突。

13.3.4 命名空间规划 ​

命名空间怎么规划?——怎么分命名空间?

常见的分法:

  • 按环境分:dev、test、staging、prod——每个环境一个命名空间
  • 按团队/业务线分:每个团队一个命名空间
  • 按应用分:每个大应用一个命名空间

一般是组合——比如按环境 + 按团队。不要太粗也不要太细——太少了隔离不够,太多了管理麻烦。

13.4 生产环境检查清单:上线前要检查什么? ​

集群搭好了,应用部署了,能不能上线?——上线前要做检查。

我给你列一个生产环境检查清单:

13.4.1 集群层面 ​

  • [ ] 控制平面高可用:3个Master节点,etcd集群正常
  • [ ] apiserver前面有负载均衡
  • [ ] 节点跨可用区部署
  • [ ] 网络插件安装配置正确
  • [ ] CoreDNS正常
  • [ ] Ingress Controller部署好了,高可用
  • [ ] 存储类配置好了,动态供给正常
  • [ ] 监控告警系统部署好了(Prometheus + Grafana + Alertmanager)
  • [ ] 日志系统部署好了(EFK/Loki)
  • [ ] RBAC配置好了,权限最小化
  • [ ] Pod安全策略配置了(PSA / OPA)
  • [ ] NetworkPolicy配置了(可选,但建议有)
  • [ ] etcd定期备份,并且验证过备份能恢复
  • [ ] 集群升级方案准备好了
  • [ ] 灾备方案准备好了

13.4.2 应用层面 ​

  • [ ] 应用有多副本,至少2个
  • [ ] 配置了Pod反亲和性,分散在不同节点
  • [ ] 配置了资源requests和limits
  • [ ] 配置了存活探针和就绪探针
  • [ ] 配置了优雅终止(preStop + 合理的优雅终止时间)
  • [ ] 配置了HPA(如果需要自动伸缩)
  • [ ] 配置用ConfigMap和Secret,不打在镜像里
  • [ ] 敏感信息用Secret,或者外部密钥管理
  • [ ] 镜像来自可信仓库,扫描过漏洞
  • [ ] 应用日志输出到标准输出
  • [ ] 应用暴露了metrics接口,接入了监控
  • [ ] 发布策略配置合理(滚动更新参数)
  • [ ] 回滚方案验证过

13.4.3 运维层面 ​

  • [ ] 有完整的监控大盘
  • [ ] 关键告警配置好了,告警通道正常
  • [ ] 日志能正常查询
  • [ ] 故障排查文档有了
  • [ ] 运维人员培训过
  • [ ] 有oncall机制
  • [ ] 定期演练——故障演练、灾备演练

这个清单可以根据你的实际情况增减——但关键的项一定要检查,不然上线了出问题就麻烦了。

13.5 生产环境常见的坑 ​

最后,讲讲生产环境常见的坑——很多人都踩过,你可以避开。

13.5.1 资源相关的坑 ​

坑1:不设资源requests/limits

  • 后果:Pod随便用资源,一个应用把节点资源吃光了,影响其他应用;调度也不准
  • 解决:所有Pod都设requests和limits,用LimitRange给命名空间设默认值

坑2:requests设太大,浪费资源

  • 后果:资源利用率低,节点很多但跑不了几个Pod,浪费钱
  • 解决:根据实际用量合理设置requests,用VPA或者监控数据调优

坑3:内存limits设得太接近实际用量

  • 后果:稍微涨一点就OOM Kill,频繁重启
  • 解决:给内存留一定余量,监控内存使用率,及时调整

13.5.2 网络相关的坑 ​

坑4:Service没配就绪探针,发布有抖动

  • 后果:发布的时候,Pod还没准备好就接流量,有5xx错误
  • 解决:所有对外服务都配就绪探针,就绪了再接流量

坑5:没配置优雅终止,删除Pod有5xx

  • 后果:缩容、发布、节点维护的时候,请求失败
  • 解决:配置preStop + 优雅终止,应用支持优雅退出

坑6:网络插件选得不对

  • 后果:性能差、功能不够、出问题不好排查
  • 解决:根据需求选合适的CNI,生产环境优先选Calico这种成熟的

13.5.3 存储相关的坑 ​

坑7:用本地存储跑有状态应用

  • 后果:节点挂了数据就没了,Pod迁移到别的节点数据跟不上
  • 解决:用分布式存储或者云存储,不要用本地存储跑重要数据

坑8:PV的回收策略是Delete,误删PVC数据没了

  • 后果:手滑删了PVC,数据就没了,哭都来不及
  • 解决:重要数据的PV回收策略设为Retain

坑9:不备份数据

  • 后果:存储集群挂了、误操作了,数据全丢
  • 解决:定期备份,并且验证备份能恢复——没验证过的备份等于没有备份

13.5.4 安全相关的坑 ​

坑10:默认ServiceAccount权限太大

  • 后果:Pod被攻破了,攻击者能用ServiceAccount的权限搞事情
  • 解决:每个应用用自己的ServiceAccount,最小权限原则

坑11:容器用root用户运行

  • 后果:容器被攻破了,攻击者就是root,危害大
  • 解决:用非root用户运行容器,配置Pod安全策略

坑12:镜像里有敏感信息

  • 后果:密码、密钥打到镜像里了,谁都能看到
  • 解决:用Secret,不要把敏感信息打到镜像里

13.5.5 运维相关的坑 ​

坑13:不备份etcd

  • 后果:etcd挂了数据丢了,整个集群就没了
  • 解决:定期备份etcd,并且测试恢复

坑14:直接在生产环境改东西,不记录

  • 后果:出了问题不知道改了什么,回滚都不知道怎么回
  • 解决:用GitOps,所有变更走Git,有记录,可回滚

坑15:没有监控告警

  • 后果:出了问题不知道,用户先发现了
  • 解决:建好监控告警体系,关键指标都要告警

这些坑都是前人踩过的——你知道了,就能避开。

13.6 扩展知识:K8s发行版与生态选择 ​

前面提到了很多工具和方案——K8s生态太丰富了,有时候选择太多也是一种烦恼。

怎么选?我给你一些建议:

网络插件:

  • 通用生产场景 → Calico
  • 追求性能、eBPF → Cilium
  • 简单测试 → Flannel

存储:

  • 云上 → 用云厂商的云盘、云NAS
  • 自建块存储 → Ceph RBD
  • 自建文件存储 → CephFS / NFS
  • 对象存储 → MinIO / 云OSS

Ingress Controller:

  • 通用 → Nginx Ingress
  • 云原生、简单 → Traefik
  • 服务网格 → Istio Gateway / Cilium Gateway

监控:

  • 指标 → Prometheus + Grafana
  • 日志 → Loki(轻量) / Elasticsearch(功能强)
  • 链路追踪 → Jaeger
  • 统一标准 → OpenTelemetry

发布/GitOps:

  • GitOps → Argo CD
  • 高级发布策略(金丝雀、蓝绿) → Argo Rollouts / Flagger

安全策略:

  • 简单 → Pod Security Admission
  • 复杂 → OPA Gatekeeper / Kyverno

服务网格:

  • 功能全面 → Istio
  • 轻量、eBPF → Cilium Service Mesh / Linkerd

没有最好的,只有最合适的——根据你的需求、团队能力、环境来选。不要追求技术时髦,选成熟的、团队能hold住的。

13.7 本章小结 ​

这一章我们讲了生产环境的集群部署与高可用。

总结一下重点:

  • 部署方式:云上优先用托管K8s,自建用kubeadm/Rancher/Kubespray
  • 高可用架构:控制平面3节点起步,etcd集群,apiserver负载均衡,scheduler/controller-manager选主
  • 集群规划:规模、节点配置、网络、命名空间,提前规划好
  • 生产环境检查清单:集群层面、应用层面、运维层面,上线前都要检查
  • 常见的坑:资源、网络、存储、安全、运维,避开这些坑
  • 生态选择:根据需求选合适的工具,不要追求时髦,选成熟稳定的

生产环境跟测试环境不一样——测试环境怎么折腾都行,生产环境要稳。高可用、监控、备份、安全,这些都不能少。上线前多检查,上线后少踩坑。

下一章,我们来讲性能调优与大规模集群——万节点集群的秘密。


第14章 性能调优与大规模集群:万节点集群的秘密 ​

小集群怎么都好说——几个节点,几十个Pod,怎么跑都没问题。但集群大了——几百上千个节点,几万个Pod——性能问题就出来了。

K8s能支持多大的集群?官方说单集群最多5000节点、15万Pod——但这是调优过的。你要是默认配置跑,可能几百节点就扛不住了。

这一章我们就来讲讲K8s的性能调优——怎么让集群跑得更快、更稳、更大规模。

14.1 性能瓶颈在哪?——K8s的性能分析 ​

集群慢了,卡了,瓶颈在哪?我们先分析一下K8s各个组件的性能特点。

14.1.1 etcd:K8s的心脏,最容易成瓶颈 ​

etcd是K8s的"数据库"——所有数据都存在etcd里。etcd的性能,直接决定了整个集群的性能。

etcd容易成为瓶颈的原因:

  • 所有组件都要跟apiserver打交道,apiserver又要读写etcd
  • etcd是强一致的,写操作要过半节点确认,延迟相对高
  • etcd对磁盘IO非常敏感——磁盘慢的话,etcd性能会很差
  • 集群大了,数据量多了,etcd的压力也大

etcd的常见性能问题:

  • 磁盘IO不够——用机械盘的话,etcd会非常慢
  • 网络延迟高——etcd节点之间网络不好,Raft共识慢
  • 数据量太大——存了太多没用的资源,历史版本太多
  • 写请求太多——大量的创建、更新操作

etcd是K8s性能的重中之重——etcd慢了,整个集群都慢。

14.1.2 apiserver:集群的入口,压力最大 ​

apiserver是所有请求的入口——kubectl、控制器、调度器、kubelet、用户……都要访问apiserver。

集群大了,apiserver的压力会非常大:

  • 大量的读请求——各种控制器、kubelet都在Watch资源
  • 大量的写请求——Pod创建、更新、状态上报
  • 大的List请求——比如全量拉取所有Pod

apiserver的常见性能问题:

  • 请求太多,QPS太高
  • 大查询太多——比如一次list所有Pod,数据量大,耗内存耗CPU
  • 认证授权慢——插件太多,每个请求都要走一遍
  • etcd慢——apiserver读写etcd慢

14.1.3 调度器:大规模下的挑战 ​

调度器的压力主要在大规模集群——节点多了,Pod多了,调度的开销就大了。

调度器的性能问题:

  • 预选阶段:节点多了,每个Pod都要遍历所有节点,开销大
  • 优选阶段:要给所有合格节点打分,开销也大
  • 调度吞吐量:每秒能调度多少个Pod?大规模下调度速度跟不上

14.1.4 kubelet:节点上的管家 ​

kubelet的压力主要在节点上Pod多了的时候:

  • 节点上Pod多了,kubelet要管理的容器就多
  • 状态上报频繁,给apiserver造成压力
  • Pod生命周期管理的开销

默认每个节点最多110个Pod——一般也不会跑这么多,但跑个五六十个是常有的。

14.1.5 控制器:控制循环的压力 ​

各种控制器——Deployment、ReplicaSet、Endpoint、Node controller……

控制器的压力主要在:

  • 资源多了,控制循环的开销大
  • Watch事件多了,处理不过来
  • 比如Endpoint控制器,Service和Pod多了,Endpoint更新非常频繁

14.2 etcd性能调优 ​

etcd是重中之重,我们先讲etcd的调优。

14.2.1 用SSD,最好NVMe ​

etcd对磁盘IO延迟非常非常敏感——磁盘慢,etcd就慢,整个集群都慢。

etcd一定要用SSD,最好是NVMe SSD——绝对不要用机械盘。

怎么看etcd的磁盘性能?看etcd的wal_fsync_duration_seconds和backend_commit_duration_seconds指标——

  • wal_fsync正常应该在10ms以内,超过50ms就有问题了
  • backend_commit正常应该在100ms以内,超过1s就有问题了

如果这两个指标很高,说明磁盘IO不够,赶紧换SSD。

14.2.2 etcd节点规划 ​

etcd节点怎么规划?

  • 数量:3个或5个——不要太多,也不要太少。3个可以容忍1个故障,5个可以容忍2个。再多的话,写性能反而下降——因为要更多节点确认。
  • 配置:CPU不用太多,4核8核就行;内存要够,能把数据都放内存里最好;磁盘一定要快。
  • 独立部署:大集群建议etcd独立部署——不跟其他控制平面组件混部,专门的节点,专门的磁盘。这样etcd的性能更稳定,不受其他组件影响。
  • 网络:etcd节点之间网络要好,延迟低——最好在同一个机房,同一个交换机。跨地域的话延迟太高,etcd性能会很差。

14.2.3 压缩和碎片整理 ​

etcd是MVCC的——会保留历史版本。时间长了,历史版本越来越多,数据量越来越大,性能就下降了。

怎么办?

  • 自动压缩:开启自动压缩——定期删除老的历史版本。比如保留最近1小时的历史,之前的都删掉。
  • 碎片整理:压缩之后,磁盘空间不会自动释放,会有碎片——要定期做碎片整理,把空间收回来。

K8s默认会做一些,但大集群建议你自己调优一下压缩策略,定期做碎片整理。

14.2.4 控制etcd的数据量 ​

etcd里存的东西越多,性能越差。所以要控制etcd的数据量:

  • 不要在etcd里存大的Secret、ConfigMap——太大的东西别往etcd里放
  • 定期清理没用的资源——旧的ReplicaSet、没用的Pod、过期的Job……
  • Event保留时间不要太长——Event默认只保留1小时,不要改太大,Event很占空间
  • 不要用etcd存应用数据——etcd是K8s的元数据存储,不是应用数据库

控制etcd的数据量,是保持etcd性能的关键。

14.3 apiserver性能调优 ​

apiserver是集群的入口,压力大,也要调优。

14.3.1 水平扩展apiserver ​

apiserver是无状态的,可以水平扩展——部署多个apiserver实例,前面放负载均衡。

集群大了,apiserver的CPU、内存不够了,加实例就行——横向扩展。

一般3-5个apiserver实例就够很大的集群用了。

14.3.2 开启API优先级与公平性 ​

K8s 1.20之后,有个功能叫API Priority and Fairness(API优先级与公平性)——给不同的请求分优先级,重要的请求优先处理,防止不重要的请求把apiserver占满了。

比如:

  • 控制平面的请求(控制器、调度器)优先级高
  • 普通用户的请求优先级中等
  • 事件上报、领导选举之类的优先级低

这样就算请求很多,也不会把重要的请求饿死——保证控制平面正常工作。

大集群一定要开启这个功能——不然请求多了,apiserver容易被打挂。

14.3.3 优化List请求 ​

大的List请求非常耗资源——比如一次list所有Pod,要从etcd里把所有Pod都读出来,序列化,返回给客户端——非常耗CPU和内存。

怎么优化?

  • 用分页(Limit + Continue)——不要一次全拉出来,分页拉
  • 用Watch代替List——不要频繁全量List,用Watch增量更新
  • 用Selector过滤——只拉需要的,不要全拉

客户端也要注意——不要动不动就全量List,尽量用Watch。

14.3.4 缓存与Watch ​

apiserver前面可以加缓存——比如用apiserver的watch cache,或者用API Aggregation,或者用第三方的缓存层。

大部分读请求,其实可以从缓存里读——不用每次都去etcd读。这样既能减轻etcd的压力,也能提高apiserver的性能。

14.4 调度器性能调优 ​

大规模集群,调度器的性能也很重要。

14.4.1 调度器调优参数 ​

调度器有一些参数可以调:

  • kube-api-qps / kube-api-burst:调度器跟apiserver通信的QPS限制——大集群可以调大一点,不然调度速度上不去
  • numThreads:调度线程数——默认是1,大集群可以调大,并行调度多个Pod
  • percentageOfNodesToScore:优选阶段,只给一定比例的节点打分,不是所有节点都打分——节点多了之后,不用所有节点都打分,选一部分就行,能大大提高调度速度

比如percentageOfNodesToScore默认是动态的——节点越多,比例越低。5000节点的话,只给10%的节点打分——这样调度速度快很多,而且对调度质量影响不大。

14.4.2 调度器框架优化 ​

调度器框架本身也有优化——比如:

  • 预选阶段并行化——多个节点并行检查
  • 缓存优化——节点信息缓存,不用每次都去apiserver拉
  • 增量更新——只有变化的才更新,不用全量算

新版本的K8s调度器性能已经很好了——几千个节点的集群,调度延迟也能接受。

14.4.3 多调度器 ​

如果调度吞吐量还是不够,可以用多个调度器——不同的Pod用不同的调度器,分散压力。

但一般不需要——单个调度器就能处理很大的集群了。

14.5 kubelet与节点性能调优 ​

节点上的kubelet也有调优空间。

14.5.1 kubelet调优参数 ​

kubelet的一些参数:

  • kube-api-qps / kube-api-burst:kubelet跟apiserver通信的QPS——节点上Pod多的话可以调大一点
  • podsPerCore:每个核最多跑多少个Pod——限制节点上的Pod密度
  • maxPods:节点最多跑多少个Pod——默认110,可以根据节点配置调整
  • eventRecordQPS:事件上报的QPS——限制事件上报的频率,减轻apiserver压力
  • syncFrequency:Pod同步频率——多久同步一次Pod状态,不用太频繁

14.5.2 节点资源预留 ​

节点上的资源,不能全部分给Pod——还要留一部分给系统进程、kubelet、Docker这些用。

如果不留,Pod把资源用光了,系统进程、kubelet就没资源了——节点就会出问题。

所以要配置资源预留——给系统和kubelet预留一部分CPU和内存:

  • system-reserved:给系统进程预留的资源
  • kube-reserved:给kubelet等K8s组件预留的资源
  • eviction-threshold:驱逐阈值——节点资源不够了,就驱逐Pod,保证节点稳定

生产环境一定要配置资源预留——不然节点容易出问题。

14.5.3 容器运行时调优 ​

容器运行时(containerd、Docker)也可以调优:

  • 镜像拉取的并发数
  • 镜像清理策略
  • 日志大小限制——容器日志不要无限增长,不然磁盘会满
  • 容器的资源限制

特别是日志——一定要限制容器日志的大小,不然跑久了日志把磁盘占满了,节点就挂了。

14.6 大规模集群的架构优化 ​

集群大了,光调优单个组件不够,还要从架构上优化。

14.6.1 分片与分区 ​

怎么应对超大规模?——分片。

比如:

  • 按节点分片:把节点分成多个组,每个调度器只管一组节点——这样每个调度器要管的节点数就少了,调度更快
  • 按命名空间分片:不同的命名空间用不同的控制器——分散压力
  • etcd分片:不同的资源存在不同的etcd集群里——比如事件存在单独的etcd集群,不跟核心资源混

K8s的一些新特性,比如调度器框架、API分组,都是为了支持更大规模的。

14.6.2 事件分离 ​

Event(事件)是非常占etcd空间的——而且Event不重要,丢了也没关系。

大集群建议把Event分离出去——存在单独的etcd集群里,或者存在其他存储里,不跟核心资源共用etcd。

这样既能减轻核心etcd的压力,也能减少核心etcd的数据量。

14.6.3 控制平面与数据平面分离 ​

大集群一定要把控制平面和数据平面分开——Master节点专门跑控制平面组件,不跑业务Pod。

为什么?

  • 控制平面很重要,不能受业务Pod影响——业务Pod把资源吃光了,影响控制平面就麻烦了
  • 控制平面的性能有保障——专门的节点,资源有保证
  • 安全——控制平面跟业务隔离,更安全

生产环境,Master节点一定要打污点,不让业务Pod调度上去。

14.6.4 什么时候该分集群? ​

不是所有情况都要搞单集群超大规模——集群太大了,管理复杂,出问题影响面大。

什么时候应该分集群?

  • 不同环境(开发/测试/生产)——必须分开
  • 不同业务线——重要的跟不重要的分开
  • 不同地域——不同地域的分开,延迟低
  • 不同安全等级——高安全的跟普通的分开
  • 单集群太大了——超过几千节点了,建议分

一般来说,单集群几千节点是比较合适的规模——再大的话,管理成本和风险都会上升。

不要追求"一个集群管所有"——多集群,故障隔离,反而更稳。

14.7 性能监控:怎么知道性能好不好? ​

调优的前提是——你知道性能好不好,瓶颈在哪。

怎么监控K8s的性能?

14.7.1 关键性能指标 ​

要监控哪些指标?

etcd指标:

  • 读写延迟(wal_fsync_duration、backend_commit_duration)
  • QPS
  • 数据库大小
  • Raft提案延迟
  • 节点之间的网络延迟

apiserver指标:

  • 请求QPS
  • 请求延迟(按API、按方法、按状态码)
  • 工作队列长度
  • 认证授权延迟
  • etcd请求延迟

调度器指标:

  • 调度延迟(预选、优选、绑定各阶段的时间)
  • 调度吞吐量(每秒调度多少个Pod)
  • 调度失败的数量
  • 调度队列长度

kubelet指标:

  • Pod同步延迟
  • PLEG(Pod生命周期事件生成器)延迟
  • 节点上Pod数量
  • 容器运行时延迟

整体指标:

  • API请求成功率、错误率
  • 各种控制器的工作队列长度、同步延迟
  • Pod启动时间——从创建到Running要多久

这些指标都能从Prometheus里拿到——各个组件都暴露了metrics接口。

14.7.2 性能基准测试 ​

怎么测集群的性能?——用基准测试工具。

常用的工具有:

  • kubemark:K8s官方的性能测试工具,可以模拟大量节点和Pod
  • clusterloader2:K8s SIG Scalability的测试工具,用来测集群的性能和规模
  • knb:K8s网络基准测试,测网络性能

通过基准测试,你可以知道你的集群能扛多大规模,性能瓶颈在哪。

14.8 扩展知识:K8s的性能演进历史 ​

最后,我们聊聊K8s的性能演进——看看K8s这些年在性能上做了哪些优化。

14.8.1 早期的K8s:1.0时代 ​

K8s 1.0的时候,性能其实不怎么样——

  • 单集群最多支持几百个节点
  • etcd用的是v2 API,性能一般
  • 调度器比较简单,性能一般
  • 各种控制器也比较简单

那时候大家都说K8s重、慢,不适合小集群。

14.8.2 etcd v3:性能飞跃 ​

K8s 1.6左右,切换到了etcd v3 API——这是个大的性能飞跃。

etcd v3比v2好太多了:

  • 性能更好,延迟更低
  • 支持更多的并发连接
  • 存储更高效
  • 有gRPC接口,比HTTP JSON快多了

切换到etcd v3之后,K8s的性能上了一个大台阶——支持的集群规模大了很多。

14.8.3 调度器优化 ​

调度器也做了很多优化:

  • 预选并行化
  • 优选只给部分节点打分(percentageOfNodesToScore)
  • 调度器框架重构,更高效
  • 调度吞吐量大大提高

现在的调度器,几千个节点的集群,调度一个Pod也就几十毫秒——非常快。

14.8.4 API Server优化 ​

apiserver也做了很多优化:

  • Watch缓存优化
  • 分页支持
  • API优先级与公平性
  • 等等

现在的apiserver,单实例就能扛很高的QPS——多实例水平扩展,能支持更大的规模。

14.8.5 未来的方向 ​

K8s性能优化的未来方向:

  • eBPF:用eBPF优化网络、负载均衡,替代iptables/IPVS
  • 更高效的存储:etcd继续优化,或者探索新的存储后端
  • 更智能的调度:AI辅助调度,更高效的装箱
  • 分片架构:控制平面分片,支持更大规模

K8s的性能一直在进步——现在能支持5000节点,未来可能能支持更大的规模。

但还是那句话——不是集群越大越好。适合你的规模,才是最好的。

14.9 本章小结 ​

这一章我们讲了K8s的性能调优与大规模集群。

总结一下重点:

  • K8s的性能瓶颈主要在etcd、apiserver、调度器、kubelet这几个组件
  • etcd是重中之重——一定要用SSD,独立部署,控制数据量,定期压缩
  • apiserver可以水平扩展,开启API优先级与公平性,优化大查询
  • 调度器可以调优线程数、打分节点比例,提高调度吞吐量
  • kubelet要配置资源预留,限制Pod密度,优化跟apiserver的通信
  • 大规模集群要从架构上优化——控制平面与数据平面分离、事件分离、分片
  • 不是集群越大越好——到了一定规模就该分集群了
  • 要监控关键性能指标,知道瓶颈在哪,针对性调优

性能调优是个持续的过程——不是调一次就完了。集群在变,应用在变,性能也会变。持续监控,持续优化,才能让集群始终保持良好的性能。

下一章,我们来讲故障排查方法论——出了问题怎么查。


基于 Vite 强力驱动 | 纯静态轻量托管