前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为你的Go应用创建轻量级Docker镜像?

为你的Go应用创建轻量级Docker镜像?

原创
作者头像
iginkgo18
修改2021-07-02 18:05:46
6640
修改2021-07-02 18:05:46
举报
文章被收录于专栏:devops_k8sdevops_k8sdevops_k8s

缩小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 来压缩。一般在服务器端独立运行的后台服务,无需压缩体积。

介绍

多阶段允许在创建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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缩小Go二进制文件大小
    • 环境
      • 默认二进制打包
        • -ldflags
          • UPX
            • upx的压缩选项
            • UPX的原理
          • 介绍
            • 示例工程
              • 编译阶段
                • 生产阶段
                相关产品与服务
                容器镜像服务
                容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档