Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Golang】CGO项目Docker镜像打包优化指南,来点新鲜不一样的多阶段构建

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

作者头像
编码如写诗
发布于 2025-01-08 04:43:42
发布于 2025-01-08 04:43:42
26602
代码可运行
举报
文章被收录于专栏:编码如写诗编码如写诗
运行总次数:2
代码可运行

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

目的

本文将介绍go语言Docker打包时镜像瘦身和Dockerfile优化,将镜像构建提速(提速至20秒,如果按**的对比方式,提速90倍)。主要介绍使用了CGO的项目打包时Dockerfile的编写并提供一些新的思路。为后续介绍Golang调用Oracle打包做准备。

普通Go应用打包

由于多阶段已经非常普及,本文不再过多介绍,若有不会的可参考上述好未来go-zero 微服务实践文章。本文只介绍些与其他文章不一样的。先看下本公众号关联小程序(公众号菜单可以体验)后端服务的Dockerfile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ARG GO_VERSION=1.23.0
ARG ALPINE_VERSION=3.20
FROM  golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build
WORKDIR /src
ENV 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操作。前两行定义了Golangalpine的版本,方便Golang版本升级时进行修改。

具体妙用可查看之前的文章 天行1st,公众号:编码如写诗基于Docker的交叉编译和打包多平台镜像

通过查看build截图,前两个构建记录为普通项目,后两个记录为cgo项目。两个项目都是20多秒构建完成,已经非常迅速。再查看log记录可以看到耗时主要为go build,其他copy和go mod耗时非常少

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镜像中就可以了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 多阶段构建
#构建一个 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。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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镜像不就可以了嘛,于是进行修改。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 多阶段构建
#构建一个 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

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

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

最终优化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 多阶段构建
#构建一个 builder 镜像,目的是在其中编译出可执行文件
#构建时需要将此文件放到代码根目录下
FROM cgo-mck:mck as builder
ENV GOOS=linux
ENV GOPROXY=https://goproxy.cn,direct
WORKDIR /build/src
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
#交叉编译,需要制定CGO_ENABLED=1
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    CGO_ENABLED=1 GOOS=linux  go build -ldflags="-s -w" -o /bin/mck ./cmd/mck/main.go

#编译
FROM alpine:mck
WORKDIR /app
#从第一个镜像里 copy 出来可执行文件
COPY --from=builder  /bin/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

CMD ["./mck"]
EXPOSE 9008
EXPOSE 9080

构建测试,总耗时:19.1s对比以前的将近半个小时现在不到20秒,已经可以用极速构建来形容了。

PS:该项目包含TCPHTTP服务,并非微服务架构,为单一应用,包含mysql,redis,prometheus相关操作等众多功能。

查看构建过程,也是主要耗时在go build 其他耗时都非常少,另外alpine so有些许耗时,由于较少,这里未在进行优化,若想要继续优化可关注后续文章介绍:Golang调用oracle镜像打包

总结

在使用CG0进行Golang项目打包时,优化镜像构建的过程至关重要。通过多阶段构建,我们可以有效地减少最终镜像的大小,同时提高构建速度。

在构建过程中,利用Docker的缓存机制,可以将依赖项的下载和编译过程进行缓存,避免重复操作。通过将C语言的动态库直接复制到最终镜像中,确保应用能够正常运行,避免因缺少依赖而导致的运行时错误。

此外,定期清理无用的dangling镜像,保持Docker环境的整洁避免磁盘空间被占满。可以将清理命令集成到自动化构建流程中,确保每次构建后环境的干净。

最终,通过这些优化措施,我们不仅能构建出小巧的镜像,还能提升开发效率,为后续的服务部署打下良好的基础。希望这些经验能为其他开发者在Golang项目的Docker化过程中提供帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编码如写诗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基于Docker的交叉编译和打包多平台镜像
效果预览 本机:X86_64 Windows(Docker Desktop)+WSL(Ubuntu)。
编码如写诗
2024/09/12
9800
基于Docker的交叉编译和打包多平台镜像
Docker 镜像多阶段构建
本文内容来自我参与维护的 《Docker 从入门到实践》 项目。 之前的做法 在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式: 全部放入一个 Dockerfile 一种方式是将所有的构建过程编包含在一个 Dockerfile 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题: Dockerfile 特别长,可维护性降低 镜像层次多,镜像体积较大,部署时间变长 源代码存在泄露的风险 例如 编写 app.go 文件,该程序输出 He
康怀帅
2018/02/28
1.6K0
宜信技术大牛教你如何编写优雅的 Docker file
Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件。
马哥linux运维
2019/06/25
8660
宜信技术大牛教你如何编写优雅的 Docker file
Docker 镜像多阶段构建实战总结
为了解决以上这些问题,Docker v17.05 开始支持多镜像阶段构建 (multistage builds)。只需要编写一个 Dockerfile 即可。通过一段简单的 C 语言代码的编译、执行来具体演示。demo.c 的内容如下:
耕耘实录
2020/06/29
1.6K0
听说你还不会分阶段构建 Docker 镜像?
用 Go 语言开发的程序打包后就一个可执行的二进制文件,一般情况下是不需要什么环境依赖就能执行运行跑起来,如果拿到 Docker 里面跑,是非常有优势的。
小锟哥哥
2022/05/10
3960
听说你还不会分阶段构建 Docker 镜像?
Dockerfile 实践:构建 Java、Python、Vue 和 Go 环境
在这篇文章中,我将分享如何使用 Dockerfile 为不同的编程语言和框架创建 Docker 镜像。我们将覆盖 Java、Python、Vue3 和 Go。
蚂蚁蚂蚁
2024/03/29
4380
常见的dockerfile汇总
python FROM python:3.7-alpine WORKDIR /code ENV FLASK_APP=app.py ENV FLASK_RUN_HOST=0.0.0.0 COPY requirements.txt requirements.txt RUN pip install -r requirements.txt EXPOSE 5000 COPY . . CMD ["flask", "run"] php FROM php:7.2.34-fpm-alpine WORKDIR /app ENV
章工运维
2023/05/19
3660
docker的多阶段构建
如何执行go程序 写一个go的程序: package main import "fmt" func main() {    fmt.Println("hello world") } 在本地的话,我
仙士可
2022/09/13
9560
docker的多阶段构建
【Golang】全网首发:Oracle数据库godror驱动docker打包,含CGO Dockerfile终极指南
具体参考:godror https://godror.github.io/godror
编码如写诗
2025/02/18
1480
【Golang】全网首发:Oracle数据库godror驱动docker打包,含CGO Dockerfile终极指南
Docker学习——多阶段构建(六) 顶
之前的做法 在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式: 全部放入一个 Dockerfile 一种方式是将所有的构建过程编包含在一个 Dockerfile 中,包括项目及其依赖库的编译、 测试、打包等流程,这里可能会带来的一些问题: 1、Dockerfile 特别长,可维护性降低 2、镜像层次多,镜像体积较大,部署时间变长 3、源代码存在泄露的风险 例如 编写 app.go 文件,该程序输出 Hello World! package main import
wuweixiang
2018/12/07
6360
使用 Docker 开发 - 使用多阶段构建镜像
多阶段构建是一个新特性,需要 Docker 17.05 或更高版本的守护进程和客户端。对于那些努力优化 Dockerfiles 并使其易于阅读和维护的人来说,多阶段构建非常有用。
用户8803964
2021/07/05
1K0
Go打包和部署:从编译到运行的全指南
今天我们来聊聊Go语言项目如何打包和部署。无论你是初学者还是资深开发者,了解如何将你的代码打包成可执行文件,并在不同环境下部署运行,都是一项非常重要的技能。
南山竹
2024/06/06
2.6K0
Go打包和部署:从编译到运行的全指南
使用 CODING 对 Go 项目进行持续集成
https://github.com/seth-shi/golang-coding
seth-shi
2023/12/18
4930
全栈容器化部署篇
截止昨天已经完成了前后端应用的基础开发,那么传统的部署相信大家都是知道的。我们这里还是来说说一般一个vue的项目该如何部署,有那些部署方式:
希里安
2023/10/30
4380
全栈容器化部署篇
go框架中使用CGO,docker build image打包镜像注意事项
编写Dockerfile时候注意以下几点。 可以参考,但不要照搬。 RUN apk add build-base CGO_ENABLED=1 这两个命令是关键。 # 编译 FROM golang:1.15.2-alpine as builder #ENV CGO_ENABLED=0 ENV GOOS=linux ENV GOPROXY=https://goproxy.cn ENV GO111MODULE=off ENV GOPATH="/go/release:/go/release/src/gopath
whileideath
2020/10/22
4.2K1
【Golang】企业内网/专网离线搭建Go环境实战指南
随着数据泄露问题日益严重和大模型的快速发展,越来越多的公司将研发环境迁移至内网甚至专网。而由于网络限制或安全策略,直接访问外部资源(如Go模块仓库)可能会受到限制。这就需要我们在公司内网中搭建一个离线的Go语言开发环境,以确保能够顺利地进行项目开发。
编码如写诗
2025/04/10
2030
【Golang】企业内网/专网离线搭建Go环境实战指南
# 谈谈 Docker 镜像多阶段构建
为了解决以上这些问题,Docker v17.05 开始支持多镜像阶段构建 (multistage builds)。只需要编写一个 Dockerfile 即可。
看、未来
2022/07/04
1K1
通过多阶段构建减小Golang镜像的大小
我们如何通过引入具有多阶段构建过程的Dockerfiles来减小Golang镜像的大小?
用户1107783
2024/03/06
2230
通过多阶段构建减小Golang镜像的大小
Dockerfile 中的 multi-stage(多阶段构建)
在应用了容器技术的软件开发过程中,控制容器镜像的大小可是一件费时费力的事情。如果我们构建的镜像既是编译软件的环境,又是软件最终的运行环境,这是很难控制镜像大小的。所以常见的配置模式为:分别为软件的编译环境和运行环境提供不同的容器镜像。比如为编译环境提供一个 Dockerfile.build,用它构建的镜像包含了编译软件需要的所有内容,比如代码、SDK、工具等等。同时为软件的运行环境提供另外一个单独的 Dockerfile,它从 Dockerfile.build 中获得编译好的软件,用它构建的镜像只包含运行软件所必须的内容。这种情况被称为构造者模式(builder pattern),本文将介绍如何通过 Dockerfile 中的 multi-stage 来解决构造者模式带来的问题。
星哥玩云
2022/07/14
1.2K0
Dockerfile 中的 multi-stage(多阶段构建)
Dockerfile多阶段构建镜像
作者:matrix 被围观: 11 次 发布时间:2024-07-06 分类:Golang Linux | 无评论 »
HHTjim 部落格
2024/07/06
2330
推荐阅读
相关推荐
基于Docker的交叉编译和打包多平台镜像
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验