You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 lines
20 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 51 | 基于 GitHub Actions 的 CI 实战
你好,我是孔令飞。这是本专栏正文的最后一讲了,恭喜你坚持到了最后!
在Go项目开发中我们要频繁地执行静态代码检查、测试、编译、构建等操作。如果每一步我们都手动执行效率低不说还容易出错。所以我们通常借助CI系统来自动化执行这些操作。
当前业界有很多优秀的CI系统可供选择例如 [CircleCI](https://circleci.com/)、[TravisCI](https://travis-ci.org/)、[Jenkins](https://github.com/jenkinsci/jenkins)、[CODING](https://coding.net/)、[GitHub Actions](https://github.com/features/actions) 等。这些系统在设计上大同小异为了减少你的学习成本我选择了相对来说容易实践的GitHub Actions来给你展示如何通过CI来让工作自动化。
这一讲我会先介绍下GitHub Actions及其用法再向你展示一个CI示例最后给你演示下IAM是如何构建CI任务的。
## GitHub Actions的基本用法
GitHub Actions是GitHub为托管在github.com站点的项目提供的持续集成服务于2018年10月推出。
GitHub Actions具有以下功能特性
* 提供原子的actions配置和组合actions的workflow配置两种能力。
* 全局配置基于[YAML配置](https://help.github.com/en/articles/migrating-github-actions-from-hcl-syntax-to-yaml-syntax)兼容主流CI/CD工具配置。
* Actions/Workflows基于[事件触发](https://help.github.com/en/articles/events-that-trigger-workflows)包括Event restrictions、Webhook events、Scheduled events、External events。
* 提供可供运行的托管容器服务包括Docker、VM可运行Linux、macOS、Windows主流系统。
* 提供主流语言的支持包括Node.js、Python、Java、Ruby、PHP、Go、Rust、.NET。
* 提供实时日志流程,方便调试。
* 提供[平台内置的Actions](https://help.github.com/en/articles/about-github-actions#discovering-actions-in-the-github-community)与第三方提供的Actions开箱即用。
### GitHub Actions的基本概念
在构建持续集成任务时我们会在任务中心完成各种操作比如克隆代码、编译代码、运行单元测试、构建和发布镜像等。GitHub把这些操作称为Actions。
Actions在很多项目中是可以共享的GitHub允许开发者将这些可共享的Actions上传到[GitHub的官方Actions市场](https://github.com/marketplace?type=actions)开发者在Actions市场中可以搜索到他人提交的 Actions。另外还有一个 [awesome actions](https://github.com/sdras/awesome-actions) 的仓库里面也有不少的Action可供开发者使用。如果你需要某个 Action不必自己写复杂的脚本直接引用他人写好的 Action 即可。整个持续集成过程,就变成了一个 Actions 的组合。
Action其实是一个独立的脚本可以将Action存放在GitHub代码仓库中通过`<userName>/<repoName>`的语法引用 Action。例如`actions/checkout@v2`表示`https://github.com/actions/checkout`这个仓库tag是v2。`actions/checkout@v2`也代表一个 Action作用是安装 Go编译环境。GitHub 官方的 Actions 都放在 [github.com/actions](https://github.com/actions) 里面。
GitHub Actions 有一些自己的术语,下面我来介绍下。
* workflow工作流程一个 `.yml` 文件对应一个 workflow也就是一次持续集成。一个 GitHub 仓库可以包含多个 workflow只要是在 `.github/workflow` 目录下的 `.yml` 文件都会被 GitHub 执行。
* job任务一个 workflow 由一个或多个 job 构成,每个 job 代表一个持续集成任务。
* step步骤每个 job 由多个 step 构成,一步步完成。
* action动作每个 step 可以依次执行一个或多个命令action
* on一个 workflow 的触发条件,决定了当前的 workflow 在什么时候被执行。
### workflow文件介绍
GitHub Actions 配置文件存放在代码仓库的`.github/workflows`目录下,文件后缀为`.yml`,支持创建多个文件,文件名可以任意取,比如`iam.yml`。GitHub 只要发现`.github/workflows`目录里面有`.yml`文件,就会自动运行该文件,如果运行过程中存在问题,会以邮件的形式通知到你。
workflow 文件的配置字段非常多,如果你想详细了解,可以查看[官方文档](https://docs.github.com/cn/actions/reference/workflow-syntax-for-github-actions)。这里,我来介绍一些基本的配置字段。
1. `name`
`name`字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名。
```yaml
name: GitHub Actions Demo
```
2. `on`
`on`字段指定触发 workflow 的条件,通常是某些事件。
```yaml
on: push
```
上面的配置意思是,`push`事件触发 workflow。`on`字段也可以是事件的数组,例如:
```yaml
on: [push, pull_request]
```
上面的配置意思是,`push`事件或`pull_request`事件都可以触发 workflow。
想了解完整的事件列表,你可以查看[官方文档](https://docs.github.com/en/actions/reference/events-that-trigger-workflows)。除了代码库事件GitHub Actions 也支持外部事件触发,或者定时运行。
3. `on.<push|pull_request>.<tags|branches>`
指定触发事件时,我们可以限定分支或标签。
```yaml
on:
push:
branches:
- master
```
上面的配置指定,只有`master`分支发生`push`事件时,才会触发 workflow。
4. `jobs.<job_id>.name`
workflow 文件的主体是`jobs`字段,表示要执行的一项或多项任务。
`jobs`字段里面,需要写出每一项任务的`job_id`,具体名称自定义。`job_id`里面的`name`字段是任务的说明。
```yaml
jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job
```
上面的代码中,`jobs`字段包含两项任务,`job_id`分别是`my_first_job`和`my_second_job`。
5. `jobs.<job_id>.needs`
`needs`字段指定当前任务的依赖关系,即运行顺序。
```yaml
jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]
```
上面的代码中,`job1`必须先于`job2`完成,而`job3`等待`job1`和`job2`完成后才能运行。因此,这个 workflow 的运行顺序为:`job1`、`job2`、`job3`。
6. `jobs.<job_id>.runs-on`
`runs-on`字段指定运行所需要的虚拟机环境,它是必填字段。目前可用的虚拟机如下:
* ubuntu-latest、ubuntu-18.04或ubuntu-16.04。
* windows-latest、windows-2019或windows-2016。
* macOS-latest或macOS-10.14。
下面的配置指定虚拟机环境为`ubuntu-18.04`。
```yaml
runs-on: ubuntu-18.04
```
7. `jobs.<job_id>.steps`
`steps`字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定下面三个字段。
* `jobs.<job_id>.steps.name`:步骤名称。
* `jobs.<job_id>.steps.run`:该步骤运行的命令或者 action。
* `jobs.<job_id>.steps.env`:该步骤所需的环境变量。
下面是一个完整的 workflow 文件的范例:
```yaml
name: Greeting from Mona
on: push
jobs:
my-job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Print a greeting
env:
MY_VAR: Hello! My name is
FIRST_NAME: Lingfei
LAST_NAME: Kong
run: |
echo $MY_VAR $FIRST_NAME $LAST_NAME.
```
上面的代码中,`steps`字段只包括一个步骤。该步骤先注入三个环境变量,然后执行一条 Bash 命令。
8. `uses`
`uses` 可以引用别人已经创建的 actions就是上面说的 actions 市场中的 actions。引用格式为`userName/repoName@verison`,例如`uses: actions/setup-go@v1`。
9. `with`
`with` 指定actions的输入参数。每个输入参数都是一个键/值对。输入参数被设置为环境变量,该变量的前缀为 `INPUT_`,并转换为大写。
这里举个例子:我们定义 `hello_world` 操作所定义的三个输入参数(`first_name`、`middle_name` 和 `last_name`),这些输入变量将被 `hello-world` 操作作为 `INPUT_FIRST_NAME`、`INPUT_MIDDLE_NAME` 和 `INPUT_LAST_NAME` 环境变量使用。
```yaml
jobs:
my_first_job:
steps:
- name: My first step
uses: actions/hello_world@master
with:
first_name: Lingfei
middle_name: Go
last_name: Kong
```
10. `run`
`run`指定执行的命令。可以有多个命令,例如:
```yaml
- name: Build
run: |
go mod tidy
go build -v -o helloci .
```
11. `id`
`id`是step的唯一标识。
## GitHub Actions的进阶用法
上面我介绍了GitHub Actions的一些基本知识这里我再介绍下GitHub Actions的进阶用法。
### 为工作流加一个Badge
在action的面板中点击`Create status badge`就可以复制Badge的Markdown内容到README.md中。
之后我们就可以直接在README.md中看到当前的构建结果
![图片](https://static001.geekbang.org/resource/image/45/af/453a97b0776281873dee5671c53347af.png?wh=1280x765)
### 使用构建矩阵
如果我们想在多个系统或者多个语言版本上测试构建就需要设置构建矩阵。例如我们想在多个操作系统、多个Go版本下跑测试可以使用如下workflow配置
```yaml
name: Go Test
on: [push, pull_request]
jobs:
helloci-build:
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
go_version: [1.15, 1.16]
os: [ubuntu-latest, macOS-latest]
steps:
- name: Set up Go ${{ matrix.go_version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
id: go
```
上面的workflow配置通过`strategy.matrix`配置了该工作流程运行的环境矩阵(格式为`go_version.os``ubuntu-latest.1.15`、`ubuntu-latest.1.16`、`macOS-latest.1.15`、`macOS-latest.1.16`。也就是说会在4台不同配置的服务器上执行该workflow。
### 使用Secrets
在构建过程中,我们可能需要用到`ssh`或者`token`等敏感数据,而我们不希望这些数据直接暴露在仓库中,此时就可以使用`secrets`。
我们在对应项目中选择`Settings`\-> `Secrets`,就可以创建`secret`,如下图所示:
![图片](https://static001.geekbang.org/resource/image/c0/d3/c00b11a1709838c1a205ace7976768d3.png?wh=1920x1046)
配置文件中的使用方法如下:
```yaml
name: Go Test
on: [push, pull_request]
jobs:
helloci-build:
name: Test with go
runs-on: [ubuntu-latest]
environment:
name: helloci
steps:
- name: use secrets
env:
super_secret: ${{ secrets.YourSecrets }}
```
secret name不区分大小写所以如果新建secret的名字是name使用时用 `secrets.name` 或者 `secrets.Name` 都是可以的。而且,就算此时直接使用 `echo` 打印 `secret` , 控制台也只会打印出`*`来保护secret。
这里要注意你的secret是属于某一个环境变量的所以要指明环境的名字`environment.name`。上面的workflow配置中的`secrets.YourSecrets`属于`helloci`环境。
### 使用Artifact保存构建产物
在构建过程中我们可能需要输出一些构建产物比如日志文件、测试结果等。这些产物可以使用Github Actions Artifact 来存储。你可以使用[action/upload-artifact](https://github.com/actions/upload-artifact) 和 [download-artifact](https://github.com/actions/download-artifact) 进行构建参数的相关操作。
这里我以输出Jest测试报告为例来演示下如何保存Artifact产物。Jest测试后的测试产物是coverage
```yaml
steps:
- run: npm ci
- run: npm test
- name: Collect Test Coverage File
uses: actions/upload-artifact@v1.0.0
with:
name: coverage-output
path: coverage
```
执行成功后我们就能在对应action面板看到生成的Artifact
![图片](https://static001.geekbang.org/resource/image/4c/66/4c4a8d6aec12a5dd1cdc80d238472566.png?wh=1280x208)
## GitHub Actions实战
上面我介绍了GitHub Actions的用法接下来我们就来实战下看下使用GitHub Actions的6个具体步骤。
**第一步,**创建一个测试仓库。
登陆[GitHub官网](https://github.com/),点击**New repository**创建,如下图所示:
![图片](https://static001.geekbang.org/resource/image/6d/a0/6d76d02f0418671a32f5346fccf616a0.png?wh=1920x810)
这里,我们创建了一个叫`helloci`的测试项目。
**第二步,**将新的仓库 clone 下来,并添加一些文件:
```bash
$ git clone https://github.com/marmotedu/helloci
```
你可以克隆[marmotedu/helloci](https://github.com/marmotedu/helloci),并将里面的文件拷贝到你创建的项目仓库中。
**第三步,**创建GitHub Actions workflow配置目录
```bash
$ mkdir -p .github/workflows
```
**第四步,**创建GitHub Actions workflow配置。
在`.github/workflows`目录下新建`helloci.yml`文件,内容如下:
```yaml
name: Go Test
on: [push, pull_request]
jobs:
helloci-build:
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
environment:
name: helloci
strategy:
matrix:
go_version: [1.16]
os: [ubuntu-latest]
steps:
- name: Set up Go ${{ matrix.go_version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Tidy
run: |
go mod tidy
- name: Build
run: |
go build -v -o helloci .
- name: Collect main.go file
uses: actions/upload-artifact@v1.0.0
with:
name: main-output
path: main.go
- name: Publish to Registry
uses: elgohr/Publish-Docker-GitHub-Action@master
with:
name: ccr.ccs.tencentyun.com/marmotedu/helloci:beta # docker image 的名字
username: ${{ secrets.DOCKER_USERNAME}} # 用户名
password: ${{ secrets.DOCKER_PASSWORD }} # 密码
registry: ccr.ccs.tencentyun.com # 腾讯云Registry
dockerfile: Dockerfile # 指定 Dockerfile 的位置
tag_names: true # 是否将 release 的 tag 作为 docker image 的 tag
```
上面的workflow文件定义了当GitHub仓库有`push`、`pull_request`事件发生时会触发GitHub Actions工作流程流程中定义了一个任务Job`helloci-build`Job中包含了多个步骤Step每个步骤又包含一些动作Action
上面的workflow配置会按顺序执行下面的6个步骤。
1. 准备一个Go编译环境。
2. 从[marmotedu/helloci](https://github.com/marmotedu/helloci)下载源码。
3. 添加或删除缺失的依赖包。
4. 编译Go源码。
5. 上传构建产物。
6. 构建镜像并将镜像push到`ccr.ccs.tencentyun.com/marmotedu/helloci:beta`。
**第五步,**在push代码之前我们需要先创建`DOCKER_USERNAME`和`DOCKER_PASSWORD` secret。
其中,`DOCKER_USERNAME`保存腾讯云镜像服务CCR的用户名`DOCKER_PASSWORD`保存CCR的密码。我们将这两个secret保存在`helloci` Environments中如下图所示
![图片](https://static001.geekbang.org/resource/image/c0/d3/c00b11a1709838c1a205ace7976768d3.png?wh=1920x1046)
**第六步,**将项目push到GitHub触发workflow工作流
```bash
$ git add .
$ git push origin master
```
打开我们的仓库 Actions 标签页可以发现GitHub Actions workflow正在执行
![图片](https://static001.geekbang.org/resource/image/1a/8a/1afb7860d68635c5e3eaba4ff8da208a.png?wh=1920x691)
等workflow执行完点击 **Go Test** 进入构建详情页面,在详情页面能够看到我们的构建历史:
![图片](https://static001.geekbang.org/resource/image/a4/95/a4b83a122379db4f2fe9538afdfb5a95.png?wh=1920x701)
然后,选择其中一个构建记录,查看其运行详情(具体可参考[chore: update step name Go Test #10](https://github.com/marmotedu/helloci/actions/runs/1144156183)
![图片](https://static001.geekbang.org/resource/image/48/4f/481f64aabccf30ed61d0a7c85ab30d4f.png?wh=1920x1084)
你可以看到,`Go Test`工作流程执行了6个Job每个Job执行了下面这些自定义Step
1. Set up Go 1.16。
2. Check out code into the Go module directory。
3. Tidy。
4. Build。
5. Collect main.go file。
6. Publish to Registry。
其他步骤是GitHub Actions自己添加的步骤`Setup Job`、`Post Check out code into the Go module directory`、`Complete job`。点击每一个步骤,你都能看到它们的详细输出。
## IAM GitHub Actions实战
接下来我们再来看下IAM项目的GitHub Actions实战。
假设IAM项目根目录为 `${IAM_ROOT}`它的workflow配置文件为
```bash
$ cat ${IAM_ROOT}/.github/workflows/iamci.yaml
name: IamCI
on:
push:
branchs:
- '*'
pull_request:
types: [opened, reopened]
jobs:
iamci:
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
environment:
name: iamci
strategy:
matrix:
go_version: [1.16]
os: [ubuntu-latest]
steps:
- name: Set up Go ${{ matrix.go_version }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Run go modules Tidy
run: |
make tidy
- name: Generate all necessary files, such as error code files
run: |
make gen
- name: Check syntax and styling of go sources
run: |
make lint
- name: Run unit test and get test coverage
run: |
make cover
- name: Build source code for host platform
run: |
make build
- name: Collect Test Coverage File
uses: actions/upload-artifact@v1.0.0
with:
name: main-output
path: _output/coverage.out
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build docker images for host arch and push images to registry
run: |
make push
```
上面的workflow依次执行了以下步骤
1. 设置Go编译环境。
2. 下载IAM项目源码。
3. 添加/删除不需要的Go包。
4. 生成所有的代码文件。
5. 对IAM源码进行静态代码检查。
6. 运行单元测试用例,并计算单元测试覆盖率是否达标。
7. 编译代码。
8. 收集构建产物`_output/coverage.out`。
9. 配置Docker构建环境。
10. 登陆DockerHub。
11. 构建Docker镜像并push到DockerHub。
IamCI workflow运行历史如下图所示
![图片](https://static001.geekbang.org/resource/image/2b/b0/2b542f9101be0c3a83576fb99bf882b0.png?wh=1920x844)
IamCI workflow的其中一次工作流程运行结果如下图所示
![图片](https://static001.geekbang.org/resource/image/e9/6a/e9ebf13fdb6e4f41a1b00406e646ec6a.png?wh=1920x887)
## 总结
在Go项目开发中我们需要通过CI任务来将需要频繁操作的任务自动化这不仅可以提高开发效率还能减少手动操作带来的失误。这一讲我选择了最易实践的GitHub Actions来给你演示如何构建CI任务。
GitHub Actions支持通过push事件来触发CI流程。一个CI流程其实就是一个workflowworkflow中包含多个任务这些任务是可以并行执行的。一个任务又包含多个步骤每一步又由多个动作组成。动作Action其实是一个命令/脚本,用来完成我们指定的任务,如编译等。
因为GitHub Actions内容比较多这一讲只介绍了一些核心的知识更详细的GitHub Actions教程你可以参考 [官方中文文档](https://docs.github.com/cn/actions)。
## 课后练习
1. 使用CODING实现IAM的CI任务并思考下GitHub Actions和CODING在CI任务构建上有没有本质的差异
2. 这一讲我们借助GitHub Actions实现了CI请你结合前面所学的知识实现IAM的CD功能。欢迎提交Pull Request。
这是我们这门课的最后一次练习题了,欢迎把你的思考和想法分享在留言区,也欢迎把课程分享给你的同事、朋友,我们一起交流,一起进步。