概述
“控制面全托管”——这句话让不少团队以为迁移到 ACK 托管集群后就能当甩手掌柜。实际上,托管只解决了 etcd、kube-apiserver、kube-controller-manager、kube-scheduler 这几个控制面组件的运维,数据面的问题一个不少。
我曾在一次 120+ 微服务的 K8s 迁移项目中,把自建集群切到 ACK 托管集群 Pro 版。迁移过程用了 3 个月,零故障切换——但切换之后的第一个月,凌晨告警没断过。不是 ACK 本身有问题,而是托管集群和自建集群在"看不见的地方"有很多行为差异,这些差异在自建集群时被你自己的运维习惯掩盖了,到托管集群上才暴露出来。
这篇文章把我踩过的 5 个最痛的坑整理出来,每个都附根因分析和修复方案。如果你正准备从自建 K8s 迁移到 ACK,或者刚迁移完正在经历告警风暴,这篇文章能帮你少走弯路。
先说结论:这 5 个坑不是 ACK 的 bug,而是从自建迁移到托管时的认知差。自建集群时你完全掌控每个组件,出了问题能从头排查;托管后控制面是个黑盒,你得换一套思路来运维。
踩坑一:网络插件选型——Flannel 还是 Terway,选错就是性能灾难
问题背景
迁移到 ACK 时第一个决策就是网络插件。ACK 支持两种:Flannel 和 Terway。这两个东西选错了,后面所有网络相关的性能问题都跟它有关。而且集群创建完成后不支持切换——选错了只能重建集群。
我在这个项目上选了 Flannel,原因很简单:自建集群用的也是 Flannel,迁移时 Pod 网段不用改,应用配置零调整。上线两周后,问题开始暴露。
先是某个微服务的 P99 延迟从自建集群的 170ms 飙升到 230ms。应用层没改过,代码没变,唯一变的是底层网络。然后是等保 2.0 审计,审计员要求 Pod 流量可审计,Flannel 的 Pod IP 在独立网段,VPC 流日志抓不到——直接开了一个中风险项。
根因分析
Flannel 在阿里云上基于 VPC 自定义路由实现跨节点 Pod 通信。简单说就是每个节点分一个 Pod CIDR 子网,Pod 的流量通过 VPC 路由表转发。这个方案能用,但有三个硬伤:
第一,性能损耗。 Flannel 的 VXLAN 模式需要封包解包,每个网络包多 50 字节开销。在微服务高频调用的场景下,这个损耗会被放大。我做了一组对比测试:
| 网络模式 | Pod 间延迟(ms) | TCP 吞吐(Mbps) | Service ClusterIP 吞吐提升 |
|---|---|---|---|
| Flannel VXLAN | 0.82 | 3200 | 基准 |
| Terway 共享 ENI | 0.45 | 4800 | +50% |
| Terway 独占 ENI | 0.38 | 5200 | +62% |
| Terway IPvlan(eBPF) | 0.35 | 5100 | +59% |
测试环境:2 节点 ecs.g7ne.4xlarge,netperf TCP_CRR 测试 Pod 间通信,wrk 压测 Nginx Service 的 100 字节小页面。数据参考了阿里云 Terway 与 Cilium 集成的性能测试报告。
Flannel 的数据链路:客户端 Pod → cni0 网桥 → flannel.1 接口 → VXLAN 封包 → 对端 flannel.1 → cni0 → 服务端 Pod。四次转发加两次封包解包,延迟和吞吐都受影响。
Terway 共享 ENI 的数据链路:客户端 Pod → ENI → VPC 直达 → 对端 ENI → 服务端 Pod。没有封包,没有隧道,直接走 VPC 二层网络。在 100 字节小页面压测中,Terway 共享 ENI 的 ClusterIP 吞吐比 Flannel 提升 277%,延迟降低 50%。
第二,Pod IP 不在 VPC 网段。 Flannel 的 Pod IP 是独立的 CIDR(如 172.20.0.0/16),和 VPC 子网(如 192.168.0.0/16)不在一个平面。这意味着你没法用安全组直接控制 Pod 的出入流量,也没法用 VPC 流日志做网络审计。在等保 2.0 审计时,审计员看到 Pod 流量不可审计,直接开了一个中风险项。
Terway 模式下 Pod IP 和 ECS 在同一个 VPC 子网,安全组、流日志、网络 ACL 全部可用。这一点在做安全合规时差别巨大。
第三,NetworkPolicy 不支持。 Flannel 不支持 Kubernetes 原生的 NetworkPolicy。如果你的微服务需要做网络隔离(比如数据库 Pod 只允许应用 Pod 访问),Flannel 做不到,得额外装 Calico——但 Flannel 和 Calico 在阿里云上共存的坑更多,路由表冲突、ARP 广播风暴,排查起来非常痛苦。
Terway 独占 ENI 模式支持 Kubernetes 原生 NetworkPolicy,共享 ENI 模式支持通过安全组实现等价能力。两种模式都能满足网络隔离需求。
修复方案
重建集群,切换到 Terway 共享 ENI 模式。为什么选共享 ENI 而不是独占 ENI?因为独占 ENI 模式下每个 Pod 占用一个弹性网卡,ECS 实例的 ENI 数量有上限(比如 ecs.g7.large 最多 6 个 ENI,扣除主网卡只剩 5 个),Pod 密度太低。共享 ENI 模式下多个 Pod 共享一个 ENI,通过 IP 分配实现网络隔离,Pod 密度不受 ENI 限制。
ECS 实例规格与 ENI 数量的对应关系,这个直接决定了独占 ENI 模式下的 Pod 密度上限:
| ECS 规格 | vCPU/内存 | ENI 上限 | 独占 ENI 模式 Pod 上限 | 共享 ENI 模式 Pod 上限 |
|---|---|---|---|---|
| ecs.g7.large | 2C/8G | 3 | 2 | 10-15 |
| ecs.g7.xlarge | 4C/16G | 4 | 3 | 20-30 |
| ecs.g7.2xlarge | 8C/32G | 5 | 4 | 30-50 |
| ecs.g7ne.4xlarge | 16C/64G | 8 | 7 | 50-80 |
共享 ENI 模式的 Pod 上限取决于 ENI 的辅助 IP 数量,不同规格的辅助 IP 数不同。比如 ecs.g7.2xlarge 每个 ENI 有 10 个辅助 IP,5 个 ENI 共 50 个辅助 IP,扣除主网卡 IP 后约 45 个 Pod 可用。
Terway 共享 ENI 模式的关键配置:
# Terway 共享 ENI 模式的 eni-conf ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: eni-conf
namespace: kube-system
data:
eni_conf: |
{
"version": "1",
"enable_eni_shared": true, # 开启共享 ENI 模式
"eni_subnet_id": "vsw-xxx", # 指定 Pod 使用的交换机(可与节点不同)
"eni_security_group": "sg-xxx", # Pod 级别安全组(独立于节点安全组)
"max_pool_size": 20, # ENI IP 池大小(预分配)
"min_pool_size": 5 # 最小预留 IP 数(避免 Pod 创建等待)
}
配置要点:
eni_subnet_id可以和节点不在同一个交换机。我推荐 Pod 和节点用不同子网,方便做网络策略和流量隔离max_pool_size不要设太大,否则会预占大量 IP 导致交换机 IP 耗尽。按节点上预期 Pod 数的 1.5 倍设置即可min_pool_size确保有预留 IP,Pod 创建时不用等 ENI 分配新 IP,创建延迟从秒级降到毫秒级
迁移步骤(如果已经选了 Flannel 需要切换):
- 创建新的 Terway 集群
- 通过灰度发布把应用逐步迁移到新集群
- 使用
[相关文章:K8s 日志采集方案](/posts/k8s-log-collection-strategy/)中的 DaemonSet 方案确保日志不中断 - 旧 Flannel 集群下线
我的推荐:
- 50 节点以下、对网络性能不敏感的测试集群:Flannel 够用,配置简单
- 50 节点以上、微服务高频调用的生产集群:Terway 共享 ENI + DataPathv2 加速模式
- 对网络性能有极致要求(如 AI 训练):Terway 独占 ENI 或 IPvlan 模式
别被"Flannel 简单"骗了。在阿里云上,Terway 才是亲儿子,Flannel 只是兼容性保留。
踩坑二:云盘 CSI 跨可用区调度——Pod 永远 Pending 的元凶
问题背景
迁移到 ACK 后,我们配置了多可用区部署来满足高可用要求。3 个可用区各 10 个 Worker 节点,StatefulSet 跑了 30 个 Pod,每个 Pod 挂一块云盘做持久化存储。
自建集群时用的是 Ceph RBD,跨节点共享存储不存在可用区限制。迁移到 ACK 后直接用了云盘(Block Storage),这是可用区级别的资源——一块云盘创建在可用区 A,只能挂载到可用区 A 的 ECS 上。
结果 StatefulSet 扩容时,新 Pod 一直 Pending。事件日志报 had volume node affinity conflict——PV 创建在可用区 A,但 Pod 被调度到了可用区 B。或者 PV 创建失败报 The specified AZone inventory is insufficient——指定可用区云盘库存不足。
根因分析
ACK 默认的 StorageClass 使用 csi-disk provisioner,它的绑定模式是 Immediate——PVC 一创建就马上绑定 PV 并创建云盘。但此时 Pod 还没被调度,CSI 不知道 Pod 会落在哪个可用区,于是云盘创建在了默认可用区。
等 Pod 被调度到另一个可用区的节点上时,发现云盘不在同一个可用区,挂载失败。Pod 状态一直停在 ContainerCreating,事件日志显示:
Warning FailedAttachVolume 2m (x10 over 5m) attachdetach-controller AttachVolume.Attach failed for volume "pvc-xxx" : rpc error: code = Internal desc = "had volume node affinity conflict"
这个问题在自建集群里不存在,因为 Ceph RBD 是跨节点的分布式存储,Pod 在哪个节点都能挂载。云盘是可用区绑定资源,这是 IaaS 层的限制,不是 K8s 的问题。
常见的云盘 CSI 错误和根因:
| 错误信息 | 根因 | 解决方案 |
|---|---|---|
had volume node affinity conflict | PV 和 Pod 不在同一可用区 | 改用 WaitForFirstConsumer |
The specified AZone inventory is insufficient | 指定可用区云盘库存不足 | StorageClass 配置多可用区 |
no topology key found on CSINode | CSI Node 未注册拓扑信息 | 检查 CSI 组件版本 |
Multi-Attach error for volume | 云盘被多个 Pod 同时挂载 | 云盘不支持 ReadWriteMany |
Previous attach action is still in process | 上一次挂载操作未完成 | 等待或检查 CSI Controller |
exceed max volume count | 节点挂载云盘数超限 | 检查 ECS 规格的云盘挂载上限 |
修复方案
把 StorageClass 的 volumeBindingMode 从 Immediate 改为 WaitForFirstConsumer。改成延迟绑定后,PVC 创建时不会马上创建云盘,而是等 Pod 被调度到某个节点后,根据节点所在的可用区创建云盘。
# 修复后的 StorageClass——延迟绑定 + 多可用区
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-ssd-topology
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_ssd # 云盘类型:cloud_ssd/cloud_essd/cloud_efficiency
regionId: cn-shenzhen # 地域
zoneId: cn-shenzhen-a,cn-shenzhen-b,cn-shenzhen-c # 多可用区
encrypted: "true" # 加密云盘(等保要求)
performanceLevel: PL2 # ESSD 性能等级(PL0/PL1/PL2/PL3)
reclaimPolicy: Retain # 生产环境用 Retain,别用 Delete
volumeBindingMode: WaitForFirstConsumer # 关键:延迟绑定
allowVolumeExpansion: true # 允许在线扩容
mountOptions:
- noatime # 挂载选项:不更新访问时间
- nodiratime
改完后效果:Pod 先被调度到节点 → CSI 读取节点的拓扑信息 → 在节点所在可用区创建云盘 → 挂载成功。had volume node affinity conflict 错误消失。
云盘类型选型建议:
| 云盘类型 | IOPS 上限 | 吞吐上限 | 单位价格 | 推荐场景 |
|---|---|---|---|---|
| cloud_efficiency(高效云盘) | 5,000 | 140 MB/s | 1x | 测试环境 |
| cloud_ssd(SSD 云盘) | 25,000 | 300 MB/s | 3x | 通用生产 |
| cloud_essd PL1 | 50,000 | 350 MB/s | 3x | 数据库 |
| cloud_essd PL2 | 100,000 | 450 MB/s | 5x | 高性能数据库 |
| cloud_essd PL3 | 1,000,000 | 1,800 MB/s | 15x | 极致性能场景 |
我推荐生产环境统一用 ESSD PL1 起步。高效云盘的 IOPS 太低(5,000),在 MySQL 这种高 IOPS 场景下直接成为瓶颈。SSD 云盘和 ESSD PL1 价格差不多但 ESSD 性能更好,没必要用 SSD 云盘。
额外踩坑:如果你用了 NAS(文件存储),NAS 不存在跨可用区挂载限制,但存在另一个问题——NAS 的权限组。如果 Pod 以非 root 用户运行,挂载 NAS 时会报 chown: Operation not permitted。解决方法是把 NAS 权限组配置为 no_squash 模式,或者在 PV 中配置 securityContext.fsGroup:
# NAS 挂载权限修复
spec:
securityContext:
fsGroup: 1000 # 容器以 GID 1000 运行
fsGroupChangePolicy: "OnRootMismatch" # 只在根目录属主不匹配时 chown
containers:
- name: app
securityContext:
runAsUser: 1000 # 非 root 用户
runAsGroup: 1000
volumeMounts:
- name: nas-data
mountPath: /data
踩坑三:节点池缩容延迟——Cluster Autoscaler 的 10 分钟空窗
问题背景
迁移到 ACK 后启用了 Cluster Autoscaler(CA)做节点自动伸缩。白天流量高峰自动扩容,晚上流量低谷自动缩容。配置完成后扩容没问题,但缩容总是不及时——节点利用率已经降到 10% 以下了,CA 还是等了快 20 分钟才开始缩容。
在自建集群时我们用 ESS(弹性伸缩服务)手动管理节点,缩容策略是自己写的脚本,利用率低于 30% 持续 5 分钟就缩容。迁移到 CA 后默认参数比我们的脚本保守得多。
这个问题最痛的地方在于:它不报错,不告警,就是默默地多花你几十分钟的钱。一个月下来多花几千块,老板问你为什么云费涨了 15%,你才发现 CA 的默认参数太保守。
根因分析
Cluster Autoscaler 的缩容逻辑有三个时间参数控制:
| 参数 | 默认值 | 作用 |
|---|---|---|
--scale-down-unneeded-time | 10m | 节点持续空闲多久才触发缩容 |
--scale-down-delay-after-add | 10m | 扩容后多久才允许缩容(防止刚扩就缩的抖动) |
--scale-down-delay-after-failure | 3m | 缩容失败后多久重试 |
默认 scale-down-unneeded-time 是 10 分钟,加上 scale-down-delay-after-add 的 10 分钟保护期,最坏情况下节点空闲 20 分钟才被回收。对于按量付费的 ECS 节点,这 20 分钟就是白白烧钱。
另一个坑:CA 缩容时需要驱逐节点上的 Pod。如果 Pod 没有配置 PodDisruptionBudget(PDB),CA 会按照默认策略驱逐。但如果 Pod 使用了 hostPath 或 local 卷,CA 默认不会驱逐这些 Pod(怕丢数据),导致节点永远无法缩容。
自建集群时我们写脚本来管理缩容,脚本会强制驱逐所有 Pod。CA 的设计更保守——它优先保证数据安全,宁可多花钱也不冒险。这个设计没错,但默认参数太保守了。
CA 的扩缩容决策流程:
1. 扫描间隔(--scan-interval,默认 10s)
2. 检查是否有 Pending Pod → 有则扩容
3. 检查节点利用率是否低于 50%(--scale-down-utilization-threshold)
4. 检查节点持续空闲时间是否超过 scale-down-unneeded-time(默认 10m)
5. 检查扩容后保护期是否已过(scale-down-delay-after-add,默认 10m)
6. 尝试驱逐节点上所有 Pod(遵守 PDB)
7. 如果驱逐成功 → 调用云 API 删除节点
8. 如果驱逐失败 → 等待 scale-down-delay-after-failure 后重试
修复方案
根据业务特征调整 CA 参数。在 ACK 上通过组件管理修改 cluster-autoscaler 的启动参数:
# Cluster Autoscaler 关键参数调优
spec:
containers:
- name: cluster-autoscaler
command:
- ./cluster-autoscaler
- --scale-down-unneeded-time=5m # 从 10m 降到 5m
- --scale-down-delay-after-add=5m # 从 10m 降到 5m
- --scale-down-delay-after-failure=1m # 从 3m 降到 1m
- --scan-interval=10s # 扫描间隔(默认 10s 不变)
- --max-node-provision-time=15m # 节点创建超时
- --balance-similar-node-groups=true # 多可用区节点池自动平衡
- --expendable-pods-priority-cutoff=-10 # 低优先级 Pod 不触发扩容
- --scale-down-utilization-threshold=0.5 # 利用率低于 50% 触发缩容
- --max-graceful-termination-sec=120 # Pod 优雅终止最长等待时间
同时给所有使用 hostPath 的 Pod 加上注解,明确告诉 CA 可以安全驱逐:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
给关键应用配置 PDB 防止被误驱逐:
# PodDisruptionBudget 保护关键服务
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-gateway-pdb
namespace: production
spec:
minAvailable: 2 # 至少保持 2 个 Pod 可用
selector:
matchLabels:
app: api-gateway
我的实测数据:
| 参数 | 默认值 | 调整后 | 效果 |
|---|---|---|---|
| 空闲判定时间 | 10m | 5m | 缩容响应缩短 5 分钟 |
| 扩容后保护期 | 10m | 5m | 抖动保护仍有效,但缩容提前 5 分钟 |
| 缩容失败重试 | 3m | 1m | 快速重试减少等待 |
| 日均节省费用 | — | — | 按量付费节点少跑 10 分钟/次,月省约 15% |
别把 scale-down-unneeded-time 设得太短。我试过设 2 分钟,结果流量短暂波动导致节点反复扩缩,ECS 创建费用反而增加了。5 分钟是个经过验证的平衡点——既不会在流量波峰时误缩容,又不会让空闲节点白跑太久。
节点池设计建议:
ACK 的节点池是分层弹性架构的基础。我推荐的节点池配置:
| 节点池名称 | 实例规格 | 弹性策略 | 用途 |
|---|---|---|---|
| baseline-pool | ecs.g7.2xlarge | 固定 10 节点,不弹 | 常驻服务 |
| spot-pool | ecs.g7.xlarge(竞价) | 弹性 0-20 | 批处理任务 |
| on-demand-pool | ecs.g7.xlarge(按量) | 弹性 0-10 | 突发流量 |
baseline 用包年包月,spot 和 on-demand 用按量付费。流量高峰先扩 spot 池(便宜),spot 被回收时自动切 on-demand 池。这个组合比单一节点池省钱 30% 以上。
踩坑四:镜像拉取限速——ACR 免费加速器的隐性代价
问题背景
迁移到 ACK 后,我们用了阿里云容器镜像服务(ACR)的免费个人版做镜像仓库,同时配置了阿里云镜像加速器。大部分时候没问题,但某天早上 CI/CD 流水线批量部署 30 个微服务时,一半 Pod 卡在 ImagePullBackOff。
查看 Pod 事件日志:
Warning Failed 3m (x5 over 4m) kubelet Error: ErrImagePull
Warning Failed 3m kubelet Failed to pull image "registry.cn-shenzhen.aliyuncs.com/xxx/app:latest":
rpc error: code = Unknown desc = failed to pull and unpack image "registry.cn-shenzhen.aliyuncs.com/xxx/app:latest":
failed to extract layer sha256:xxx: process "/bin/sh -c apt-get install -y ..." did not complete successfully: exit code: 1
Normal BackOff 2m (x8 over 4m) kubelet Back-off pulling image
看起来像是镜像构建有问题,但实际上是加速器同步延迟——拉到的 latest 标签是前天的旧版本,旧版本里有个 bug 导致 apt-get 失败。
根因分析
阿里云在 2024 年 7 月对免费镜像加速器做了限制变更:
- 仅限阿里云 ECS/ACK 内网使用——非阿里云机器直接返回 403 Forbidden
- 停止实时同步 Docker Hub 最新镜像——
latest标签经常拉到旧版本,同步延迟可达数天 - 大规模批量拉取触发限流——免费加速器没有独立 SLA,不承诺可用性,30 个 Pod 同时拉镜像触发 429 Too Many Requests
我们在 CI/CD 中用 image: latest 标签,加速器同步延迟导致拉到的镜像是前天的版本。批量部署时 30 个 Pod 同时拉镜像,免费加速器的并发连接数被耗尽,直接返回 429。
| 加速器类型 | 同步延迟 | 并发限制 | SLA | 跨云可用 | 月费 |
|---|---|---|---|---|---|
| 免费个人加速器 | 数天 | 有(429 限流) | 无 | 仅阿里云内网 | 免费 |
| ACR 个人版 | 实时(ACR 内) | 有软限制 | 无 | 仅阿里云内网 | 免费 |
| ACR 企业版标准版 | 实时 | 无独立限制 | 99.9% | 支持跨域同步 | ~300 元 |
| ACR 企业版高级版 | 实时 | 无限制 | 99.95% | 全球同步 | ~800 元 |
修复方案
两步走:
第一步:CI/CD 全部使用精确版本标签,禁用 latest。在构建流水线中强制使用 Git commit SHA 或语义化版本号:
#!/bin/bash
# CI/CD 流水线镜像构建脚本
set -euo pipefail
IMAGE_REGISTRY="registry.cn-shenzhen.aliyuncs.com"
NAMESPACE="my-namespace"
APP_NAME="app"
IMAGE_TAG=$(git rev-parse --short HEAD)
# 构建并推送精确版本镜像
docker build -t ${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:${IMAGE_TAG} .
docker push ${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:${IMAGE_TAG}
# 同时推送一个稳定标签(方便回滚)
docker tag ${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:${IMAGE_TAG} \
${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:stable-${CI_PIPELINE_ID}
docker push ${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:stable-${CI_PIPELINE_ID}
# 输出镜像地址供部署步骤使用
echo "IMAGE=${IMAGE_REGISTRY}/${NAMESPACE}/${APP_NAME}:${IMAGE_TAG}" > deploy.env
第二步:升级到 ACR 企业版,配置制品订阅。ACR 企业版支持从 Docker Hub、GCR、k8s.io 自动同步镜像到内网私有仓库,延迟在分钟级。同时配置 Pod 的 imagePullPolicy: IfNotPresent 避免重复拉取:
# Pod 镜像拉取策略
spec:
containers:
- name: app
image: registry.cn-shenzhen.aliyuncs.com/my-namespace/app:a1b2c3d
imagePullPolicy: IfNotPresent # 本地有就不拉
imagePullSecrets:
- name: acr-credential # ACR 企业版认证 Secret
节点 containerd 镜像加速配置:
# /etc/containerd/config.toml 镜像加速配置
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://registry.cn-shenzhen.aliyuncs.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["https://registry.cn-shenzhen.aliyuncs.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
endpoint = ["https://registry.cn-shenzhen.aliyuncs.com"]
成本对比:ACR 企业版月费约 300 元(按实例规格不同),但省下的调试时间和故障停机成本远超这个数字。我统计过那次批量部署故障:30 个微服务 × 平均 2 分钟排查时间 × 3 人参与 = 3 小时人力浪费。一次故障就够买半年 ACR 企业版。
镜像预热策略(大规模集群推荐):
当集群节点超过 50 个时,批量部署时所有节点同时拉镜像会打爆镜像仓库。可以在部署前先在一批节点上预热镜像:
#!/bin/bash
# 镜像预热脚本——在所有节点上预先拉取镜像
IMAGE="registry.cn-shenzhen.aliyuncs.com/my-namespace/app:a1b2c3d"
NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}')
for NODE in $NODES; do
ssh root@$NODE "crictl pull $IMAGE" &
done
wait
echo "所有节点镜像预热完成"
预热完成后再执行部署,Pod 直接用本地缓存的镜像启动,不再拉取。100 节点集群部署时间从 8 分钟降到 2 分钟。
踩坑五:监控盲区——托管控制面的可观测性缺失
问题背景
自建集群时,我们的 Prometheus 直接抓取 kube-apiserver、etcd、kube-controller-manager 的 metrics。迁移到 ACK 托管集群后,控制面由阿里云托管,kube-apiserver 的地址是内网域名,etcd 直接不可达。原来那套监控告警全废了。
上线第一周出现了一个诡异问题:API 请求偶尔超时,但 Worker 节点资源水位正常,Pod 也没重启。查了半天才发现是控制面的 kube-apiserver 响应慢,但我们在托管集群上看不到控制面的任何指标——就像自建集群时你能看到 etcd 的写入延迟,现在你看不到了。
这个问题在 [相关文章:告警风暴治理](/posts/alerting-strategy-design-noise-to-signal/) 中提到的告警治理体系里是典型的"盲区告警"——你知道有问题但看不到根因。
根因分析
ACK 托管集群的控制面对用户不可见。你拿不到 kube-apiserver 的 /metrics 端点,也看不到 etcd 的状态。ACK Pro 版提供了控制面组件的可观测性增强,但默认不开——需要在组件管理中手动安装 ack-kubernetes-dashboard 或开启控制面监控。
自建集群时习以为常的这些监控指标,在托管集群上要么看不到,要么需要额外开启:
| 监控指标 | 自建集群 | ACK 托管集群 | 获取方式 |
|---|---|---|---|
| kube-apiserver QPS/延迟 | 直接抓取 /metrics | 需开启控制面监控 | ACK 控制台 → 组件管理 |
| etcd 状态(读写延迟) | 直接抓取 /metrics | Pro 版提供,基础版无 | CloudMonitor 自定义指标 |
| kube-scheduler 延迟 | 直接抓取 /metrics | 需开启控制面监控 | ACK 控制台 → 组件管理 |
| kube-controller-manager | 直接抓取 /metrics | 需开启控制面监控 | ACK 控制台 → 组件管理 |
| Worker 节点指标 | node_exporter | node_exporter | 和自建一样 |
| Pod 指标 | cAdvisor | cAdvisor | 和自建一样 |
关键差异:自建集群你能直接 curl http://kube-apiserver:6443/metrics 拿到原始 Prometheus 格式数据。托管集群只能通过 CloudMonitor 的 API 间接获取聚合后的指标,粒度和灵活性都差一截。
修复方案
第一步:在 ACK 控制台开启控制面监控。 进入集群详情 → 组件管理 → 安装 ack-kubernetes-dashboard 和 metrics-server。Pro 版集群还可以开启 etcd 监控指标,在 CloudMonitor 中查看。
第二步:配置 Prometheus 抓取控制面指标。 ACK 托管集群的控制面监控通过 CloudMonitor 暴露,需要配置 Prometheus 抓取 CloudMonitor 指标:
# Prometheus 抓取 ACK 控制面指标(通过 CloudMonitor exporter)
scrape_configs:
- job_name: 'ack-control-plane'
cloudmon_configs:
- region: 'cn-shenzhen'
metrics:
- 'acs_k8s_controlplane_ApiServerQPS' # API QPS
- 'acs_k8s_controlplane_ApiServerLatency' # API 延迟
- 'acs_k8s_controlplane_EtcdRequestLatency' # etcd 读写延迟
- 'acs_k8s_controlplane_SchedulerLatency' # 调度延迟
- 'acs_k8s_controlplane_ControllerManagerQueue' # 控制器队列深度
- job_name: 'ack-worker-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: '(.*):10250'
target_label: __address__
replacement: '${1}:9100' # node_exporter 端口
第三步:增加告警规则。 控制面的关键告警阈值:
# 控制面告警规则
groups:
- name: ack-control-plane-alerts
rules:
- alert: ApiServerHighLatency
expr: histogram_quantile(0.99, rate(acs_k8s_controlplane_ApiServerLatency_bucket[5m])) > 1
for: 5m
labels:
severity: critical
annotations:
summary: "kube-apiserver P99 延迟超过 1 秒"
description: "集群 {{ $labels.cluster }} 的 API Server P99 延迟为 {{ $value }}秒"
- alert: EtcdHighLatency
expr: histogram_quantile(0.99, rate(acs_k8s_controlplane_EtcdRequestLatency_bucket[5m])) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "etcd P99 延迟超过 100ms"
description: "etcd 读写延迟过高,可能导致集群不稳定"
- alert: SchedulerHighLatency
expr: histogram_quantile(0.99, rate(acs_k8s_controlplane_SchedulerLatency_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "kube-scheduler P99 延迟超过 5 秒"
description: "调度延迟过高,新 Pod 可能长时间处于 Pending 状态"
第四步:配置 Grafana Dashboard。 控制面监控的 Dashboard 应该包含以下面板:
| 面板 | 查询语句 | 告警阈值 |
|---|---|---|
| API Server QPS | rate(acs_k8s_controlplane_ApiServerQPS[1m]) | > 500 QPS 需关注 |
| API Server P99 延迟 | histogram_quantile(0.99, ...) | > 1s 告警 |
| etcd 读写延迟 | histogram_quantile(0.99, ...) | > 100ms 告警 |
| 调度延迟 | histogram_quantile(0.99, ...) | > 5s 告警 |
| 控制器队列深度 | acs_k8s_controlplane_ControllerManagerQueue | > 100 告警 |
| 节点就绪率 | kube_node_status_condition{condition="Ready"} == 1 | < 95% 告警 |
关键经验:托管不等于不监控。控制面虽然不由你运维,但出了问题第一个被叫醒的还是 SRE。在迁移完成前就把控制面监控配好,别等出问题再补。这个坑在 [相关文章:可靠性度量不是堆指标](/posts/sre-reliability-measurement-framework/) 中也提到了——可观测性缺失是 SRE 的最大盲区。
迁移决策框架:什么情况下选 ACK 托管集群
根据这次迁移经验,我总结了一个决策框架。不是所有场景都适合托管集群:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 100+ 节点大规模生产 | ACK Pro 托管 | etcd 冷热备+异地容灾,SLA 99.95% |
| 10 节点以下测试 | ACK 基础版或自建 | 基础版免费,10 节点够用 |
| 需要自定义控制面参数 | ACK 专有版(已停新建) | 控制面自己管,可调参 |
| 极致弹性、突发流量 | ACK Serverless | ECI 秒级启动,无需管理节点 |
| 混合云(IDC+云) | ACK One 注册集群 | 统一管理,云上弹性扩容 |
我明确的推荐:生产环境用 ACK Pro 托管集群。理由:
- etcd 的运维是自建 K8s 最大的风险点——节点磁盘满了、网络分区了、写入了——每一个都能让整个集群挂掉。托管后 etcd 由阿里云运维,冷热备+异地容灾,你只需要关注应用层。
- 控制面的 SLA 有赔付标准。自建集群 kube-apiserver 挂了只能自己扛,托管版至少有合同兜底。
- 但你必须接受一个前提:控制面的可观测性不如自建。你需要通过 CloudMonitor 指标而非直接抓取 metrics 来监控控制面——这是个思维转变,很多人不适应。
迁移前的 Checklist:
| 检查项 | 自建集群现状 | ACK 目标状态 | 迁移动作 |
|---|---|---|---|
| 网络插件 | Flannel/Cilico | Terway 共享 ENI | 评估 Pod IP 变更影响 |
| 存储方案 | Ceph/NFS | 云盘 CSI + NAS | StorageClass 改 WaitForFirstConsumer |
| 弹性伸缩 | ESS 脚本 | Cluster Autoscaler | 调整缩容参数 |
| 镜像仓库 | Harbor | ACR 企业版 | 配置制品订阅和镜像同步 |
| 监控系统 | Prometheus 直抓 | CloudMonitor + Prometheus | 重配控制面监控指标 |
| 告警规则 | 自定义 | CloudMonitor 告警 + Prometheus | 迁移告警规则和阈值 |
| CI/CD | Jenkins/自研 | ArgoCD/自研 | 镜像标签改精确版本 |
成本对比:自建 vs ACK 托管
迁移决策最终绕不开成本。我做了详细的成本对比:
| 成本项 | 自建 K8s(3 Master + 10 Worker) | ACK Pro 托管(10 Worker) | 差异 |
|---|---|---|---|
| Master 节点 | 3 × ecs.g7.2xlarge ≈ 2,700 元/月 | 0(托管) | 节省 2,700 元 |
| 集群管理费 | 0 | Pro 版 0.64 元/小时 × 730 ≈ 467 元/月 | 增加 467 元 |
| etcd 运维人力 | 0.5 人月 ≈ 8,000 元 | 0(托管) | 节省 8,000 元 |
| 控制面监控 | Prometheus 自建(已摊销) | CloudMonitor 自定义指标 ≈ 200 元/月 | 增加 200 元 |
| Worker 节点 | 10 × ecs.g7.xlarge ≈ 4,500 元/月 | 10 × ecs.g7.xlarge ≈ 4,500 元/月 | 无差异 |
| 月度总成本 | 15,200 元 | 5,167 元 | 节省 66% |
成本节省主要来自两个方面:省掉了 Master 节点的硬件成本,省掉了 etcd 运维的人力成本。集群管理费 467 元/月远低于自建 Master 的 2,700 元/月硬件费。人力成本节省更是大头——etcd 运维是自建 K8s 最费人力的部分。
但有一个隐性成本容易被忽略:迁移本身的成本。3 个月的迁移周期、2 人全职投入、灰度切流期间的额外资源消耗——这些一次性成本大约 5-8 万元。按月度节省 1 万元计算,6-8 个月可以收回迁移成本。
总结
迁移到 ACK 托管集群后,控制面确实不用管了,但数据面的坑一个不少。这 5 个坑的共性是:它们都来自自建集群和托管集群在"看不见的地方"的行为差异。自建时你的运维习惯掩盖了这些差异,到托管集群上就暴露了。
5 个坑的核心教训:
- 网络插件选 Terway 不选 Flannel——性能差 50%,不支持 NetworkPolicy,Pod IP 不在 VPC 网段。50 节点以上生产集群别犹豫。
- StorageClass 用 WaitForFirstConsumer——云盘是可用区级资源,延迟绑定确保 Pod 和云盘在同一可用区。这个改不了,只能预防。
- Cluster Autoscaler 参数要调——默认 10 分钟空闲判定太保守,调到 5 分钟能省 15% 弹性节点费用。但别低于 3 分钟,否则抖动。
- 镜像用精确版本标签+ACR 企业版——免费加速器有限流和同步延迟,CI/CD 批量部署时必崩。一次故障的人力成本够买半年企业版。
- 控制面监控迁移前就配好——托管不等于不监控。kube-apiserver 延迟、etcd 读写延迟这些指标在 ACK Pro 版通过 CloudMonitor 暴露,但默认不开。
最后说一个更大的教训:迁移到托管集群不是终点,而是新的起点。托管解决的是"控制面可用性"问题,但引入了"可观测性下降"的新问题。好的 SRE 不是不犯错,而是在迁移前就把可能踩的坑想清楚,提前做好预案。我在那次迁移中犯的最大的错误就是——以为托管意味着可以少配一套监控。实际上,托管的监控配置方式变了,但监控本身不能少。
如果你正在做类似迁移,建议把这篇文章的 5 个坑逐条对照检查。不是说每个都会遇到,但提前知道有这些坑,出了问题至少知道往哪个方向排查——而不是凌晨三点在 Google 上搜"ACK Pod Pending volume node affinity conflict"。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- 阿里云 ACK 网络插件文档 — 阿里云官方,Terway 与 Flannel 网络插件特性对比及配置说明
- 2026年阿里云ACK托管集群深度解析 — 知乎,ACK 托管集群 Pro 版与基础版的功能差异及网络存储实战
- 云盘存储卷常见问题 — 阿里云官方,CSI 云盘存储卷跨可用区调度失败的原因和解决方案
- Cilium 首次集成国内云服务,阿里云 ENI 被纳入新版本特性 — CSDN,Terway IPvlan 模式与 Cilium eBPF 的性能对比测试数据
- Cluster Autoscaler 社区案例:大型生产环境应用经验 — CSDN,CA 多可用区节点组平衡和缩容参数调优实践
- 阿里云云原生弹性方案 — 知乎,ACK 弹性伸缩体系架构和 HPA/CronHPA 配置指南
- 阿里云 Docker 镜像加速器完整评测 — CSDN,2026 年镜像加速器使用限制变更和 ACR 企业版制品订阅方案
- Pod异常问题排查SOP — 阿里云官方,ACK 集群 Pod 异常状态排查方法论
- 相关文章:别被多云忽悠了:从供应商绑架到跨云容灾的架构决策与踩坑实录 — 多云架构决策和供应商锁定规避经验
- 相关文章:K8s 日志采集方案:DaemonSet、Sidecar 与 Agentless 模式的选型与实战 — K8s 日志采集方案在 ACK 上的选型实践
- 相关文章:可靠性度量不是堆指标:用四层模型把"系统稳不稳"变成可决策的数字 — 可观测性体系建设和监控盲区识别方法