Kubernetes高可用集群实战:多Control Plane、etcd与Load Balancer架构详解
1. 为什么单 Master 集群迟早要出事
1.1 从一次真实的翻车现场说起
前两年我帮一个朋友的公司做技术咨询,他们有一套跑了快两年的 Kubernetes 集群,规模不算大,十几个节点,跑着公司所有的核心业务。平时一直挺稳的,直到有天晚上他们的 Master 节点磁盘满了,kube-apiserver 直接起不来。结果就是整个集群彻底失控——已经在跑的 Pod 倒是没挂,但任何调度、扩缩容、发布、回滚全部停摆,连 kubectl get pod 都超时。他们运维半夜爬起来清磁盘、重启服务,折腾了将近两个小时才恢复。
这件事之后他们才意识到,单 Master 的 Kubernetes 集群本质上就是一个单点故障的定时炸弹。平时不出事是因为运气好,一旦出事就是全公司业务跟着遭殃。
这就是我们今天要聊的核心话题:多 Control Plane、etcd 和 Load Balancer 怎么组成高可用集群。这是 Kubernetes 生产环境落地的必修课,也是很多教程里一笔带过、但实际踩坑最多的地方。如果你正准备把测试环境的单 Master 集群升级成生产可用的高可用架构,或者你正在规划一套新的集群,那这篇内容应该能帮你少走不少弯路。
1.2 高可用到底在“高”什么
很多人一提高可用,第一反应就是“多搞几台机器”。但 Kubernetes 的高可用不是简单堆机器,你得先搞清楚集群里哪些组件是“挂了就全完”的关键角色。
一个标准的 Kubernetes 控制面包含这几个核心组件:
- kube-apiserver:整个集群的唯一入口,所有操作都要经过它。它是无状态的,可以水平扩展多个实例。
- etcd:集群的唯一数据源,保存着所有对象的状态。它是有状态的,需要保证数据一致性。
- kube-scheduler:负责 Pod 调度,通过选举机制同一时刻只有一个实例在工作。
- kube-controller-manager:负责各种控制器逻辑,同样通过选举保证只有一个活跃实例。
- cloud-controller-manager:对接云厂商的控制器,也是选举模式。
这里面的关键区别在于:apiserver 是无状态的,可以多个同时工作;etcd 是有状态的,需要组成集群保证一致性;scheduler 和 controller-manager 是选举模式,多个实例里只有一个干活,其他的待命。
所以高可用的核心思路就清晰了:apiserver 多实例负载均衡,etcd 组成奇数节点集群,scheduler 和 controller-manager 多实例选举,最后用一个统一的入口(Load Balancer)把流量分发到各个 apiserver 上。
1.3 高可用架构的整体拓扑长什么样
在动手之前,你得先在脑子里把整个拓扑画出来。一个典型的高可用集群拓扑是这样的:
- 3 台(或更多)Control Plane 节点,每台上都跑着 apiserver、scheduler、controller-manager 和 etcd。
- 一个 Load Balancer(可以是硬件 F5、软件 HAProxy/Keepalived,也可以是云厂商的 LB),对外暴露一个统一的 VIP(虚拟 IP)和端口,比如
6443。 - 所有 Worker 节点的 kubelet、kube-proxy 都通过这个 VIP 去连接 apiserver。
- 管理员用 kubectl 也是连这个 VIP。
这个架构里,任何一台 Control Plane 挂掉,Load Balancer 会自动把流量切到其他健康的节点上,集群照常工作。etcd 集群因为有 3 个节点,允许挂掉 1 个而不丢失数据(quorum 机制)。这就是高可用的基本盘。
注意:etcd 集群的节点数必须是奇数,3、5、7 这样。因为 etcd 用的是 Raft 一致性算法,需要多数派(quorum)才能写入。3 节点容忍 1 个故障,5 节点容忍 2 个,7 节点容忍 3 个。偶数节点没有意义,因为 4 节点的容错能力和 3 节点一样,但成本更高。
2. 核心组件的高可用原理拆解
2.1 etcd 集群:整个高可用的地基
etcd 是整个 Kubernetes 集群里最不能出事的部分。它存着集群所有的状态数据——你创建的每一个 Deployment、Service、ConfigMap,最终都落在 etcd 里。etcd 挂了,集群就等于失忆了。
etcd 高可用的核心是 Raft 一致性协议。简单说,Raft 把集群里的节点分成 Leader 和 Follower。所有的写请求都走 Leader,Leader 把日志复制给 Follower,超过半数节点确认后,这次写入才算成功。
这就解释了为什么节点数要奇数:
| 集群规模 | 多数派要求 | 容忍故障数 | 说明 |
|---|---|---|---|
| 1 节点 | 1 | 0 | 无高可用,仅测试用 |
| 3 节点 | 2 | 1 | 生产最低要求 |
| 5 节点 | 3 | 2 | 大型集群推荐 |
| 7 节点 | 4 | 3 | 超大规模,一般用不到 |
3 节点是生产环境的最低配置,也是性价比最高的选择。5 节点适合规模较大、对可用性要求极高的场景。再往上,Raft 的写入延迟会因为节点间同步而增加,反而得不偿失。
etcd 部署时有几个关键参数必须注意:
--initial-cluster:定义集群所有节点的初始列表,格式是name=peerURL。--initial-cluster-token:集群的唯一标识,防止不同集群的节点误连。--initial-cluster-state:新建集群用new,加入已有集群用existing。--listen-peer-urls:节点间通信的地址,通常是2380端口。--listen-client-urls:客户端访问的地址,通常是2379端口。--advertise-client-urls:对外通告的客户端地址,这个必须写对,否则其他组件连不上。
我见过最常见的坑就是 --advertise-client-urls 写成了 127.0.0.1,结果 apiserver 连不上 etcd。这个地址必须是其他节点能访问到的真实 IP。
2.2 kube-apiserver:无状态的多活设计
apiserver 是整个集群的“前台”,所有请求都从它进。它的高可用最简单——因为它是无状态的,你可以在每个 Control Plane 节点上都跑一个,然后用 Load Balancer 把流量分发过去。
apiserver 本身不需要做任何集群配置,每个实例都是独立的,它们通过 etcd 来共享状态。你请求哪个 apiserver,最终读写的都是同一份 etcd 数据。
不过有几个细节要注意:
- 每个 apiserver 实例的
--advertise-address要设置成本机 IP,这样其他组件知道该连哪个。 --etcd-servers要列出所有 etcd 节点的地址,用逗号分隔,这样某个 etcd 挂了 apiserver 还能连其他的。--service-cluster-ip-range在所有 apiserver 上必须一致,否则 Service 的 ClusterIP 会冲突。
apiserver 前面挂 Load Balancer 的时候,健康检查很关键。一般用 TCP 检查 6443 端口,或者用 HTTPS 检查 /healthz 路径。后者更准确,但配置稍微复杂点。
2.3 kube-scheduler 和 controller-manager:选举模式的待命机制
这两个组件和 apiserver 不一样,它们不能同时有多个实例在工作。如果两个 scheduler 同时调度,会出现资源竞争和重复调度的问题。所以它们用的是 Leader Election(领导者选举) 机制。
具体来说,多个实例启动后,会通过 apiserver 里的一个 Lease 对象来竞争 Leader。谁抢到了,谁就开始工作,其他的进入待命状态,持续监控 Leader 的心跳。一旦 Leader 挂了,心跳超时,其他实例会重新发起选举,选出新的 Leader 接管工作。
这个切换过程通常在 15 秒左右(取决于 --leader-elect-lease-duration 和 --leader-elect-renew-deadline 参数)。也就是说,scheduler 挂掉后,最多 15 秒左右集群会恢复调度能力。这个时间对于大多数业务来说是可以接受的。
配置上,你只需要在每个实例上加上 --leader-elect=true 就行。默认情况下这个参数在新版本里已经是 true 了,但老版本或者手动部署的时候要确认一下。
2.4 Load Balancer:流量的统一入口
Load Balancer 是高可用架构里的“门卫”,所有对 apiserver 的请求都要经过它。它的作用是:
- 提供一个固定的 VIP,让所有客户端只需要记住一个地址。
- 健康检查后端 apiserver,自动剔除故障节点。
- 把流量分发到健康的 apiserver 上。
Load Balancer 有几种常见选择:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| HAProxy + Keepalived | 自建机房、物理机 | 免费、可控、成熟 | 需要自己维护 |
| 云厂商 LB | 公有云环境 | 免维护、高可用 | 有费用、依赖厂商 |
| Nginx + Keepalived | 自建机房 | 配置简单 | 四层转发性能不如 HAProxy |
| LVS | 大规模场景 | 性能极高 | 配置复杂 |
对于大多数自建场景,HAProxy + Keepalived 是最经典的组合。HAProxy 负责四层负载均衡,Keepalived 负责 VIP 的漂移。两台 LB 节点组成主备,主节点挂了 VIP 自动漂到备节点。
这里有个关键点:Load Balancer 本身也不能是单点。如果你只搞一台 HAProxy,那它挂了整个集群还是进不去。所以 LB 也要做高可用,通常是两台机器跑 Keepalived,一主一备共享一个 VIP。
3. 手把手搭建高可用集群
3.1 环境规划与前置准备
假设我们要搭建一个 3 Master + N Worker 的高可用集群,先做规划。以下是我推荐的一套配置:
| 角色 | 主机名 | IP | 配置 |
|---|---|---|---|
| LB 主 | lb-01 | 192.168.1.10 | 2C4G |
| LB 备 | lb-02 | 192.168.1.11 | 2C4G |
| Master1 | master-01 | 192.168.1.21 | 4C8G |
| Master2 | master-02 | 192.168.1.22 | 4C8G |
| Master3 | master-03 | 192.168.1.23 | 4C8G |
| Worker | worker-01 | 192.168.1.31 | 8C16G |
VIP 我们规划为 192.168.1.100,apiserver 端口用默认的 6443。
前置准备在所有节点上都要做,包括:
- 关闭 swap:
swapoff -a,并注释掉/etc/fstab里的 swap 行。Kubernetes 要求必须关闭 swap,否则 kubelet 起不来。 - 加载内核模块:
br_netfilter和overlay,写入/etc/modules-load.d/k8s.conf。 - 配置内核参数:开启网桥转发和 iptables 转发,写入
/etc/sysctl.d/k8s.conf。 - 安装容器运行时:推荐 containerd,比 Docker 更轻量,也是现在 Kubernetes 官方推荐的选择。
- 配置时间同步:所有节点时间必须一致,否则证书验证和 etcd 集群都会出问题。
这些步骤看起来琐碎,但每一步都有它的道理。比如时间同步,如果节点间时间差超过证书的有效期范围,TLS 握手就会失败,apiserver 直接连不上。我见过有人因为 NTP 没配好,折腾了一整天才发现问题。
3.2 用 kubeadm 初始化第一个 Control Plane
kubeadm 是官方推荐的集群部署工具,它把很多复杂的步骤自动化了。但高可用场景下,kubeadm 的用法和单 Master 有些区别。
首先你得准备一个 kubeadm 的配置文件,因为高可用场景下需要指定 controlPlaneEndpoint 指向 LB 的 VIP:
这里 controlPlaneEndpoint 是关键,它告诉 kubeadm 集群的统一入口是 LB 的 VIP,而不是某台 Master 的 IP。这样生成的 kubeconfig 和证书里都会用这个地址,后续加节点、加 Master 都不会乱。
然后在第一台 Master 上执行:
--upload-certs 这个参数很重要,它会把加入集群需要的证书加密上传到 etcd(实际上是存到 kubeadm 的一个 Secret 里),这样其他 Master 节点加入时可以直接下载证书,不用手动拷贝。
初始化完成后,你会看到类似这样的输出:
注意看,join 命令里的地址是 192.168.1.100:6443,也就是我们的 VIP,而不是 master-01 的 IP。这就是 controlPlaneEndpoint 生效的结果。
3.3 加入其余 Control Plane 节点
拿到 join 命令后,在 master-02 和 master-03 上分别执行带 --control-plane 的那条命令。这个命令会:
- 从 etcd 里下载证书(用
--certificate-key解密)。 - 生成该节点的 kubeconfig。
- 启动该节点的 apiserver、scheduler、controller-manager。
- 把该节点加入 etcd 集群。
执行过程中你会看到类似这样的输出:
这里有个细节:Control Plane 节点默认会打上 NoSchedule 的污点,意思是普通业务 Pod 不会被调度到 Master 上。这是出于安全和资源隔离的考虑。如果你资源紧张想让 Master 也跑业务,可以手动去掉这个污点,但生产环境不建议这么做。
3.4 验证 etcd 集群状态
三个 Master 都加入后,第一件事是验证 etcd 集群是否健康。在任意一台 Master 上执行:
正常输出应该能看到三个成员,状态都是 started:
再用 endpoint health 检查健康状态:
三个节点都应该返回 healthy。如果有一个不健康,先别急着往下走,把 etcd 问题解决了再说。etcd 是整个集群的地基,地基不稳后面全是坑。
3.5 配置 HAProxy 和 Keepalived
现在来处理 Load Balancer。在两台 LB 节点上安装 HAProxy 和 Keepalived:
HAProxy 的配置(/etc/haproxy/haproxy.cfg)核心部分:
这里的 check fall 3 rise 2 意思是:连续 3 次检查失败就标记为不可用,连续 2 次成功就恢复。这个参数可以根据实际情况调整,网络抖动大的环境可以适当放宽。
Keepalived 的配置(/etc/keepalived/keepalived.conf)主节点:
备节点的配置基本一样,只需要把 state 改成 BACKUP,priority 改成 90。priority 高的节点会成为 MASTER,持有 VIP。
配置完成后启动服务,用 ip addr 检查 VIP 是否在主节点上。然后手动停掉主节点的 Keepalived,看 VIP 是否漂移到备节点。这个切换测试一定要做,不然真出事的时候你都不知道能不能切。
4. 高可用集群的验证与故障演练
4.1 验证 apiserver 的负载均衡
集群搭好了,怎么确认 Load Balancer 真的在工作?最简单的办法是停掉一台 Master 的 apiserver,然后看集群是否还能正常操作。
如果一切正常,kubectl 应该还能返回节点列表,说明 HAProxy 已经把流量切到了其他 Master 上。等测试完再把文件移回去,apiserver 会自动重启。
这个测试建议在业务低峰期做,虽然理论上不影响业务,但保险起见还是谨慎点。
4.2 验证 etcd 的容错能力
etcd 的容错测试更关键。停掉一台 Master 的 etcd:
然后检查集群状态:
如果 3 节点 etcd 集群正常工作,停掉 1 个节点后集群仍然可以读写。这时候 etcd 集群处于“降级但可用”的状态,quorum 从 2 变成了 2(3 节点里剩 2 个,仍然满足多数派)。
但如果你再停掉一台,剩 1 个节点,quorum 就达不到了,etcd 会变成只读,所有写操作都会失败。这就是为什么 3 节点只能容忍 1 个故障。
注意:etcd 节点故障后要尽快恢复,不要长期处于降级状态。因为降级期间如果再挂一台,整个集群就彻底不可写了。恢复的方法就是把 etcd 的 manifest 文件移回去,kubelet 会自动拉起 etcd 容器,它会自动重新加入集群并同步数据。
4.3 验证 scheduler 和 controller-manager 的选举切换
这两个组件的切换验证稍微麻烦点,因为它们本身就是选举模式,你停掉一个,另一个会自动接管。验证方法是:
输出里的 holderIdentity 字段就是当前 Leader 的主机名。然后停掉那台 Master 上的 scheduler:
等 15 秒左右,再查一次 lease,你会发现 holderIdentity 变成了另一台 Master。这就说明选举切换成功了。
4.4 常见问题速查表
在实际搭建和运维高可用集群的过程中,我整理了一些高频问题和排查思路:
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| kubeadm join 卡在 preflight | 网络不通或端口未开 | 检查 6443、2379、2380 端口 | 开放对应端口 |
| etcd 集群只有一个成员 | join 时 initial-cluster 配置错误 | etcdctl member list | 重新配置 etcd |
| apiserver 连不上 etcd | advertise-client-urls 写错 | 检查 etcd 启动参数 | 改成真实 IP |
| kubectl 连 VIP 超时 | HAProxy 未启动或后端全挂 | 检查 HAProxy 状态和后端健康 | 修复 HAProxy 或后端 |
| 证书过期导致集群不可用 | 证书默认 1 年有效期 | kubeadm certs check-expiration | 续期或重新生成 |
| VIP 不漂移 | Keepalived 配置或防火墙问题 | 检查 vrrp 组播是否被拦截 | 放行 vrrp 协议 |
| 新 Master 加入后 etcd 不同步 | 时间不同步或网络延迟大 | 检查 NTP 和网络质量 | 同步时间、优化网络 |
这里面我特别想强调证书问题。kubeadm 生成的证书默认有效期是 1 年,到期后集群直接不可用。我见过好几个团队因为没注意这个,一年后集群突然挂了,手忙脚乱。建议的做法是:
- 用
kubeadm certs check-expiration定期检查证书有效期。 - 在证书到期前用
kubeadm certs renew all续期。 - 或者干脆在初始化时就把证书有效期改长(需要修改 kubeadm 源码或使用自定义证书)。
5. 生产环境的进阶考量
5.1 etcd 的数据备份策略
高可用解决的是“节点挂了”的问题,但解决不了“数据被误删”的问题。如果有人误删了一个 Namespace,etcd 集群再高可用也没用,数据是真的没了。所以 etcd 的定期备份是必须的。
备份命令很简单:
建议做成定时任务,每小时备份一次,保留最近 7 天的快照。备份文件最好传到远程存储,别跟 etcd 放同一台机器上,不然机器挂了备份也没了。
恢复的时候用 etcdctl snapshot restore,但要注意恢复是覆盖式的,会替换掉当前 etcd 的所有数据。所以恢复前一定要确认清楚,别把好的数据覆盖成旧的。
5.2 控制面节点的资源规划
Control Plane 节点的资源规划经常被低估。很多人觉得 Master 不跑业务,给个 2C4G 就够了。但实际上:
- etcd 对磁盘 IO 非常敏感,必须用 SSD。机械硬盘的 etcd 在高负载下延迟会飙升,导致整个集群卡顿。
- apiserver 在高并发场景下内存消耗不小,尤其是集群规模大了之后。
- 如果开启了审计日志,apiserver 的磁盘写入量会很大。
我的建议是生产环境 Control Plane 节点至少 4C8G,etcd 单独用 SSD,最好把 etcd 的数据目录挂载到独立的磁盘上,避免和其他 IO 竞争。
5.3 升级策略与版本管理
高可用集群的升级比单 Master 复杂,因为你要保证升级过程中集群始终可用。kubeadm 的升级流程是:
- 先升级第一个 Control Plane 节点。
- 然后升级其他 Control Plane 节点。
- 最后升级 Worker 节点。
升级第一个 Master 时,apiserver 会短暂不可用,但 HAProxy 会自动把流量切到其他 Master 上,所以整体服务不受影响。这就是高可用的价值——升级不用停机。
但要注意版本跨度。Kubernetes 不支持跨大版本升级,比如从 1.24 直接升到 1.26 是不行的,必须先升到 1.25 再升到 1.26。而且升级前一定要看官方的 changelog,有些 API 版本会被废弃,升级后可能导致业务异常。
5.4 监控与告警的关键指标
高可用集群搭好了不是就完事了,你得能知道它什么时候出问题。以下是我认为必须监控的指标:
- etcd:leader 变化次数、WAL fsync 延迟、backend commit 延迟、DB 大小。leader 频繁变化说明网络或磁盘有问题。
- apiserver:请求延迟、请求 QPS、错误率、当前连接数。
- scheduler:调度延迟、调度失败次数、pending Pod 数量。
- controller-manager:工作队列长度、处理延迟。
- 节点:CPU、内存、磁盘 IO、网络。
这些指标用 Prometheus + Grafana 采集展示,配合 Alertmanager 做告警。etcd 的告警尤其重要,因为它是整个集群最脆弱的部分。
6. 几个我踩过的坑和实操心得
6.1 关于 Load Balancer 的健康检查
HAProxy 默认的 TCP 健康检查只是检查端口是否可连,但 apiserver 可能端口在监听但实际已经不可用了(比如 etcd 连不上)。这种情况下 TCP 检查会误判为健康,流量还是往故障节点发。
更可靠的做法是用 HTTPS 检查 /healthz 或 /readyz 路径。HAProxy 的配置可以这样写:
这样只有真正健康的 apiserver 才会被加入后端。不过要注意,/healthz 检查的是 apiserver 自身的健康,如果 etcd 挂了,/healthz 可能还是返回 200。更严格的是 /readyz,它会检查所有依赖。
6.2 关于 etcd 的磁盘选择
我前面提过 etcd 必须用 SSD,这里再展开说一下。etcd 的 Raft 协议需要频繁地 fsync 写 WAL 日志,如果磁盘 IO 延迟高,整个写入链路都会变慢。我实测过,同样的集群在机械硬盘上,etcd 的写入延迟能到几十毫秒,换成 SSD 后降到 1 毫秒以内。
如果你用的是云服务器,注意看云盘的 IOPS 指标。有些云厂商的“高效云盘”实际上是机械盘,IOPS 很低,跑 etcd 会很难受。建议选 SSD 云盘或者本地 NVMe。
6.3 关于证书管理的自动化
证书过期是高可用集群的隐形杀手。我建议的做法是:
- 用
kubeadm certs check-expiration写一个定时脚本,每天检查一次。 - 如果发现证书在 30 天内过期,自动触发告警。
- 在维护窗口用
kubeadm certs renew all续期,然后重启控制面组件。
续期后记得重启 apiserver、scheduler、controller-manager 和 etcd,让它们加载新证书。kubeadm 的 renew 命令只更新证书文件,不会自动重启服务。
6.4 关于 etcd 集群的扩容
如果一开始用了 3 节点 etcd,后来想扩到 5 节点,是可以的。用 etcdctl member add 添加新节点,然后在新节点上启动 etcd 并指定 --initial-cluster-state existing。但扩容过程中要一个一个加,加完一个确认健康了再加下一个,不要一次性加多个,否则容易出问题。
缩容也一样,用 etcdctl member remove 移除节点,然后停掉对应的 etcd 实例。缩容同样要一个一个来。
6.5 关于 Control Plane 节点的反亲和性
如果你在云环境或者虚拟化环境里,Control Plane 节点最好分散在不同的物理机上。如果三台 Master 都在同一台物理宿主机上,那宿主机挂了三个一起挂,高可用就形同虚设了。
在 Kubernetes 里可以用 PodAntiAffinity 来保证 etcd 和 apiserver 分散部署,但如果是 kubeadm 部署的静态 Pod,这个得在基础设施层面保证。规划的时候就要注意,别把鸡蛋放在一个篮子里。
6.6 关于网络插件的选择
高可用集群的网络插件选择也很关键。Calico、Cilium、Flannel 各有优劣。Calico 支持网络策略,适合对安全有要求的场景;Cilium 基于 eBPF,性能好,可观测性强;Flannel 简单,但功能少。
我个人的经验是,如果集群规模不大(几十个节点),Calico 的 BGP 模式或者 IPIP 模式都挺好用。如果规模大、对性能要求高,Cilium 是更好的选择。Flannel 适合快速验证,生产环境不太推荐。
网络插件选错了,后期迁移成本很高,所以一开始就要想清楚。
6.7 关于 kube-proxy 的模式
kube-proxy 有 iptables 和 ipvs 两种模式。iptables 模式在 Service 数量多了之后性能会下降,因为规则是线性匹配的。ipvs 模式用的是哈希表,性能更好,适合大规模集群。
如果你的集群 Service 数量超过 1000 个,建议用 ipvs 模式。切换方法是在 kube-proxy 的配置里把 mode 改成 ipvs,然后重启 kube-proxy。不过 ipvs 需要内核支持,有些老内核可能没有编译 ipvs 模块,切换前先确认一下。
6.8 关于灾难恢复演练
最后说一个很多人忽略的点:灾难恢复演练。你搭了高可用集群,配了备份,但从来没演练过恢复,那真出事的时候大概率会手忙脚乱。
我建议至少每季度做一次演练,包括:
- 模拟一台 Master 完全宕机,验证集群是否正常。
- 模拟 etcd 数据损坏,从备份恢复,验证恢复流程。
- 模拟 VIP 漂移,验证 Load Balancer 切换。
演练的时候要在测试环境做,别拿生产环境练手。演练完要写复盘,记录遇到的问题和改进措施。
这套高可用架构搭下来,说实话前期投入不小,但相比单 Master 出事后业务停摆的损失,这点投入完全值得。我见过太多团队在测试环境用单 Master 跑得好好的,上了生产也不改,结果出事的时候追悔莫及。Kubernetes 的高可用不是可选项,是生产环境的必选项。