这阵子还挺忙的,上周二飞天津参加第五空间线下赛,回来后周五又是助手那边的面试,周六又是省赛线下赛,这星期又是协会这边的面试。
忙里偷闲抽了点时间继续在看 Redis,目前已经看到原理篇了,了解到了不少有意思的算法与实现原理,整本书看完后会写点东西总结一下。近期助手的 GitHub 教师认证通过了,并开通了团队的一堆福利。考虑到 GitHub 这边的工单系统更加完善,因而逐渐开始从自建 Gitea 逐步转移到 GitHub。CI 也由原来的 Drone,转到 GitHub Actions。
因此今天就想聊聊 GitHub Actions。它是 GitHub 今年新推出的可用于创建项目自动化构建工作流,说白了就是 GitHub 上也可以做 CI/CD 了。Circle CI 和 Travis CI 瑟瑟发抖
GitHub Actions 对于个人的免费额度是一个月 3000 分钟。对于个人项目足足有余。那么说干就干,来试试用 GitHub Actions 编译 Golang 后构建 Docker 镜像并发布到阿里云镜像仓库。

YML 格式

点击项目 Repo 上方的 Actions 进入面板。在这里可以看到项目的构建情况。

GitHub Actions 面板

Build & Deploy/.github/workflows
name: Build & Deploy
on: [push]
jobs:
    build:
        name: Build
        runs-on: ubuntu-latest
        steps:
            ....
    dockerfile:
        ....
nameBuild & Deployonpush
jobsjobssteps

这里使用 Drone CI 来进行对比说明。

DroneCI 对比

.drone.ymlpipeline

Job
A defined task made up of steps. Each job is run in a fresh instance of the virtual environment. You can define the dependency rules for how jobs run in a workflow file. Jobs can run at the same time in parallel or be dependent on the status of a previous job and run sequentially. For example, a workflow can have two sequential jobs that build and test code, where the test job is dependent on the status of the build job. If the build job fails, the test job will not run.

而 Jobs 下面又有许多 Steps,这才是我们 Drone CI 中的一个个遵循从上到下进行执行的“步骤”。

使用预制的 Golang 构建

现在我们开始编写自动编译 Golang 代码的配置,这里直接使用 GitHub Actions 的预制构建即可。

name: Build & Deploy
on: [push]
jobs:
    build:
        name: Build
        runs-on: ubuntu-latest
        steps:

        - name: Set up Go 1.12
        uses: actions/setup-go@v1
        with:
            go-version: 1.12
        id: go

        - name: Check out code into the Go module directory
        uses: actions/checkout@v1

        - name: Get dependencies
         run: |
            go get -v -t -d ./...
            if [ -f Gopkg.toml ]; then
                curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
                dep ensure
            fi

        - name: Build
          run: |
            go build -v .
            pwd

        - name: Archive production artifacts
        uses: actions/upload-artifact@v1
        with:
            name: drone_test
            path: /home/runner/work/drone_test/drone_test

这里个 Job 里共有四个 Steps,前面三个都是 GitHub Actions 预制的,第四个是我自己加的,这里聊一下第四个配置 —— Artifact。

Artifact

ArtifactArtifactArtifactactions/upload-artifactwith

打包 Docker 镜像

needs
dockerfile:
    name: Build Image
    runs-on: ubuntu-latest
    needs: build
    steps:

    - name: Get artifacts
    uses: actions/download-artifact@master
    with:
        name: drone_test
        path: /home/runner/work/drone_test/drone_test
needs: builddockerfilebuildactions/download-artifact

二进制文件打包进镜像并发布

jerray/publish-docker-action

起初是想通过环境变量动态生成 Tag,但折腾了两三天后依然无果。便开始自己魔改这个 Action。

魔改 publish-docker-action

helper.goresolveSemanticVersionTag
func getFormatTag(tagFormat string) []string{
    tags := make([]string, 0)

    t := tagFormat
    t = strings.Replace(t, "%TIMESTAMP%", strconv.Itoa(int(time.Now().Unix())), -1)     // %TIMESTAMP% Timestamp
    t = strings.Replace(t, "%YYYY%", strconv.Itoa(time.Now().Year()), -1)               // %YYYY% Year
    t = strings.Replace(t, "%MM%", strconv.Itoa(int(time.Now().Month())), -1)           // %MM% Month
    t = strings.Replace(t, "%DD%", strconv.Itoa(time.Now().Day()), -1)                  // %DD% Day
    t = strings.Replace(t, "%H%", strconv.Itoa(time.Now().Hour()), -1)                  // %H% Hour
    t = strings.Replace(t, "%m%", strconv.Itoa(time.Now().Minute()), -1)                // %m% Minute
    t = strings.Replace(t, "%s%", strconv.Itoa(time.Now().Second()), -1)                // %s% Second

    tags = append(tags, t)
    return tags
}

这里将传入的字符串中的格式化符号进行替换,然后返回。
同时在上面的代码中:

tags := make([]string, 0)
// Add `latest` version
tags = append(tags, "latest")
tags = append(tags, getFormatTag(inputs.TagFormat)...)

inputs.Tags = tags
latestTagFormataction.yml
tag_format:
    description: 'Set the tag format'
    default: '%TIMESTAMP%'
action.yml

发布!!

最后一步,就是使用我魔改后的 Action 进行发布部署啦~

- name: Build & Publish to Registry
uses: wuhan005/publish-docker-action@master
with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}
    registry: registry.cn-hongkong.aliyuncs.com
    repository: registry.cn-hongkong.aliyuncs.com/eggplant/drone-test
    tag_format: "%YYYY%_%MM%_%DD%_%H%%m%%s%"
    auto_tag: true
secrets

最终配置

name: Build & Deploy
on: [push]
jobs:
    build:
        name: Build
        runs-on: ubuntu-latest
        steps:

    - name: Set up Go 1.12
        uses: actions/setup-go@v1
        with:
            go-version: 1.12
        id: go

        - name: Check out code into the Go module directory
        uses: actions/checkout@v1

        - name: Get dependencies
        run: |
            go get -v -t -d ./...
            if [ -f Gopkg.toml ]; then
                curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
                dep ensure
            fi
        - name: Build
        run: |
            go build -v .
            pwd

        - name: Archive production artifacts
        uses: actions/upload-artifact@v1
        with:
            name: drone_test
            path: /home/runner/work/drone_test/drone_test

    dockerfile:
        name: Build Image
        runs-on: ubuntu-latest
        needs: build
        steps:

        - name: Get artifacts
        uses: actions/download-artifact@master
        with:
            name: drone_test
            path: /home/runner/work/drone_test/drone_test

        - name: Build & Publish to Registry
        uses: wuhan005/publish-docker-action@master
        with:
            username: ${{ secrets.DOCKER_USERNAME }}
            password: ${{ secrets.DOCKER_PASSWORD }}
            registry: registry.cn-hongkong.aliyuncs.com
            repository: registry.cn-hongkong.aliyuncs.com/eggplant/drone-test
            tag_format: "%YYYY%_%MM%_%DD%_%H%%m%%s%"
            auto_tag: true

总结

每一次配 CI 都要踩好多坑啊……不过最后总算是搞定了!
不过还没结束,把镜像发布到阿里云镜像仓库后还要部署到服务器上,同时还要能实现版本回滚。这些我打算是使用 Swarm + Portainer 实现。
但这俩东西还只是听说过,自己还没实际用过。学校图书馆好像有 Swarm 的书,等把手头这本 Redis 的看完就去借!