自从 18 年 GitHub 被 Microsoft 收购后,围绕服务开发者,陆陆续续推出了一些十分贴心的服务,比如:

服务解决问题
Codespaces代码编写
Actions自动测试
Packages托管分发
Code security漏洞探测
Discussions用户交流
Sponsors盈利
Pages网站托管

不得不说微软在下一盘大棋,不过今天并不是讨论微软的商业战略(我也不懂🙃),主要是想介绍两个 GitHub 的使用心得,以及它的一些替代品。

Actions 缓存

为了加速 CI 的执行,缓存是非常有效的手段。保证缓存的最高利用率,是使用缓存时最需要关注的点。比如在把整个 target 目录缓存后,何时更新这个缓存?最好的方式是在有依赖变更时,对于 Rust 来说就是 Cargo.lock 有变化,对于 Node 来说就是 package.lock 有变化。

下面我们看看如何利用 cache 这个组件来实现上述效果,主要有三个的参数:

  • key 缓存 ID,可以把整个缓存空间看出是一个 KV 对
  • path 即缓存哪些路径
  • restore-keys 这里指定了当 key 没有命中时,可以选择的缓存

下面用缓存 Rust 项目作为示例进行讲解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
jobs:
  test:
    timeout-minutes: 20
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
      - name: Cache Crates
        uses: actions/cache@v3
        env:
          cache-name: rust
        with:
          path: |
            ./target
            ~/.cargo            
          key: ${{ runner.os }}-${{ env.cache-name }}-debug-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ env.cache-name }}-debug-${{ hashFiles('rust-toolchain.toml') }}-
            ${{ runner.os }}-${{ env.cache-name }}-debug-
            ${{ runner.os }}-${{ env.cache-name }}            
      - run: cargo test

首先看 key 的定义,分为五个部分,其中四个是变量,分别是:

  1. 变量,操作系统
  2. 变量,缓存名
  3. 固定值, debug 为了与 release 编译区分开
  4. toolchain 文件 Hash
  5. Cargo.lock 文件 Hash

对于一个 cache 来说,当使用 key 命中时,称为 cache hit ,这时在 Actions 结束时,不需要更新 cache。因此设计这个 key 时,需要注意两点:

  1. key 要能够表示缓存变更,上面四个变量即可以确定一个完整有效缓存
  2. 能够表示缓存变更的字段可能有多个,经常变动的需要放在最后面,上面四个变量就遵循了这个顺序

第二点的设计需要与 restore-key 一起来看。当 Actions 执行时,如果 key 没有命中(说明缓存内容有变),这时缓存还是可以用的,比如项目中有 10 个依赖,只是对其中一个做了升级,那么其余 9 个的缓存还是有效的,这时缓存的选取就用到 restore-key 了。因为匹配是按照 begin_with 的方式,所以 restore-key 的长度是依次递减。缓存选择方式如下:

  • 如果只是 Cargo.lock 变更了,那么就用第一个 restore-key 指向的缓存,因为它的缓存有效率最高
  • 如果 Cargo.lock 与 toolchain 文件都变更了,那么就用第二个,原因同上
  • 如果当前是 release 编译,不管 Cargo.lock、toolchain 文件有无变更,都只能用第三个

只要 key 没有命中,在 Actions 执行结束后,就会去更新缓存,便于下次直接命中。可以看到,通过 restore-key 的巧妙设计,可以保证当前最有效的缓存一直是最“热”的。Actions 两次执行示意图:

第一次执行

第二次执行

注意事项

为了安全、成本,GitHub 对缓存做了如下几点的限制:

  • 如果缓存是在 main 分支上产生,那么所有 main 分支衍生的其他分支都可以用到;但是,同时由 main 衍生的分支 B1、B2,他们产生的缓存,是不能共享的
  • 对于 7 天没有访问的缓存,会被自动删掉
  • 缓存空间只有 10G,超过会按照访问时间 LRU 淘汰

基于 Artifact 部署 Pages

早期的 Pages 需要把网站源码发布到一个 gh-pages 的分支上,对于纯手写 HTML 的网站来说还算可以,但是对于使用工具生成的网站来说,就有些浪费,因为这个分支根本不需要版本管理,不仅会污染仓库,还会导致仓库体积变大。

现在 GitHub 解决了这个问题,允许使用 Actions 中构建出来的产物(Artifact)直接部署网站,无需新建分支,毫无疑问这种方式更优雅。使用方式两步:

  1. 配置 Pages 生成方式为基于 Action
    Pages 配置
  2. 根据 hugo.yml 编写自己项目的 action

这个功能虽然还是 beta 阶段,但我在使用过程比较顺滑,没有遇到什么问题,推荐读者使用。

GitHub 替代品

GitHub 无疑已经是现在开发者的必备,但还是希望读者能意识到它本质还是家商业公司,在提供这么多好用、免费的功能同时,肯定会在其他方面找回来,比如前不久收费的副驾驶员 Copilot,毕竟天下没有免费的午餐。甚至以后在 VSCode 中出现 Azure 广告我都不会觉得惊讶,也不担心,毕竟我用 Emacs 🤪(图片来源

Evil GitHub

而且,GitHub 在 19 年禁用过伊拉克用户的访问,当时的 CEO 在 Twitter 上是这么回应的:

It is painful for me to hear how trade restrictions have hurt people. We have gone to great lengths to do no more than what is required by the law, but of course people are still affected. GitHub is subject to US trade law, just like any company that does business in the US.

在俄罗斯攻打乌克兰时,也有群众呼吁禁用俄罗斯的用户访问,只不过最后没有实施而已

当然,不仅仅是上述原因,重要文件多处备份永远都是非常必要的。对于追求 FOSS(Free and open-source software),不想被 GitHub/GitLab 等商业公司 lock in 的读者,推荐了解下面几个托管平台:

  • Codeberg.org,由一家位于德国的非盈利组织创办,整个生态都是基于开源软件构建,和 GitHub 体验类似,Issue、Pages、Packages、Release 都有,CI 虽然还在测试中,但是申请开通也比较简单,是迁移 GitHub 的首选
  • Why I Use SourcehutDrew DeVault 的个人项目,100% 开源。与 GitHub 等平台最主要的区别是基于邮件来沟通,项目托管收费
  • Why Choose Savannah,主要托管 GNU 项目,虽然最近也发布了 nongnu 的支持,但是使用门槛还是相对较高:

    • 要求项目必须用 GPL 类协议
    • 不能依赖 non-free 的软件;
    • 如果两周以上不活跃,可能会被删号

个人觉得,追求纯 FOSS 有些属于软件开发领域的意识形态,属于吃饱没事干才会考虑的问题,但人就是这样的物种,多多少少都有些自己的坚持。