Why
在这之前的项目中,我们在 golang 中使用 rocksdb 的时候,由于 rocksdb 使用 CGO 的模式引入,所以都是采用动态链接的方式引用相关的库。这就导致即使使用 Alpine 的镜像,也使得 Alpine 的基础镜像的容量达到了 1 G 以上。所以,为了尽可能的减小镜像的大小,所以决定使用静态方式打包 golang 程序。事实上也证明效果很明显,最后应用的程序是 30M,加上基础镜像 7M+,最后的生产环境镜像也就是 38M+。
开发环境
macOS Big Sur
Golang 1.16.3
docker desktop 3.2.2(61853)
docker engin 20.10.5
构建 Golang SDK 的 Alpine 镜像
使用以下的脚本来构建 Golang SDK 的 Alpine 镜像,用来编译 Golang 的应用。
FROM alpine:3.13.4 as builder
RUN echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >>/etc/apk/repositories
RUN apk add --update --no-cache build-base linux-headers git perl \
cmake bash zlib zlib-dev bzip2 bzip2-dev snappy snappy-dev lz4 lz4-dev \
zstd@testing zstd-dev@testing libtbb-dev@testing libtbb@testing
RUN cd tmp && \
git clone https://github.com/gflags/gflags.git && \
cd gflags && \
mkdir build && \
cd build && \
cmake -DBUILD_SHARED_LIBS=1 -DGFLAGS_INSTALL_SHARED_LIBS=1 .. && \
make install && \
cd tmp && \
rm -R tmp/gflags/
RUN cd tmp && \
git clone https://github.com/facebook/rocksdb.git && \
cd rocksdb && \
git checkout v6.16.4 && \
make static_lib
FROM golang:1.16.3-alpine3.13
COPY --from=builder /tmp/rocksdb/librocksdb.* /usr/local/lib/rocksdb/
COPY --from=builder /tmp/rocksdb/librocksdb.* /usr/lib/
COPY --from=builder /tmp/rocksdb/librocksdb.* /usr/x86_64-alpine-linux-musl/lib64/
COPY --from=builder /tmp/rocksdb/include /usr/local/rocksdb/include/
COPY --from=builder /usr/local/lib/libgflags* /usr/local/lib/
RUN echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >>/etc/apk/repositories && \
apk add --update --no-cache pkgconfig tzdata ca-certificates gcc openssl cmake linux-headers \
alpine-sdk build-base libc-dev musl-dev libstdc++ libsodium-dev perl \
zlib zlib-static \
snappy snappy-dev snappy-static \
zstd@testing zstd-dev@testing zstd-static \
bzip2 bzip2-dev bzip2-static \
lz4 lz4-dev lz4-static \
libtbb-dev@testing libtbb@testing
其中,使用 Alpine 3.13.4 做为编译镜像,主要用来编译 gflags 和 RocksDB。RocksDB 的版本是 6.16.4。
然后使用 Golang 的 Alpine 镜像,版本是 1.16.3 来做基础镜像。
基础镜像除了安装一些必须的库外,额外安装了 RocksDB 的依赖库的静态库,例如 snappy-static, zstd-static 等等。
将以上的内容保存为文件 golang-rocksdb-sdk
编译 SDK 镜像
运行以下的命令编译 SDK 镜像:
docker build -t golang:1.16.3-alpine3.13-rocksdb6.16.4-sdk -f golang-rocksdb-sdk .
编译过程中,基于某些原因,某些网络需要科学手段。
编译 Golang 应用
镜像编译好之后,就可以使用编译好的镜像来编译适用在 Alpine 容器里的 Golang 应用了。
使用以下的命令来编译:
export srcpath=项目路径
export projectname=项目名称
export subprojectname=子项目名称
export pkgpath=module的缓存路径
docker run --rm \
-e CGO_ENABLED=1 \
-e CGO_CFLAGS="-I/usr/local/rocksdb/include/ -I/usr/include/" \
-e CGO_LDFLAGS="-L/usr/local/lib -L/usr/lib -lrocksdb -lsnappy -llz4 -lz -lbz2 -lzstd" \
-e GOOS=linux \
-e GOARCH=amd64 \
-e GOMODCACHE=/go/pkg/mod \
-v $(srcpath):/go/src/$(projectname) \
-v $(GOPATH)/pkg/mod:/go/pkg/mod \
-w /go/src/$(projectname) \
golang:1.16.3-alpine3.13-rocksdb6.16.4-sdk \
go build -v \
-ldflags='-s -w -extldflags "-static -fpic"' \
/go/src/$(projectname)/cmd/$(subprojectname)/...
这里额外说明一下,我一般用如下的目录来组织代码:
/usr/projects
|_/项目名称
|_/cmd
|_/子项目名称
|_main.go
|_/internal
|_/子项目名称
|_子项目代码
|_/pkg
|_项目间共享代码
所以在上面的变量中:
srcpath=/usr/projects/项目名称
projectname=项目名称
subprojectname=子项目名称
编译时,声明了 GOMODCACHE 变量,让 go 到指定的位置查找第三方库的缓存,并且把本地的第三方库的缓存目录 $(GOPATH)/pkg/mod 映射到容器里的 GOMODCACHE 的指定位置,这样就可以直接使用本地的缓存进行编译,而不是重新去下载。
这样编译之后就可以在 /usr/projects/项目名称 目录下得到一个以 subprojectname 命名的二进制文件。
构建 Alpine 运行时镜像
使用如下的脚本构建一个 Alpine 的运行时镜像:
FROM alpine:3.13.4
RUN apk add --update --no-cache tzdata ca-certificates && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
保存为 alpine-runtime
运行如下的命令编译镜像:
docker build -t alpine:3.13.4-runtime -f alpine-runtime .
构建生产用镜像
接下来将生成二进制的打包进运行时镜像中,生成生产用镜像
FROM alpine:3.13.4-runtime
ADD 子项目名称 /usr/bin/子项目名称
CMD ["子项目名称"]