一、为什么不是自建 K8s,而是 ACK
前两篇文章我们用 kind 和 minikube 在本地跑通了 K8s,也用 kubectl apply 部署了 Spring Boot 应用。但真正上生产,你面临一个选择:自建 K8s 还是用云厂商托管?
我先把结论放这儿:除非你有特殊合规要求或极端成本敏感,否则选托管。
| 维度 | 自建 K8s | ACK 托管版 |
|---|---|---|
| Master 节点 | 自己搭 3 台 etcd + kube-apiserver,挂了自己修 | 阿里云托管,SLA 99.95% |
| 版本升级 | 自己 drain、升级、回滚,一个周末没了 | 控制台一键升级,自动兼容检查 |
| 证书轮换 | 手动生成 kubelet 证书,过期了集群直接挂 | 自动轮换,无感知 |
| 安全补丁 | 自己跟踪 CVE,手动打补丁 | 云安全中心自动告警+修复建议 |
| 运维人力 | 至少 2 个专职 SRE | 1 个兼职 DevOps 搞定 |
自建 K8s 的隐藏成本极高。一个 3 Master 的集群,光是 etcd 备份策略、证书过期监控、apiserver 调优这些活儿,就能吃掉一个 SRE 大半精力。ACK 托管版把这些全包了,你只管 Worker 节点。
ACK 目前有三种形态:
-
ACK 托管版 (Pro/Pro 版):Master 免费托管,按 Worker 节点付费。绝大多数团队选这个。
-
ACK Serverless:连 Worker 都不用管,按 Pod 付费。适合突发流量场景,冷启动稍慢。
-
ACK 专有版:Master 也自己管,但用阿里云的 IaaS。极端合规需求才用。
下面以 ACK Pro 托管版为主线,讲生产级集群的落地流程。
二、集群创建:那些控制台不会告诉你的坑
2.1 网络规划------最容易被忽略的前置工作
创建集群前,先规划 VPC 和交换机。这是最容易被忽略的一步,但一旦搞错,后期迁移成本极高。
bash
# 用 aliyun CLI 创建 VPC 和交换机(也可在控制台操作)
# 创建 VPC
aliyun vpc CreateVpc \
--RegionId cn-hangzhou \
--CidrBlock 10.0.0.0/8 \
--VpcName k8s-prod-vpc
# 拿到 VpcId 后,创建两个交换机(跨可用区高可用)
aliyun vpc CreateVSwitch \
--RegionId cn-hangzhou \
--ZoneId cn-hangzhou-h \
--CidrBlock 10.0.1.0/24 \
--VpcId vpc-xxx \
--VSwitchName k8s-prod-sw-h
aliyun vpc CreateVSwitch \
--RegionId cn-hangzhou \
--ZoneId cn-hangzhou-i \
--CidrBlock 10.0.2.0/24 \
--VpcId vpc-xxx \
--VSwitchName k8s-prod-sw-i
三条铁律:
-
Pod 网段和 Service 网段不能跟 VPC 网段重叠。ACK 默认 Pod 用
192.168.0.0/16,Service 用172.21.0.0/20,如果跟你的 VPC 冲突,一定要改。 -
至少两个可用区的交换机。单可用区一旦机房故障,整个集群直接没了。
-
交换机网段留足余量。一个
/24只有 254 个 IP,中型集群很快耗尽,建议/20起步。
2.2 集群参数配置
创建集群时,有几个参数必须按生产标准配:
bash
# ACK 集群创建关键参数(控制台 / Terraform 均可配置)
# 这里用 Terraform 示例,方便版本化管理
resource "alicloud_cs_managed_kubernetes" "prod" {
name = "k8s-prod"
cluster_spec = "ack.pro.small" # Pro 版,控制面规格
worker_vswitch_ids = ["vsw-h", "vsw-i"] # 双可用区
pod_cidr = "192.168.0.0/16"
service_cidr = "172.21.0.0/20"
new_nat_gateway = true # 自动创建 NAT 网关,拉镜像要用
proxy_mode = "ipvs" # 生产用 IPVS,不用 iptables
delete_options = ["delete-resourcegroups"] # 删集群时清理关联资源
kube_config_path = "~/.kube/config-prod"
# 安全组配置
security_group_id = alicloud_security_group.k8s_sg.id
# 日志服务集成(SLS)
log_config {
type = "SLS"
project = alicloud_log_project.k8s_log.name
}
# 监控插件
addons {
name = "arms-prometheus"
config = jsonencode({ enable = true })
}
}
几个关键点:
-
proxy_mode: ipvs:iptables 模式在节点 Pod 数超过 1000 后性能急剧下降。IPVS 用哈希表查找,O(1) 复杂度,生产集群必须切。 -
log_config:创建时就关联 SLS,后面再补配置很麻烦。 -
arms-prometheus:ARMS 监控插件在集群创建时就装好,省去后期手动安装的麻烦。
2.3 存储插件选择
ACK 默认安装 CSI 插件。如果你要用云盘做持久化存储,确保 StorageClass 配置正确:
bash
# storageclass-disk.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-essd
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd # ESSD 云盘,性能最好
performanceLevel: PL1 # PL1/PL2/PL3,按 IOPS 需求选
fsType: ext4
reclaimPolicy: Retain # 生产用 Retain,别用 Delete
volumeBindingMode: WaitForFirstConsumer # 延迟绑定,等 Pod 调度到节点再建盘
allowVolumeExpansion: true # 允许在线扩容
血泪经验 :
reclaimPolicy一定要设Retain。默认是Delete,PVC 删了云盘跟着删,数据直接蒸发。我见过好几个团队在这上面翻车。
三、节点池管理:弹性伸缩的正确姿势
3.1 节点池概念
节点池(NodePool)是 ACK 对一组相同配置的 Worker 节点的抽象。你可以按业务维度划分:
-
系统节点池:跑 kube-system、日志 Agent、监控 Agent 等基础设施 Pod。用稳定机型,不弹。
-
业务节点池:跑你的业务应用。用弹性伸缩,按需扩缩。
-
AI 节点池:带 GPU 的节点池,专门跑推理服务。贵,但可以按需弹。
bash
# 节点池配置示例(通过 ACK 控制台或 OpenAPI 创建)
# 这里展示一个业务节点池的 Terraform 配置
resource "alicloud_cs_kubernetes_node_pool" "business" {
cluster_id = alicloud_cs_managed_kubernetes.prod.id
name = "business-pool"
vswitch_ids = ["vsw-h", "vsw-i"]
instance_types = ["ecs.g7.xlarge"] # 4C16G 通用型
instance_charge_type = "PostPaid" # 按量付费,弹性场景
desired_size = 3 # 期望节点数
min_size = 2 # 最小节点数
max_size = 10 # 最大节点数
# 弹性伸缩配置
scaling_policy {
type = "proactive" # 主动预测型伸缩
proactive {
scale_down_debounce = 300 # 缩容冷却 5 分钟,防止抖动
}
}
# 节点标签和污点
labels = {
"node-pool" = "business"
"workload" = "java-service"
}
taints {
key = "dedicated"
value = "business"
effect = "NoSchedule" # 只允许声明 toleration 的 Pod 调度上来
}
# 系统盘配置
system_disk_category = "cloud_essd"
system_disk_size = 120
# 登录密钥
key_name = "k8s-prod-key"
}
3.2 弹性伸缩的核心原则
原则一:扩容快,缩容慢。
缩容太快会导致正在处理的请求被中断。scale_down_debounce 设 5~10 分钟,让系统有缓冲。扩容则要快------ACK 的弹性节点池从触发到节点 Ready 通常需要 1~2 分钟,对于突发流量这已经很慢了。应对方案是预留 Buffer:HPA 的目标 CPU 设 60% 而不是 80%,给扩容留反应时间。
原则二:别让系统 Pod 和业务 Pod 抢资源。
bash
# 给系统 Pod 加亲和性,只调度到系统节点池
# nginx-ingress-controller 的 deployment 示例
spec:
template:
spec:
nodeSelector:
node-pool: system # 只跑在系统节点池上
tolerations:
- key: "dedicated"
value: "system"
effect: "NoSchedule"
containers:
- name: ingress
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
系统组件不设 resources limits 是另一个大坑。日志 Agent、监控 Agent 如果不限制资源,在流量高峰时会跟业务 Pod 抢 CPU,导致业务响应变慢。
原则三:GPU 节点池单独管。
AI 推理服务需要 GPU,而 GPU 实例贵得多(一台 ecs.gn7i-c8g1.2xlarge 带一张 A10,按量约 15 元/小时)。GPU 节点池建议:
-
min_size: 0,没任务时缩到 0 节省成本 -
用
cluster-autoscaler+ 自定义扩容策略 -
GPU Pod 加
nvidia.com/gpu资源请求,确保调度到 GPU 节点
四、日志采集:SLS 让 Pod 日志自动汇聚
4.1 SLS 日志服务集成
ACK 集群创建时如果关联了 SLS,会自动在每个节点安装 Logtail Agent。Logtail 以 DaemonSet 形式运行,采集节点上所有 Pod 的 stdout/stderr 日志,发到 SLS。
但默认配置只是「能用」,离「好用」还有几步。
4.2 为 Spring Boot 应用配置结构化日志
第一步,让应用输出 JSON 格式日志。Logback 配置:
java
<!-- logback-spring.xml -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>
{"app":"ai-service","env":"prod","version":"${project.version}"}</customFields>
<fieldNames>
<timestamp>@timestamp</timestamp>
<version>[ignore]</version>
<levelValue>[ignore]</levelValue>
</fieldNames>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
用 JSON 日志的好处:SLS 可以直接做字段索引,查询时不用写正则提取。例如查所有 ERROR 级别且包含特定 traceId 的日志,直接
level: ERROR AND traceId: abc123。
第二步,在 SLS 侧创建 Logstore 和采集配置:
java
// SLS 采集配置(通过 API 或控制台配置)
{
"configName": "ai-service-stdout",
"inputType": "container_stdout",
"inputDetail": {
"Stdout": {
"IncludeLabel": {
"app": "ai-service" // 只采集带这个 label 的 Pod
},
"ExcludeLabel": {}
}
},
"outputType": "LogService",
"outputDetail": {
"logstoreName": "ai-service-log"
}
}
4.3 日志查询与告警
日志进 SLS 后,可以配告警规则。比如「5 分钟内 ERROR 日志超过 50 条就告警」:
sql
-- SLS 告警 SQL
* and level: ERROR |
SELECT
count(*) as error_count,
max(@timestamp) as last_error_time
FROM ai-service-log
WHERE __time__ > to_unixtime(now() - 300)
GROUP BY app
HAVING error_count > 50
配告警通知到钉钉机器人:
java
// 告警通知配置
{
"actionName": "钉钉告警",
"type": "dingding",
"service": {
"webhook": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
"content": "告警:${results[0].app} ERROR 日志 ${results[0].error_count} 条,请检查"
}
}
五、监控告警:ARMS 实现 JVM 级别可观测
5.1 ARMS 是什么
ARMS(Application Real-Time Monitoring Service)是阿里云的应用监控服务。跟 Prometheus + Grafana 自己搭相比,ARMS 的优势是零侵入------你不用改代码,不用加 JMX exporter,ACK 会自动给 Java 应用注入探针。
在 ACK 控制台给命名空间开启 ARMS 监控后,所有新启动的 Java Pod 会自动被注入 ARMS Agent(通过 init-container + JVM -javaagent 参数实现)。
5.2 关键监控指标
ARMS 采集到的 JVM 级别指标包括:
| 指标 | 含义 | 告警阈值建议 |
|---|---|---|
jvm_thread_count |
JVM 线程数 | > 500 告警 |
jvm_heap_used |
堆内存使用量 | > 85% 持续 3 分钟告警 |
jvm_gc_count |
GC 次数 | Full GC > 5 次/分钟告警 |
jvm_gc_time |
GC 耗时 | 单次 GC > 500ms 告警 |
http_request_count |
HTTP 请求量 | 按业务基线配 |
http_response_time |
HTTP 响应时间 | P99 > 2s 告警 |
http_error_count |
HTTP 5xx | > 10 次/分钟告警 |
5.3 配置自定义业务指标
ARMS 支持通过 @ArmsMethod 注解或 Micrometer 上报自定义指标。如果你的 Spring Boot 已经用了 Micrometer,直接配 ARMS 的 Prometheus Remote Write:
java
# application.yml --- Micrometer 对接 ARMS
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
metrics:
tags:
application: ai-service
env: prod
export:
prometheus:
enabled: true
# ARMS 会自动抓取 /actuator/prometheus 端点
自定义业务指标示例------统计 AI 调用的 Token 消耗:
java
// AI 调用 Token 计量器
@Component
public class AiUsageMetrics {
private final MeterRegistry meterRegistry;
private final Counter inputTokenCounter;
private final Counter outputTokenCounter;
private final Counter requestCounter;
public AiUsageMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.inputTokenCounter = Counter.builder("ai_tokens_total")
.tag("type", "input")
.tag("model", "qwen-turbo")
.description("AI input tokens consumed")
.register(meterRegistry);
this.outputTokenCounter = Counter.builder("ai_tokens_total")
.tag("type", "output")
.tag("model", "qwen-turbo")
.description("AI output tokens consumed")
.register(meterRegistry);
this.requestCounter = Counter.builder("ai_requests_total")
.tag("model", "qwen-turbo")
.description("Total AI API requests")
.register(meterRegistry);
}
public void recordUsage(int inputTokens, int outputTokens) {
// Counter 只增不减,适合累计统计
inputTokenCounter.increment(inputTokens);
outputTokenCounter.increment(outputTokens);
requestCounter.increment();
}
}
这段代码执行后,ARMS 的 Prometheus 端点会暴露三个时序指标:ai_tokens_total{type="input",model="qwen-turbo"}、ai_tokens_total{type="output",model="qwen-turbo"} 和 ai_requests_total{model="qwen-turbo"}。在 ARMS 控制台可以直接用 PromQL 查询并配告警。
5.4 告警最佳实践
生产环境的告警要遵循「少而准」原则。我见过太多团队配了上百条告警,结果每天收到几十条通知,最后全部静音------告警失去了意义。
建议只配三类告警:
-
可用性告警:Pod 重启次数 > 3 次/5分钟、HPA 扩到上限、节点 NotReady
-
性能告警:HTTP P99 > 2s、Full GC 频繁、堆内存 > 85%
-
业务告警:AI 调用 5xx 率 > 5%、Token 消耗异常飙升(可能被刷)
每条告警必须配可执行的 Runbook------告警里带上排查链接,告诉值班人第一步干什么、第二步干什么。没 Runbook 的告警等于没告警。
六、三条建议
1. 集群创建用 Terraform 管理,别在控制台点。
控制台创建的集群没有版本记录,出问题时无法回溯配置。把 VPC、交换机、ACK 集群、节点池、SLS、ARMS 全部写成 Terraform 代码,放 Git 仓库。改配置走 PR Review,有审计有回滚。
2. 日志和监控在集群创建时就配好,别等上线后补。
见过太多团队先上线业务、后补日志和监控,结果第一周出问题完全是裸奔状态。log_config 和 arms-prometheus 在创建集群时就带上,应用一部署日志就在 SLS 里了,监控大盘自动生成。
3. GPU 节点池的 min_size 设为 0,用模型预热池解决冷启动。
AI 推理服务用 GPU 节点池,空闲时缩到 0 是省钱利器。但冷启动 1~2 分钟的延迟可能让用户体验很差。解法是:部署一个轻量的「预热 Pod」始终占用 1 个 GPU(用 Deployment replicas=1),真正的推理 Pod 按需弹。预热 Pod 只加载模型到显存,不接流量,等新节点起来后推理 Pod 能快速调度上去。
自建 K8s 是技术能力的体现,但生产环境从不为「炫技」买单------稳定、可观测、可回滚,才是运维的唯一标准。
下篇预告
Day 60:Serverless 开发入门------函数计算改写传统 Spring Boot。 K8s 是当前主流,但 Serverless 正在蚕食低流量场景。下一篇我们看阿里云函数计算(FC)怎么跑 Spring Boot,冷启动到底有多慢,以及什么场景该上 Serverless。