为你的Go应用创建轻量级Docker镜像?
缩小Go二进制文件大小
环境
youmen@youmendeMacBook-Pro % gcc -dumpversion
12.0.5
youmen@youmendeMacBook-Pro % go version
go version go1.16.5 darwin/amd64
go build使用的是静态编译,会将程序的依赖一起打包,这样一来编译得到的可执行文件可以直接在目标平台运行,无需运行环境(例如 JRE)或动态链接库(例如 DLL)的支持。
虽然 Go 的静态编译很方便,但也存在一个问题:打包生成的可执行文件体积较大,毕竟相关的依赖都被打包进来了;
默认二进制打包
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run("0.0.0.0:8080")
}
# go build -o test1 main.go
# du -sh test1
14M test1
-ldflags
# go build -ldflags "-s -w" -o test2 main.go
# du -sh test2
11M test2
下面假设我们将本地编译好的 bluebell 二进制文件、配置文件和静态文件等上传到服务器的/data/app/bluebell目录下。
补充一点,如果嫌弃编译后的二进制文件太大,可以在编译的时候加上-ldflags "-s -w"参数去掉符号表和调试信息,一般能减小20%的大小;
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/bluebell
- 在程序编译的时候可以加上-ldflags "-s -w"参数来优化编译,原理是通过去除部分链接和调试等信息来减小编译生成的可执行程序体积,具体参数如下:
- -a:强制编译所有依赖包
- -s:去掉符号表信息,不过panic的时候stace trace就没有任何文件名/行号信息了
- -w:去掉DWARF调试信息,不过得到的程序就不能使用gdb进行调试了
- 若对符号表无需求,-ldflags直接添加"-s"即可
注:不建议-w和-s同时使用
UPX
brew/yum install upx
# upx test2
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
11490768 -> 4063248 35.36% macho/amd64 test2
Packed 1 file.
# upx --brute test2
# du -sh test2
4.6M test2
upx的压缩选项
- -o:指定输出的文件名
- -k:保留备份原文件
- -1:最快压缩,共1-9九个级别
- -9:最优压缩,与上面对应
- -d:解压缩decompress,恢复原体积
- -l:显示压缩文件的详情,例如upx -l main.exe
- -t:测试压缩文件,例如upx -t main.exe
- -q:静默压缩be quiet
- -v:显示压缩细节be verbose
- -f:强制压缩
- -V:显示版本号
- -h:显示帮助信息
- --brute:尝试所有可用的压缩方法,slow
- --ultra-brute:比楼上更极端,very slow
UPX的原理
upx 压缩后的程序和压缩前的程序一样,无需解压仍然能够正常地运行,这种压缩方法称之为带壳压缩,压缩包含两个部分:
- 在程序开头或其他合适的地方插入解压代码;
- 将程序的其他部分压缩;
执行时,也包含两个部分:
- 首先执行的是程序开头的插入的解压代码,将原来的程序在内存中解压出来;
- 再执行解压后的程序;
也就是说,upx 在程序执行时,会有额外的解压动作,不过这个耗时几乎可以忽略。
如果对编译后的体积没什么要求的情况下,可以不使用 upx 来压缩。一般在服务器端独立运行的后台服务,无需压缩体积。
构建轻量级docker镜像
这个Dockerfile中使用了两次FROM指令,第二条FROM scratch行,它告诉Docker从一个全新的,完全空的容器镜像重新开始,然后将上个阶段编译好的程序复制到其中。这个才是我们随后将用于运行的Go应用程序的容器镜像。
scratch镜像是Docker项目预定义的最小的镜像。 Docker用于Go程序的多阶段构建很常见,使用scratch镜像可以节省大量空间,因为我们实际上不需要Go工具或其他任何东西来运行我们的编译好的程序,这可能也是Go在容器时代的一个优势吧。
使用scratch镜像制作的Go应用镜像在运行时会有一个不识别时区的问题,这个也是我们最近项目往Kubernetes上迁移时遇到的第一个问题,不过还好经过Google和查看Go加载时区的源码找到了解决方法
介绍
多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序。举个例子,首先我们使用Golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。
在上述的案例中,我们总共有三个阶段:
1 . build编译阶段
2 . certs(可选,可有可无)证书认证阶段
3 . prod生产阶段
在build阶段主要是编译我们的应用程序,证书认证阶段将会安装我们所需要的CA证书,最后的生产发布阶段会将我们构建好的镜像推到镜像仓库中。而且发布阶段将会使用build阶段编译完毕的二进制文件和certs阶段安装的证书;

项目发布的多个build阶段
示例工程
main.go
[root@rabbitmq-2 gin_app]# cat /root/go/gin_app/main.go
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", hello)
server := &http.Server{
Addr: ":8888",
}
fmt.Println("server startup...")
if err := server.ListenAndServe(); err != nil {
fmt.Printf("server startup failed, err:%v\n", err)
}
}
func hello(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("hello youmen.com!"))
}
编译阶段
Dockerfile
[root@rabbitmq-2 gin_app]# cat Dockerfile
FROM golang:alpine AS build
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64 \
GOPROXY="https://goproxy.io"
# 移动到工作目录:/build
WORKDIR $GOPATH/src/gin_docker
# 将代码复制到容器中
ADD . ./
# 将我们的代码编译成二进制可执行文件 app
RUN go build -o app
# 需要运行的命令
CMD ["./app"]
[root@rabbitmq-2 gin_app]# docker build -t gin_app -t gin_app . --target=build
[root@rabbitmq-2 gin_app]# docker images |grep gin_app
gin_app latest c35bb6310fce 10 minutes ago 321MB
[root@rabbitmq-2 gin_app]# docker run --rm -it -p 8888:8888 goweb_app
server startup...
[root@rabbitmq-2 ~]# curl localhost:8888
hello youmen.com!
生产阶段
[root@rabbitmq-2 gin_app]# cat Dockerfile
FROM golang:alpine AS build
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64 \
GOPROXY="https://goproxy.io"
# 移动到工作目录:/build
WORKDIR $GOPATH/src/gin_docker
# 将代码复制到容器中
ADD . ./
# 将我们的代码编译成二进制可执行文件 app
RUN go build -ldflags "-s -w" -o app .
###################
# 接下来创建一个小镜像
###################
FROM scratch As prod
# 从builder镜像中把/go/src/gin_docker 拷贝到当前目录
# 设置应用程序以非 root 用户身份运
# User ID 65534 通常是 'nobody' 用户.
# 映像的执行者仍应在安装过程中指定一个用户。
COPY --chown=65534:0 --from=build /go/src/gin_docker .
USER 65534
# 需要运行的命令
CMD ["./app"]
[root@rabbitmq-2 gin_app]# docker build -t gin_app -t gin_app . --target=pro
[root@rabbitmq-2 gin_app]# docker images |grep gin_app
gin_app latest 592cd0dca666 32 seconds ago 4.42MB
为你的Go应用创建轻量级Docker镜像?的更多相关文章
- 如何为你的Go应用创建轻量级Docker镜像?
介绍 多什么? 简单来讲,多阶段. 多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序.举个例子,首先我们使用Golang的基础镜像,然 ...
- Dockerfile创建自定义Docker镜像以及CMD与ENTRYPOINT指令的比较
1.概述 创建Docker镜像的方式有三种 docker commit命令:由容器生成镜像: Dockerfile文件+docker build命令: 从本地文件系统导入:OpenVZ的模板. 关于这 ...
- 根据Dockerfile创建hello docker镜像
一.编写hello可执行c文件: 1.安装:gcc glibc glibc-static yum install -y gcc glibc glibc-static 2.编写hello.c:vim h ...
- docker学习系列(二):使用Dockerfile创建自己的镜像
dockerfile可以允许我们自己创建镜像,通过编写里面的下载软件命令,执行docker build 即可生成镜像文件. 初尝dockerfile 新建一个目录test,然后进入这个目录,创建一个名 ...
- Docker笔记--镜像&基于GO项目创建Docker镜像
Docker笔记--镜像&基于GO项目创建Docker镜像 核心概念 Doker镜像--包含一个基本的操作系统运行环境和应用程序,镜像是创建Docker容器的基础. Docker容器--如果把 ...
- 019.nexus搭建docker镜像仓库/maven仓库
一.安装docker CE 参考docker doc https://docs.docker.com/install/linux/docker-ce/centos/ 二.docker启动nexus3 ...
- [开源]制作docker镜像不依赖linux和Docker环境
背景 最近群友们经常反馈docker镜像制作起来有点麻烦,我开源的antdeploy工具虽然可以制作镜像但是必须有一个提前:有一台安装好docker的linux服务器.因为大家开发环境基本上都是win ...
- docker快速创建轻量级的可移植的容器(一)
系列其他内容 docker快速创建轻量级的可移植的容器✓ docker&flask快速构建服务接口 docker&uwsgi高性能WSGI服务器生产部署必备 docker&gu ...
- docker学习(5) 在mac中创建mysql docker容器
github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql ...
随机推荐
- Redis学习笔记六:持久化实验(AOF,RDB)
作者:Grey 原文地址:Redis学习笔记六:持久化实验(AOF,RDB) Redis几种持久化方案介绍和对比 AOF方式:https://blog.csdn.net/ctwctw/article/ ...
- 分析型CRM系统都分析什么?
在之前的文章中我们曾经讲过,目前市面上常见的CRM系统大概可以分为通用型.协助型和分析型三种类型.由于每个企业的类型.业务的不同,就需要选择一款适合的CRM客户关系管理系统.今天我们就来说一说,分析型 ...
- [软件开发的目录规范、区分py文件的两种用途]
[软件开发的目录规范] 软件开发的目录规范 bin: 执行文件 conf: 存放配置文件相关代码 core: 存放源文件,业务逻辑相关代码 db: 存放数据库文件,主要用于与数据库交互 lib: 存放 ...
- .NET平台系列9 .NET Core 3.0 / .NET Core 3.1 详解
系列目录 [已更新最新开发文章,点击查看详细] .NET Core 3.0 于 2019年9月23日发布,重点是增加对同时支持使用 Windwos Forms.WPF 和 Entity Frm ...
- Spring 注解动态数据源设计实践
Spring 动态数据源 动态数据源是什么?解决了什么问题? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过Spr ...
- [DB] Spark Core (2)
RDD WordCount处理流程 sc.textFile("/root/temp/data.txt").flatMap(_.split(" ")).map(( ...
- 【转载】Pycharm调试高效,还是pdb调试高效? (在服务端)
https://segmentfault.com/q/1010000005067119 Pycharm调试高效,还是pdb调试高效? (在服务端) python 3.9k 次浏览 问题对人有帮助, ...
- 服务器硬件必须支持M2 或PCIE才能支持NVME
兆芯服务器不支持NVME. 服务器硬件必须支持M2 或PCIE才能支持NVME.1 因为物理接口只有M2 SATA 和PCIE这三中但是NVME只支持M2 和PCIE这2种2所以 NVME不支持SAT ...
- NFS PersistentVolume(11)
一.部署nfs服务端 1.需在 k8s-master 节点上搭建了一个 NFS 服务器,目录为 /nfsdata: yum install -y nfs-utils rpcbind vim /etc/ ...
- 4 系统的 CPU 使用率很高,但为啥却找不到高 CPU的应用?
上一节讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过就 ...