前段时间我更新了我的分布式爬虫管理框架—— Gerapy
现在 DevOps 的理念可谓是相当火,其中 CI/CD(持续集成、持续部署)是必不可少的环节。有了它们,我们开发完软件之后,一些测试、构建、部署的环节就可以自动化完成了。
我开发的的这款分布式爬虫管理框架—— Gerapy,代码也是放在了 GitHub 上面,但在之前 GitHub 上面是缺少原生的 CI/CD 功能支持的,可能需要根据第三工具或者 Webhook 等来配合实现项目的自动测试、构建和部署。
比如我可能有这么一些需求:
•每次合并代码到 master 分支时,想测试这个项目能否在各个版本的 Python 环境下正常安装和运行。
•我为 Gerapy 新建了一个独立的 Repo,叫做 Gerapy/Gerapy,在 docs 文件夹下存放文档说明,但我还另外新建了一个 Repo 专门用来存放文档,叫做 Gerapy/Docs,希望能把 Gerapy/Gerapy 的 docs 子文件夹下的内容整个自动同步到 Gerapy/Docs 这个 Repo 的根目录。
•每次 Gerapy 发布新版本的时候,自动构建 Docker 镜像,并上传到 Docker Hub,打上 latest 标签和版本号标签。
•每次 master 分支提交代码的时候,自动构建 Docker 镜像,并上传到 Docker Hub,打上 master 标签,代表当前 master 分支版本。
上面的功能之前有一部分工作是手工操作的,有一部分是借助于第三方工具来自动操作的,感觉并不是一个很好的解决方案
在最近一段时间,GitHub 上面上线了 Actions 功能,它就是为 CI/CD 而生的,和 GitHub 项目原生紧密结合。然而几个月以来一直处于内测阶段。就在 11 月 13 日,GitHub Actions 功能正式上线了。
上线之后,我就开始正式使用这个功能了,是真的香!
上面的四个需求,我用 GitHub Actions 已经完全实现了自动化,非常简单方便。
接下来简单介绍下我的一些实现方式。

GitHub Actions
首先简单介绍下 GitHub Actions,其官方介绍页面为:https://github.com/features/actions,介绍语如下:
Automate your workflow from idea to production. GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.
简而言之就是提供了一个高效易用的 CI/CD 工作流,帮助我们自动构建、测试、部署我们的代码。

GitHub Actions 的官方文档可以见:https://help.github.com/en/actions/automating-your-workflow-with-github-actions,如果大家想好好研究下的话,一定要好好看看。
下面我就介绍我使用 GitHub Actions 实现上文所述的四个需求的方法。
自动测试
由于我开发的 Gerapy 是一个 Python Package,因此我看重的是测试它是否可以在各个 Python 平台下安装和正常使用,于是我新建了一个 GitHub Action,它会自动在项目目录下生成一个 .github/workflows/*.yml 文件,内容如下:
name: build
on:
push:
branches:
- master
- dev
jobs:
test:
runs-on:
- ubuntu-latest
strategy:
max-parallel: 3
matrix:
python-version: [3.5, 3.6, 3.7]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install .
- name: Run Gerapy
run: |
gerapy -v
gerapy init
cd gerapy
gerapy migrate
gerapy initadmin
其实在这里一个 Action 就是一个 YAML 文件,其后缀为 yml,它规定了一系列语法规则,我们根据它的语法规则写出一些工作流,在符合一定条件时,这些工作流会被触发,自动执行。
比如这里最开头,on 就是监听某个事件,其内容为 push,意思就是当 push 代码的时候,就会触发。再进一步地,这里定义了两个分支 master 和 dev。这什么意思呢?就是当我往 master 或者 dev 分支 push 代码的时候,我们定义的工作流就会执行。
下面的 jobs 就是工作流的定义了,包括在什么平台运行,具体执行什么步骤。
比如这里 runs-on 我就定义了在 ubuntu-latest 版本上运行,另外定义了一些并行策略和参数,比如这里就定义了 Python 的三个版本参数,在 3.5、3.6、3.7 版本上运行。

下面的 steps 就是具体执行哪些步骤了。第一步和第二步,我们可以看到它都有一个 uses 参数,内容都为 actions 开头,这就说明我们使用了 GitHub 提供的写好的 Action,我们只需要引用它的名字就能使用了。这两步运行完毕之后,Python 环境会被初始化,同时会从 GitHub Clone Gerapy 项目代码到本地。
在第三步和第四步,就是我自定义的 Task 了,这里自持直接写入 Shell 脚本。在这里我分了两步。
第三步 Install Dependencies 就是安装 pip 和 Gerapy 安装包,其中一句 pip install .
就是安装当前 Gerapy 目录下的内容到系统中,安装完成之后,就可以使用 gerapy 命令了。
于是第四步 Run Gerapy 就是测试了 gerapy 命令的一些初始化使用,包括初始化工作环境、数据库迁移、初始化账号等等,当然还有更多,比如运行某些测试,运行服务等等,这里我只把一些必要的内容写进去了。
好,基本内容就是这样。
保存这个 Action,命名为 build.yml,它会保存为 .github/workflows/build.yml 文件。同时在保存的时候,我们就相当于执行了一次 Push 任务,这时候我们就可以看到这个 Action 已经启动了,页面如下:

我们所定义的每一个步骤以及对应的执行结果都会显示在控制台中,一目了然。
可以看到这里初始化了三个版本的 Python 环境,同时都运行了其中的测试流程。如果测试成功,会打绿色的勾,如果失败,会提示红色的叉,并有邮件提示。
这样以来,一些自动化的测试就完成了!!!

同步文档到新的 Repo
接下来我这个需求可以说稍微有点奇葩了。
写项目免不了的要写文档,这里文档我是用 Sphinx 来写的,可以借助于 ReadTheDocs 自动构建并分发到 readthedocs.io 上面,类似这样子:

但文档的源代码我是放在了 Gerapy/Gerapy 这个 Repo 的 docs 文件夹,向 Scrapy 看齐,是这样子的:

但我想着还新建一个 Repo,来单独存放文档,比如我新建一个 Gerapy/Docs 这个 Repo,我在 Gerapy/Gerapy docs 子文件夹下的内容可以被自动同步到 Gerapy/Docs 根目录下面,这样我只需要往 Gerapy/Gerapy 上面提交代码,docs 子文件夹下面的内容变了,Gerapy/Docs 下面的内容也会跟着变。
那这个能不能做到呢?能!(我问你答,快乐神仙;自问自答,法力无边~~
这个流程可以分为四步:
•下载 Gerapy/Gerapy Repo 的源代码。•利用 git 的 subtree 命令将 docs 文件夹下的内容分离到新的分支。•将新分离的分支推送到 Docs 这个 Repo 下面。•推送 Docs 这个 Repo 到远程 Gerapy/Docs Repo。
这里面就有一个关键地方,那就是怎样无需密码将内容推送到远程 Gerapy/Docs 这个 Repo 下面,当然就是 SSH 了。(啊,超爽der)
那 SSH 的话应该怎么设置呢?我们首先要有一对公钥和私钥,这个我们用 ssh-keygen 命令自己生成就好了。
那接下来 Gerapy/Docs 里面需要存有公钥,怎么办呢?我们可以借助于 GitHub 提供的 Deploy Key 配置好公钥即可:


嗯,做好这两部分工作之后,接下来完善一下 yml 文件就好了,内容如下:
name: sync docs
on:
push:
branches:
- master
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Set SSH Environment
env:
DOCS_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$DOCS_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com > ~/.ssh/known_hosts
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
git config --global user.email "cqc@cuiqingcai.com"
git config --global user.name "Germey"
- name: Sync Docs of Gerapy
run: |
cd /tmp
git clone git@github.com:Gerapy/Docs.git docs
cd docs
git branch -D docs || true
git push origin --delete docs || true
git clone https://github.com/Gerapy/Gerapy.git gerapy
cd gerapy
git subtree split --prefix=docs --squash -b docs
git checkout docs
git push /tmp/docs docs:docs
cd /tmp/docs
git checkout docs
git checkout -b master || git checkout master || true
git reset --hard docs
git push origin master --force
可以看到,这里主要就分了两步。
第一部分就是设置虚拟机的 SSH 环境,这里 secrets.DOCS_DEPLOY_KEY 就是我们刚才在 Secrets 里面定义的私钥,对应的运行命令就是将私钥添加到 ~/.ssh/id_rsa 里面。
第二部分就是分离 docs 文件夹到新的分支,然后将其上传到新的 Repo 下了。

那么这里有两条比较关键的命令:
git subtree split --prefix=docs --squash -b docs
这条命令就是将 docs 文件夹的内容分离到一个新的分支的根目录下,新的分支的名称为 docs。
git push /tmp/docs docs:docs
这条命令就是将本地的分支推送到另外一个本地 Repo 下,注意这里 push 的目标不一定是远端的 Repo 地址,也可以是本地的 Repo 地址。
最后,将新的 Repo 内容强制推送到远程即可。
这样我们就可以实现,Gerapy/Gerapy Repo docs 文件夹下内容的变动,会自动更新到 Gerapy/Docs Repo 了。
例如 docs 下是这样的:

Gerapy/Docs Repo 下和子文件的内容会一直维持同步,并在 master 分支上面:

自动构建 Docker 镜像
由于 Gerapy 是一个 Web 工程,所以它非常适合于打包一个 Docker 镜像。对于 Docker 的镜像,我期望有三个版本:
•当前 master 分支的版本,比较稳定,但未发布版本。•最新版本,latest,代表最新的发布版本。•每个历史版本,每次发布版本的版本号,都标记一个 tag。
最后我们自动构建的镜像都自动 Push 到 Docker Hub 上面,这样大家都可以使用了。
那这个怎么做到呢,同样借助于 GitHub Action 也可以轻松做到。
首先 master 版本,由于没有发版,所以前端需要自行 build,然后 Python Package 需要安装本地代码。废话不多说了,上代码:
name: build docker image master
on:
push:
branches:
- master
paths:
- .github/workflows/**
- gerapy/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v1
- name: Docker Login
run: docker login -u germey -p ${{ secrets.DOCKERHUB_LOGIN_PASSWORD }}
- name: Setup Node.js
uses: actions/setup-node@v1.1.0
with:
version: 10.x
- name: Build Frontend Source
run: |
cd gerapy/client
npm install
npm run build
- name: Build the Docker Image
run: |
docker build -t germey/gerapy:master -f ./docker/Dockerfile .
- name: Push the Docker Image
run: docker push germey/gerapy:master
可以看到这里,监听了 master 分支的变动,同时限定了路径 workflows 文件夹和 gerapy 文件夹下变动。
流程包括了前端的构建和 Docker 的打包,Docker 打包的时候使用了 -f 命令指定了 Dockerfile 的路径,并将打包完成之后的镜像标记为 gerapy:master,推送到 Docker Hub 即可。
对于发布新版本的时候,则直接监听 tag 的变动即可:
name: build docker image release
on:
push:
tags:
- 'v*.*.*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v1
- name: Docker Login
run: docker login -u germey -p ${{ secrets.DOCKERHUB_LOGIN_PASSWORD }}
- name: Setup Node.js
uses: actions/setup-node@v1.1.0
with:
version: 10.x
- name: Build Frontend Source
run: |
cd gerapy/client
npm install
npm run build
- name: Build and Push the Docker Image
run: |
tag=${GITHUB_REF:11}
echo "Build Tag '$tag'"
docker build -t germey/gerapy:$tag -f ./docker/Dockerfile .
docker push germey/gerapy:$tag
regex='^([0-9]+\.){0,2}(\*|[0-9]+)$'
if [[ $tag =~ $regex ]]; then
echo "Build Stable Version '$tag'"
docker tag germey/gerapy:$tag germey/gerapy:latest
docker push germey/gerapy:latest
fi
可以看到这里监听的配置改成了 tags,tag 也变成了一个变量,可以通过 ${GITHUB_REF:11} 获取到。
同时这里还加了一个正则判断是不是正式的发版,如果是 beta、rc 版本,则不构建正式 latest 的 Docker 镜像。
最后我们看看我再一次发版之后,构建完成之后,Docker Hub 的效果:

可以看到,我发布了 0.9.2 版本之后,它就自动构建了 0.9.2 版本的镜像,同时将 latest 镜像指向 0.9.2 版本。另外对应 maser 版本也构建了一个版本。
这样,以后妈妈再也不用担心我忘记打 Docker 镜像啦。
以上便是我将 GitHub Actions 应用到我的开源项目上的记录。

最后,如果大家对 Scrapy 爬虫感兴趣的话,也(非常)欢迎大家(高高兴兴的)了解一下我写的 Gerapy 框架,利用它我们可以(无敌)更方便地管理(呀)、监控(呀)、(或者是)部署 Scrapy 爬虫项目(什么的)。
其 GitHub 地址为:https://github.com/Gerapy/Gerapy,文档:https://docs.gerapy.com/。
掰掰!
