Post

GitHub Pages 的暗礁

距离博客的框架设计分离出来成为一个独立项目,已经过去一段时间了,所以想把框架项目变成更加纯粹的 Jekyll Theme,项目名称也改为了 Chirpy,为项目部署一个新的 Demo 站点也是水到渠成的事。 那么主题项目 Chirpy(也就是博客的架构)的 Travis-CI 流程要在原来础上增加一个步骤:部署到 Chirpy 的 Demo 站点。

合并下来,Chirpy 线上 CI 的工作流就变成:

  1. 部署主题 Demo
  2. 部署个人博客

GitHub Pages 掉链子

本来一切看起来十分清晰流畅,然而 GitHub Pages 却不甘心让部署变得如此平凡。在一个 Travis-CI build 里推送更新到上述两个 GitHub 仓库,后一个仓库(博客仓库)的 Pages 内容并不会更新。

先来分析一下问题:博客仓存放的内容是静态文件,无需 GitHub 编译构建,所以不会存在构建错误的问题,而且前后 GitHub 也没有发送任何错误提示的邮件。接着,追溯到仓库文件,Commit 记录显示文件已更新到最新版本。那么就只有一个可能:GitHub Pages  服务器没有按照仓库文件的更新的内容去重新构建站点

GitHub 网页上的 Commit 记录证明了这个推断,最新的 Commit 没有被 Pages 构建。这时我想也许是因为太多用户使用 Pages 服务,导致服务器构建队列拥挤,产生延时。结果耐心等了一个小时,还是不会有任何变化:

commit-not-buildt

经过 Pages 构建的 Commit 记录是这样的,会有个绿色的小钩:

commit-after-buildt

进一步试验后发现,单独更新博客仓库是可以触发 Pages 刷新的,唯独在同一时间段(间隔很短,几秒钟之内)更新名下的两个 Pages 仓库,就会出现后一个不被构建的问题。是不是 GitHub 有限制机制呢?就这个问题,我向 GitHub 客服反馈了两次,而客服作为代理人,需要将问题反馈到工程师团队,在内部完成问题追踪后才有答案。因为不想干等,所以我选择亲自出手解决。

API 救场

为了拯救被遗忘的构建,可以通过 GitHub Pages API 1 去触发指定仓库的 Pages 构建。通过 curl 发送 POST 请求:

1
2
3
$ curl -H "Authorization: token <GH_TOKEN>" \
    -H "Accept: application/vnd.github.mister-fantastic-preview+json" \
    -X POST https://api.github.com/repos/<USERNAME>/<REPO>/pages/builds

其中 GH_TOKEN 来自 GitHub 账户的 Personal access tokens ,下文相同。这样就不用去 GitHub 网页上傻傻的检查部署了。

进一步细化

上述提到,相同时间内推送更新若干个 Pages 仓库文件,第一个仓库总是可以正常触发 Pages 构建的,所以应该添加一层检查机制,不必每个仓库都发送指令。先观察仓库是否按照最新 Commit 被自动构建,如果不是,才需要 API 发起构建。

GitHub Pages 可以通过 API 查询仓库最新的 Pages 构建状态2,通过 curl 发送 GET 请求:

1
2
$ curl -H "Authorization: token <GH_TOKEN>" \
    https://api.github.com/repos/<USERNAME>/<REPO>/pages/builds/latest

以本站仓库为例,响应内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "url": "https://api.github.com/repos/cotes2020/cotes2020.github.io/pages/builds/140717683",
  "status": "built",
  "error": {
    "message": null
  },
  "pusher": {
    "login": "cotes2020",
    "id": 11503861,
    "node_id": "MDQ6WXNlcjExMxcxMzQw",
    "avatar_url": "https://avatars0.githubusercontent.com/u/11502841?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/cotes2020",
    "html_url": "https://github.com/cotes2020",
    "followers_url": "https://api.github.com/users/cotes2020/followers",
    "following_url": "https://api.github.com/users/cotes2020/following{/other_user}",
    "gists_url": "https://api.github.com/users/cotes2020/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/cotes2020/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/cotes2020/subscriptions",
    "organizations_url": "https://api.github.com/users/cotes2020/orgs",
    "repos_url": "https://api.github.com/users/cotes2020/repos",
    "events_url": "https://api.github.com/users/cotes2020/events{/privacy}",
    "received_events_url": "https://api.github.com/users/cotes2020/received_events",
    "type": "User",
    "site_admin": false
  },
  "commit": "196791a3b4026178807b70f93cdb189c6bffad74",
  "duration": 29445,
  "created_at": "2019-08-12T15:21:01Z",
  "updated_at": "2019-08-12T15:21:31Z"
}

需要关注的是 statuscommit

  • "status": "built" 表示站点构建已经完成。
  • "commit": "196791a..." 表示站点是依照 196791a... 这个版本的文件去构建的。

掌握这些信息后,只要在 status: built 的状态下,检查 Pages 最后构建的 commit 和仓库最后一个 commit 的 SHA-1,如果不相同,就可以确定构建没有启动,需要 API 发送指令触发构建,bash 版的实现逻辑如下:

1
2
3
4
5
sleep 10    # Wait 10 seconds, let GitHub Pages go first

if [[ $status == 'built' && $repo_last_commit != $pages_commit ]]; then
    # Send build request to GitHub Pages
fi

先休眠 10 秒,是为了给 GitHub Pages 留出启动时间。

通过调用 GitHub Pages API 可以确保自动化部署的站点页面得到实时更新,但是心中依然对 GitHub 在处理多个仓库同时更新时出现的必然性遗漏表示不解。

迟来的回应(2019-08-30 更新)

经历大半个月,前天 (Aug 28, 1:34 AM UTC) GitHub Developer Support 终于发来一封邮件给出实质性答复:

The trouble you’re facing is due to a limitation in GitHub Pages, with regard to parallel builds triggered by a single user account. Due to certain technical limitations, we can only successfully process one site build at a time, per user account. Our engineers are working to remove this limitation, but we can’t promise if or when this work will be completed.

As your setup involves pushing to (and thus triggering builds for) more than one repository at a time, this can result in some build requests colliding, stalling, and silently failing.

根据上述回应,证明了我的猜想,这是 GitHub Pages 服务器对并行构建实施了限制,另外还提到工程师准备去掉这个限制,但是不保证何时完成。

接着邮件中还给出解决建议:

To avoid this trouble, I’d suggest adding a delay to your automation process, such that the repositories are pushed in sequence, rather than in parallel. You could use a simple delay of a couple of minutes to achieve this, but a most deterministic approach would be to listen for PageBuildEvent, which will only be returned once a build has completed:

https://developer.github.com/v3/activity/events/types/#pagebuildevent

基本和我之前的解决方案一样,用 API 精确检测 GitHub Pages build 的状态,但其实还是可以把处理流程改的更贴合官方的建议:先等待前一个 GitHub Pages 构建完成了(经过实测,静态页面仓库可以在 20 秒以内完成构建),再推送下一个仓库,这样就省去发送 API 触发构建的那一步。

总结

从 8 月 16 日 到 28 日这十来天没有收到任何回应,感觉 GitHub 就打算这样不了了之,而我也做好心理准备了,反正已经琢磨出解决方案了。可后来官方对我等免费用户不放弃的态度,着实令人感动。接下来,就是坐等 GitHub Pages 服务器正式解除并行推送的限制,可以让项目的 CI 代码再精简几行。

参考资料

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.