Docker分享-CI/CD(2)
第一篇内容,分享了大神介绍如何容器化项目的经历,我真的看着么久,唯一看到大神介绍多平台交叉编译的。因为绝大多数的docker项目都只提供linux产品。当然只会容器化我们的项目还远远不够,CI/CD的目的能让团队协同工作,持续集成持续部署。保持开发环境一直是另一个大问题。
添加额外的依赖
上个教程,我们基本没有使用第三方依赖,但是实际开发中,为了更加专注于开发我们自己的功能,可能会有很多3PP的辅助。大神也用了个简单的例子
package main import ( "fmt" "os" "strings" "github.com/pkg/errors" ) func echo(args []string) error { if len(args) < 2 { return errors.New("no message to echo") } _, err := fmt.Println(strings.Join(args[1:], " ")) return err } func main() { if err := echo(os.Args); err != nil { fmt.Fprintf(os.Stderr, "%+v\n", err) os.Exit(1) } }
简单的echo功能,使用go modules做包管理,跑一下
go mod init go mod tidy
有了依赖之后,明显的效率低下并且减慢了速度。我们可以通过在Dockerfile中单独下载依赖项目来解决这个问题。
FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS build WORKDIR /src ENV CGO_ENABLED=0 COPY go.* . RUN go mod download COPY . . ARG TARGETOS ARG TARGETARCH RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example . FROM scratch AS bin-unix COPY --from=build /out/example / ...
与之前的一版dockerfile对比,这一版加了两行内容
COPY go.* .
RUN go mod download
大神说先添加所有go.*的文件,然后下载依赖这样会让Docker缓存下载的modules,dockerfile会少重跑一些内容。
将下载依赖和构建分开是相当大的改进,但是每次构建都要从头开始编译,对于小型项目这没什么,但是随着项目越来越大,我们就应该考虑Go的编译器缓存。
再更新一次dockerfile看看怎么改
# syntax = docker/dockerfile:1-experimental FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS build ARG TARGETOS ARG TARGETARCH WORKDIR /src ENV CGO_ENABLED=0 COPY go.* . RUN go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example . FROM scratch AS bin-unix COPY --from=build /out/example / ...
大神在dockerfile的顶部加了一个语句,选择了试验性的docker前端,外加--mount。这意味着每次运行go build命令的时候,容器都会将缓存挂在到Go的编译器缓存文件夹中。大神说无缓存构建该例子需要11秒,但是使用缓存的话,用了不到2秒,不可思议。
加入单元测试
我发誓我之后如果做个人项目一定认真写单元测试,大神也说了,不几乎我读过的每个文档都提到了单元测试的重要性。贴上测试代码:
package main import ( "testing" "github.com/stretchr/testify/require" ) func TestEcho(t *testing.T) { // Test happy path err := echo([]string{"bin-name", "hello", "world!"}) require.NoError(t, err) } func TestEchoErrorNoArgs(t *testing.T) { // Test empty arguments err := echo([]string{}) require.Error(t, err) }
CI测试来了
这里是我认为应该学习的地方,大神在dockerfile里的构建阶段,加入了单元测试
# syntax = docker/dockerfile:1-experimental FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS base WORKDIR /src ENV CGO_ENABLED=0 COPY go.* . RUN go mod download COPY . . FROM base AS build ARG TARGETOS ARG TARGETARCH RUN --mount=type=cache,target=/root/.cache/go-build \ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example . FROM base AS unit-test RUN --mount=type=cache,target=/root/.cache/go-build \ go test -v . FROM scratch AS bin-unix COPY --from=build /out/example / ...
Go的测试使用了与构建相同的缓存,也是为了能在测试的时候更快。
更新makefile
all: bin/example test: unit-test PLATFORM=local .PHONY: bin/example bin/example: @docker build . --target bin \ --output bin/ \ --platform ${PLATFORM} .PHONY: unit-test unit-test: @docker build . --target unit-test
大神的docker CI三件套,第二篇中讲了如何有效的添加go的依赖,缓存用于加快构建速度,以及对容器化Go开发环境中添加单元测试。
下一篇内容将会将如何添加linter,设置Github Actions CI以及一些额外的构建优化。