缩小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镜像?的更多相关文章

  1. 如何为你的Go应用创建轻量级Docker镜像?

    介绍 多什么? 简单来讲,多阶段. 多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序.举个例子,首先我们使用Golang的基础镜像,然 ...

  2. Dockerfile创建自定义Docker镜像以及CMD与ENTRYPOINT指令的比较

    1.概述 创建Docker镜像的方式有三种 docker commit命令:由容器生成镜像: Dockerfile文件+docker build命令: 从本地文件系统导入:OpenVZ的模板. 关于这 ...

  3. 根据Dockerfile创建hello docker镜像

    一.编写hello可执行c文件: 1.安装:gcc glibc glibc-static yum install -y gcc glibc glibc-static 2.编写hello.c:vim h ...

  4. docker学习系列(二):使用Dockerfile创建自己的镜像

    dockerfile可以允许我们自己创建镜像,通过编写里面的下载软件命令,执行docker build 即可生成镜像文件. 初尝dockerfile 新建一个目录test,然后进入这个目录,创建一个名 ...

  5. Docker笔记--镜像&基于GO项目创建Docker镜像

    Docker笔记--镜像&基于GO项目创建Docker镜像 核心概念 Doker镜像--包含一个基本的操作系统运行环境和应用程序,镜像是创建Docker容器的基础. Docker容器--如果把 ...

  6. 019.nexus搭建docker镜像仓库/maven仓库

    一.安装docker CE 参考docker doc https://docs.docker.com/install/linux/docker-ce/centos/ 二.docker启动nexus3 ...

  7. [开源]制作docker镜像不依赖linux和Docker环境

    背景 最近群友们经常反馈docker镜像制作起来有点麻烦,我开源的antdeploy工具虽然可以制作镜像但是必须有一个提前:有一台安装好docker的linux服务器.因为大家开发环境基本上都是win ...

  8. docker快速创建轻量级的可移植的容器(一)

    系列其他内容 docker快速创建轻量级的可移植的容器✓ docker&flask快速构建服务接口 docker&uwsgi高性能WSGI服务器生产部署必备 docker&gu ...

  9. docker学习(5) 在mac中创建mysql docker容器

    github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql ...

随机推荐

  1. Eclipse中System.out.println()快捷键生成方法

    输入syso,再按ALT+/,如果不显示,就在输入完整一行   System.out.println();  之后点击5次shift键,显示是否使用粘滞键,点击是,  再输入sout,再按ALT+/ ...

  2. 论文翻译:Conv-TasNet: Surpassing Ideal Time–Frequency Magnitude Masking for Speech Separation

    我醉了呀,当我花一天翻译完后,发现已经网上已经有现成的了,而且翻译的比我好,哎,造孽呀,但是他写的是论文笔记,而我是纯翻译,能给读者更多的思想和理解空间,并且还有参考文献,也不错哈,反正翻译是写给自己 ...

  3. .jnlp 文件打开方式

    .jnlp 文件打开方式 jnlp文件打开需要安装jre ,java环境,通过java环境运行即可,下面介绍详细步骤 1.下载.安装最新版jre环境,直接下一步即可 2 java配置 打开控制面板,查 ...

  4. && echo suss! || echo failed

    ### && echo suss! || echo failed 加在bash后 ########ls /proc && echo suss! || echo fail ...

  5. 把一个整体目标设置成多个分阶段目标,完成了一个目标后,就相当于一件事OVER

    如果事情有变坏的可能,不管这种可能性有多小,它总会发生 . 一.任何事都没有表面看起来那么简单:二.所有的事都会比你预计的时间长:三.会出错的事总会出错:四.如果你担心某种情况发生,那么它就一定会发生 ...

  6. 使用 parted 命令可以查看系统采用什么类型的分区表 gpt/mbr/msdos/ext/ext/ext2/ext3/ext4

      Linux磁盘表示方式 Linux以字母标识磁盘的个数 a:第一块 b:第二块 Linux用数字标识分区:1-4标识主分区或扩展分区 逻辑分区从5开始 例如:sda.sda1.sda2 低级格式化 ...

  7. Linux进阶之进程管理

    本节内容 1.进程管理 2.ps 3.uptime 4.top 5.ss -tnl------ lsof -i :22 一. 进程管理的概念 程序:二进制文件,静态 /bin/date,/usr/sb ...

  8. CSS 重置技术

    元素默认样式显示问题 每个浏览器对不同元素都有自己的默认样式.Google Chrome 渲染正文标题.段落.列表等,与 IE 浏览器可能都有所不同.这就导致同一个页面元素在不同的浏览器中显示效果不一 ...

  9. 10.8 ss:查看网络状态

    ss命令 是类似并将取代netstat的工具,它能用来查看网络状态信息,包括TCP.UDP连接.端口等.它的优点是能够显示更多更详细的有关网络连接状态的信息,而且比netstat更快速更高效.    ...

  10. Scrapy的Request和Response

    Scrapy的Request和Response   本文链接:https://blog.csdn.net/kissazhu/article/details/80865773 上节课我们学习了中间件,知 ...