golang编译并创建镜像
Dockerfile
FROM golang:alpine AS builder
WORKDIR /app
ENV GO111MODULE=on
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags "-s -w" -o server .
FROM alpine
WORKDIR /app
COPY --from=builder /app/server /app/server
RUN set -xe && \
chmod +x server && \
apk add -U --no-cache tzdata ca-certificates && \
apk cache clean && \
rm -rf /var/cache/apk/*
ENTRYPOINT ["./server"]
一次报错
最近项目用到了go-sqlite3
, 导致在golang:alpine
编译成功且生成镜像, 但镜像启动过程中, 容器内运行go编译文件会报错, 导致镜像无法启动
报错如下 :
Binary was compiled with ‘CGO_ENABLED=0’, go-sqlite3 requires cgo to work. This is a stub
原因
go-sqlite3
在golang代码内, 用到cgo
命令, 所以必须开启cgo编译开关
,编译时设置CGO_ENABLED=1
解决
于是, 把编译命令改成 :
CGO_ENABLED=1 go build -ldflags "-s -w" -o server .
二次报错
然而这次在编译阶段就报错了, 镜像生成失败, 报错如下:
cgo: C compiler “gcc” not found
原因
golang使用cgo, 需要 gcc
环境; 然而golang:alpine
镜像为了最小化默认的C库却是 musl
解决
在
golang:alpine
内安装gcc
觉得是多此一举了, 干脆改用golang:latest
镜像, 虽然其镜像大导致生成镜像慢, 但不影响最终生成镜像的大小;
Dockerfile修改为 :
FROM golang:latest AS builder
三次报错
这次跟一次报错一样, 生成镜像成功了; 然而镜像启动失败, 报错如下:
./server: not found
排查
./server 正是编译后的文件, 明明存在的, 却执行失败
于是使用docker run --tty=true
的形式进入容器, 执行:
ldd ./server
报错:
Error relocating server: fcntl64: symbol not found
原因
编译后的二进制文件需要依赖glibc
, 也就是gcc
环境; 而运行文件的环境是alpine
而不是基于golang:latest
, 并没有gcc
;
很疑惑, 这不是编译成功了嘛? 怎么还需要依赖外部系统环境?
原来golang编译有 静态编译
和 动态编译
的区别, 默认后者;
前者是把外部依赖的相关程序(如glibc
)打包进编译文件; 后者需要动态链接到外部环境, 换句话讲, 就是需要特定环境
解决
真相大白, 如果默认golang动态编译
, 需要alpine
镜像内安装gcc
,这也是多此一举了; 因此改用 静态编译
于是, 把编译命令改成 :
CGO_ENABLED=1 go build -ldflags "-s -w --extldflags '-static -fpic'" -o server .
最终
Dockerfile
FROM golang:latest AS builder
WORKDIR /app
ENV GO111MODULE=on
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w --extldflags '-static -fpic'" -o server .
FROM alpine
LABEL org.opencontainers.image.vendor="忐忑"
WORKDIR /app
COPY --from=builder /app/server /app/server
RUN set -xe && \
chmod +x server && \
apk add -U --no-cache tzdata ca-certificates && \
apk cache clean && \
rm -rf /var/cache/apk/*
ENTRYPOINT ["./server"]