使用 alpine 打包镜像注意事项、多阶段构建镜像减小镜像体积

Alpine Linux 是一个相当精简的操作系统,而基于它的 Docker 镜像可以仅有数 MB 的尺寸。如果软件基于这样的系统镜像之上构建而得,可以想象新的镜像也是十分小巧的。

由于基于 Alpine 系统建立的软件镜像远远小于基于其他系统的软件镜像,它在网络传输上的优势尤为明显。如果我们选择这类的镜像,不但可以节约网络传输的时间,也能减少镜像对硬盘空间的占用。

当然,有优点也会有缺点,Alpine 镜像的缺点就在于它实在过于精简,以至于麻雀虽小,也无法做到五脏俱全了。

Alpine 中缺少很多常见的工具和类库,以至于如果我们想基于软件 Alpine 标签的镜像进行二次构建,那搭建的过程会相当烦琐。所以如果你想要对软件镜像进行改造,并基于其构建新的镜像,那么 Alpine 镜像不是一个很好的选择 (这时候我们更提倡基于 UbuntuDebianCentOS 这类相对完整的系统镜像来构建)。

目前,大部分 Docker 镜像都已经支持 Alpine 作为基础镜像,可以很容易进行迁移。例如:

  • Ubuntu/Debian -> alpine
  • python:2.7 -> python:3.6-alpine
  • ruby:2.6 -> ruby:2.6-alpine

如果使用 Alpine 镜像,安装软件包时可以使用 apk 工具,例如:

apk add --no-cache <package>

Alpine 中软件安装包的名字可能会与其它发行版有所不同,可以在 https://pkgs.alpinelinux.org/packages 网站搜索并确定安装包名称,如果需要的安装包不在主索引内,但是在测试或者社区索引中,那么首先需要更新仓库列表,如下所示:

echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
apk --update add --no-cache <package>

Apline 相关资源如下:

  • 官网:http://alpinelinux.org
  • 官方仓库:https://github.com/alpinelinux
  • 官方镜像:https://hub.docker.com/_/alpine
  • 官方镜像仓库:https://github.com/gliderlabs/docker-alpine

1. 没有内置 HTTPS 证书

有可能你的程序里面需要像外部发送网络请求,打包后镜像在发送网络请求时会报下面的这个错:

x509: certificate signed by unknown authority

这是因为 alpine 镜像没有内置 HTTPS 证书,所以需要在打包时安装下证书。

调整后可以这样写:

FROM alpine:latest

RUN apk update \
        && apk upgrade \
        && apk add --no-cache \
        ca-certificates \
        && update-ca-certificates 2>/dev/null || true

WORKDIR /app

COPY ./build/app_linux /app/app

ENTRYPOINT ["/app/app"]

2. 时区问题

在 Docker 镜像里面可以安装 tzdata 这个包,然后设置 TZ 环境变量就好了。再次调整 Dockerfile,变成这样:

FROM alpine:latest

RUN apk update \
        && apk upgrade \
        && apk add --no-cache \
        ca-certificates \
        tzdata \
        && update-ca-certificates 2>/dev/null || true

# 设置默认时区为上海
ENV TZ Asia/Shanghai

WORKDIR /app

COPY ./build/app_linux /app/app

ENTRYPOINT ["/app/app"]

更多关于 Alpine 介绍 https://yeasy.gitbook.io/docker_practice/os/alpine

不要轻易使用 Alpine 镜像来构建 Docker 镜像,有坑!

3. 指定版本

FROM golang:1.15-alpine3.12
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories
RUN apk add alpine-sdk
RUN go env -w GOPROXY=https://goproxy.cn,direct
# RUN go get -u github.com/cosmtrek/air  # 它是用来实现热重载的

4. 打包 Go 镜像


FROM golang:1.18-alpine
LABEL maintainer="[email protected]"
WORKDIR /app
COPY . /app
RUN go mod download
RUN go build -o crawler main.go
EXPOSE 8080
CMD [ "./crawler","worker" ]

  • golang:1.18-alpine 是 Go 官方提供的包含了 Go 指定版本与 Linux 文件系统的基础层。
  • WORKDIR 指令用于设置镜像的工作目录,这里我们设置为 /app。
  • COPY 指令用于将文件复制到镜像中,在这里我们将项目的所有文件复制到了 /app 路径下。
  • RUN 指令,用于执行指定的命令。在这里,我们执行 go mod download 来安装 Go 项目的依赖。
  • RUN go build 用于构建项目的二进制文件
  • EXPOSE 8080 声明了容器暴露的服务端口,它主要用于描述,没有正式的作用。
  • CMD 声明了容器启动时运行的命令,在这里我们运行的是./crawler worker。

由于容器网络具有隔离性,容器在查找 127.0.0.1 回环地址时,流量直接转发到了当前容器中,无法访问到宿主机网络。为了让容器访问宿主机上的程序,我们可以将 MySQL 的地址修改为宿主机对外的 IP 地址,例如当前我的局域网地址为192.168.0.105(你可以使用 ifconfig 指令查看本机 IP 地址)。或者我们可以在 docker run 时使用–network host,取消容器与宿主机之间的网络隔离。


docker run -p 8081:8080 --network host crawler:latest

5. 多阶段构建镜像

我们前面构建的镜像很大,是因为我们在构建程序时包含了很多额外的环境和依赖。例如,Go 编译器的环境和 Go 项目的依赖文件。但是如果我们可以在构建完二进制程序之后,清除这些无用的文件,镜像将大大减小。

要实现这个目标,就不得不提到镜像的多阶段构建(multi-stage builds)了。

有了多阶段构建,我们可以在一个 Dockerfile 中包含多个 FROM 指令 ,每个 FROM 指令都是一个新的构建阶段,这样就可以轻松地从之前的阶段复制生成好的文件了。

下面是我们多阶段构建的 Dockerfile 文件。这里第一个阶段命名为 builder,它是应用程序的初始构建阶段。第二个阶段以 alpine:latest 作为基础镜像,去除了很多无用的依赖。我们利用 COPY --from=builder,只复制了第一阶段的二进制文件和配置文件。


FROM golang:1.18-alpine as builder
LABEL maintainer="[email protected]"
WORKDIR /app
COPY . /app
RUN go mod download
RUN go build -o crawler main.go

FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/crawler ./
COPY --from=builder /app/config.toml ./
CMD ["./crawler","worker"]

接下来让我们再次执行 dokcer build,会发现最新生成的镜像大小只有 41MB 了。相比最初的 782MB,节省了七百多兆空间。

你可能感兴趣的:(Docker,docker,运维)