Docker分享-CI/CD之路(2)

2022-04-12  本文已影响0人  糖醋沼跃鱼

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以及一些额外的构建优化。

上一篇 下一篇

猜你喜欢

热点阅读