11. 构建和部署Go代码
# 11. 构建和部署Go代码
在我们掌握了编写高性能Go代码的方法之后,就需要将其交付、验证并持续迭代。这个过程的第一步是部署新的Go代码。Go代码会被编译成二进制文件,这使得我们在代码开发的迭代过程中,能够以模块化的方式部署新的Go代码。我们可以将其推送到一个或多个地方,以便在不同的环境中进行测试。这样做能让我们优化代码,充分利用系统提供的吞吐量。
在本章中,我们将全面了解Go的构建过程。我们会探讨Go编译器如何构建二进制文件,并利用这些知识为当前平台构建大小合适、经过优化的二进制文件。我们将涵盖以下主题:
- 构建Go二进制文件
- 使用
go clean
删除目标文件 - 使用
go get
下载并安装依赖项 - 使用
go mod
进行依赖项管理 - 使用
go list
列出包和模块 - 使用
go run
执行程序 - 使用
go install
安装包
这些主题将帮助我们从源代码构建高效的Go二进制文件。
# 构建Go二进制文件
在第10章 “Go中的编译时评估” 中,我们讨论了一些Go构建优化方法,这些方法可能有助于优化我们的构建策略。Go的构建系统有很多选项,可以帮助系统管理员为他们的构建策略添加更多的参数化设置。
Go工具提供了多种构建源代码的方法。让我们先对每种方法有一个大致的了解,然后再深入讨论每个包。了解这些命令之间的关键差异,有助于您理解它们如何相互作用,并为特定任务选择合适的工具。让我们来看看这些命令:
go build
:为项目构建二进制文件,编译包及其依赖项。go clean
:从包源目录中删除目标文件和缓存文件。go get
:下载并安装包及其依赖项。go mod
:Go(相对较新)的内置依赖项模块系统。go list
:列出指定的包和模块,并可以显示有关文件、导入和依赖项的重要构建信息。go run
:运行并编译指定的Go程序。go install
:为项目构建二进制文件,将二进制文件移动到$GOPATH/bin
目录,并缓存所有非主包。
在本章中,我们将深入研究Go构建系统的这些不同部分。随着我们对这些程序如何相互协作的了解不断加深,我们将能够明白如何利用它们构建精简且功能丰富的二进制文件,使其在支持的架构和操作系统上按预期运行。
在下一节中,我们将深入了解go build
。
# go build - 构建你的Go代码
go build
的调用语法如下:
go build [-o output] [build flags] [packages]
使用-o
定义输出,可使用指定名称的文件来编译二进制文件。当您希望为文件遵循特定的命名约定,或者想根据不同的构建参数(平台/操作系统/git
SHA等)来命名二进制文件时,这会很有帮助。
包可以定义为Go源文件的列表,也可以省略。如果指定了Go源文件列表,构建程序将把传入的文件列表视为一个指定单个包的组。如果未定义包,构建程序将验证目录中的包是否可以构建,但会丢弃构建结果。
# 构建标志
Go的构建标志可用于build
、clean
、install
、list
、run
和test
命令。以下是构建标志及其用法描述的表格:
构建标志 | 描述 |
---|---|
-a | 强制重新构建包。如果您想确保所有依赖项都是最新的,这个标志会特别有用。 |
-n | 打印编译器使用的命令,但不运行这些命令(类似于其他语言中的预演)。这有助于查看包是如何编译的。 |
-p n | 并行化构建命令。默认情况下,该值设置为构建系统可用的CPU数量。 |
-race | 启用竞态检测。只有特定的架构能够进行竞态检测: - linux/amd64 - freebsd/amd64 - darwin/amd64 - windows/amd64 |
-msan | 检测C语言中未初始化的内存读取。仅在具有amd64 或arm64 架构的Linux系统上受支持,并且主机需要使用clang/LLVM 编译器。可以通过CC=clang go build -msan example.go 来调用。 |
-v | 在编译程序时,将构建的包名输出到标准输出。这有助于验证构建过程中使用了哪些包。 |
-work | 打印Go用于构建二进制文件的临时工作目录的值。默认情况下,该目录通常存储在/tmp/ 中。 |
-x | 显示构建过程中使用的所有命令。这有助于确定包是如何构建的。更多信息,请参阅 “构建信息” 部分。 |
-asmflags '[pattern=]arg list' | 调用go tool asm 时传递的参数列表。 |
-buildmode=type | 告诉构建命令我们想要构建的目标文件类型。目前buildmode 有几个类型选项:- archive :将非主包构建成.a 文件。- c-archive :将主包及其所有导入的包构建成一个C归档文件。- c-shared :将主包及其导入的包构建成一个C共享库。- default :创建一个主包列表。- shared :将所有非主包合并成一个共享库。- exe :将主包及其导入的包构建成可执行文件。- pie :将主包及其导入的包构建成位置无关可执行文件(PIE)。- plugin :将主包及其导入的包构建成一个Go插件。 |
-compiler name | 确定使用哪个编译器。常见的有gccgo 和gc 。 |
-gccgoflags | gccgo 编译器和链接器的调用标志。 |
-gcflags | gc 编译器和链接器的调用标志。更多详细信息,请参阅 “编译器和链接器” 部分。 |
-installsuffix suffix | 在包安装目录的名称中添加一个后缀。这用于将输出与默认构建区分开来。 |
-ldflags '[pattern=]arg list' | go tool link 的调用参数。更多详细信息,请参阅 “编译器和链接器” 部分。 |
-linkshared | 在执行-buildmode=shared 之后,此标志用于链接新创建的共享库。 |
-mod | 确定使用哪种模块下载模式。在撰写本文时,有两个选项:-readonly 或vendor 。 |
-pkgdir dir | 使用指定的目录来安装和加载所有包。 |
-tags tag,list | 构建过程中需要满足的构建标签列表。该列表以逗号分隔的形式传递。 |
-trimpath | 在构建可执行文件时,生成的可执行文件将对文件系统路径使用不同的命名方案。具体如下: - Go (用于标准库)- path @version (用于Go模块)- plain import path (在使用GOPATH 时) |
-toolexec 'cmd args' | 调用工具链程序,如调试器或其他交互式程序。这用于诸如vet 和asm 等程序。 |
掌握了这些信息,您将能够有效地设置正确的链接器标志。
# 构建信息
为了更好地理解构建过程,让我们看一些构建示例,以便深入了解构建工具是如何协同工作的。
假设我们要构建一个带有Prometheus导出器的简单HTTP服务器。我们可以这样创建一个导出器:
package main
import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/", promhttp.Handler())
port := ":2112"
fmt.Println("Prometheus Handler listening on port ", port)
http.ListenAndServe(port, nil)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
准备好包之后,我们可以使用以下命令构建包:
go build -p 4 -race -x prometheusExporterExample.go
在构建这个二进制文件时,我们会在标准输出中看到一些信息(因为我们传递了-x
标志来查看构建过程中使用的命令)。让我们来看看:
- 为了便于阅读,我们将截断输出内容。如果您自己测试,会看到更详细的构建输出:
WORK=/tmp/go-build924967855
这为构建设置了一个临时工作目录。如前所述,除非另有指定,该目录通常位于/tmp/
目录下:
mkdir -p $WORK/b001/
- 编译器还会创建一个子工作目录:
cat >$WORK/b001/importcfg.link << 'EOF' # internal
- 创建并添加链接配置。这会向链接配置中添加各种不同的参数:
packagefile command-line-arguments=/home/bob/.cache/go-build/aa/aa63d73351c57a147871fde4964d74c9a39330b467c6d73640815775e6673084-d
- 从缓存中引用命令行参数的包:
packagefile fmt=/home/bob/.cache/go-build/74/749e110dc104578def1859fbd4ca5c5546f4032f02ffd5ea4d14c730fbd65b81-d
fmt
是我们用于显示fmt.Println("Prometheus Handler listening on port ", port)
的打印包。它的引用如下:
packagefile github.com/prometheus/client_golang/prometheus/promhttp=/home/bob/.cache/go-build/e9/e98940b17504e2f647dccc7832793448aa4e8a64047385341c94c1c4431d59cf-d
- 编译器还会添加Prometheus HTTP客户端库的包。在此之后,还有许多其他引用会添加到构建过程中。为简洁起见,这里进行了截断。文件末尾以
EOF
表示。 - 创建一个可执行目录:
mkdir -p $WORK/b001/exe/
- 然后,编译器使用之前创建的
importcfg
来构建二进制文件:
/usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -installsuffix race -buildmode=exe -buildid=bGYa4XecCYqWj3VjKraU/eHfXIjk2XJ_C2azyW4yU/8YHxpy5Xa69CGQ4FC9Kb/bGYa4XecCYqWj3VjKraU -race -extld=gcc /home/bob/.cache/go-build/aa/aa63d73351c57a147871fde4964d74c9a39330b467c6d73640815775e6673084-
- 然后添加一个构建ID:
/usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
- 接下来,将二进制文件重命名为我们的导出器示例的文件名(因为我们没有使用
-o
指定不同的二进制文件名):
cp $WORK/b001/exe/a.out prometheusExporterExample
- 最后,删除工作目录:
rm -r $WORK/b001/
这个程序的构建结果是一个Go二进制文件。在下一节中,我们将讨论编译器和链接器标志。
# 编译器和链接器标志
在构建Go二进制文件时,-gcflags
标志允许您传递可选的编译器参数,而-ldflags
标志允许您传递可选的链接器参数。通过调用以下命令,可以找到编译器和链接器标志的完整列表:
go tool compile -help
go tool link -help
2
让我们看一个使用编译器和链接器标志的示例。我们可以构建一个简单的程序,该程序返回一个未初始化的字符串变量的值。下面这个程序看起来没什么问题:
package main
import "fmt"
var linkerFlag string
func main() {
fmt.Println(linkerFlag)
}
2
3
4
5
6
7
8
9
如果使用一些常见的编译器和链接器标志来构建此代码,我们将看到一些有用的输出:
我们这里传递的编译器标志实现了以下功能:
- “-m -m”:打印关于编译器优化决策的信息。这就是我们在构建命令执行后,在上图中看到的输出内容。
- “-N”:禁用Go二进制文件中的优化。
- “-l”:禁用内联(inlining)。
我们传递的链接器标志的作用如下:
- “-X main.linkerFlag=Hi_Gophers”:为
main
包中的linkerFlag
变量设置一个值。在构建时能够添加变量很重要,因为许多开发者希望在编译时向代码中添加某种构建参数。我们可以使用date -u +.%Y%m%d%.H%M%S
传递构建日期,或者使用git rev-list -1 HEAD
传递Git提交版本。这些值在之后用于引用构建状态时会很有帮助。 - “-s”:禁用符号表(symbol table),符号表是一种数据结构,它将源代码中的每个标识符与声明信息一起存储。对于生产环境的二进制文件,通常不需要这个符号表。
- “-w”:禁用DWARF生成。由于Go二进制文件已经包含了基本类型信息、程序计数器到行数据(PC-to-linedata)和符号表,所以DWARF表通常无需保存。
如果我们先用标准方法构建二进制文件,然后再使用一些可用的编译器和链接器标志,就可以看到二进制文件大小的差异:
- 未优化的构建:
$ go build -ldflags "-X main.linkerFlag=Hi_Gophers" -o nonOptimized
- 优化后的构建:
$ go build -gcflags="-N -l" -ldflags "-X main.linkerFlag=Hi_Gophers -s -w" -o Optimized
可以看到,优化后的二进制文件比未优化的二进制文件小28.78%:
这两个二进制文件对最终用户来说功能相同,所以可以考虑使用编译器和链接器标志去除一些构建优化,以减小最终生成的二进制文件大小。这在存储和部署这些二进制文件时会很有帮助。
# 构建约束(Build constraints)
如果你想为Go构建添加构建约束,可以在文件开头添加注释行,注释行前面只能是空白行和其他注释。这种注释的格式是// +build darwin,amd64,!cgo,android,386,cgo
。
这对应的布尔表达式是(darwin AND amd64 AND (NOT cgo)) OR (android AND 386 AND cgo)
。
构建约束需要在包声明之前,且构建约束和包初始化之间要有一个换行符。其格式如下:
// +build [OPTIONS]
package main
2
完整的构建约束列表可以在 https://golang.org/pkg/go/build/#hdr-Build_Constraints 找到。这个列表包括以下构建约束:
GOOS
GOARCH
- 编译器类型(
gc
或gccgo
) cgo
- 所有1.x版本的Go(没有针对测试版或小版本发布的构建标签)
ctxt.BuildTags
中列出的其他单词
如果你想在库中排除某个文件不参与构建,也可以添加如下形式的注释:
// +build ignore
相反,你可以使用如下形式的注释将文件构建限制在特定的GOOS
、GOARCH
和cgo
条件下:
// +build windows,386,cgo
只有在使用cgo
并且在Windows操作系统的386处理器上进行构建时,这个文件才会被构建。这是Go语言中一个强大的功能,因为你可以根据必要的构建参数来构建包。
# 文件名约定
如果一个文件在去掉任何扩展名和_test
后缀(用于测试用例)后,匹配GOOS
和GOARCH
模式,那么这个文件将针对该特定的GOOS
或GOARCH
模式进行构建。常见的模式如下:
*_GOOS
*_GOARCH
*_GOOS_GOARCH
例如,如果你有一个名为example_linux_arm.go
的文件,它只会作为Linux arm架构构建的一部分被构建。
在下一节中,我们将探索go clean
命令。
# go clean - 清理你的构建目录
Go命令会在一个临时目录中构建二进制文件。go clean
命令用于删除其他工具创建的或手动调用go build
时生成的多余目标文件。go clean
的使用格式为go clean [clean flags] [build flags] [packages]
。
clean
命令有以下可用标志:
-cache
标志:删除整个Go构建缓存。如果你想在多个系统上比较全新的构建,或者想了解全新构建所需的时间,这个标志会很有用。-i
标志:删除go install
创建的归档文件或二进制文件。-n
标志:不执行任何操作,仅打印删除命令但不执行它们。-r
标志:对导入路径的包的所有依赖项递归应用清理逻辑。-x
标志:打印并执行生成的删除命令。-cache
标志:删除整个Go构建缓存(重复列出,疑为原文重复)。-testcache
标志:删除构建缓存中的测试结果。-modcache
标志:删除模块下载缓存。
如果我们想在没有现有依赖项的情况下进行全新构建,可以使用一个命令来删除Go构建系统中许多重要缓存中的内容。下面来看一下具体步骤:
- 我们将构建
prometheusExporterExample
,以验证构建缓存大小的变化。我们可以使用Go环境变量GOCACHE
来找到构建缓存的位置:
- 为了进行验证,我们将连续使用几个命令。首先,使用
rm -rf ~/.cache/go-build/
删除整个缓存目录。 - 接下来,运行
go build prometheusExporterExample.go
命令构建我们的Go二进制文件。 - 然后,使用
du -sh ~/.cache/go-build/
检查缓存大小,以验证缓存是否显著增大。 - 现在,我们可以使用
go clean
程序清理缓存,即go clean -cache -modcache -i -r 2&>/dev/null
。
需要注意的是,一些缓存信息存储在主库中,普通用户无法删除这些内容。如果需要,我们可以以超级用户身份运行清理命令来解决这个问题,但通常不建议这样做 。
然后,我们可以验证缓存是否变小。清理后查看缓存目录,会发现其中只剩下三个文件:
一个
README
文件,用于解释该目录。一个
log.txt
文件,记录缓存信息。一个
trim.txt
文件,记录上次完成缓存清理的时间。
验证构建过程中缓存的内容是否正确,有助于加快构建速度,并让开发过程更加轻松。
在下一节中,我们将介绍go get
和go mod
命令。
# 使用go get和go mod获取包依赖项
在构建Go程序时,你可能经常需要添加依赖项。go get
命令用于下载并安装包及其依赖项。go get
的调用格式为go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]
。
Go 1.11版本增加了对Go模块的初步支持。我们在第6章“编写可读性强的Go代码”的“Go模块”部分学习了如何使用Go模块。
Go模块供应商(Go mod vendor)通常是Go构建系统的一部分,因为我们可以在Go程序中使用供应商依赖项。在代码库中使用供应商依赖项有利有弊。在构建时所有所需的依赖项都在本地可用,可以加快构建速度。但是,如果用于构建依赖项的上游存储库发生更改或被删除,构建就会失败,因为你的程序无法满足其上游依赖项。
使用供应商依赖项的缺点还包括,更新依赖项的责任落在了程序员身上。如果使用了供应商依赖项但未进行更新,那么上游的更新,如安全更新、性能改进和稳定性增强等,都可能无法应用。
许多企业选择使用供应商依赖项的方式,因为他们认为存储所有所需依赖项的安全性,比在有新版本可用时更新供应商目录更为重要。
初始化Go模块后,我们可以将依赖项进行供应商管理,并使用供应商模块进行构建:
如上述输出所示,我们有满足项目构建约束所需的供应商依赖项(来自https://github.com/和https://golang.org/)。我们可以在构建过程中使用go mod tidy
来验证go.mod
文件是否包含存储库所需的所有元素。
go mod tidy
命令会添加缺失的模块,并删除未使用的模块,以确保源代码与目录中的go.mod
文件匹配。
在下一节中,我们将学习go list
命令。
# go list
go list
命令用于列出指定的包和模块,以及显示有关文件、导入和依赖项的重要构建信息。go list
的调用格式为usage: go list [-f format] [-json] [-m] [list flags] [build flags] [packages]
。
能够访问作为构建过程主要部分的数据结构非常有用。我们可以使用go list
命令来深入了解正在构建的程序。例如,对于下面这个简单的程序,它会打印一条消息并为用户计算平方根:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("Hello Gophers")
fmt.Println(math.Sqrt(64))
}
2
3
4
5
6
7
8
9
10
11
如果我们想了解特定项目的所有依赖项,可以调用如下命令:
go list -f '{{.Deps}}'
结果将是存储库中包含的所有依赖项的切片:
go list
的数据结构可以在https://golang.org/cmd/go/#hdr-List_packages_or_modules找到。它有许多不同的参数。go list
程序另一个常用的输出是JSON格式的输出。在下面的截图中,你可以看到对listExample.go
执行go list -json
的输出:
go list -m -u all
命令也会显示项目的依赖项。如果有可用的升级版本,输出结果中还会在括号里列出第二个版本号。如果我们想通过go mod
包密切监控依赖项及其升级情况,这个命令会很有帮助。
以Prometheus导出器(Prometheus exporter)为例,我们可以查看包的依赖项是否需要升级:
在这个例子中,可以看到有几个包有可用的升级版本。如果对其中一个依赖项执行go get
命令,就能有效地进行升级。比如,使用go get github.com/pkg/errors@v0.8.1
,可以将上图中列出的errors
包从v0.8.0
升级到v0.8.1
。
完成升级后,我们可以运行go list -m -u github.com/pkg/errors
来验证依赖项是否已升级。
下面的截图展示了输出结果:
从上述输出中可以看到,现在引用的errors
包是v0.8.1
,而不是之前输出中的v0.8.0
。
接下来,让我们了解一下go run
命令。
# go run - 运行你的包
go run
命令用于运行并编译指定的Go程序。go run
的调用格式为go run [构建标志] [-exec xprog] 包 [参数...]
。
go run
允许开发者通过一次操作快速编译并运行Go二进制文件。在这个过程中,go run
会构建可执行文件,运行它,然后删除该可执行文件。这在开发环境中特别有用。在快速迭代Go程序时,go run
可以作为一种快捷方式,用于验证正在修改的代码能否生成可接受的构建产物。正如我们在本章前面所学,这些工具的许多构建标志是一致的。
goRun.go
是一个非常简单的Go程序,它没有参数,只有一个空的main()
函数调用。我们以此为例,展示这个过程,且该示例没有额外的依赖项或开销:
package main
func main() {}
2
3
通过执行go run -x goRun.go
命令,我们可以查看go run
调用相关的工作输出。
执行此操作时,我们能够看到作为go run
程序一部分被调用的构建参数:
这个输出看起来应该很熟悉,因为它与我们在go build
示例中看到的输出非常相似。然后,我们可以看到我们的包被调用。
如果对Prometheus HTTP服务器执行相同的操作,会发现执行go run
程序后,Prometheus HTTP服务器会启动并运行。在go run
调用过程中终止进程后,会注意到本地目录中没有存储任何二进制文件。默认情况下,go run
调用不会保存这些输出。
下一节要介绍的Go命令(go install
)是本章的最后一个命令。让我们来了解一下它的作用。
# go install - 安装你的二进制文件
go install
命令用于编译并安装指定的Go程序。go install
的调用格式为go install [-i] [构建标志] [包]
。
这些包会被导入到$GOPATH/pkg
目录。如果缓存的项目没有被修改,下次编译时会使用它们。go install
的结果是生成一个可执行文件,它与使用go build
命令编译的文件相同,并安装在系统的$GOBIN
路径下。例如,如果我们想在主机上安装Prometheus HTTP服务器,可以执行go install
命令,即GOBIN=~/prod-binaries/ go install -i prometheusExporterExample.go
。
设置GOBIN
变量可以告诉编译器编译完成后将编译好的二进制文件安装到哪里。go install
程序允许我们将二进制文件安装到GOBIN
指定的位置。-i
标志用于安装指定包的依赖项。下面的截图展示了这一过程:
完成上述操作后,可以看到在示例中定义的GOBIN
位置有一个prometheusExporterExample
二进制文件。
在本章的最后一节,我们将了解如何使用Docker构建Go二进制文件。
# 使用Docker构建Go二进制文件
根据目标架构的不同,你可能希望使用Docker构建Go二进制文件,以保持构建的可重复性、限制构建大小,并最小化服务的攻击面。使用多阶段Docker构建可以帮助我们完成这项任务。
要执行这些操作,你必须安装最新版本的Docker。我们将要使用的多阶段构建功能要求Docker守护进程和客户端的版本都在17.05或更高。你可以在https://docs.docker.com/install/上找到适合你操作系统的最新Docker版本以及安装说明。
考虑下面这个简单的包,它会在屏幕上打印一条调试消息:
package main
import "go.uber.org/zap"
func main() {
zapLogger := zap.NewExample()
defer zapLogger.Sync()
zapLogger.Debug("Hi Gophers - from our Zap Logger")
}
2
3
4
5
6
7
8
9
如果我们想在最小化依赖项的情况下,在Docker容器中构建并运行这个包,可以使用多阶段Docker构建。为此,可以执行以下步骤:
- 通过执行以下命令,将当前目录初始化为模块的根目录:
go mod init github.com/bobstrecansky/HighPerformanceWithGo/11-deploying-go-code/multiStageDockerBuild
- 通过执行以下命令添加供应商仓库:
go mod vendor
现在,我们的仓库中就有了所有必需的供应商包(在这个例子中是Zap日志记录器)。下面的截图展示了这一情况:
- 构建
zapLoggerExample
的Docker容器。我们可以使用以下Dockerfile来构建容器:
# Builder - stage 1 of 2
FROM golang:alpine as builder
COPY . /src
WORKDIR /src
RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -o zapLoggerExample
# Executor - stage 2 of 2
FROM alpine:latest
WORKDIR /src/
COPY /src/zapLoggerExample .
CMD ["./zapLoggerExample"]
2
3
4
5
6
7
8
9
10
11
请注意,我们使用golang:alpine
镜像来构建Go二进制文件,因为它是包含成功构建Go二进制文件所需元素的最简单的Docker镜像之一。我们使用alpine:latest
镜像来运行Go二进制文件,因为它是包含成功运行Go二进制文件所需元素的最简单的Docker镜像之一。
在这个Dockerfile示例中,我们使用多阶段Docker构建来构建和运行二进制文件。在第一阶段(构建阶段),我们以golang alpine
镜像为基础。将当前目录中的所有文件复制到Docker容器的/src/
目录中,将/src/
设置为工作目录,然后构建Go二进制文件。禁用cgo
、为Linux架构构建以及添加在第一步中创建的供应商目录,这些都有助于最小化构建大小和时间。
在第二阶段(执行阶段),我们使用基本的alpine
Docker镜像,将/src/
设置为工作目录,并将第一阶段构建的二进制文件复制到这个Docker容器中。然后,我们在这个Docker构建中执行日志记录器作为最后一个命令。
4. 准备好必要的依赖项后,我们可以通过执行以下命令来构建Docker容器:
docker build -t zaploggerexample .
- 构建好Docker容器后,我们可以通过执行以下命令来运行它:
docker run -it --rm zaploggerexample
在下面的截图中,你可以看到我们的构建和执行步骤都已完成:
在多阶段Docker容器中构建Go程序,有助于创建可复现的构建、限制二进制文件大小,并通过仅使用所需的部分内容来最小化服务的攻击面。
# 总结
在本章中,我们学习了如何构建Go二进制文件,了解了如何高效且稳定地进行构建。我们还学习了如何理解和管理依赖项,使用go run
测试Go代码,以及使用go install
将Go二进制文件安装到特定位置。理解这些二进制文件的工作原理,将帮助你更高效地迭代代码。
在下一章中,我们将探讨如何对Go代码进行性能分析,以找出功能瓶颈。