
GitHub Action:让静态网站实现定时发布
定时发布:静态网站的遗憾
定时发布顾名思义就是在特定时间发布文章或内容,是CMS系统的基础功能。目前,几乎所有动态博客框架都能实现,例如Ghost、WordPress等,然而,对于静态类型的博客来说,由于缺乏能够后台定时的服务端,使得无法将定时发布变为原生功能,需要站长各凭本事实现,较为繁琐。
为什么研究这个问题?因为本博客就是一个用 Zola SSG 工具构建的静态网站,从开始移植主题建站到现在,我已经构思并调研了好几种方案,最终决定基于 GitHub Action 来实现定时发布。
几种实现方式对比
这里我先把可以实现静态网站定时发布的几种方式列举出来,并对比关键指标。
方法 | 描述 | 心智负担 | 可靠性 | 运维难度 | 费用 |
---|---|---|---|---|---|
自己干 | 通过手机闹钟、手机日历提醒自己该发布啦 | 高 | 中,节假日不保证 | 低 | 无 |
别人干 | 安排一个工具人帮我发布 | 低 | 中,取决于报酬,节假日不可用 | 低 | 高 |
自建发布服务 | 预先构建网站,然后在本地或云端自建定时服务部署网站 | 中 | 高 | 中 | 取决于是否使用付费云资源 |
自建CI触发服务 | 在本地或云端自建定时服务触发CI接口构建网站,然后自动发布 | 低 | 高 | 低 | 取决于是否使用付费云资源 |
使用平台原生功能 | 依托平台能力定时触发CI实现定时发布 | 低 | 高 | 低 | 取决于是否使用付费云资源 |
通过对比我们可以发现:
- 靠人力(自己干、别人干)实现定时发布在可靠性上难以保证,因为定时发布的一个重要使用场景就是在节日等重要节点发布信息,其稳定性至关重要。
- 依托外部服务(自建发布服务、自建 CI 触发服务)是符合直觉的,但是会引入额外的复杂度,同事可能产生一定的费用。
- 最理想的情况是使用的工具或平台自带定时发布的功能(使用平台原生功能),但是遗憾的是,大部分平台并不具备这类功能。
为了实现最好的效果,我决定尽可能通过平台自身实现定时发布的功能。
通过 GitHub Action 实现定时发布
本博客使用 GitHub 私有仓库存储代码和文章,也就是 Git-Based 静态博客,那么接下来的工作势必要围绕 GitHub 生态进行。
在此之前,我已经通过 GitHub Action (GitHub 的持续集成 CI 服务) 实现了主分支的自动部署,若在此之上添加定时发布,必然是围绕分支合并和 CI 流水线做文章。
比较典型的方案是用新分支存放内容 + 定时合并 PR。将要发布的内容存在新分支中,到达特定时间后,自动合并,然后自动触发发布流水线,以此实现定时发布。
沿着该技术路线,我搜集到一些能够定时合并 PR 的 GitHub 扩展工具,然而,这类工具基本都需要使用付费高级版。比如有一款工具的免费版额度是每月 4 次定时合并,我想这很难满足博主的需求,毕竟定时发布除了用于发文章,也可用于发版。
最终,我找到了 Merge Schedule,可以完全基于 GitHub Action 实现定时合并 PR 进而实现自动发布。
前提条件
本方案需要具备以下条件,我想大部分静态网站应该都能轻易达成。
- 网站存放在 GitHub,并且是 Git-Based 静态博客。
- 代码仓库主分支已经实现自动构建、部署的流水线,能做到更新代码后自动部署到生产环境。
- 重要更新(如文章发布、重大改版等)采用分支 + PR 的工作流,能做到通过合并 PR 即可完成所有发布内容的更新工作。
基本原理
经过深入研究,现将技术原理梳理了大概:
- 设置专门用于定时合并 PR 的 GitHub Action 流水线,并设置定时执行,如每小时
cron: '0 * * * *'
。 - 在计划定时合并的 PR 描述中添加定时器指令,如
/schedule 2022-06-08T09:00:00.000Z
。 - 每次运行定时合并流水线时,检查所有打开的 PR,如果时间匹配,则调用 API 完成 PR 合并。
这种方式巧妙的利用了 GitHub Action 的定时触发机制,实现了原生的定时合并 PR,进而做到定时发布。
部署过程
这个方案的部署十分简单,只需新增一个 GitHub Action 定义文件即可。
在代码库新建文件.GitHub/workflows/merge-schedule.yml
,用于存放流水线定义。
在文件中粘贴以下内容,这里设置了时区为国内Asia/Shanghai
,合并方式为merge
,可以根据需要自行修改。
name: Merge Schedule
# see https://GitHub.com/gr2m/merge-schedule-action
on:
pull_request:
types:
- opened
- edited
- synchronize
schedule:
# https://crontab.guru/every-hour
- cron: '0 * * * *'
jobs:
merge_schedule:
runs-on: ubuntu-latest
steps:
- id: merge-schedule
uses: gr2m/merge-schedule-action@v2
with:
# Merge method to use. Possible values are merge, squash or
# rebase. Default is merge.
merge_method: merge
# Time zone to use. Default is UTC.
time_zone: 'Asia/Shanghai'
# Require all pull request statuses to be successful before
# merging. Default is `false`.
require_statuses_success: 'true'
# Label to apply to the pull request if the merge fails. Default is
# `automerge-fail`.
automerge_fail_label: 'merge-schedule-failed'
env:
GitHub_TOKEN: ${{ secrets.GitHub_TOKEN }}
可以在官方文档中找到更高级的用法,例如如何绕过存储库安全规则等。
最后,将代码提交并推送到 GitHub 的主分支就大功告成了。
下面分享一种更高级的流水线配置,可以在定时发布之后推送通知。
这里我以 Bark 推送服务为例,需要提前配置好 GitHub Action 的 Secret 变量Bark_KEY
,将推送服务的 API KEY 放进去。
name: Merge Schedule
# see https://GitHub.com/gr2m/merge-schedule-action
on:
pull_request:
types:
- opened
- edited
- synchronize
schedule:
# https://crontab.guru/every-hour
- cron: '0 * * * *'
jobs:
merge_schedule:
runs-on: ubuntu-latest
steps:
- id: merge-schedule
uses: gr2m/merge-schedule-action@v2
with:
# Merge method to use. Possible values are merge, squash or
# rebase. Default is merge.
merge_method: merge
# Time zone to use. Default is UTC.
time_zone: 'Asia/Shanghai'
# Require all pull request statuses to be successful before
# merging. Default is `false`.
require_statuses_success: 'true'
# Label to apply to the pull request if the merge fails. Default is
# `automerge-fail`.
automerge_fail_label: 'merge-schedule-failed'
env:
GitHub_TOKEN: ${{ secrets.GitHub_TOKEN }}
# run if there is any merged pull request
- name: notification
uses: shink/bark-action@v2
if: ${{ fromJson(steps.merge-schedule.outputs.merged_pull_requests)[0] != null }}
with:
key: ${{ secrets.Bark_KEY }}
title: GitHub PR 已自动合并
body: 稍后请检查部署结果
sound: alarm
isArchive: 0
automaticallyCopy: 0
这个流水线会在成功合并 PR 后,给用户推送通知,这样用户可以等待自动部署完成,然后访问网站看看结果。
使用流程
至此,我们可以按照直观的逻辑步骤来完成定时发布。
- 基于主分支创建用于定时发布的子分支,一般是文章分支。
- 完成内容更新,并推送到 GitHub。
- 在 GitHub 创建 PR 以合并到主分支,并在 PR 的描述内容最末尾,添加指明时间的以下内容。
- 特定日期,如
/schedule 2022-06-08
- 特定日期的整点时间,如
/schedule 2022-06-08T09:00:00.000Z
代表2022年6月8日的上午9点,时间格式遵循ISO 8601
规范 - 下一次整点时间
/schedule
- 特定日期,如
- 等待自动发布~
- 如果需要修改或者取消,直接删除或编辑 PR 的描述内容即可,你甚至可以简单粗暴地删除 PR ~
方案的局限性与解决措施
虽然我们实现了不出 GitHub 就能定时发布,但是仍存在一些局限性:
- 可选时间颗粒度较粗,默认只能按照整点时间进行配置,如8点、9点等。虽然可以满足大部分应用场景了,但存在用户希望半点发布,如8点30分,可以通过自行调整流水线定时触发频率来结婚,如每半小时。目前另外尚未测试是否存在时间的模糊匹配或近似匹配,
- 定时器的设置仍然较为繁琐,在 PR 中需要输入较长的指令,缺少正确性校验,可能导致失败。针对该问题,可以通过接下来在定时合并流水线中添加新增通知来解决。
- 缺少发布结果的预览,用户对最终效果缺少掌控。针对该问题,需要进一步定制 CI 流水线,增加子分支的自动部署,预览最终效果,还可以为项目增加预生产分支,确保合并后没有 Bug,不过这些实现起来就比较繁琐了。
总结与展望
这个方案本质是持续集成流水线的高级应用,依托 GitHub Action 的定时功能实现定时发布。围绕流水线可以添加许多有意思且实用的功能,例如构建预览环境、提前的错误检查等,这就需要发挥大家的想象力了。
其实,我最初的构想是基于免费的函数服务(如 Cloudflare Worker)实现定时触发功能,但是函数服务的冷启动问题可能会影响定时触发的稳定性,所以这个方案暂时搁置了,如果大家有解决方案欢迎在评论区分分享~
这篇文章提供了一种基于 GitHub Action 的静态网站定时发布方法,目标是为了让静态网站能享受到动态网站的功能与便利。接下来,我会继续研究如何提升静态网站的维护者体验,敬请期待!