GitHub Actions 初体验

春丽 春丽说 2018-12-16

前两天朋友说他拿到了 GitHub Actions 的 Beta 体验资格, 给我们开了一个私有库让我们体验. 就在今天(划掉)两天前, 我也收到邮件了, 终于可以深度体验一番, 并期望可以通过这篇文章回答一下 GitHub Actions 是什么、能做什么以及对我们日常开发流程会有什么价值.

背景

在 GitHub Actions 的宣传网页上, 是这么定义 GitHub Actions 的:


Focus on what matters: code. With GitHub Actions you can automate your workflow from idea to production.

— From GitHub https://github.com/features/actions

即可以通过 GitHub Actions 让你的日常开发流程很多事情可以自动化起来. 对于自动化, 开发者一定非常熟悉, 从日常离不开的 CI/CD (自动集成测试和持续部署), 到被无数人膜拜和吐槽的 Jenkins. 我们通过各种各样的自动化工具来节省人力和确保代码质量.

而 Workflow 的价值不仅局限于开发者, 从最早的 IFTTT(https://ifttt.com/) 到 iOS 12 的捷径(https://support.apple.com/zh-cn/guide/shortcuts/welcome/ios), 再到专注企业市场的 zapier(https://zapier.com), 我们可以通过 workflow 串联各种服务, 让我们的生活自动化, 从而节省更多的时间. 从某种角度来说, 这也是一种编程, Everyone can code.

GitHub Actions 原理

为了更好的写作这篇文章, 确保不出错, 我去研读了一下 GitHub Actions 的文档(https://developer.github.com/actions/). 总结如下:

  • 通过 Docker 隔离

  • GitHub 提供慷慨的计算资源(每个 workflow 会独享 1 核虚拟 CPU, 3.75GB 内存, 网络权限和 100GB 的磁盘空间, 感觉比我的 VPS 性能还好)

  • 代码上下文(可以获取触发 Actions 的代码上下文环境, 比如当前分支)

  • workflow 有一定限制

  • 提供一种新的配置语言和一个体验非常现代化的 workflow 编辑器

  • 支持在代码仓库的 Settings 中增加不超过 100 个密钥(比如 Slack, S3 等), 供 workflow 使用; 也可以直接使用可视化编辑器增加

  • Workflow 的 Action 支持设置 Icon 和 Icon 背景色

通过文档可以看出, GitHub Actions 拥有非常大的灵活度和足够的计算资源, 足够做很多很多以前需要靠 CI 或者通过配置 Jenkins 来实现的功能. GitHub Actions 会不会抢占一些第三方类似服务的市场不好说, 但是对这份充足而且目前免费的计算资源, 会让很多开源项目和小公司技术团队的开发流程更灵活和自动化.

当然, GitHub 在发布 Actions 的时候, 也明确说过会防止被滥用当成服务器计算机资源. 目前 GitHub workflow 本身有一些限制, 比如每个 workflow, 包括排队和执行时间, 最多 58 分钟; 每个 workflow 最多可以包含 100 个 Action; 每个仓库同一时刻只能运行两个 workflow (足够慷慨了, 这相当于你同时占据了 GitHub 近 8G 的内存资源).

挺期待 GitHub 会推出 Actions 服务增强包, 就类似于 GitLFS(https://git-lfs.github.com/) 扩展包一样, 这样我们就可以把钱花在 GitHub 上, 而不用各种第三方服务绕来绕去.

GitHub Actions 的文档没有写具体的原理是什么, 但是通过使用 Docker 隔离和文档中关于 Runtime 的描述, GitHub 会在每个 workflow 触发的时候, 给 workflow 创建一台虚拟机, 作为 workflow 的执行环境(Runtime), 然后之后一系列的 Actions 是在这个 Runtime 环境里面执行 docker 命令, pull 对应的 docker 镜像, 执行脚本. 而这一切都需要在 58 分钟之内完成.

在我日常的开发工作中, 目前有两个痛点, 一个是跑测试, 一个是 build docker 镜像执行部署. 目前这两个我都是在我的 MacBook Pro 上跑的, 原因无他, 就是穷, CI 资源其实都很昂贵, 毕竟计算资源都是钱堆出来的.

GitHub Actions 演练

Talk is cheap, show me the code.

为了更好的体验 GitHub Actions, 我设计了一个简单的应用场景: 使用 Jekyll 搭建一个博客, 然后部署到 surge.sh 上; 通过 GitHub Pull&Request 编写博客文章, Merge 之后通过 GitHub Actions 同步到 surge.sh 上.

Surge.sh(https://surge.sh/)提供免费的静态网页托管, 但是不提供 Ruby 运行环境, 所以无法在 Surge.sh 上使用 Jekyll.

而 Jekyll(https://jekyllrb.com/)是 GitHub 创始人 Tom Preston Werner(
https://github.com/mojombo)用 Ruby(https://www.ruby-lang.org/en/)开发的一款静态网页程序. Jekyll 需要 Ruby 环境进行执行, 将你用 Markdown (https://zh.wikipedia.org/wiki/Markdown)等标记语言编写的文章转化为静态网页.



当然, 如果你不用 Surge.sh 来托管你的 Jekyll 网站的话, 你可以直接用 GitHub 提供的 Pages 服务(https://pages.github.com/), 国内的 Coding.net 也提供类似的服务(https://coding.net/pages).

Welcome Jekyll

首先确保你的电脑已经装上了 Ruby 和 Jekyll, 因为这个不是重点, 所以不再赘述. 有需要的同学可以自行去官网看一下.

首先我们在本地创建我们的新项目 awesome-actions

jekyll new awesome-actions

进到项目目录, 之后执行

jekyll server

然后通过浏览器打开 http://127.0.0.1:4000, 可以看到我们的网站已经可以正常运行了.



Push to GitHub

我们现在 GitHub 上建立一个我们的私有仓库, 因为 GitHub Actions 目前仍然在公测中, 只支持私有仓库.

之后我们把代码 push 到这个仓库中:

git init git add . git commit -m "first commit" 
git remote add origin git@github.com:chunlea/awesome-actions.git 
git push -u origin master

第一个 Workflow

在 GitHub 打开我们刚刚新建的仓库, 可以看到我们的代码已经 push 上去了. 点击 「Actions」进入 GitHub Actions 的可视化编辑器.


让我们来创建第一个 workflow, 我们的 workflow 有两步, 第一步是用 Jekyll build 我们的网站到静态网页, 第二步是用 surge.sh 的 CLI 将 Jekyll Build 的结果发布到 surge.sh 上.

GitHub Actions 可视化编辑器的页面如图:


按照我们的设想, 我们的 Actions 如图:



首先对每一次 push, 我们都会执行 Actions. 但是因为我们不想我们在分支里面操作随便 push 一次都会影响最终部署的网站, 所以使用了 GitHub 默认提供的 Filters for GitHub Actions, 并且通过 branch master 限定只有在 master 分支上的时候, 才会继续 Actions 的执行.

当 Filters 成功之后, Actions 会执行到下一个 Action: Build static website, 这里我们使用了 docker://jekyll/jekyll, 即 Jekyll 官方的 docker 镜像. 但是在第一次测试的时候, 我遇到了这个问题:


这是因为 GitHub Actions 在文档中明确了我们应该在 Dockerfile 里面要用默认的 root 用户, 但是 Jekyll 的 docker 镜像里面默认使用的是 Jekyll 用户, 导致没有权限创建 _site 目录. 解决方案是在代码库里面创建 _site 目录, 然后在里面加一个 .gitkeep的空文件, 把它加到代码库里面即可.

在 Build static website 这一步里, 通过设置



runs 来执行 jekyll build 命令. build 之后的结果会在 workflow 的 runtime 系统的 /github/workspace/_site 目录, 供下一步 Action 来使用.

下一步的 Action 是 Publish to awesome-actions.surge.sh, 由于没有找到合适的官法的 surge.sh docker 镜像, 我选择自己写一个 Dockerfile 来实现. Surge.sh 的 CLI 是一个 npm 包, 我们只需要使用 node 的官方镜像即可.


# .github/actions/surge/Dockerfile
FROM node:lts
RUN npm install --global surge 

Surge.sh CLI 支持通过设置 SURGE_TOKEN 和 SURGE_LOGIN 来实现自动化部署, 避免登录流程. 获取 surge.sh token 可以通过在本机安装 surge CLI, 然后执行 surge token.

请不要直接把 token 放在代码或者环境变量里面, GitHub 建议通过 secrets 来保存此类敏感信息, 并且可以在执行过程中通过环境变量来访问. 参考: https://developer.github.com/actions/creating-workflows/storing-secrets/

保存, push 代码, 然后默默的等待 Actions 跑完.



GitHub 在 Actions 页面显示了每一步的流程和已经执行完成的步骤的 Log 日志.



访问 https://awesome-actions.surge.sh/ 发现网站已经部署成功.



Actions 加持的 Jekyll 发布流程

因为有 GitHub Actions 来实现自动化发布, 我只需要进入 master 分支的 _post 目录, 点击 Create new file




然后按照 Jekyll 的约定, 输入新文章的名称 2018-12-16-actions-works.md



然后输入文字的内容, 记得加入文章的元信息. 之后选择最下面的 Create a new branch for this commit and start a pull request. 输入一个 branch 的名字, 或者就使用默认的 branch 名称. 点击 Propose new file



GitHub 会自动带我们到 Open a pull request 页面, 点击 Create pull request, 会自动创基一个新的 PR. 而之后你需要做的, 就是 merge 这个 PR, 或者继续改动你的文章, 等到打磨完美之后再 merge.




当我们 merge 之后, 我们可以去 Actions 页面, 看到我们的 workflow 正在有条不紊的执行.



等待大概 1 分钟之后, 我们再去刷新我们的网站, 可以看到新的文章已经出现在了文章列表页面. 我们的第一个 GitHub Actions workflow 工作正常.


workflow 源代码

最后附上这个例子的 workflow 源代码, 因为 GitHub Actions 目前还在有限的测试中, 公开库不支持 Actions. 留下源代码供参考:

workflow "Publish to Surge.sh" {
on = "push"
resolves = ["Publish to awesome-actions.surge.sh"]
}
action "Filters for GitHub Actions" {
uses = "actions/bin/filter@e96fd9a"
args = "branch master"
}
action "Build static website" {
uses = "docker://jekyll/jekyll"
needs = ["Filters for GitHub Actions"]
runs = "jekyll build"
}
action "Publish to awesome-actions.surge.sh" {
uses = "./.github/actions/surge"
needs = ["Build static website"]
runs = "surge _site awesome-actions.surge.sh"
secrets = ["SURGE_TOKEN"]
env = {
SURGE_LOGIN = "ichunlea@me.com"
}
}

总结

在编写上面例子的时候, 我也发现了 GitHub Actions 的局限性, 太难调试. 因为你不能像绝大多数 CI 一样, 可以 SSH 到执行环境手工调试, 只能靠输出日志来排查问题. GitHub Actions 整个系统的缓存机制是依赖于 docker 的镜像层缓存, 靠 GitHub 内网的 docker register 来实现快速的拉取. 很难评价这种设计的优劣, 但是至少 GitHub 把更多的可能性交给了开发者.

我已经决定后面逐渐把公司新项目迁移到 GitHub Actions, 通过 GitHub Actions 配合 Kubernetes 实现自动化运维, 从而节省更多的时间关注于业务本身. 在整套系统搭建完成并稳定之后, 我会写一篇文章介绍我们开发工作流程, 敬请期待.