侧边栏壁纸
  • 累计撰写 48 篇文章
  • 累计创建 7 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

【Golang】CGO应用Docker镜像打包指南,来点新鲜不一样的多阶段构建

Administrator
2025-01-07 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

第一次知道多阶段构建还是3年前看到go-zero公众号的文档。之前使用Ubuntu/Centos基础镜像,构建后最小也要100多M,当时跟着go-zero文档学完后构建出来的镜像只有20多M,被深深的折服,对于微服务盛行的时代,golang的小而美终于完善了。

https://mp.weixin.qq.com/s/udpNP2LzF0bfn8w_wNcMYQ

可能是吃了细糠后吃不了糙米,以至于后来看到关于镜像瘦身之类的文档都是嗤之以鼻。千篇一律,还在用以前的多阶段构建,连缓存加速都没有,而且关于Golang的没有一个完整介绍cgo Dockerfile怎么写的。

目的

本文主要介绍Golang语言使用了CGO的项目打包时Dockerfile的编写并提供一些新的思路。为后续介绍Golang调用Oracle打包做准备。

普通Go应用打包

由于多阶段已经非常普及,本文不再过多介绍,若有不会的可参考上述`好未来go-zero微服务实践`文章。本文只介绍些与其他文章不一样的。先看下基于字节go `hertz`框架开发的web管理系统`https://github.com/gjing1st/hertz-admin`修改的`Dockerfile`
ARG GO_VERSION=1.23.0
ARG ALPINE_VERSION=3.20
FROM  golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,source=go.sum,target=go.sum \
    --mount=type=bind,source=go.mod,target=go.mod \
    go mod download -x
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    CGO_ENABLED=0 GOOS=linux  go build -ldflags="-s -w" -o /bin/server ./cmd/ha/main.go

FROM alpine:${ALPINE_VERSION}
COPY --from=build /bin/server /bin/
EXPOSE 9680
ENTRYPOINT [ "/bin/server" ]

可以看到整体很简洁,其中1,2,13,16行还可以省略。与大多数文档不同,这里的第一阶段构建Golang二进制程序并没有使用copy代码到golang镜像,然后在镜像中下载依赖再打包的方式。这里使用缓存挂载,将依赖缓存起来,避免每次打包都下载依赖,而源代码也没有进行copy操作。

CGO应用打包

接下来开始介绍CGO应用的打包,CGO由于Golang中调用了C的代码,so库等,导致编译时需要有gcc环境,而且最终的二进制程序可能也需要有依赖的静态库,所以相比较于普通应用,CGO的打包要麻烦很多。

第一次尝试

大概2年半以前第一次写CGO程序时,遇到Docker打包问题,当时在网上翻了好多博客最终发现在`米开朗基杨`的文章里找到蛛丝马迹,今天写文章回看才发现,竟然是`米开朗基杨`大神写的,膜拜。

https://cloud.tencent.com/developer/article/1632733

当时的难点主要在于使用golang基础镜像构建出了二进制可执行程序mck后,将其放入alpine镜像后一直无法运行,后来在alpine容器中通过ldd mck命令发现缺少so库。找到了问题就好解决了,于是在alpine系统中,将C的代码重新编译后,打出alpine的so,后面直接将so放入alpine镜像中就可以了。

# 多阶段构建
#构建一个 builder 镜像,目的是在其中编译出可执行文件
#构建时需要将此文件放到代码根目录下
FROM golang:alpine  as builder
ENV GOOS=linux
ENV GOPROXY=https://goproxy.cn
#安装编译需要的环境gcc等
RUN apk add build-base
WORKDIR /build
#将上层整个文件夹拷贝到/build
ADD . /build/src
WORKDIR /build/src
#交叉编译,需要制定CGO_ENABLED=1,默认是关闭的
#去掉了调试信息 -ldflags="-s -w" 以减小镜像尺寸
RUN  GOOS=linux CGO_ENABLED=1 GOARCH=amd64 go build -ldflags="-s -w" -installsuffix cgo -o mck ./cmd/mck/main.go

#编译
FROM alpine
RUN apk update --no-cache && apk add --no-cache tzdata
#设置本地时区,这样我们在日志里看到的是北京时间了
ENV TZ Asia/Shanghai
WORKDIR /app
#从第一个镜像里 copy 出来可执行文件
COPY --from=builder  /build/src/mck /app/mck
COPY ./config/alpine/libgcc_s.so.1 /usr/lib/
COPY ./config/alpine/libstdc++.so.6.0.28 /usr/lib/libstdc++.so.6.0.28
RUN ln -s /usr/lib/libstdc++.so.6.0.28 /usr/lib/libstdc++.so.6
#VOLUME ["/home/tianxing/project/mck/mck-service-core/config/config.yml","/app/config/config.yml"]

#CMD ["./mck"]
EXPOSE 9008
EXPOSE 9080
# 构建镜像:docker build -t mckserver .
# 运行容器:docker run -itd --name mckserver -v /app/mck/mck.log:/app/mck.log -p 9008:9008 -p 9080:9080  mckserver

搞定,打包后不到30M。

root@DESKTOP-BB0KRFQ:/mnt/e# docker images | grep mck-s
mck-server    v3.0.0     764c83bc959d   5 days ago      29.1MB
mck-server    2.15.0     e060e64c206e   7 months ago    27.3MB

然而同事吐槽说,每次打包太慢了而且在他的vm虚拟机,打几次之后磁盘满了。ps:因为每次基础镜像都需要下载安装gcc,即使后来配置了阿里云的加速依然很慢,而且每次打包都会产生一个dangling镜像,大概1个G,他的虚拟机一共50G,确实很容易满。

第二次进行优化

既然基础镜像需要gcc,那就把带gcc的`golang`基础镜像重新做一个`cgo-mck`镜像不就可以了嘛,于是进行修改。
# 多阶段构建
#构建一个 builder 镜像,目的是在其中编译出可执行文件mck
#构建时需要将此文件放到代码根目录下
FROM cgo-mck:mck as builder
ENV GOOS=linux
ENV GOPROXY=https://goproxy.cn,direct
#安装编译需要的环境gcc等,使用阿里云加速
#RUN apk add --repository https://mirrors.aliyun.com/alpine/v3.18/main build-base
#安装编译需要的环境gcc等
#RUN apk add build-base
#WORKDIR /build
#将上层整个文件夹拷贝到/build
ADD . /build/src
WORKDIR /build/src
#交叉编译,需要制定CGO_ENABLED=1,默认是关闭的
#去掉了调试信息 -ldflags="-s -w" 以减小镜像尺寸
RUN go env -w GO111MODULE=on \
    && go mod tidy \
    && go env -w CGO_ENABLED=1 \
    && go build -ldflags="-s -w"  -o mck ./cmd/mck/main.go

#编译
FROM alpine:mck
#RUN apk update --no-cache && apk add --no-cache tzdata
#设置本地时区,这样我们在日志里看到的是北京时间了
#ENV TZ Asia/Shanghai
WORKDIR /app
#从第一个镜像里 copy 出来可执行文件
COPY --from=builder  /build/src/mck /app/mck
COPY ./config/alpine/libgcc_s.so.1 /usr/lib/
COPY ./config/alpine/libstdc++.so.6.0.28 /usr/lib/libstdc++.so.6.0.28
RUN ln -s /usr/lib/libstdc++.so.6.0.28 /usr/lib/libstdc++.so.6
#VOLUME ["/home/tianxing/project/mck/mck-service-core/config/config.yml","/app/config/config.yml"]

#CMD ["./mck"]
EXPOSE 9008
EXPOSE 9080
# 构建镜像:docker build -t mckserver .
# 运行容器:docker run -itd --name mckserver2.4 -v /app/mck/config:/app/config -p 19008:9008 -p 19080:9080  mckserver:v2.4.0
# 导出镜像:docker save -o mck_server.tar mckserver:latest

修改后,打包终于变快了,一般不到1分钟多一点就可以了。然后关于dangling镜像的,使用命令:docker images prune就可以清除了,可以将改命令加入到Makefile或者自动打包的CI脚本中。

若有需要cgo镜像的也可以直接拉取:docker pull gjing1st/cgo:1.22.0-alpine3.19

最终优化

最终优化方案将在下一篇`Golang`调用`oracle`打包时介绍,若着急的也可参考上面`普通Go应用打包`

总结

本文针对普通Golang应用给出最终打包优化方案,针对CGO应用,通过两版Dockerfile进行介绍注意点

0

评论区