ESA部署Pages时的限额问题
ESA部署Pages时的限额问题
云梦镜像博客(该站点)部署在阿里云的边缘安全加速 ESA,本文主要解决的是ESA的Pages限额问题。
问题介绍
在ESA的构建中,每个函数和Pages只允许同时存在5个版本,当用户试图更新第六个时,就会显示构建失败,具体提示如下:
...
...... Create version failed: er_center create routine with assets code version error: code version number exceeds the quota limit, requestId: 7fa8efd9-4136-4c40-b5a5-57fa7d0a197b
其原因是由于阿里云ESA的函数计算与Pages服务是依赖OSS和FC(函数计算)迁移改造来的,在原始的FC中,金丝雀更新允许用户利用百分比覆盖模式进行验证,因此需要多版本切换,但所有版本均需要保存,其成本过高,因此需要5个版本限额避免免费用户滥用。
解决思路
本质上的解决思路是,由于ESA的Pages只支持Github,因此可以利用阿里云的OpenAPI功能和Github Actions功能来提前清空版本,代价是只允许最新版本在部署。
采用的最终策略是极简的直接推送模式:
- 直接在main分支上开发和提交 - 无需dev分支
- 每次推送main分支时自动清理ESA旧版本
- ESA检测到main分支变化后自动触发部署构建
策略优势:
- 极度简化工作流程,无需管理分支和PR
- 每次推送后立即清理版本并部署
- 完全自动化,无需人工干预
- 避免了分支管理带来的复杂性和错误
体验下来的总体效果便是,每次推送main分支,ESA的版本都会被自动清理,清理完成后ESA立即开始部署,不会遇到限额问题。
背景信息
1. Pages的API
- ListUserRoutines
阿里云的ESA每个Pages被命名为Routine,可以通过查询用户Routine列表这个API获取到所有的Pages实例。
相应如下:
{
"Routines": [
{
"RoutineName": "blog",
"Description": "",
"DefaultRelatedRecord": "xxxxx.aliyun-esa.net",
"CreateTime": "2025-12-24T07:48:05Z",
"HasAssets": true
}
],
"TotalCount": 1,
"UsedRoutineNumber": 1,
"RequestId": "D8772F14-5186-5C37-BDCA-CA11B0E057BF",
"PageSize": 20,
"QuotaRoutineNumber": 20,
"PageNumber": 1
}
Routines里面的RoutineName便是唯一标识符,也是Pages的名字。
- ListRoutineCodeVersions
每个版本被命名为CodeVersions在查询Routine代码版本列表这个API可以看到Pages的所有版本
相应如下:
{
"TotalCount": 5,
"RequestId": "AEDEF687-E668-5642-A36B-3BA3A6098CDF",
"PageSize": 20,
"PageNumber": 1,
"CodeVersions": [
{
"Status": "Available",
"CodeDescription": "",
"BuildId": <BuildId>,
"CreateTime": "2025-12-24T15:27:02Z",
"CodeVersion": "<CodeVersion>",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"<CommitId>\",\"CommitMessage\":\"docs(learning): add database, middleware, and microservices components\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"ConfOptions": {}
},
...
]
}
当这个CodeVersions数量超过5个时便会触发限额。
- GetRoutine
而通过查询边缘函数配置这个API可以看到当前的部署信息。
响应体如下:
{
"Description": "",
"RequestId": "BC7D8B1B-3CC4-55F7-9FA2-FB139EAF926E",
"DefaultRelatedRecord": "blog.ebc32e28.er.aliyun-esa.net",
"CreateTime": "2025-12-24T07:48:05Z",
"Envs": [
{
"CodeVersion": "1766590022381161014",
"CodeDeploy": {
"DeployId": "1766590027913271190",
"CreationTime": "2025-12-24T15:27:07Z",
"CodeVersions": [
{
"Description": "",
"Percentage": 100,
"CreateTime": "2025-12-24T15:27:02Z",
"CodeVersion": "1766590022381161014"
}
],
"Strategy": "percentage"
},
"Env": "production",
"CanaryCodeVersion": "1766590022381161014"
},
{
"CodeVersion": "1766590022381161014",
"CodeDeploy": {
"DeployId": "1766590027519628121",
"CreationTime": "2025-12-24T15:27:07Z",
"CodeVersions": [
{
"Description": "",
"Percentage": 100,
"CreateTime": "2025-12-24T15:27:02Z",
"CodeVersion": "1766590022381161014"
}
],
"Strategy": "percentage"
},
"Env": "staging",
"CanaryCodeVersion": "1766590022381161014"
}
],
"HasAssets": true
}
能够看到Envs字段有两个对象,Env分别是staging和production,由于没有设定AB测试或金丝雀策略,因此他们尽管都使用percentage策略,但都是100%的覆盖率。
- DeleteRoutineCodeVersion
而删除某个版本的API是删除边缘函数版本代码,在这个API中,填入Name和CodeVersion两个字段便可以删除这个版本。
当响应体是OK时便删除成功。
{
"Status": "OK",
"RequestId": "D7479AAC-D01E-5FAD-A403-4E5A4C6D5526"
}
此时不满足5个版本时,再次进行构建便可以构建成功。
2. RAM策略
新增一个用户,给这个用户授权AliyunESAFullAccess
3. 阿里云CLI
阿里云CLI是阿里云调用API的一个Go语言编写的二进制程序。
下载:
/bin/bash -c "$(curl -fsSL https://aliyuncli.alicdn.com/install.sh)"
配置:
aliyun configure set \
--profile AkProfile \
--mode AK \
--access-key-id <ID> \
--access-key-secret <Secret> \
--region "cn-hangzhou"
获取GetRoutineAPI信息命令
aliyun esa GetRoutine --region cn-hangzhou --Name blog
blog为你的目标Pages名称,可从配置信息中读取。响应如下
root@phil616-home-server:~# aliyun esa GetRoutine --region cn-hangzhou --Name blog
{
"CreateTime": "2025-12-24T07:48:05Z",
"DefaultRelatedRecord": "blog.ebc32e28.er.aliyun-esa.net",
"Description": "",
"Envs": [
{
"CanaryCodeVersion": "1766591259026072336",
"CodeDeploy": {
"CodeVersions": [
{
"CodeVersion": "1766591259026072336",
"CreateTime": "2025-12-24T15:47:39Z",
"Description": "",
"Percentage": 100
}
],
"CreationTime": "2025-12-24T15:47:44Z",
"DeployId": "1766591264631481854",
"Strategy": "percentage"
},
"CodeVersion": "1766591259026072336",
"Env": "production"
},
{
"CanaryCodeVersion": "1766591259026072336",
"CodeDeploy": {
"CodeVersions": [
{
"CodeVersion": "1766591259026072336",
"CreateTime": "2025-12-24T15:47:39Z",
"Description": "",
"Percentage": 100
}
],
"CreationTime": "2025-12-24T15:47:44Z",
"DeployId": "1766591264258280327",
"Strategy": "percentage"
},
"CodeVersion": "1766591259026072336",
"Env": "staging"
}
],
"HasAssets": true,
"RequestId": "5950C920-FDA4-5278-AC33-3681EC9F0A63"
}
已知1766591259026072336是当前的构建信息,需要删除额外的除1766591259026072336的其他构建版本。
获取所有版本命令:
aliyun esa ListRoutineCodeVersions --region cn-hangzhou --Name blog
blog为你的目标Pages名称,可从配置信息中读取。响应如下
root@phil616-home-server:~# aliyun esa ListRoutineCodeVersions --region cn-hangzhou --Name blog
{
"CodeVersions": [
{
"BuildId": 4210235297703936,
"CodeDescription": "",
"CodeVersion": "1766591259026072336",
"ConfOptions": {},
"CreateTime": "2025-12-24T15:47:39Z",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"9ac7b7174260a6b4a088f72f8fec279babf95956\",\"CommitMessage\":\"docs(learning): add database, middleware, and microservices components\\n\\n\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"Status": "Available"
},
{
"BuildId": 4210154238322752,
"CodeDescription": "",
"CodeVersion": "1766590022381161014",
"ConfOptions": {},
"CreateTime": "2025-12-24T15:27:02Z",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"9ac7b7174260a6b4a088f72f8fec279babf95956\",\"CommitMessage\":\"docs(learning): add database, middleware, and microservices components\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"Status": "Available"
},
{
"BuildId": 4210149166885056,
"CodeDescription": "",
"CodeVersion": "1766589951252631893",
"ConfOptions": {},
"CreateTime": "2025-12-24T15:25:51Z",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"9ac7b7174260a6b4a088f72f8fec279babf95956\",\"CommitMessage\":\"docs(learning): add database, middleware, and microservices components\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"Status": "Available"
},
{
"BuildId": 4210146728749056,
"CodeDescription": "",
"CodeVersion": "1766589907904250563",
"ConfOptions": {},
"CreateTime": "2025-12-24T15:25:07Z",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"9ac7b7174260a6b4a088f72f8fec279babf95956\",\"CommitMessage\":\"docs(learning): add database, middleware, and microservices components\\n\\n\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"Status": "Available"
},
{
"BuildId": 4210026067732672,
"CodeDescription": "",
"CodeVersion": "1766588070246841790",
"ConfOptions": {},
"CreateTime": "2025-12-24T14:54:30Z",
"ExtraInfo": "{\"Source\":\"Github\",\"Branch\":\"main\",\"CommitId\":\"551df6e76a5dbe857d16954e271cd0314843ed85\",\"CommitMessage\":\"docs(readme): add Chinese notes about article ordering and formatter\\n\\n\",\"LoginName\":\"dreamreflex\",\"Repository\":\"blog\"}",
"Status": "Available"
}
],
"PageNumber": 1,
"PageSize": 20,
"RequestId": "3FCBB4EB-DC7D-553A-B3B8-A5C564F391C0",
"TotalCount": 5
}
删除其余几个命令:
aliyun esa DeleteRoutineCodeVersion --region cn-hangzhou --Name blog --CodeVersion 1766588070246841790
aliyun esa DeleteRoutineCodeVersion --region cn-hangzhou --Name blog --CodeVersion 1766589907904250563
...
相应如下:
root@phil616-home-server:~# aliyun esa DeleteRoutineCodeVersion --region cn-hangzhou --Name blog --CodeVersion 1766588070246841790
{
"RequestId": "EB8E7788-6B29-5FD2-931F-1E20CD09C457",
"Status": "OK"
}
完成后ESA会自动检测main分支的变化并启动部署流程。
构建步骤
1. Github Action
采用极简的直接推送策略:
每次推送main分支时,自动触发清理workflow:
- 获取当前ESA的所有版本信息
- 根据创建时间排序,保留最新的n个版本(可配置,默认1个)
- 删除多余的旧版本
- ESA自动检测main分支变化并触发部署
核心优势:
- 推送即部署,无需等待
- 自动清理版本,避免限额问题
- 完全无需手动管理分支或PR
1.1 清理配置文件 (clean-esa-main.yml)
name: Clean ESA Versions on Main
on:
push:
branches: [ main ]
permissions:
contents: read
jobs:
clean-esa-versions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 下载阿里云CLI https://help.aliyun.com/zh/cli/
- name: Download Aliyun Cli
run: |
set -euo pipefail
/bin/bash -c "$(curl -fsSL https://aliyuncli.alicdn.com/install.sh)"
# 配置阿里云CLI
- name: Configure Aliyun CLI
run: |
aliyun configure set \
--profile AkProfile \
--mode AK \
--access-key-id ${{ secrets.ALIYUN_ACCESS_KEY_ID }} \
--access-key-secret ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} \
--region "cn-hangzhou"
# 获取Pages名称(可配置)
- name: Get Pages Name
id: get-pages-name
run: |
# 从配置中读取Pages名称,这里使用默认值blog,可根据需要修改
echo "pages_name=blog" >> $GITHUB_OUTPUT
# 获取所有代码版本并清理旧版本
- name: Clean ESA Code Versions
env:
PAGES_NAME: ${{ steps.get-pages-name.outputs.pages_name }}
# 配置保留的版本数,默认为1(只保留最新版本)
RETAIN_VERSIONS: ${{ vars.RETAIN_VERSIONS || '1' }}
run: |
set -euo pipefail
echo "开始清理阿里云ESA版本..."
echo "目标Pages: $PAGES_NAME"
echo "保留版本数: $RETAIN_VERSIONS"
# 获取所有版本信息
VERSIONS_JSON=$(aliyun esa ListRoutineCodeVersions --region cn-hangzhou --Name $PAGES_NAME)
# 解析版本数量
TOTAL_COUNT=$(echo $VERSIONS_JSON | jq -r '.TotalCount')
echo "当前共有 $TOTAL_COUNT 个版本"
# 如果版本数不超过保留数量,不需要清理
if [ "$TOTAL_COUNT" -le "$RETAIN_VERSIONS" ]; then
echo "版本数 ($TOTAL_COUNT) 未超过保留数量 ($RETAIN_VERSIONS),无需清理"
echo "ESA会自动检测main分支更新并部署"
exit 0
fi
# 计算需要删除的版本数(保留指定数量的最新版本)
DELETE_COUNT=$((TOTAL_COUNT - RETAIN_VERSIONS))
echo "需要删除 $DELETE_COUNT 个旧版本,保留最新的 $RETAIN_VERSIONS 个版本"
# 解析版本列表,按创建时间排序(最早的在前)
VERSIONS_TO_DELETE=$(echo $VERSIONS_JSON | jq -r '.CodeVersions | sort_by(.CreateTime) | .[0:'$DELETE_COUNT'] | .[].CodeVersion')
echo "将要删除的版本: $VERSIONS_TO_DELETE"
# 删除旧版本
DELETED_COUNT=0
for VERSION in $VERSIONS_TO_DELETE; do
echo "正在删除版本: $VERSION"
DELETE_RESULT=$(aliyun esa DeleteRoutineCodeVersion --region cn-hangzhou --Name $PAGES_NAME --CodeVersion $VERSION)
STATUS=$(echo $DELETE_RESULT | jq -r '.Status')
if [ "$STATUS" = "OK" ]; then
echo "版本 $VERSION 删除成功"
DELETED_COUNT=$((DELETED_COUNT + 1))
else
echo "版本 $VERSION 删除失败: $DELETE_RESULT"
exit 1
fi
done
echo "版本清理完成!成功删除 $DELETED_COUNT 个旧版本"
# 验证清理结果
- name: Verify Clean Result
env:
PAGES_NAME: ${{ steps.get-pages-name.outputs.pages_name }}
RETAIN_VERSIONS: ${{ vars.RETAIN_VERSIONS || '1' }}
run: |
set -euo pipefail
echo "验证清理结果..."
RESULT_JSON=$(aliyun esa ListRoutineCodeVersions --region cn-hangzhou --Name $PAGES_NAME)
REMAINING_COUNT=$(echo $RESULT_JSON | jq -r '.TotalCount')
echo "清理后剩余版本数: $REMAINING_COUNT"
if [ "$REMAINING_COUNT" -le "$RETAIN_VERSIONS" ]; then
echo "版本清理成功,当前版本数: $REMAINING_COUNT (保留设置: $RETAIN_VERSIONS)"
echo "ESA会自动检测main分支更新并开始部署"
else
echo "版本清理失败,仍有 $REMAINING_COUNT 个版本,期望保留: $RETAIN_VERSIONS"
exit 1
fi
2. 配置说明
2.1 GitHub Secrets 配置
需要在GitHub仓库的Settings > Secrets and variables > Actions中添加以下密钥:
ALIYUN_ACCESS_KEY_ID: 阿里云访问密钥IDALIYUN_ACCESS_KEY_SECRET: 阿里云访问密钥Secret
2.2 RAM用户权限
需要创建一个RAM用户并授予以下权限:
AliyunESAFullAccess: ESA完全访问权限
2.3 自定义配置
1. 修改Pages名称
如需修改Pages名称,请在clean-esa-main.yml的”Get Pages Name”步骤中修改pages_name的值:
echo "pages_name=your_pages_name" >> $GITHUB_OUTPUT
2. 配置保留版本数
默认情况下,清理逻辑会保留最新的1个版本。如需修改保留的版本数量,请在GitHub仓库的Settings > Variables中添加变量:
- 变量名:
RETAIN_VERSIONS - 值: 需要的保留版本数(如:
2、3等),可作为回滚的保留。
添加后,系统会保留指定数量的最新版本,清理更早的版本。
例如:
- 设置为
1(默认):只保留最新的1个版本 - 设置为
2:保留最新的2个版本 - 设置为
3:保留最新的3个版本
3. 工作流程说明
- 开发阶段: 直接在main分支上进行开发和提交
- 自动清理: 每次push到main分支时,clean-esa-main.yml会自动触发,清理阿里云ESA的旧版本
- ESA部署: 清理完成后,ESA会自动检测main分支的变化并触发部署构建
简化流程优势:
- 去掉了复杂的dev分支和PR流程
- 用户只需直接push到main分支
- 自动清理版本后立即部署
- 完全无需手动干预
4. 注意事项
- 确保RAM用户有足够的权限访问ESA服务
- 清理策略保留最新的n个版本(可配置),删除所有较旧的版本
- 如果清理过程中出现错误,整个CI流程会失败,ESA不会进行部署
- 可以根据需要调整保留的版本数量(通过GitHub Variables配置)
- 每次推送main分支都会触发清理,无需手动管理
在实际使用中,在ESA控制台可能会发现,总会多出一个无用的CodeVersion,本质上是ESA的Github触发条件是WebHook触发,当代码一旦推送,构建流程就会开始,而CI始终在构建已经开始的情况下再删除,且不能删除当前的生产版本,因此不能够彻底删除原有的生产构建版本。
5. 故障排除
5.1 CLI配置问题
如果出现阿里云CLI配置错误,请检查:
- Access Key ID和Secret是否正确
- RAM用户是否有AliyunESAFullAccess权限
- 区域设置是否正确(默认为cn-hangzhou)
5.2 版本清理失败
如果版本清理失败,请检查:
- Pages名称是否正确
- 当前用户是否有删除版本的权限
- 网络连接是否正常
5.3 ESA部署不触发
如果推送main分支后ESA没有自动部署,请检查:
- 清理workflow是否执行成功
- ESA是否正确连接到GitHub仓库的main分支
- GitHub和ESA之间的网络连接是否正常