构建小而精的容器镜像

Mar 15, 2019 00:20 · 1088 words · 3 minute read Docker Container DevOps

Docker 使得构建容器镜像从未如此简单。

  1. 指定一个基础镜像
  2. 更改
  3. 构建
FROM node:onbuild
EXPOSE 8080
$ docker build myapp .

这用来入门固然方便,使用默认镜像却会导致大型镜像充满安全漏洞。大多 Docker 镜像使用 Debian 或 Ubuntu 作为基础镜像,虽然兼容性不错,但是它们带来了上百兆字节的额外开销。例如,Node.js 和 Golang 官方镜像八九百兆字节左右,而实际上开发的应用程序可能只有几字节,这些额外的开销不仅浪费空间,还会造成安全漏洞和程序错误。

这里介绍两种能够有效减少容器镜像体积的方法。

使用小型基础镜像

使用小型基础镜像或许是减少容器大小最简单的方法。你的技术栈可能提供了比默认镜像更小的镜像。

比如,来看看 Node.js 容器镜像:

  • node:10 -> 899MB
  • node:10-wheezy -> 532MB
  • node:10-slim -> 143MB
  • node:10-alpine -> 71MB

从默认的 node:10 到 node:10-alpine,基础镜像小了十几倍。重写 Dockerfile 来迁移到一个较小的基础镜像。从一个崭新的基础镜像开始,把代码复制到容器内,并安装依赖。

FROM node:alpine
WORKDIR /app
COPY package.json /app/package.json
RUN npm install
COPY server.js /app/server.js
EXPOSE 8080
CMD npm start

此容器会从 node:alpine 镜像开始,为代码构建一个目录,通过 npm 安装依赖,最后运行 Node.js 服务器。这个合成的容器小了十几倍。

如果你的技术栈没有小型镜像的选项,也可以将原始的 Alpine Linux 作为起点,这也让你能够接管你的容器。

生成器模式

通过使用生成器模式更小化容器体积。

解释型语言(Node.js、Python、Ruby、PHP)的源码会被送至解释程序并直接被运行。而编译型语言,源码会被转换成二进制文件(编译),此过程通常需要一些代码运行时不需要的工具,这表示可以将这些工具从最终容器中都去掉。

代码被复制到首个容器内,接着二进制文件被包装在最终容器中,而所有编译二进制文件所需的编译程序和工具并不包括在内。

用 Go 应用程序来举个例子:

FROM go:onbuild
EXPOSE 8080

首先将 onbuild 镜像迁移至 Alpine Linux,在新的 Dockerfile 里,容器会从 golang:alpine 镜像开始。接着将源码复制到容器内,编译,最后运行应用程序。

FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp

这个容器会比 onbuild 容器小很多,但是它仍然有个编译器和其他一些我们不怎么需要的 Go 工具,我们要把编译好的东西取出,丢到新的容器中。

FROM golang:alpine AS build-env
WORKDIR /app
RUN cd /app && go build -o goapp
ENTRYPOINT ./goapp

FROM alpine
RUN apk update && apk add ca-certificates && rm /var/cache/apk/*
WORKDIR /app
COPY --from=build-env /app/goapp /app

EXPOSE 8080
ENTRYPOINT ./goapp

你或许注意到了此 Dockerfile 的与众不同之处,它有两行 FROM,前部分看起来与上面的 Dockerfile 一模一样,只是用 AS 关键字来为这个步骤命名。下半部分中有一个新的 FROM 命令,这将会从一个崭新的镜像开始,与其使用 golang:alpine,不如用原始 Alpine 为基础镜像。而原始的 Alpine Linux 没有任何已安装的 SSL 证书,会导致多数 API 走 HTTPS 失败。有趣的部分来了,使用 COPY 命令来将二进制文件从第一个容器复制到第二个容器,而不带来其余的 Go 工具。

这个多层的 Dockerfile 能够构建出一个十几兆字节的容器镜像,和动辄上百兆的原始镜像相比,差别蛮大的。