之前我司所执行的CI方案,其中对于业务来说是不开放Dockerfile的,由系统生成对应的Dcokerfile,业务唯一需要关心的只有build.sh文件。有优势(简单、高效、且安全)也有劣势(不灵活、无法多阶段构建、构建环境前依赖Jenkins机器环境),所以此处也做一个支持自定义的Dockerfile的方案。
凡事都有两面性,各有优势,各位可执行选择哈
1、现行方案梳理
2、自定义Dockerfile方案
建议业务代码仓库存在CI的三个 标准文件: Dockerfile、Makefile、build.sh
1 2 3 4 5 6 ├── Dockerfile // 业务代码基于此构建出响应的容器镜像 ├── Makefile // make命令执行,多命令入口组合;可结合docker 实现编译(分平台)、打包、发布等过程阶段 ├── build.sh // 与业务本身无关,CI执行入口,含特定CI过程,也可满足复杂CI场景需求,如:构建制品处理,前端文件Oss上传等 ├── src └── ...
Dockerfile
Dockerfile 是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。它们简化了从头到尾的流程并极大的简化了部署工作。Dockerfile 从 FROM 命令开始,紧接着跟随者各种方法,命令和参数。其产出为一个新的可以用于创建容器的镜像。
自定义的Dockerfile,可以支持多阶段构建
,可以避免依赖构建的基础环境(比如:我们使用的Jenkins打包机)。
**备注:**在应用CI集成过程中,不建议
在Docker镜像中构建,一是新起构建容器有一定资源开销;二是整个构建过程相关依赖缓存无法使用;三是获取对应构建产出的制品文件不方便,总体来说 效率不高 。但是在开源或者演示项目,可以采用这种方式。
2022-11-24补充:
关于下文提供构建缓存问题,已经找到解决方案,同样以Go项目编译为示例说明:
1 2 3 4 5 6 7 // docker新增一层生成依赖的镜像,以go为例,优先处理go.mod go.sum,达到层级共用 参考:https://evilmartians.com/chronicles/speeding-up-go-modules-for-docker-and-ci FROM golang:1.18.8 COPY go.mod go.sum /opt/build/ RUN go mod download // go.mod go.sum 这里COPY的文件有变化的话,docker build会重新生成镜像
② 【推荐】 实用buildkit,RUN --mount=type=cache 可以缓存中间文件到host,同时可以兼容使用①中的go mod缓存,且避免了mod变动需要重新生成分层的问题
备注:buildkit is available from docker 18.09,同时需要在 docker build 命令前加入 DOCKER_BUILDKIT=1
启用buildKit特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 参考:https://yeasy.gitbook.io/docker_practice/buildx/buildkit # 官网参考:https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md # docker compose示例参考:https://github.com/docker/compose-cli/blob/main/Dockerfile # 示例 # =================================================================================== # syntax=docker/dockerfile:1.2 FROM golang:1.18.8 as build # 设置go 的相关环境变量 ENV GO111MODULE=on \ GOPROXY=https://goproxy.cn,direct \ GOPRIVATE=gitlab.2345.cn COPY go.mod go.sum /opt/build/ WORKDIR /opt/build RUN --mount=type=cache,target=/go/pkg/mod \ go mod download ARG LDFLAGS="-s -w" COPY . . RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build,id=jszx-yfzt_go-build-cache \ GOOS=linux go build -ldflags "${LDFLAGS}" -trimpath -o ./bin/yfzt ./cmd/web/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 FROM golang:1.16 .15 as buildARG LDFLAGS="-s -w" ENV GOPROXY=https://goproxy.cn,direct \ GOPRIVATE=gitlab.xxx.cn WORKDIR /opt/build COPY . . RUN GOOS=linux go build -ldflags "${LDFLAGS} " -trimpath -o ./bin/demo ./cmd/web/main.go FROM golang:1.16 .15 as prodWORKDIR /opt/case /cloud COPY --from=build /opt/build/bin/. . COPY --from=build /opt/build/conf . COPY --from=build /opt/build/image-syncer . COPY --from=build /opt/build/resource . RUN chown 2000:2000 -R /opt/case EXPOSE 8089 CMD ["./demo" ] FROM scratch AS export-artifactsCOPY --from=build /opt/build/bin/. .
Makefile
make
命令是GNU的工程化编译工具,用于编译众多相互关联的源代码文件,以实现工程化的管理,提高开发效率,make 执行时会在当前目录下寻找 Makefile 或 makefile 文件。如果存在相应的文件,它就会依据其中定义好的规则完成构建任务。Makefile
主要出现在c/c++
的项目居多,用来管理c/c++
的编译依赖。实际上 Makefile 内都是你根据 make 语法规则,自己编写的特定 Shell 命令。
它原生支持多入口子命令执行,是一个系统变量或者命令的集合。在容器化应用中引用dockerfile,或者读取默认的dockerfile,来完成自动按照某个流程 build的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 NOW = $(shell date -u '+%Y%m%d%I%M%S') GOVET = go tool vet -composites=false -methods=false -structtags=false GOFMT ?= gofmt "-s" GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") LDFLAGS += -s -w LDFLAGS += -X "demo/pkg/version.gitBranch=$(shell git symbolic-ref --short -q HEAD)" LDFLAGS += -X "demo/pkg/version.gitCommit=$(shell git rev-parse HEAD)" LDFLAGS += -X "demo/pkg/version.goVersion=$(shell go version)" LDFLAGS += -X "demo/pkg/version.buildTime=$(shell date " +%Y-%m-%d %T %Z")" IMAGE_NAME = harbor.xxx.cn/defalut_project/default_name IMAGE_TAG = 0.1-demo ARTIFACT_OUTPUT = output_scm export GOPROXY=https://goproxy.io.PHONY : clean build linux macos windows docker docker-push export-artifactsall: clean linux clean: rm -rf demo fmt: @$(GOFMT) -w $(GOFILES) build: go build -ldflags '$(LDFLAGS) ' -trimpath -o demo cmd/web/main.go linux: @ GOOS=linux go build -ldflags '$(LDFLAGS) ' -trimpath -o ./bin/demo cmd/web/main.go macos: GOOS=darwin go build -ldflags '$(LDFLAGS) ' -trimpath -o ./bin/demo cmd/web/main.go windows: GOOS=windows go build -ldflags '$(LDFLAGS) ' -trimpath -o ./bin/demo cmd/web/main.go docker: docker build --build-arg LDFLAGS='$(LDFLAGS) ' --target prod -t $(IMAGE_NAME) :$(IMAGE_TAG) -f ./Dockerfile . docker-push: docker docker push $(IMAGE_NAME) :$(IMAGE_TAG) docker rmi $(IMAGE_NAME) :$(IMAGE_TAG) export-artifacts: DOCKER_BUILDKIT=1 docker build --target export-artifacts --output ${ARTIFACT_OUTPUT} -f ./Dockerfile .
满足相对复杂的构建场景需求,作为CI(本文使用工具为:Jenkins)的执行的唯一入口。一般来说涵盖的执行过程大体和应用本身关联不大,主要是业务或者特殊场景处理情况,比如:构建制品的处理,前端文件Oss上传等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #!/bin/bash APP_CODE="$1 " BUILD_ENV="$2 " IS_DOCKER="$3 " APP_PKG_NAME="$4 " IMAGE_NAME="$5 " IMAGE_TAG="$6 " BASE_DIR=$(cd "$(dirname "$0 " ) " ;pwd ) OUTPUT_DIR="${BASE_DIR} /output_scm" function check () { if [ -z ${APP_PKG_NAME} ]; then _showError '参数错误!请检查是否传入 APP_PKG_NAME 参数!' fi if [[ ! -z ${IS_DOCKER} && "${IS_DOCKER} " -eq "1" ]]; then if [[ -z ${IMAGE_NAME} || -z ${IMAGE_TAG} ]]; then _showError '参数错误!容器化应用需传入 IMAGE_NAME 和 IMAGE_TAG 参数!' fi fi } function build () { echo "make docker-push IMAGE_NAME=${IMAGE_NAME} IMAGE_TAG=${IMAGE_TAG} " make docker-push "IMAGE_NAME=${IMAGE_NAME} " "IMAGE_TAG=${IMAGE_TAG} " if [ $? != 0 ]; then _showError '构建错误!请检查控制台日志!' fi } function package () { [ -d "${BASE_DIR} /output_scm" ] && rm -rf "${BASE_DIR} /output_scm" mkdir -p ${BASE_DIR} /output_scm/${APP_PKG_NAME} make export-artifacts "ARTIFACT_OUTPUT=${BASE_DIR} /output_scm/${APP_PKG_NAME} " cp -r "${BASE_DIR} /conf" "${BASE_DIR} /output_scm/${APP_PKG_NAME} " cp -r "${BASE_DIR} /image-syncer" "${BASE_DIR} /output_scm/${APP_PKG_NAME} " cp -r "${BASE_DIR} /resource" "${BASE_DIR} /output_scm/${APP_PKG_NAME} " cd "${BASE_DIR} /output_scm" tar -zcf "${BASE_DIR} /output_scm/${APP_PKG_NAME} .tar.gz" "${APP_PKG_NAME} " md5sum "${BASE_DIR} /output_scm/${APP_PKG_NAME} .tar.gz" >"${BASE_DIR} /output_scm/${APP_PKG_NAME} .tar.gz.md5" } function _showError () { echo '' echo '################################################' echo " $1 " echo '################################################' echo '' exit 1 } check build