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"]

相关