为什么Golang的可执行文件不能静态编译?

为什么Golang的可执行文件不能静态编译? 我在Alpine Linux上使用静态编译的Go程序,这样它们就不依赖外部库(Alpine上的库有所不同)。但最近当我尝试直接在Alpine Linux系统上运行Go"脚本"时,标准分发的Go可执行文件无法运行(Go 1.13.1)。难道Go不能静态编译,使其不依赖外部库吗?

我知道Alpine上有Go软件包,但我不想安装它。我更愿意使用在多个Alpine系统(实际上是容器)之间共享的目录中的Go可执行文件。

5 回复

那么"go"可执行文件之所以链接到libc,是因为它使用了系统的DNS解析器和用户数据库吗?

我有一个使用net/http的Go程序(它会下载URL,所以使用了DNS解析器),当我静态链接它时,它在Alpine上运行良好。我使用这个别名来编译:

alias goinstall='CGO_ENABLED=0 go install -ldflags ''-extldflags -static'''

更多关于为什么Golang的可执行文件不能静态编译?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go 二进制文件默认是静态编译的。
但如果你使用了 netos/user 包,则会生成动态编译的可执行文件(链接到 libc)。

你需要提供 netgousergo 构建标签,以使用基于 Go 的 DNS 解析器(替代 libc 的解析器)以及基于 Go 的函数来查询 Unix 用户“数据库”(替代 libc 的函数)。

这样是可行的。 但正如我所说,netgo 应该能达到同样的效果:

package main
import _ "net/http"
func main() {}
$> go build -o exe-1 ./main.go
$> go build -o exe-2 -tags netgo ./main.go

$> ldd ./exe-1
	linux-vdso.so.1 (0x00007ffe3abae000)
	libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f4afcdcf000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007f4afcc08000)
	/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f4afce22000)

$> ldd ./exe-2
	not a dynamic executable

谢谢。我之前不知道有 -tags netgo 这个选项,也不了解 ldd 命令。

我希望 Go 编译器本身能够静态编译。

我在主机上使用 LXD 容器,并希望在多个容器中使用 Go,而不需要在每个容器中都安装 Go。因此,我在主机上将 Go 发行版解压到一个与所有容器共享的目录中。这样,我就可以在所有容器中使用同一个 Go 副本。但这只适用于带有 libc 的容器(例如 Ubuntu),对于 Alpine 容器则无法使用。

我找到了一个针对 Alpine Linux 的解决方案:

  • 在一个临时的 alpine/edge 容器中安装 Alpine 版本的 Go(该容器包含最新版本的 Go):apk add go
  • /usr/lib/go/ 复制到主机,并与所有其他 Alpine 容器共享。
  • 删除最初安装 Go 的容器。

现在,我为所有 Alpine 容器准备了一个 Go 副本,为所有 Ubuntu 容器(以及其他带有 libc 的容器)准备了另一个 Go 副本。

Go语言本身支持静态编译,但默认情况下,Go的可执行文件可能依赖外部C库(如glibc),尤其是在使用netcgo或某些系统调用时。Alpine Linux使用musl libc而不是glibc,这可能导致标准Go二进制文件在Alpine上无法运行。要生成完全静态的可执行文件,您需要在编译时禁用cgo并设置适当的构建标签。

以下是示例代码,展示如何编译一个静态Go程序,使其在Alpine Linux上独立运行:

  1. 使用环境变量禁用cgo并设置静态链接: 在编译前,设置以下环境变量:

    export CGO_ENABLED=0
    export GOOS=linux
    export GOARCH=amd64  # 根据您的架构调整,例如arm64
    

    然后编译您的Go程序:

    go build -o myapp main.go
    
  2. 在Go代码中,如果使用标准库,确保没有隐式cgo依赖。例如,一个简单的程序:

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hello, static Go on Alpine!")
    }
    

    使用上述环境变量编译后,可执行文件myapp应不依赖外部库。

  3. 验证可执行文件的依赖: 编译后,使用ldd命令检查依赖:

    ldd myapp
    

    如果输出显示"not a dynamic executable"或类似信息,说明是静态链接的。

  4. 如果程序必须使用cgo,则需要交叉编译针对musl libc,但这更复杂。推荐在Alpine容器内安装Go工具链进行编译:

    FROM golang:alpine AS builder
    WORKDIR /app
    COPY . .
    RUN CGO_ENABLED=0 go build -o myapp .
    
    FROM scratch
    COPY --from=builder /app/myapp /
    CMD ["/myapp"]
    

通过这种方法,您可以生成可移植的静态二进制文件,在多个Alpine系统或容器中共享,无需安装Go运行时。

回到顶部