基于 Wrangler 的 Cloudflare Pages 持续集成/部署 (CI/CD)
Cloudflare Pages 不支持直接集成饭桶集线器和饭桶实验室以外的远程饭桶服务器。但是,用 wrangler CLI 上传静态网页是被支持的^1,这个过程可以在私有的 CI/CD Runner 上执行。
此处的 git 服务器是自建的 forgejo(gitea 也适用),与 Woodpecker CI 共同作用。Woodpecker 的搭建可以看这里。
打镜像
Woodpecker 的 workflow 操作均在 Docker 容器中进行。想在其中调用特殊的命令行程序,事先构建一个镜像会节省很多时间。
wrangler
wrangler 是一个 Node.js 包,不妨用 node 打个镜像。
Dockerfile
FROM node:20-alpine3.18
WORKDIR /tmp
RUN npm install wrangler -g
构建
docker build . -t wrangler-node
mkdocs-material
mkdocs-material 需要添加 jieba 来优化中文搜索,然而原版镜像锁死了 ENTRYPOINT,不如自己打镜像。
Dockerfile
FROM python:3.11-alpine3.18
WORKDIR /tmp
RUN pip install --no-cache-dir mkdocs-material jieba
构建
docker build . -t mkdocs-material-jieba
以上 DockerFile 都锁定了版本,未来可能会出现安全性问题,请注意及时更新。
此外,为减小镜像体积,使用基于 alpine 的镜像。
设置 Secrets
Secrets 可以让你安全地将 Token 等重要凭据添加在环境变量中。Woodpecker 的文档中提及了 secrets 的用法。
secrets 的作用范围可以是单个 repo、组织内的 repo, 或者整个实例的所有 repo. 在对应层次(repo/org/global)的设定中都可以看到 secrets 的设置。
点击 add secret 即可添加新的 secret. 设置好 name value 和 scope 就能在 workflow 里面引用了。
需要注意的是,Woodpecker 中的 secrets 命名为小写,但是在 command 中引用时为大写。
似乎是出于安全考虑,在修改 secret 时,无法看到之前填写的 value,如需修改可以用新的值覆盖,留空则不改变原有的值。
获取 Cloudflare API Token
新版 wrangler 的默认登录方式似乎是浏览器弹窗,但是在其他地方还是能找到 API Token 的用法。
前往 https://dash.cloudflare.com/profile/api-tokens 获取一个 API Token 用来读写 Cloudlfare Workers 的内容。
或者点击任意一个域名进入 overview, 也能看到 “Get your API token”, 并且在上方能看到 Account ID. (此时在地址栏也能看到 Account ID)
对于 Pages 的情形,直接使用 Edit Cloudflare Workers 的模板就可以了。其他诸如作用范围、Client IP 白名单和有效时间等属性,自行斟酌。
拿到 Token 以后记得及时复制,并添加到 secrets 中。
编写 pipeline
随后在 repo 里面添加 .woodpecker/*.yml
或者 .woodpecker.yml
作为 pipeline 配置文件。
对于这次的情况,基本上就三个 workflow,按部就班写出来就行
- clone
- build
- deploy
Woodpecker 的 clone 操作就是 pull 外加自动初始化 submodule 的过程。不太需要修改了。因此只需要处理后两步。
mkdocs-material
构建过程一目了然,除了添加一个聊胜于无的 robots.txt 以外。
publish 步骤的指令也只有一句。CLOUDFLARE_ACCOUNT_ID
和 CLOUDFLARE_API_TOKEN
已经由 secrets 引入环境变量了。
注意,这里的 project name 最好事先确定好,这会在建立 Cloudflare Pages 项目时使用到。
--branch-name
已隐去,直接部署到 Production Branch. 如果指定的是 Production Branch 以外的值,会以 preview 形式部署。^3
Cloudflare Pages 默认的 Production Branch 是 main
, 如需更改,需要使用能够读写 Workers 的 API Token, 执行一次以下指令,以 master 为例:^4
(请不要忘记 export 前面的空格,防止 Token 进入 shell history)
export CLOUDFLARE_ACCOUNT_ID=<YOUR_ACCOUNT_ID>
export CLOUDFLARE_API_TOKEN=<YOUR_API_TOKEN>
export PROJECT_NAME=<YOUR_PROJECT_NAME>
export PRODUCTION_BRANCH=master
curl --request PATCH \
--url "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/pages/projects/$PROJECT_NAME" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--data '{ "production_branch": "'"$PRODUCTION_BRANCH"'" }'
回到 pipeline 配置文件:
# .woodpecker/default.yml
steps:
- name: build
image: mkdocs-material-jieba:latest
commands:
- mkdocs build
- cp robots.txt site/
- name: publish
image: wrangler-node:latest
commands:
- npx wrangler pages deploy site --project-name=<YOUR_PROJECT_NAME> # --branch-name=master
secrets: [ cloudflare_account_id, cloudflare_api_token ]
# when:
# - branch: master
docsify
docsify 自身就是一个完整的静态网站了,直接送上去就可以。当然,因为我之前的一些历史遗留,把它做成了和 mkdocs 一样的结构。如下:
├── docker-compose.yml
├── Dockerfile
├── docs
│ ├── 00
│ │ └── README.md
│ ├── 01
│ │ └── index.md
│ ├── 02
│ │ └── index.md
│ ├── index.html
│ ├── README.md
│ └── _sidebar.md -> sidebar/_sidebar.md
├── robots.txt
└── sidebar
└── _sidebar.md
这里因为要推送到多个上游,在一个完整的 pipeline 里用到了不同的 Account ID 和 API Token, 因此定义了多个 id 和 token,用 export
指令赋值给 CLOUDFLARE_ACCOUNT_ID
和 CLOUDFLARE_API_TOKEN
这两个环境变量。当然也可以直接写在 command 最前面,但我暂时不想折腾 $
的 escape 问题。
同时,使用 when
可以限定各个 workflow 的触发条件。
# .woodpecker/default.yml
steps:
- name: rearrange
image: alpine
commands:
- rm docs/_sidebar.md
- cp sidebar/_sidebar.md docs/
- cp robots.txt docs/
- name: publish-to-1
image: wrangler-node:latest
commands:
- export CLOUDFLARE_ACCOUNT_ID=$ID1
- export CLOUDFLARE_API_TOKEN=$TOKEN1
- npx wrangler pages deploy docs --project-name=<PROJECT_NAME_1> # --branch-name=master
secrets: [ id1, token1 ]
when:
- branch: master
- name: publish-to-2
image: wrangler-node:latest
commands:
- export CLOUDFLARE_ACCOUNT_ID=$ID2
- export CLOUDFLARE_API_TOKEN=$TOKEN2
- npx wrangler pages deploy docs --project-name=<PROJECT_NAME_2> # --branch-name=master
secrets: [ id2, token2 ]
when:
- branch: master
我实际部署的方式可能更混乱一些,并且注意到最终部署的结果更像是一个 overlayfs, 还不好删除,非常奇妙。
部署
在 Cloudflare Dashboard 中点击左侧 “Workers & Pages” 进入,随后点击 “Create Application” 开始创建新项目。这是为了产生一个 Project Name, 否则无法上传资源。
随后选择应用类型,点击 “Pages” 后向下滑,点击 “Upload Assets”.
随后键入事先确定好的 Project Name 并点击 “Create Project”, 固化 Project Name.
随后进入上传页面。Project Name 已经固化,可以退回到 overview, 就能看到空的 Project.
此时,在 Woodpecker 中点击 “Add repository” 启用对应的 repo, 将 pipeline 文件 push 上去,即可触发构建。或者也可以点击 Run Pipeline 手动触发。
记得关注构建结果,只要最后 ci 结果是绿色的,应该就大功告成了。