在 Windows ECS 上自动发布 IIS 站点和 Windows 服务,也能回退、执行 PowerShell、检查和更换 SSL 证书。
- 服务器上不用另装东西、不用开端口:aca 经阿里云云助手操作服务器,只需 AccessKey 和一个 OSS bucket。
- 一条命令发完所有服务器:aca 发完一台再发下一台,每台都是停下站点或服务、备份、覆盖、启动、检查,失败就停;配了负载均衡(CLB、ALB、NLB)还先把这台服务器摘出,发完放回。
- 发布前有预检查:拿错了旧构建、程序集引用发布后才会报错、服务器没装要的 .NET Framework 版本,aca 在停站之前就拦下。
- .NET Framework 项目的
web.config以服务器上那份为准:连接串这类按服务器填的值不动,aca 只把绑定重定向这类由构建决定的部分合进去。 - 发坏了退得回去:aca 每次发布都备份被覆盖的文件,用
aca rollback一次能退回多次发布。 - 排查不用登录服务器:用
aca logs、aca events一次取回一个站点在每台服务器上的 IIS 日志和相关的 Windows 事件,用aca diff找出哪台服务器的文件不一样。 - 查和换 SSL 证书:用
aca certs查每台服务器握手时实际返回的证书和剩余天数,用aca certs replace批量换,aca 换完在服务器本机握手,不对就换回旧证书。 - 附带 Agent Skill:对 Claude Code、Codex 等 AI Agent 说"把 MyApp 发到测试站",它会先跑
--check把结果给你看,等你确认了再发;说"给每台服务器做个体检",它能用aca run查磁盘被什么占满、数据库有没有在备份、多久没装补丁、哪些端口在监听等。
Caution
云助手以 SYSTEM 身份执行,aca run 能在服务器上执行任意 PowerShell:持有这份 AccessKey 的人和 AI Agent 就是这些服务器的管理员。skill 要求 AI Agent 改动服务器前先让你确认,这是写给 AI Agent 的规则,不是权限控制。
需要 Node 22.12+。
npm install -g @ninesols/aca-cli
aca skill install # 给 Claude Code、Codex 装上 skill,升级 aca 后再跑一次其他支持 Agent Skills 开放标准的 AI Agent 用 npx skills add YoungsunLi/aca -g 装,它会问装给哪些 AI Agent;装的是 GitHub 上最新的 skill,不一定和本机 aca 的版本一致。
在 ~/.aca/config.json 写一份配置,或设置环境变量 ACA_CONFIG 指向别的路径。先写好 region 和 oss,再跑 aca discover:aca 列出每台服务器上的站点和服务(装在 Windows、Program Files、ProgramData 目录里的服务不列),最后给出 instances、sites、services 的草稿,照着填。
{
"region": "cn-hangzhou",
"oss": { "bucket": "my-deploy-bucket", "prefix": "deploy/" },
"instances": { "web1": "i-bp1xxxxxxxx", "web2": "i-bp1yyyyyyyy" },
"sites": {
"Default Web Site": {
"instances": ["web1", "web2"],
"project": "MyApp.Web",
"publish": "D:\\Publish\\MyApp",
"exclude": ["web.config", "bin/Res"],
"stage": "Default Web Site TEST",
"clb": "lb-bp1zzzzzzzz",
"note": "正式站"
},
"Default Web Site TEST": { "instances": ["web1"], "publish": "D:\\Publish\\MyApp", "exclude": ["web.config", "bin/Res"] }
},
"services": {
"MyApp.Worker": {
"instances": ["web1", "web2"],
"publish": "D:\\Publish\\Worker",
"dir": "D:\\Services\\Worker",
"exclude": ["MyApp.Worker.exe.config"]
}
}
}sites 的 key 是 IIS 站点名,站点下的应用程序写成 站点名/路径(见站点下的应用程序);带站点或服务名的命令只认这里登记的站点和下面 services 里登记的服务。除 instances 外的字段都可选:
| 字段 | 说明 |
|---|---|
instances |
跑这个站点的服务器(别名或实例 ID),顺序即发布顺序 |
publish |
本机的发布目录,deploy 省略路径时用它 |
exclude |
包里不发布的路径(目录或文件):服务器上自己维护的密钥、环境配置列在这里,发布就不会覆盖它们 |
overwriteConfig |
设为 true 时,包里的环境配置当普通文件整份发(默认要列进 exclude,否则 aca 报错):适合环境值不放在这份文件里的项目 |
stage |
指向预发布站:本站只发预发布站验过的包,即预发布站每台服务器发布记录的最后一条都是它的成功发布,--skip-stage 跳过这项要求。用 --from-stage 直接发那个包;给本机路径时 aca 比对要上传的 zip,重新构建过,或者一边给目录一边给 zip,都算不同的包 |
keep |
每台服务器上保留的备份份数,默认 5,rollback 最多撤得掉最近这么多次发布,更早的版本只能重新发旧构建 |
clbalbnlb |
站点的请求走的负载均衡服务器组,三个最多配一个:aca 发布、回退、重启每台服务器前先把它摘出这个组,见负载均衡。clb 写传统型负载均衡的实例 ID(lb-,指它的默认服务器组)或虚拟服务器组 ID(rsp-);alb、nlb 写应用型、网络型负载均衡的服务器组 ID(sgp-) |
projectnote |
只在 aca sites、aca services 里显示,方便认出是哪个站点或服务 |
services 的 key 是 Windows 服务名(sc query 列出的那个,不是显示名),字段和站点相同,但没有 stage 和负载均衡的三个字段,另外必填 dir:服务器上的安装目录。有别的服务依赖它、而且在跑时,停它会连带停掉那些服务,aca 不发,只能手工发。
IIS 里挂在站点下的应用程序(IIS 管理器里"添加应用程序"建的)写成 站点名/路径,如 Default Web Site/api,登记在 sites 里,字段和站点相同。和站点不一样的地方:
- 停的只是应用自己的应用池,站点和别的应用照常运行;应用池和站点或别的应用共用时,预检查会列出跟着一起中断的那些。
- 应用目录在站点目录(或同站点别的应用、虚拟目录的目录)里时,aca 的备份、发布记录和锁文件放在最外层目录旁的
<最外层目录>.aca-apps\下,免得被 IIS 对外提供。站点本身也要发布时,把应用目录列进站点的exclude,aca diff比站点时就不会把应用的文件也比进去。
凭证按阿里云 SDK 的默认凭证链查找,和阿里云 CLI 共用:aliyun configure 配过就不用再配,也可以设环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET,两处都配了时环境变量优先。
RAM 权限:
| 产品 | 权限 |
|---|---|
| ECS | ecs:DescribeInstances、ecs:DescribeCloudAssistantStatus(没有它 aca instances 看不到云助手客户端的状态)、ecs:RunCommand、ecs:DescribeInvocationResults |
| OSS | oss:PutObject、oss:GetObject、oss:ListObjects、oss:GetBucketPolicyStatus、oss:DeleteObject;bucket 开了版本控制时,run -o、pull、push、logs、events、diff 和 certs replace 删 OSS 上中转的文件要 oss:DeleteObjectVersion |
| CLB | 站点配了 clb 时要:slb:DescribeLoadBalancerListeners、slb:DescribeHealthStatus;配的是实例 ID 时另要 slb:DescribeLoadBalancerAttribute、slb:SetBackendServers,是虚拟服务器组 ID 时另要 slb:DescribeVServerGroupAttribute、slb:SetVServerGroupAttribute、slb:DescribeRules |
| ALB | 站点配了 alb 时要:alb:ListServerGroupServers、alb:UpdateServerGroupServersAttribute、alb:ListServerGroups、alb:ListListeners、alb:GetListenerHealthStatus |
| NLB | 站点配了 nlb 时要:nlb:ListServerGroupServers、nlb:UpdateServerGroupServersAttribute、nlb:ListServerGroups、nlb:ListLoadBalancers、nlb:ListListeners、nlb:GetListenerHealthStatus |
| 数字证书管理服务 | 取云端证书时要:yundun-cert:ListUserCertificateOrder、yundun-cert:GetUserCertificateDetail;这个服务只支持操作级授权,资源只能写 * |
Warning
ecs:RunCommand 按实例 ID 授权,不要给 *:它授权到哪些实例,这份 AccessKey 就是哪些实例的管理员。
- 服务器和 OSS bucket 在同一地域,发布包走 OSS 内网下载。
- 服务器需装有 2.0.0.612 或更新的云助手客户端(2017 年 12 月以来用公共镜像创建的服务器已预装,客户端会自己升级),更早的版本会中止 aca 的命令。
- 系统区域不是英语(美国)的服务器上,输出里 GBK 以外的字符(如 ä、emoji)会变成问号,中文不受影响。
- Server 2019 及更早的系统上,
aca events列不出 .NET 应用记在 .NET Runtime 来源下的日志,崩溃那条除外。 - 站点目录和服务目录必须是本地盘上的普通目录,不能是磁盘根目录或 UNC 路径,也不要嵌套在另一个站点或服务的目录里(站点下的应用程序除外):备份和发布记录放在它旁边。
- 目录旁的
<目录>.bak-<时间>备份和<目录>.aca-*文件都是 aca 要用的,别手动删:删了最新的备份,rollback会跳过那次发布,恢复出一个从没发布过的混合版本。 - 本机和服务器要在同一时区:预检查拿包里的文件时间和服务器上的比,看是不是拿错了旧构建,zip 里存的是本地时间。
- 发布包(
--check也会上传)按 SHA-256 命名留在 OSS 的oss.prefix下,aca 不删,要在 bucket 上配生命周期规则按天清理;--from-stage从这里取预发布站发过的包,天数要长于从发预发布站到发正式站的间隔。 - bucket 要私有,防盗链要允许空 Referer(服务器下载包时不带 Referer):发布包不加密,aca 每次发布前确认匿名读不到,否则报错退出。
run -o、pull、push、logs、events、diff和certs replace经 OSS 中转文件:aca 用一次性密钥加密、用完就删,但密钥和 PFX 密码留在云助手的执行记录里,看得到执行记录的人也看得到它们。- 同一个站点或服务同时只能有一个发布或回退:aca 靠 OSS 上
oss.prefix下的租约互斥,几个人发同一批站点时要配同一个 bucket 和oss.prefix,否则挡不住同时发。
每个命令都输出纯文本,失败以非 0 退出码表示,参数见 aca <命令> --help。
服务器和配置
aca instances # 列出当前地域的服务器和上面云助手客户端的版本
aca discover # 列出每台 Windows 服务器上的站点和服务,给出配置草稿
aca discover web1 web2 # 只看这几台
aca sites # 列出配置里的站点和它们的项目、发布目录、服务器
aca services # 列出配置里的 Windows 服务和它们在服务器上的目录排查和临时运维
aca run web1 "Get-Website | select name,state" # 以 SYSTEM 执行任意 PowerShell
aca run web1 --file ./check.ps1 # 执行文件里的脚本,带 $、引号或换行时用它
aca run web1 --file ./list.ps1 -o list.txt # 输出存进本机文件,不受云助手输出上限的限制
aca pull web1 "C:\inetpub\logs\LogFiles\W3SVC1\u_ex260919.log" # 把服务器上的文件拉到本机当前目录
aca push web1 ./rewrite_amd64.msi "C:\Windows\Temp" # 把本机的文件送到服务器上的目录里
aca logs "Default Web Site" # 每台服务器上这个站点最新的 20 行 IIS 日志,时间是 UTC
aca logs "Default Web Site" --since 30m -n 500 # 最近 30 分钟里最新的 500 行
aca logs "Default Web Site" --httperr --since 2h # 最近 2 小时里这个站点没进到 IIS 的请求
aca events "Default Web Site" # 每台服务器上和这个站点有关的最新 20 条 Windows 事件
aca events MyApp.Worker --since 2h # 最近 2 小时里这个服务的事件
aca diff "Default Web Site" # 按内容哈希比对每台服务器上的文件,列出不一样的
aca diff "Default Web Site" bin # 只比站点目录下的一个目录
aca reboot web1 web2 --check # 不重启,只列出要核对的站点和服务、要摘出的负载均衡
aca reboot web1 web2 # 重启 web1,站点和服务都起来了再重启 web2发布和回退
aca deploy "Default Web Site" --check # 只预检查,列出要覆盖和新增的文件,不停站
aca deploy "Default Web Site" -m "release-2026-09" # 发配置里 publish 字段的目录;-m 写进发布记录
aca deploy "Default Web Site" ./MyApp.zip -m "release-2026-09" # 发别的目录或 zip,不用 publish 字段
aca deploy "Default Web Site" --from-stage -m "release-2026-09" # 直接发预发布站最近一次发布的那个包
aca deploy MyApp.Worker -m "release-2026-09" # 发 Windows 服务,参数和站点一样
aca deploy "Default Web Site/api" ./api -m "release-2026-09" # 发站点下的应用程序
aca status "Default Web Site" # 每台服务器上的状态、最新的文件时间和最近 5 条发布/回退记录
aca status # 每个站点和服务在每台服务器上一行:状态、最新文件时间、最后一条发布/回退记录
aca rollback "Default Web Site" --check # 每台服务器上还留着哪几次发布的备份、占多少空间、会撤掉哪几次
aca rollback "Default Web Site" # 回退最近一次发布
aca rollback "Default Web Site" 20260918T020100Z # 连同之后的发布一起回退,退到这次发布之前
aca lb "Default Web Site" # 每台服务器在负载均衡服务器组里的权重
aca lb restore "Default Web Site" web1 # 把摘下的 web1 放回负载均衡证书
aca certs # 每台服务器上运行中站点的 https 绑定实际返回的证书
aca certs replace ./a.pfx --password-file ./pw.txt --check # 看每台服务器会把哪些 https 绑定换成这张证书
aca certs replace ./a.pfx --password-file ./pw.txt # 换证书
aca certs cloud # 列出数字证书管理服务里没过期的证书和它们的 ID
aca certs replace --from-cloud 22863954 # 换成云端这张证书,PFX 由 aca 合成aca 以 SYSTEM 身份在服务器上执行 PowerShell,脚本以非 0 退出(包括没被捕获的异常)时 aca 用同样的退出码退出。
- 脚本里有
$、引号或换行时用--file <文件>:写在命令行上的脚本要先经本机 shell 处理,这些字符可能被悄悄改掉。 - PowerShell 里出错的命令默认只报错、不中止脚本,退出码仍是 0;要让任何错误都算失败,脚本开头加
$ErrorActionPreference = 'Stop'。 - Server 2012、2012 R2 上,等待输入的脚本(缺必填参数、
Read-Host、确认提示)会一直挂到超时。 - 脚本以 3010 或 3009 退出时,云助手客户端会接着重启或关掉服务器:安装程序常以 3010 表示需要重启,别把它的退出码原样
exit出去,装完用aca reboot重启。 - 脚本最多约 18 KB(按纯英文算):云助手限定命令 base64 后 24 KB,aca 还要加上自己的代码;加
-o时再少 2 到 5 KB。 - 改站点和服务的文件用
aca deploy:用aca run、aca push改的没有备份,aca rollback管不了。
负载均衡后面几台服务器上的文件不一致时,表现是刷新几次好一次坏一次。用 aca diff 按内容哈希比对每台服务器上的文件,列出服务器之间不一样的。
- 比的是站点目录(服务是配置里的
dir)下的全部文件,exclude里的路径除外;上传目录、日志目录也列进exclude就不比了。服务只比.dll、.exe和 .NET Core 的*.runtimeconfig.json、*.deps.json。 - 每台服务器要把目录整个读一遍,几 GB 的目录要几分钟,超过 30 分钟会被云助手强杀。
aca 先在每台服务器上同时下载解压、预检查,每台都通过后,发完一台服务器再发下一台,每台依次:停站点或服务 → 把要覆盖的文件备份到 <目录>.bak-<时间> → 覆盖复制 → 启动 → 检查 → 删掉最近 keep 份以外的旧备份。
- 发布是增量的:包里有什么就覆盖什么(
exclude的除外),包里已经没有的文件,aca 也不会从服务器上删掉。 - aca 发布完会启动站点和应用池,即使发布前是手动停掉的;发布前就停着的服务,aca 发布后也不启动:备机上的服务常常是刻意停着的。
- 启动后的检查:aca 在服务器本机请求站点首页,5xx 或连不上、且和发布前不同,就算这台服务器发布失败;首页跳到别的站点的,只看得到跳转的状态码,看不出应用起没起来。服务没回到发布前的运行状态也算失败。
任何一台服务器预检查不过,aca 哪台服务器都不发:
- 包像是源码目录、包根目录里有没列进
exclude的环境配置、疑似发错了站点或服务、磁盘不够。 - 包里有比服务器上旧的文件:多半是拿错了旧构建,或者有人在服务器上手改过。
- 发布后会加载失败的程序集引用:aca 按运行时的规则,经绑定重定向和 GAC 解析 bin 里每个程序集的引用;这次发布让原来找得到的引用找不到了,或者新代码引用的版本和 bin 里的对不上,就拦下,多半是升级了 NuGet 包却没带上绑定重定向。这类问题站点照样能启动,要等用到那段代码才报错,首页检查拦不住。新代码引用的程序集在 bin 和 GAC 里都找不到时只提示。
- 服务器没装包要的 .NET Framework 版本,或者应用池的位数不对(只核对 .NET Framework 的程序)。
后三种确认没问题,可以加 -f 照发。
环境配置指站点的 web.config、服务的 *.exe.config(包根目录下的每个都算)。它们装着连接串这类按服务器填的值,以服务器上那份为准,出现在包根目录时 aca 报错,要列进 exclude,或者配 overwriteConfig 整份发。
- 包里带着环境配置时,aca 把由构建决定的部分合进服务器上那份:程序集绑定重定向、
system.codedom的编译器、Entity Framework 的providers、compilation的assemblies按条目合并,startup整段换,targetFramework只换值;连接串、appSettings等其余内容和改动之外的每个字节都保持原样。预检查列出每处改动,aca rollback会连它一起退回。 - ASP.NET Core 的
web.config是发布时生成的,aca 当普通文件整份发:在服务器上那份里加的environmentVariables等设置会被覆盖,按服务器区分的值放到appsettings.<环境>.json(列进exclude)或服务器的环境变量里。
站点配了负载均衡时,aca 发每台服务器之前先把它在服务器组里的权重调成 0,停站期间负载均衡不再把请求转给它(包括走这个服务器组的其他站点);这台服务器发布成功后调回原值,再发下一台。回退和 aca reboot 也一样。
- aca 只在组里还有别的服务器接流量时才摘,但健康状态接口比负载均衡实际的判断慢一两分钟:别的服务器刚出故障时 aca 看不出来,照样会摘。
- 四层监听(CLB 的 TCP/UDP、NLB)上已经建立的连接,停站时会断开,开了连接优雅中断也一样;七层监听按请求转发,不受影响。
- 停过站又失败的服务器留在负载均衡外:负载均衡的健康检查查的不一定是这个站点,放回去用户可能撞上没起来的站点。
- aca 处理不了的服务器组:同一台服务器按几个端口进了组、ALB 服务器组或 NLB 实例关了跨可用区、服务器组开了 IP 版本亲和。这些情况下 aca 算不准摘出之后流量怎么走,报错,哪台服务器都不发。
一台服务器发布失败时,aca 不再发后面的服务器,已经覆盖的文件都不会自动回退。不论是撤销这次发布还是修好重发,都先跑 aca rollback <站点> --check:标 undo 的是这次发布(发布 ID 是开始时的 UTC 时间)就先回退;标的是更早的发布,说明哪台服务器都没发上,不用回退。
Warning
发上了这次的服务器不先回退就重发,会把这次的版本当作备份,之后回退一次只能退到这次,退不回发布前的版本。
-
aca 报
Polling Cloud Assistant results failed、Timed out waiting for Cloud Assistant results,或者被强杀、中途 Ctrl+C 时,服务器上的脚本可能还在跑(发布最长 30 分钟):先用aca status看发布记录,再决定要不要回退。 -
脚本跑满 30 分钟被云助手强杀时,站点或服务可能停着、文件可能只覆盖了一半,也没有发布记录。
aca rollback <站点> --check显示这台服务器要撤的是这次发布就回退,否则文件没动过,手动启起来:aca run <实例> "Start-WebAppPool (Get-Website '<站点>').applicationPool; Start-Website '<站点>'" aca run <实例> "Start-Service '<服务名>'" # 发的是服务
-
配了负载均衡的,以上情况下正在处理的服务器都留在负载均衡外:确认它上面的站点正常后,用
aca lb restore <站点> <实例>放回。
装完 .NET Framework、Windows 更新这类要重启的东西后,用 aca reboot <实例...> 重启服务器。aca 按给出的顺序重启,一台服务器恢复了才重启下一台:
- 这台服务器上有站点在配置里配了负载均衡时,aca 先把服务器摘出这些服务器组,恢复后放回;配置里没有的站点走的负载均衡,aca 不知道,也就不摘。
- 配置里的站点首页没有新出现 5xx 或连不上、重启前在跑的服务又跑起来了、Windows 装完更新,才算恢复(装完累积更新,Windows 第一次开机后还会自己再重启一次);15 分钟还没恢复,aca 报错停下,摘出过负载均衡的留在外面。
- 装更新的那次重启,关机前、开机时都在装更新,可能要几十分钟,aca 最多等 60 分钟。
aca 在配置里站点所在的每台服务器上,按每个运行中站点(没登记的也算)的 https 绑定在服务器本机握手,列出实际返回的证书和剩余天数。证书过期、30 天内到期、不含绑定的域名或握手失败时以非 0 退出,可以放进计划任务定期跑。
aca 在同一批服务器上,把正在用同名证书(按证书使用者名称,如 *.a.com)的 https 绑定全部换成这张证书,没登记的站点也换,旧证书留在服务器上。
- 换的单位是 HTTP.sys 的绑定条目而不是站点:不带 SNI 的绑定共用一个 IP:端口 条目,换其中一个站点就是全换,
--check会列出每个条目上的站点。 - 要换的条目里有 IIS 默认值以外的 HTTP.sys 设置时(客户端证书协商、吊销检查等),aca 报错,这台服务器一条都不换,后面的服务器也不再处理,
--check也一样:换证书会把这些设置丢掉,这种条目要手工换。 - Windows Server 2016 及更早的系统打不开 AES 加密的 PFX(OpenSSL 3 默认就是),报的却是密码不正确,用
openssl pkcs12 -export -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1重新导出。
Caution
aca 会停止并覆盖生产站点,也会重启服务器。请先在测试站跑通、每次发布前用 --check、保管好 AccessKey。
本软件按 MIT 协议"按原样"提供,不附带任何担保,详见 LICENSE。