Go と Rust における静的リンクのビルド方法(+ Dockerfile サンプル)

create
2021年06月29日
update
2021年06月29日

🪓動機

たとえば Alpine でビルドしたのち、他の Linux でも実行できるように静的リンクでビルドさせたい。

Table 1. 実行環境
Tool Version

go

1.16.5

rustup

1.24.3

rustc

1.53.0

cargo

1.53.0

⚙️コマンドライン

Go 言語

C 言語コードを呼び出す cgo を使っていると動的リンクされるらしい。
よって cgo を無効化したり、リンカーに静的リンクするオプションを渡したりする。

Example 1. Go
export CGO_ENABLED=0    (1)
go build -ldflags '-extldflags=-static'   (2)
strip /path/to/binary   (3)
1 C 言語コードを利用してない場合は cgo を無効化するだけで静的リンクになる。
2 リンカーに静的リンクであることを伝えてビルド。
利用しているパッケージによっては -tags オプションも使って cgo 部分をスキップする。
3 デバッグに用いられるシンボル情報を削除してサイズ縮小。

Rust 言語

*-musl ターゲットを使用すれば静的リンクになる。

Example 2. Rust
x86_64 アーキテクチャの場合
rustup target add x86_64-unknown-linux-musl   (1)
cargo build --release --target x86_64-unknown-linux-musl  (2)
strip /path/to/binary   (3)
1 musl ライブラリを利用するターゲットを追加。
2 静的リンクでビルドするには musl ターゲットを使う。
3 デバッグに用いられるシンボル情報を削除してサイズ縮小。

静的リンクの確認

file コマンドや ldd コマンドを使って確認する。

file path/to/static/binary | tr , '\n'
path/to/static/binary: ELF 64-bit LSB executable
 x86-64
 version 1 (SYSV)
 statically linked
 Go BuildID=abc...
 not stripped

🐳 Dockerfile マルチステージでの利用例

もともとは Dockerfile でビルドさせたかったので、そのサンプル。

Example 3. Go in dockerfile
FROM golang:1.16-alpine AS build-go
WORKDIR /go/src

RUN \
  apk --no-cache add \  (1)
    git \
    binutils

### Install to '/go/bin' as static binary
ENV CGO_ENABLED=0
ARG GO_INSTALL="go install -ldflags '-extldflags=-static'"  (2)
# ghq
RUN ${GO_INSTALL} github.com/x-motemen/ghq@v1.2.1

# stripped
WORKDIR /go/bin
RUN strip $(ls)

# -----------------------
FROM debian:buster-slim
COPY --from=build-go /go/bin /usr/local/bin/
RUN ...
1 必要なパッケージ[1]をインストール。
2 リモートリポジトリのパッケージをビルドするため、 go build の代わりに go install を利用。
Example 4. Rust in dockerfile

*-unknown-linux-musl ターゲットを追加して利用することで、静的リンクにできる。

FROM rust:1.53-alpine AS build-rust

ENV CARGO_HOME=/cargo
WORKDIR ${CARGO_HOME}/bin

RUN apk --no-cache add musl-dev   (1)

# Install
RUN \
  cargo install --version ^0.10.1 exa && \  (2) (3)
  cargo install --version ^2.1.0 git-interactive-rebase-tool

# Reduce binary size
RUN strip $(ls)

# -----------------------
FROM debian:buster-slim
COPY --from=build-rust /cargo/bin /usr/local/bin/
RUN ...
1 ビルドするのに必要な musl-dev パッケージをインストール。
2 Alpine ベースではデフォルトが musl ターゲットなので、ターゲットを追加・指定する必要がない。
3 crates.io に登録されているクレートをビルドするため、 cargo install を利用している。

1. binutils パッケージは strip コマンドのためにインストールしている。