定时扩缩容不是简单地到点执行一条命令,它涉及调度器、权限、目标环境和回滚策略。以Cron为触发核心,可以把节点伸缩动作做成可重复执行的自动化流程。下面先拆解Cron表达式,再分别演示Kubernetes CronJob和云API两种落地方式,最后讨论失败处理与容量保障。

一、Cron 表达式如何驱动扩缩容任务
Cron表达式通常由五个字段组成,分别表示分钟、小时、日、月、星期。某些平台支持秒级字段,会扩展成六个或七个字段。例如0 8 * * 1-5表示每周一到周五早上8点执行,30 23 * * *表示每天晚上23点30分执行。扩缩容任务必须挂在这样的时间规则下,才能实现上班前扩容、下班后缩容。
使用Cron最容易忽略的是时区。Kubernetes CronJob默认使用控制平面所在时区,通常是UTC;云函数或Linux crontab则跟随服务器本地时区。如果业务在北京时间,而调度器按UTC执行,就会出现差8小时的问题。建议在任务定义中显式声明时区,或者把目标时间换算成UTC写入表达式,避免早晨高峰节点还没起来。
另一个关键点是Cron只负责触发,不负责幂等。如果上一次任务还在运行,下一次又触发,可能产生并发扩缩容。Kubernetes CronJob可以通过concurrencyPolicy限制并发,云函数平台一般也有重试和并发控制选项。设置合理的并发策略能防止两个缩容任务同时把副本数改到不安全的低位。
二、基于 Kubernetes CronJob 实现副本扩缩容
如果集群本身运行在Kubernetes上,最直接的方式是创建CronJob,在Job里执行kubectl scale命令。假设有一个无状态服务叫api-server,部署在default命名空间,白天需要6个副本,夜间只需2个副本。可以分别创建两个CronJob:一个在早上8点扩容,一个在晚上23点缩容。
下面是一份扩容CronJob的配置示例。注意命令里使用了kubectl,这要求运行Job的容器有kubectl二进制,并且ServiceAccount具备修改deployments/scale的权限。
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-up-api-server
spec:
schedule: "0 0 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: scaler-sa
restartPolicy: OnFailure
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
kubectl scale deployment api-server --replicas=6 -n default
这里schedule写的是0 0 * * *,即每天UTC零点执行,等价于北京时间早上8点。如果控制平面时区不是UTC,需要确认后调整。concurrencyPolicy设为Forbid可以避免任务重叠。缩容任务把replicas改成2即可,但时间要放在业务低谷。
这种方式的优点是实现简单,完全使用Kubernetes原生能力,不需要外部组件。缺点是不适合管理节点池级别的伸缩,因为kubectl scale只能修改工作负载副本数,无法直接调整云主机节点。如果需要增减真正的计算节点,还要配合cluster-autoscaler或云API。
三、调用云厂商 API 完成节点池伸缩
托管Kubernetes集群或云上虚拟机集群通常由云厂商管理节点池。定时扩缩容要调整的是节点池的节点数量,而不是Deployment副本数。此时可以让云函数或本地调度器定时调用云API,例如阿里云的ModifyNodePool、腾讯云的ModifyClusterNodePool、AWS的UpdateAutoScalingGroup等。
以Python脚本调用腾讯云容器服务为例,核心流程是获取当前节点池配置、设置目标节点数、处理返回结果。下面是一个简化示例,实际使用时需要配置SecretId和SecretKey。
import os
from tencentcloud.common import credential
from tencentcloud.tke.v20180525 import tke_client, models
cred = credential.Credential(
os.environ.get("TENCENTCLOUD_SECRET_ID"),
os.environ.get("TENCENTCLOUD_SECRET_KEY")
)
client = tke_client.TkeClient(cred, "ap-guangzhou")
req = models.ModifyClusterNodePoolRequest()
req.ClusterId = "cls-xxxxxxxx"
req.NodePoolId = "np-xxxxxxxx"
req.DesiredNodesNum = 10
req.MinNodesNum = 2
req.MaxNodesNum = 20
resp = client.ModifyClusterNodePool(req)
print(resp.to_json_string())
这段脚本可以直接部署在云函数中,用定时触发器绑定Cron表达式。云函数平台会按时区执行,并且支持失败重试。相比把脚本放在某台常驻服务器上,云函数不用维护运行环境,也不用担心服务器本身宕机导致任务丢失。
调用云API时建议把目标节点数参数化,不要把扩缩容目标写死在代码里。可以通过环境变量或事件参数传入本次期望的节点数,这样同一份脚本可以同时服务扩容和缩容任务。还需要对API限流和网络超时做重试,避免因为一次瞬时失败就放弃整轮调度。
四、扩缩容策略与避坑要点
定时扩缩容最怕的是时间判断失误。比如节假日流量规律与工作日不同,固定Cron表达式仍然会执行缩容,可能造成线上容量不足。可以把节假日判断前置到脚本里,先查询业务日历再决定是否执行缩容,或者临时暂停调度任务。
冷却时间是另一个容易忽略的细节。节点池扩容后需要几分钟才能完成初始化并加入集群,如果紧接着又触发缩容,可能把刚加入的节点移除。建议在缩容逻辑中增加最小稳定时间判断,或者让云厂商的弹性伸缩组负责冷却,Cron任务只修改期望容量,不直接操作节点。
监控和告警应当与定时任务联动。每次扩缩容结束后记录实际节点数、耗时和错误码,通过日志或消息推送通知运维人员。如果某次缩容失败但未及时发现,第二天高峰到来时业务可能出现延迟升高。可以设置一个独立的心跳任务,在扩缩容动作完成后检查集群节点是否达到预期,如果偏差超过阈值就触发告警。
最后要避免配置漂移。手动调整过副本数或节点池后,Cron任务可能会覆盖人工变更。建议把扩缩容目标值集中存储在配置中心或Git仓库,每次执行前读取最新配置,而不是依赖上次运行结果。这样即使人工介入过,也能通过配置版本控制追踪变化,降低误操作风险。