从零到 3200 分:一场"闯关式"的华为云全链路部署实录
一次活动,两步闯关,一个 2048 小游戏,从本地代码走到公网可访问,再到作品陈列馆——这篇文章完整记录了我在华为云开放能力闯关挑战(open-capability-2026-1024)中的全部过程,包括每一个踩过的坑和自救方案。
背景
闯关活动分两步:
- beginner(1000 分):用 HuaweiCloud DevKit 沙箱部署一个 Web 应用,通过隧道暴露公网
- intermediate(2000 分):用按量付费 ECS + Nginx 做持久化部署
作品我选了一个经典且有辨识度的目标:纯单文件 HTML 的 2048 小游戏——不依赖 npm、Vite 或任何构建工具,CSS/JS 全部内联在一个 index.html 里。
第零步:DevKit 环境准备
按官方 README 五步走:
node --version # 1. 确认 Node.js ≥ 22(实测 v22.11.0)
npx --yes huaweicloud-devkit install-hcloud # 2. 安装 KooCLI
npx --yes huaweicloud-devkit auth init # 3. 配置 AK/SK
npx --yes huaweicloud-devkit install --target codearts # 4. 安装插件
npx --yes huaweicloud-devkit doctor # 5. 自检
踩坑 1:node:sqlite 模块不可用。 Node v22.11.0 中该模块还在实验特性后面,直接跑会报 No such built-in module。解法很简单:
export NODE_OPTIONS="--experimental-sqlite"
踩坑 2:KooCLI 首次运行会卡在隐私协议确认。 非交互会话里用管道喂一个 y 即可:
echo "y" | hcloud version
doctor 自检 10 项全过之后,必须重启 AI Agent 会话,MCP 工具(40+ 个 huaweicloud_* 工具)才会生效——这是最容易忽略的一步。
第一步:写一个零依赖的 2048
单文件 15.6KB,几个值得说的设计决策:
- CSS 变量驱动响应式:
--cell: min(100px, calc((100vw - 36px - 5*var(--gap)) / 4)),PC 和手机共用一套布局逻辑 - Tile 用
--x/--y自定义属性 +transform位移:移动只改 transform 值,避免布局重排 - 纯 CSS 三类动效:
spawn(生成缩放淡入)、pop(合并弹跳)、floatUp(得分上浮) - 移动端触屏:
touchstart/touchend位移判定 +touch-action: none防页面滚动 - 合并算法:每行按方向提取非空序列线性扫描合并(每 tile 每步仅合并一次),位移动画先行、合并消除在 130ms 后统一结算,保证视觉与数据一致
第二步:沙箱部署(beginner · 1000 分)
流程本身很顺:sandbox_check_user 预检 → sandbox_connect 连接 → 清理旧部署 → sandbox_upload_file 一次调用上传 → deploy_nginx → expose_tunnel 拿到公网 URL → curl -sI 验证 200 → 提交活动。
踩坑 3:沙箱节点池没资源 + 状态死锁。 这是最曲折的一段:
sandbox_connect连续报HD.99003001: No available node pool(节点池容量不足)- 某次重试请求超时——但这次超时实际触发了启动,环境卡在中间态
- 后续所有连接报
HD.98330021: Dev environment is not closed and cannot be started(环境未关闭不能启动)
死锁的解法是绕过 MCP 工具,直接用 AK/SK 签名调用 devstation 的 open-api,先 close 把环境打回关闭态(status=4),再循环重试 start。30 秒一次、重试 10 分钟后成功拿到节点。
踩坑 4:sandbox_upload_file 报 No such file or directory。 上传工具不会自动创建父目录,先通过 sandbox_exec_one_shot 执行 mkdir -p /workspace/game-2048 即可。
最终成果:
Tunnel URL: https://podytwvf-8080.devbridge-s2.hwtunnel.com
HTTP/1.1 200 OK
Content-Length: 15607 ← 与本地源文件一致
提交 beginner:作品提交成功,1000 分到手。
第三步:ECS 持久化部署(intermediate · 2000 分)
这一步是真正的华为云资源编排实战,踩的坑也最有含金量。
选型:规格停售是常态
活动建议 s6.medium.2/sn3.medium.2,但 ECS ListFlavors 按 AZ 查售卖状态后发现 s6/sn3 全系停售。逐 AZ 扫描 cond:operation:status=normal 的小规格,最终锁定 at7.large.1(2核2G,cn-north-4g 在售)。
磁盘同样要查售卖状态:EVS CinderListVolumeTypes 的 sold_out_availability_zones 字段显示 SATA 在 cn-north-4a/4b/4c 全部售罄——印证了"磁盘优先 SAS/GPSSD"的经验,最终选 SAS 40GB(4g 在售)。
网络复用已有 VPC/子网(DNS 已含 100.125.1.250),安全组新建:SSH 22 只放通本机出口 IP(curl ifconfig.me 查到后填 /32),HTTP 80 放通 0.0.0.0/0。
cloud-init:一次注入,开机即部署
按"必须用 cloud-init"的要求,把全部初始化写进 user_data(base64 的 shell 脚本):
#!/bin/bash
echo 'root:<随机强密码>' | chpasswd
dnf install -y nginx
mkdir -p /usr/share/nginx/html/hc-activity
echo '<index.html的base64>' | base64 -d > /usr/share/nginx/html/hc-activity/index.html
systemctl enable nginx && systemctl restart nginx
镜像选了 dnf 系的 Huawei Cloud EulerOS 3.0。EIP 用 publicip.eip(5_bgp、按流量、5Mbit/s)随实例一并创建绑定,delete_on_termination=true 为后续清理埋好伏笔。
踩坑 5:--cli-jsonInput 的 JSON 格式。 user_data base64 后近 28KB,超出命令行内联上限,必须走 JSON 文件。但文档没说的是——JSON 必须用 {"body": {...}} 包裹,平铺键名和裸 {"server": {...}} 都会报 “invalid content”。用 --dry_run=true 本地空跑校验是零成本的安全网,强烈推荐。
验证
ECS 创建是异步的:轮询 ListServersDetails 到 ACTIVE(EIP 同时就位),再轮询 curl http://<EIP>/hc-activity/index.html 到 200(cloud-init 完成 Nginx 部署的信号):
[1] ACTIVE|139.9.142.34 ← 10秒即 ACTIVE
[1] HTTP=000 ... [4] HTTP=200 ← 40秒后 cloud-init 部署完成
curl -sI 复验 200 OK + 全文 MD5 与源文件一致,提交 intermediate:又 2000 分。
第四步:作品发布到陈列馆(+200 积分)
用 huawei-cloud-publish-work-to-gallery 技能走完 8 步流水线,坑集中在三处:
踩坑 6:旧技能名是废弃桩。 npx skills add --skill publish-work-to-gallery 装到的是重定向提示文件,真名是 huawei-cloud-publish-work-to-gallery。
踩坑 7:SELF_VERIFY 委托不存在。 生成 STS 临时凭证时报 STS5.1106: agency 'SELF_VERIFY' cannot be found。这是一次性配置——按文档用 IAM CreateAgency 创建自委托(委托方=被委托方=自己账号)后,gen_sts.py 立刻通过。
踩坑 8:OAuth 授权链接是一次性的。 GitCode OAuth 的 --start 每次都生成新链接,旧链接在会话超时后失效。第一次点了个已失效的链接,授权"完成"了但 token 始终没落盘——必须点最新生成的那个链接。授权成功后 token 写入 ~/.gitcode/auth.toml(0600 权限)。
之后一切顺利:API 创建 GitCode 仓库 → token 推送代码(推完立刻把 token 从 remote URL 里剥掉)→ build-cover 生成封面 → generate_diagram 渲染架构图 → count-cjk 校验简介字数 → 打包 detail.zip → 门禁复核 → 发布:
#status=201
workUrl=https://gallery.developer.huaweicloud.com/gallery/006934e242400000
reward=success value=200 msg=成功
第五步:清理——比创建更重要
按量付费资源不清理就是持续失血。删除顺序讲究逆创建序:先 ECS(--delete_publicip=true --delete_volume=true 级联清 EIP 和系统盘),后安全组。
踩坑 9:安全组删除报 VPC.9902 in use。 ECS 删除后其 port 有几分钟回收延迟,安全组还被引用。等 2 分钟重试即成功。
最终核查清单(全部为零才算干净):
| 资源 | 查询命令 | 结果 |
|---|---|---|
| ECS | ListServersDetails |
count=0 ✅ |
| EIP | ListPublicips |
total=0 ✅ |
| EVS 磁盘 | ListVolumes |
count=0 ✅ |
| OBS | OBS ls |
Bucket 0 ✅ |
| 安全组 | ListSecurityGroups |
仅剩账号原有 default ✅ |
| 沙箱 | devenv close API | status=4(已关闭)✅ |
保留项都有理由:VPC/子网免费,IAM SELF_VERIFY 委托免费且可复用,GitCode 仓库免费留存作品代码。
心得总结
最终积分:3200 分(beginner 1000 + intermediate 2000 + 陈列馆发布 200)。
回头看,这次闯关真正的价值不在 2048 本身,而是把一条从代码到公网的完整链路走通了:
- API help 先行:每个写操作前先
hcloud <Service> <Operation> --help,华为云 API 的参数命名(multiport而非port_range、v3 安全组无vpc_id)和版本差异只有 help 不会骗人 - 售卖状态必须实测:文档里的规格名(s6/sn3/SATA)可能已停售,
ListFlavors按 AZ 查、CinderListVolumeTypes看sold_out_availability_zones - 大参数走文件:28KB 的 user_data 内联必挂,
--cli-jsonInput+{"body": {...}}包裹 +--dry_run校验是标准姿势 - 异步操作要轮询:ECS 创建、cloud-init、删除回收全是异步,固定 sleep 不可靠,盯状态字段才是正解
- 清理是部署的一部分:
delete_on_termination在创建时就埋好,收尾才能一键干净 - 坑即文档:每一个 error code(HD.99003001 / HD.98330021 / STS5.1106 / VPC.9902)背后都是一条可以复述的经验——这正是写这篇博客的原因
作品地址:
- 代码:https://gitcode.com/misaka21001/2048-huaweicloud-challenge
- 陈列馆主页:https://gallery.developer.huaweicloud.com/gallery/006934e242400000
—— 完 ——
- 点赞
- 收藏
- 关注作者
评论(0)