缩小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. Spring 中使用了那些设计模式呢?

    一. Spring 中常见的设计模式 工厂模式 : BeanFactory 装饰器模式: BeanWrapper 代理模式: AopProxy 单例模式: ApplicationContext 委派模 ...

  2. XAML常用控件2

    这节继续讲一些xaml中的常用控件. 布局控件 除了我们之前讲过的Grid,StackPanel,Border布局控件,xaml中还有如下几个布局控件: Canvas:使用这个布局,可以通过坐标来控制 ...

  3. Java集合详解(三):LinkedList原理解析

    概述 本文是基于jdk8_271源码进行分析的. LinkedList底层是基于链表实现.链表没有长度限制,内存地址不需要固定长度,也不需要是连续的地址来进行存储,只需要通过引用来关联前后元素即可完成 ...

  4. Gateway导航

    简介 最近都在弄微服务的东西,现在来记录下收获.我从一知半解到现在能从0搭建使用最大的感触有两点 1.微服务各大组件的版本很多,网上很多博客内容不一定适合你的版本,很多时候苦苦琢磨都是无用功 2.网上 ...

  5. [bug]mysql: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone

    原因: 时区设置有误 解决: 在mysql中修改时区设置: 或 在JDBC代码中增加时区设置: Connection c = DriverManager.getConnection("jdb ...

  6. Python socket 编程实验

    实验内容 1.编写一个基于UDP协议的客户机与服务器程序,实现相互通讯. 2.编写一个基于TCP协议的客户机与服务器程序,实现相互通讯. 3.捕获以上两种通讯的数据包,使用Wireshark进行分析, ...

  7. Windows 常用Cmd命令行 (持续更新...)

    查看IP ipconfig 查看WIFI密码 netsh wlan show profiles wifi_name key = clear 系统探针 systeminfo CMD重定向 输出符号> ...

  8. SQL注入和XSS攻击

    SQL注入 定义:黑客通过在请求的数据中填入特殊字符,影响服务器数据库操作程序正常执行,从而达到攻击的目的. 形式: 拼接SQL: 登录验证:前台输入 username:yan password:12 ...

  9. python基础之字典、集合

    一.字典(dictionary) 作用:存多个值,key-value存取,取值速度快 定义:key必须是不可变类型,value可以是任意类型 字典是一个无序的,可以修改的,元素呈键值对的形式,以逗号分 ...

  10. ArcGIS10从入门到精通系列实验图文教程(附配套实验数据持续更新)

    @ 目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 本教程<ArcGIS从入门到精通系列实验教程>内容包括:ArcGIS平台简介.ArcGIS应用基础.空间数据的采集 ...