Files
blog/CNB构建落地方案.md
T
zqlit e0218b42bb docs: 迁 CNB 前的架构决策文档 + 构建骨架
- 决策文档:架构总览 / 代码源与构建平台选型 / CNB构建落地方案 /
  EdgeOne双区域 / Gitee / 阿里云ESA / 精简方案
- CNB 构建骨架:deploy/Dockerfile、bin/linux/hugo
  (84MB,linux/amd64 extended 0.128.2,供 CNB 容器 COPY 用)
- scripts/setup-cnb-remotes.sh:双远端切换(CNB 主仓 + GitHub 备份)
- .gitignore 补 .workbuddy/(工作数据不入库)
2026-10-04 12:40:17 +08:00

21 KiB
Raw Blame History

CNB 构建落地方案:双 CDN 架构不动,砍掉中转链

状态:2026-10-04 · 待你确认后落地 回答的问题:「CNB 构建的前提下,能不能继续现在的架构(境外 EdgeOne + 境内多吉云 → 又拍云源站)?」


一句话结论

能,而且一个字都不用改。 域名解析、备案、EdgeOne 项目、多吉云配置、又拍云存储全部保持原样。 CNB 只替换「构建 + 搬运」这一段,顺带砍掉广州中转机上那个每分钟轮询的 sync.sh。


零、GitHub 依赖审计:全链路零依赖

回答「GitHub 账号被标记了,还能连上 CNB 吗」——能,而且这条链路从头到尾不需要 GitHub。

逐环节核实(来源:腾讯云官方文档「社区版快速入门」「个人中心」、CNB 官方 Wiki):

环节 CNB 的做法 碰 GitHub 吗
注册 / 登录 微信扫码,扫码即自动注册,无邮箱验证、无登录密码 ❌
实名认证 微信扫码授权手机号 ❌
建组织 / 仓库 CNB 内建(无「个人私有仓库」,仓库必须挂在组织下) ❌
导入代码 标准 git:git push --mirror <cnb-url>,源仓可以是自建 Gitea ❌
拉构建依赖 CNB 自带「国内外依赖源默认加速」(腾讯云 CDN + Docker/NPM/Maven 镜像) ❌
hugo 二进制 已随仓库携带(bin/linux/hugo),构建时零跨境下载 ❌
部署 EdgeOne API + 又拍云 upx v0 ❌

官方原文:

云原生构建默认使用微信扫码注册登录,未注册用户在扫码登录后,将自动完成注册。 —— 腾讯云文档《社区版快速入门》

云原生构建为微信扫码登录注册,没有登录密码。 —— 腾讯云文档《个人中心》

唯一会碰到 GitHub 的两个场景,你都不走

  1. 用 CNB 的「从 GitHub 导入」一键迁移工具 —— 那个需要 GITHUB_TOKEN。 但你从自建 Gitea 迁,用 git push --mirror,不需要它。
  2. 构建时在线拉 GitHub 资源 —— 这正是已经提前拆掉的雷:hugo 二进制改为随仓库携带。

顺带纠正一个概念

「账号被标记」≠「IP 被墙」。 标记是账号级限制;匿名 HTTP 访问 github.com 的公开资源不受影响 (实测国内节点 curl hugo release 仍返回 302 正常)。所以即便真要在构建里拉 GitHub,也只是慢,不是不通。

你真正要做的三件准备(都与 GitHub 无关)

  1. 微信扫码注册 CNB
  2. 微信扫码完成实名认证(官方说明:未实名用户禁止写行为 —— 创建仓库 / 推送代码)
  3. 先建一个组织,仓库建在组织下

一、决定性发现:答案你自己早就写在代码里了

deploy.yml 第 290 行起,那段被 if: false 停用的 Upload to UpYun,注释原话:

│ 2026-10-03 起永久跳过海外直传:本 runner 在境外,对 UpYun v0 接口
│ 无论 -w 10/--strong 还是 -w 3 均为 70 秒左右 EOF,3 轮重试全败,
│ 每次部署白耗 5 分钟(2026-10-03 build #25 日志实锤)。
│ 国内线路由腾讯云广州中转机兜底(1Panel 计划任务「又拍云同步(国内
│ 中转)」每分钟比对 COS build hash → 下载 355M → upx sync →
│ 又拍云 purge → 多吉云刷新,实测 5 分钟内完成且它自己会刷 CDN)。
│ 将来若把 runner 挪到国内,把下面的 if: false 删掉即可恢复。

当初被迫上中转机,唯一的原因是「runner 在境外」。 链路是这样被逼出来的:

环节 为什么存在
runner 在境外 Gitea 跑在境外 VPS
直传又拍云失败 境外 → 又拍云 v0 接口,跨境 + 并发敏感 → 70 秒 EOF,必败
COS 中转 境外的产物,国内机器得有个地方取
广州中转机 + sync.sh 只有它在国内,能直传又拍云

CNB 的构建节点就在国内(腾讯云)。 那句注释的复活条件直接成立 —— 中转机、COS、artifact 传递全都失去了存在理由。


二、目标架构

写作 / 本地
    │  git push
    ▼
  CNB(腾讯云,构建节点在国内 4 核 8 GiB)
    │  一次构建,三条出口
    ├──► 又拍云存储(境内源站)  upx sync + upx purge
    │        │  多吉云回源自动跟随
    │        └──► 多吉云 CDN 刷新(API 精确刷新)
    │
    └──► EdgeOne Pages(境外)   npx edgeone pages deploy --area overseas

保留不动(零改动):

  • 又拍云存储 imzql(境内源站)
  • 多吉云 CDN(境内解析,回源又拍云)
  • EdgeOne Pages 项目 hugo-blog(境外解析,--area overseas)
  • 域名解析、ICP 备案、Ying 主题、content 内容

替换掉:

  • Gitea Actions build job → CNB 流水线
  • act_runner → 不需要
  • COS / Gitea artifact 传递 → 不需要(含那串 v3/v4 坑)
  • 广州中转机 sync.sh 轮询 → 不需要
  • deploy.yml 里 8 处 github.server_url 兼容分支 → 不需要(另写 .cnb.yml)

三、CNB 能力核对(全部官方文档核实)

能力 值 对本项目的意义
构建节点 默认 8 核 16 GiB(内存 = CPU 核数 × 2) 全面优于广州机 2 核 1.9 G
免费额度 160 核时/月(0.125 元/核时超出) 见下方换算
构建最大时长 20 小时;单任务无输出 10 分钟 / 超 1 小时触发超时(可声明,上限 12 小时) 传 372MB 绰绰有余
自定义环境 docker.image 或 docker.build(Dockerfile) 可固定 hugo 0.128.2 extended
密钥管理 密钥仓库 + imports 导入环境变量 又拍云/多吉云/EdgeOne 凭据不落仓库
定时任务 支持 cron,最小间隔 5 分钟,时区 Asia/Shanghai 可替代每日定时构建
出网限制 无白名单限制(官方:出口 IP 动态,明确不建议白名单) 又拍云 / 多吉云 / EdgeOne API 都可直连
Docker-in-Docker 支持(services: docker) 备用
官方集成 腾讯云官方有 CNB → EdgeOne 的部署文档示例(npx edgeone makers deploy) 已验证路径

额度换算(关键决策数字)

规格 内存 每次构建耗(按 10 分钟) 160 核时可用次数
8 核 16 GiB 1.33 核时 120 次/月
4 核 8 GiB 0.67 核时 240 次/月
2 核 4 GiB 0.33 核时 480 次/月

你的触发频率是「每天 1 次定时 + 每次发文」≈ 60–90 次/月。

建议用 4 核 8 GiB —— 240 次/月的余量,即使构建耗时翻倍到 20 分钟也还有 120 次。hugo 构建吃 CPU(4 核够并行编译),upx sync 吃网络不吃 CPU。


四、落地骨架

4.0 第一步:仓库迁移与「双远端推送」(CNB 主仓 + GitHub 备份)

目标形态

origin → https://cnb.cool/<组织>/<仓库>   ← 主仓(fetch + push),CNB 构建由它触发
gh     → git@github.com:zqlit/blog.git   ← 备份(push),只存档不参与构建
gitea  → 自建 Gitea                       ← 暂时留着当只读参考,链路稳定后手工删

一条命令推两段:git pushall(等价于 git push origin main; git push gh main)。

★ 为什么不用「一个 remote 挂多个 pushurl」

现有 origin 就是那种写法(pushurl 同时挂了 GitHub 和 自建 Gitea)。但它有个被忽视的缺陷,我做了实测:

场景 结果
两个 pushurl 都可用 ✅ 两个都收到,一次 push 搞定
第一个失败 ❌ git 立即中止,第二个根本不会被推

也就是说:CNB 推失败时,GitHub 那份备份也不会更新 —— 备份的意义就没了。 所以拆成两个独立 remote,别名里用 ; 而不是 &&,保证互不阻塞。

脚本(已就位,可直接用)

bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库>

它会先把你现有的远端配置(含明文凭据)备份到 .workbuddy-backup/git-remotes.<时间戳>.txt, 再重建 origin/gh 并注册 pushall 别名 —— 可回退。

★ CNB 不支持 SSH(官方明确)

SSH Not Supported —— 平台不支持 SSH 协议访问。 认证方式:用户名固定填 cnb,密码填访问令牌。 —— docs.cnb.cool《Git Address and Authentication Instructions》

这跟 GitHub 的 SSH 不同,所以两段要各用各的认证:

git config --global credential.helper manager   # Windows 的 Git Credential Manager
# 首次 push 时:用户名 cnb,密码 = 访问令牌(个人设置 → 访问令牌 → 勾「代码仓库/读写」)

仓库地址直接用仓库页地址即可(https://cnb.cool/组/仓),带不带 .git 都支持。

建仓顺序不能乱(官方要求)

  1. 微信扫码注册 → 2. 微信扫码实名认证(未实名禁止建仓/推码)→ 3. 先建组织(CNB 无「个人私有仓库」,仓库必须挂在组织下)→ 4. 建仓库 → 5. 跑上面的脚本 + push

⚠️ 推 CNB 之前必须先知道的敏感文件(已审计)

这些文件当前就被 git 跟踪,会在首次 push 时连全部历史一起进 CNB:

文件 内容 状态
write-server/nginx/ssl/privkey.pem 真实 TLS 私钥(EC P-256,BEGIN PRIVATE KEY),2026-06-22 起在库 已跟踪
.env SILICONFLOW_API_KEY、ZHIPU_API_KEY 已跟踪
GITEA_SECRETS.md Gitea 相关凭据说明 已跟踪
content/posts/2024/.../setup-secrets.png 疑似密钥配置截图 已跟踪

好消息:已核实 https://github.com/zqlit/blog 返回 Page not found,仓库不可公开访问 → 私钥没有被公开暴露,无需紧急处置。

但两件事建议顺手做掉:

  1. CNB 仓库建成「私有」 —— 这些文件在私有仓里影响可控
  2. git rm --cached 停止继续跟踪(历史里的删不掉,但至少不再新增):
    git rm --cached .env GITEA_SECRETS.md write-server/nginx/ssl/privkey.pem write-server/nginx/ssl/fullchain.pem
    
    ⚠️ 注意:.gitignore 里其实已经有 .env / .env.* 规则 —— 它们失效的原因不是规则写错, 而是忽略规则对「已跟踪」文件无效,所以必须显式 git rm --cached。 (另:.gitignore 里那条 write\.env 是写错的 —— gitignore 中 \ 是转义符, 它实际匹配文件名 write.env 而非 write/.env;幸好 write/.gitignore 里有 .env* 兜住了。)

不建议现在做历史清理(git filter-repo):会重写 955 个提交、让 GitHub 备份随之分叉, 收益(去掉一个未公开的私钥)远小于风险。正确做法是轮换凭据而不是重写历史。


4.1 deploy/Dockerfile(构建环境)

CNB 默认镜像不含 hugo / upx,写一个专属镜像固定版本:

FROM node:22-bookworm-slim

# 换国内 apt 源(CNB 只明示加速 Docker/NPM/Maven,apt 默认源在国内节点可能慢)
RUN set -eux; \
    for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources; do \
      if [ -f "$f" ]; then \
        sed -i -e 's|deb.debian.org|mirrors.aliyun.com|g' \
               -e 's|security.debian.org|mirrors.aliyun.com|g' "$f"; \
      fi; \
    done; \
    apt-get update && apt-get install -y --no-install-recommends \
      ca-certificates curl tar zstd python3 xz-utils git \
    && rm -rf /var/lib/apt/lists/*

# Hugo extended 0.128.2(linux/amd64)——★ 随仓库携带,构建时零跨境下载
COPY bin/linux/hugo /usr/local/bin/hugo
RUN chmod +x /usr/local/bin/hugo && hugo version

# 又拍云 upx
RUN set -eux; \
    curl -fsSL -o /tmp/upx.tgz \
      "https://collection.b0.upaiyun.com/softwares/upx/upx_0.4.9_linux_amd64.tar.gz"; \
    tar -xzf /tmp/upx.tgz -C /usr/local/bin upx; \
    chmod +x /usr/local/bin/upx; \
    rm /tmp/upx.tgz

# EdgeOne CLI
RUN npm i -g edgeone@latest

CMD ["hugo", "version"]

✅ 已实测(2026-10-04):

  1. hugo 二进制改为随仓库携带(bin/linux/hugo,84MB,进 git 后仅 24MB)。实测国内节点直连 GitHub 拉这个 release 要 127 秒(本机只要 7 秒),而每次构建都要付这个成本 → 自带更稳。
  2. 又拍云 upx 的下载源是 collection.b0.upaiyun.com(国内 CDN),直连稳定,保留在线安装。
  3. 架构不能混用:E:\Hugo\hugo.exe 是 windows/amd64(本地预览用),CNB 构建节点是 linux/amd64(bin/linux/hugo),两者互不替代。
  4. 若以后主仓改选 Gitee(单仓 500MB),这 84MB(git 内 24MB)会挤占配额,届时改用 CNB/又拍云对象存储托管该二进制,别放仓库。
  5. apt 源改阿里云镜像:CNB 官方只明示加速「Docker / NPM / Maven 镜像」,Debian 默认 apt 源不在承诺范围内,而构建节点在国内 → 换阿里云兜底,避免构建卡在 apt-get update(首轮构建时留意这一步耗时)。

4.2 .cnb.yml(流水线)

main:
  push:
    - name: 构建并发布(境内外双线路)
      imports:
        # 密钥仓库(私有),存放 UPYUN_* / DOGE_* / EDGEONE_API_TOKEN
        - https://cnb.cool/<你的组织>/<密钥仓库>/-/blob/main/secrets.yml
      runner:
        cpus: 4                      # 4 核 → 8 GiB
      docker:
        build: deploy/Dockerfile
      stages:
        - name: Hugo 构建
          script: |
            set -euo pipefail
            hugo version
            python3 scripts/add_draft_to_hidden.py
            rm -rf public resources/_gen
            hugo --minify --gc
            BUILD_HASH=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')
            echo "$BUILD_HASH" > public/.build_hash
            echo "$BUILD_HASH" > public/build_hash.txt
            echo "$BUILD_HASH" > .build_hash.out
            echo "✅ 构建完成 hash=${BUILD_HASH:0:12} 文件数=$(find public -type f | wc -l)"

        - name: 同步到又拍云(境内源站)
          script: |
            set -euo pipefail
            upx login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD"
            upx sync public/ / -w 5 --delete
            upx put public/.build_hash /__build_hash || true
            echo "✅ 又拍云同步完成"

        - name: 刷新又拍云 CDN
          script: |
            set -euo pipefail
            # ★ 必须刷固定入口页:只刷首页会让 sitemap.xml / rss.xml /
            #   archives.html 一直发 CDN 里的旧副本(2026-10-01 实测复现)
            { echo "https://usj.cc/";
              for p in sitemap.xml rss.xml archives.html posts.html comment.html links.html; do
                echo "https://usj.cc/$p"
              done
            } > /tmp/purge.txt
            upx purge --list /tmp/purge.txt \
              || echo "⚠️ purge 失败,降级不影响源站已更新"

        - name: 刷新多吉云 CDN
          script: |
            set -euo pipefail
            python3 - <<'PY'
            import hmac, hashlib, json, os, urllib.request
            AK, SK = os.environ['DOGE_AK'], os.environ['DOGE_SK']
            SITE = 'https://usj.cc'
            P = '/cdn/refresh/add.json'
            urls = [SITE + '/', SITE + '/sitemap.xml', SITE + '/rss.xml',
                    SITE + '/archives.html', SITE + '/posts.html']
            def refresh(rtype, u, label):
                body = json.dumps({'rtype': rtype, 'urls': json.dumps(u)})
                sig = hmac.new(SK.encode(), (P + '\n' + body).encode(), hashlib.sha1).hexdigest()
                req = urllib.request.Request(
                    'https://api.dogecloud.com' + P, data=body.encode(),
                    headers={'Authorization': 'TOKEN %s:%s' % (AK, sig),
                             'Content-Type': 'application/json'}, method='POST')
                with urllib.request.urlopen(req, timeout=30) as r:
                    print('%s -> %s' % (label, r.read().decode()[:200]))
            refresh('path', [SITE + '/'], '目录刷新')
            refresh('url', urls, '精确刷新 %d 条' % len(urls))
            PY

        - name: 部署 EdgeOne(境外线路)
          script: |
            set -euo pipefail
            test -n "$EDGEONE_API_TOKEN" || { echo "缺 EDGEONE_API_TOKEN"; exit 1; }
            npx edgeone pages deploy ./public -n hugo-blog -t "$EDGEONE_API_TOKEN" --area overseas

        - name: 上报部署状态
          script: |
            set -euo pipefail
            H=$(cat .build_hash.out)
            curl -s --max-time 20 -X POST \
              "https://api.200181.xyz/api/deploy-status?token=${RSS_API_TOKEN}" \
              -H "Content-Type: application/json" \
              -d "{\"hash\":\"$H\",\"phase\":\"built\"}" || true
            curl -s --max-time 20 -X POST \
              "https://api.200181.xyz/api/deploy-notify?token=${RSS_API_TOKEN}" \
              -H "Content-Type: application/json" \
              -d "{\"hash\":\"$H\"}" || true

五、两个必须复刻的细节(sync.sh 里的血泪)

5.1 purge 不能只刷首页

sync.sh 的注释记录了一个实测坑:

历史写法只有 upx purge "$SITE/",即只刷首页 —— sitemap.xml / rss.xml / archives.html / posts.html 这些「老路径」会一直发 CDN 里的旧副本。 2026-10-01 实测复现:中转机产物 sitemap.xml 已含新文章(22812B), CDN 上那份却仍是 7/24 的 22592B;手动 purge 后立刻变新。

所以骨架里固定刷「首页 + 6 个入口页」。进阶做法(完整复刻 sync.sh 的精确刷新): 把文件清单存到又拍云 /__manifest.txt,每次 CNB 拉下来与本次产物比对,得出「变化 URL」精确刷新。 好处是新增文章时只刷那几页;代价是多维护一个清单文件。建议先跑固定入口版,观察 1–2 周再决定要不要加。

5.2 又拍云并发参数

sync.sh 用 -w 10 --strong。当时失败是因为跨境(境外 runner 直传),国内直传理论无此问题。 但建议首次仍保守用 -w 5(去掉 --strong,它会把请求量放大),实测稳定后再往上调。


六、砍掉清单

砍掉 说明
act_runner 不再需要自建 runner
Gitea artifact 传递链 含 upload-artifact@v3/v4 那串坑
腾讯云 COS 中转存储不再需要
广州中转机 sync.sh + 1Panel 定时任务 注意:只停任务,机器上还有 mysql/openresty 等 7 个容器
deploy.yml 的 8 处 github.server_url 补丁分支 .cnb.yml 重新写
两套冗余通知(TG/飞书/邮件三选一) 独立小优化

发布链路:7 环节 → 3 环节(写作 → CNB 构建 → 双线上线) 自维护构建节点:1 台 → 0 台


七、前置动作与待核实项

必须先确认(按重要性)

  1. ★ EdgeOne 从国内上传 372MB 的耗时。现在部署是从境外 runner 上传(同区域快),改到 CNB(国内)后是跨境上传到 EdgeOne 国际站。这是本方案最大的未知数,必须实测一次。
  2. ★ CNB 构建节点能否顺畅拉取 github.com 的 hugo release。不行就换国内镜像源,或把 hugo 二进制预置进 CNB 制品库。
  3. 又拍云 v0 接口从 CNB 出口的真实表现。国内直传预期无碍,但需实测确认(这是整个方案的立论基础,务必第一次就验)。
  4. 主仓放哪:
    • CNB 当主仓 → 最简,Gitea 可退役,写作后台改 origin
    • Gitea 当主仓 + push mirror 到 CNB → 写作零改动,代价是多留一台机器
  5. 多吉云刷新配额(sync.sh 里有 200 条截断逻辑,CNB 版本固定入口页不触及)。

建议的验证步骤(不动任何现有东西)

  1. 建一个 CNB 测试仓库(或就用一个分支),放最小化 .cnb.yml + Dockerfile
  2. 跑通「hugo build」→ 确认镜像与构建时长
  3. 单独测「upx sync 到一个测试 bucket」→ 确认国内直传可行
  4. 单独测「edgeone pages deploy 到测试项目」→ 拿到跨境上传耗时
  5. 四项全绿,才动正式仓库

八、与之前几份方案的关系

方案 结论 与本方案的关系
代码源与构建平台选型.md CNB 首选(100GiB 仓库 + 160 核时) 本方案是它的落地细化
Gitee方案评估.md 可用,但需瘦身 588MB → 500MB 用 CNB 就不用瘦身
EdgeOne双区域方案评估.md 建议 -a global 或双项目 双区域照旧,只是构建方换成 CNB
阿里云ESA评估.md 2000 文件上限卡死 不适用
精简方案-只留CF和Hugo.md 只留 CF 会丢又拍云+多吉云 本方案保留双 CDN,复杂度收益同样大

核心区别:之前几份在讨论「CDN 选谁」;本方案证明你不需要换 CDN,只需要换掉「构建 + 搬运」那一层。而这一层恰恰是复杂度的大头。