如何为生产优化Docker镜像

在本指南中,您将学习如何通过几个简单的步骤优化Docker镜像,使其更小,更快,更适合生产。您将在几个不同的Docker容器中为示例Go API构建映像,从Ubuntu和特定于语言的映像开始,然后转到Alpine Linux发行版。您还将使用多阶段构建来优化图像以进行生产。

作者选择Code.org作为Write for DOnations计划的一部分进行捐赠。

介绍

在生产环境中, Docker可以轻松地在容器内创建,部署和运行应用程序。 容器允许开发人员将应用程序及其所有核心必需品和依赖项收集到一个包中,您可以将其转换为Docker映像并进行复制。 Docker镜像是从Dockerfiles构建的 Dockerfile是一个文件,您可以在其中定义图像的外观,它将具有的基本操作系统以及将在其中运行的命令。

大型Docker镜像可以延长在群集和云提供商之间构建和发送图像所需的时间。 例如,如果每次开发人员触发构建时都有一个千兆字节大小的图像,那么您在网络上创建的吞吐量将在CI / CD过程中累加,使您的应用程序变得迟钝并最终导致资源耗费。 因此,适合生产的Docker映像应该只安装必需品。

有几种方法可以减小Docker镜像的大小以优化生产。 首先,这些图像通常不需要构建工具来运行其应用程序,因此根本不需要添加它们。 通过使用多阶段构建过程 ,您可以使用中间图像来编译和构建代码,安装依赖项,并将所有内容打包到可能的最小尺寸,然后将应用程序的最终版本复制到没有构建工具的空图像。 此外,您可以使用具有微小基础的图像,例如Alpine Linux Alpine是适合生产的Linux发行版,因为它只有您的应用程序需要运行的必需品。

在本教程中,您将通过几个简单的步骤优化Docker镜像,使其更小,更快,更适合生产。 您将在几个不同的Docker容器中为示例Go API构建图像,从Ubuntu和特定于语言的图像开始,然后转到Alpine发行版。 您还将使用多阶段构建来优化图像以进行生产。 本教程的最终目标是显示使用默认Ubuntu映像和优化对应项之间的大小差异,并显示多阶段构建的优势。 阅读本教程后,您将能够将这些技术应用于您自己的项目和CI / CD管道。

注意:本教程使用Go编写的API作为示例。 这个简单的API将让您清楚地了解如何使用Docker镜像优化Go微服务。 即使本教程使用Go API,您也可以将此过程应用于几乎所有编程语言。

先决条件

在开始之前,您将需要:

第1步 - 下载Sample Go API

在优化Docker镜像之前,必须首先下载将构建Docker镜像的示例API 使用简单的Go API将展示在Docker容器中构建和运行应用程序的所有关键步骤。 本教程使用Go,因为它是一种编译语言,如C ++Java ,但与它们不同,它的占用空间非常小。

在您的服务器上,首先克隆示例Go API:

git clone https://github.com/do-community/mux-go-api.git

克隆项目后,您的服务器上将有一个名为mux-go-api的目录。 使用cd进入此目录:

cd mux-go-api

这将是您项目的主目录。 您将从此目录构建Docker镜像。 在里面,你会找到在api.go文件api.go Go编写的API的源代码。 尽管此API很少且只有少数端点,但它适用于为本教程的目的模拟生产就绪的API。

现在您已经下载了示例Go API,您已准备好构建一个基本的Ubuntu Docker镜像,您可以根据该镜像比较以后优化的Docker镜像。

第2步 - 构建基础Ubuntu映像

对于你的第一个Docker镜像,当你开始使用基础Ubuntu镜像时,看看它是什么样子会很有用。 这会将您的示例API打包在类似于您已在Ubuntu服务器上运行的软件的环境中。 在映像中,您将安装运行应用程序所需的各种包和模块。 但是,您会发现此过程会创建一个相当繁重的Ubuntu映像,这将影响Dockerfile的构建时间和代码可读性。

首先编写一个Dockerfile,指示Docker创建Ubuntu映像,安装Go,然后运行示例API。 确保在克隆的repo目录中创建Dockerfile。 如果你克隆到主目录,它应该是$HOME/mux-go-api

创建一个名为Dockerfile.ubuntu的新文件。 nano或您喜欢的文本编辑器中打开它:

nano ~/mux-go-api/Dockerfile.ubuntu

在这个Dockerfile中,您将定义一个Ubuntu映像并安装Golang。 然后,您将继续安装所需的依赖项并构建二进制文件。 将以下内容添加到Dockerfile.ubuntu

〜/ MUX-GO-API / Dockerfile.ubuntu
FROM ubuntu:18.04

RUN apt-get update -y \
  && apt-get install -y git gcc make golang-1.10

ENV GOROOT /usr/lib/go-1.10
ENV PATH $GOROOT/bin:$PATH
ENV GOPATH /root/go
ENV APIPATH /root/go/src/api

WORKDIR $APIPATH
COPY . .

RUN \ 
  go get -d -v \
  && go install -v \
  && go build

EXPOSE 3000
CMD ["./api"]

从顶部开始, FROM命令指定映像将具有哪个基本操作系统。 然后RUN命令在创建映像期间安装Go语言。 ENV设置Go编译器需要的特定环境变量才能正常工作。 WORKDIR指定我们要在代码上复制的目录, COPY命令从Dockerfile.ubuntu所在的目录中Dockerfile.ubuntu代码并将其复制到映像中。 最后的RUN命令安装源代码所需的Go依赖项以编译和运行API。

注意:使用&&运算符将RUN命令串在一起对于优化Dockerfiles非常重要,因为每个RUN命令都会创建一个新图层,并且每个新图层都会增加最终图像的大小。

保存并退出该文件。 现在,您可以运行build命令从刚刚创建的Dockerfile创建Docker镜像:

docker build -f Dockerfile.ubuntu -t ubuntu .

build命令从Dockerfile构建映像。 -f标志指定您要从Dockerfile.ubuntu文件构建,而-t代表标记,这意味着您使用名称ubuntu标记它。 最后一个点表示Dockerfile.ubuntu所在的当前上下文。

这需要一段时间,所以请随时休息一下。 构建完成后,您将准备好运行API的Ubuntu映像。 但是图像的最终尺寸可能并不理想; 对于此API,任何超过几百MB的内容都将被视为过大的图像。

运行以下命令列出所有Docker镜像并找到Ubuntu镜像的大小:

docker images

您将看到显示刚刚创建的图像的输出:

REPOSITORY  TAG     IMAGE ID        CREATED         SIZE
ubuntu      latest  61b2096f6871    33 seconds ago  636MB
. . .

正如输出中突出显示的那样,对于基本的Golang API,此图像的大小为636MB ,这个数字在不同机器之间可能略有不同。 在多个版本中,这个大尺寸将显着影响部署时间和网络吞吐量。

在本节中,您构建了一个Ubuntu映像,其中包含所有需要的Go工具和依赖项,以运行您在第1步中克隆的API。在下一节中,您将使用预构建的,特定于语言的Docker映像来简化Dockerfile并简化构建过程。

第3步 - 构建特定语言的基础映像

预构建的图像是普通的基本图像,用户已对其进行了修改以包含特定于情境的工具。 然后,用户可以将这些图像推送到Docker Hub映像存储库,从而允许其他用户使用共享映像,而不必编写自己的独立Dockerfiles。 这是生产环境中的常见过程,您可以在Docker Hub上找到各种预构建的图像,几乎可用于任何用例。 在此步骤中,您将使用已安装了编译器和依赖项的Go特定映像构建示例API。

预先构建的基本映像已经包含构建和运行应用程序所需的工具,因此可以显着缩短构建时间。 因为您从预先安装了所有需要的工具的基础开始,所以可以跳过将这些工具添加到Dockerfile中,使其看起来更清晰并最终减少构建时间。

继续创建另一个Dockerfile并将其命名为Dockerfile.golang 在文本编辑器中打开它:

nano ~/mux-go-api/Dockerfile.golang

此文件将比前一个文件简洁得多,因为它预先安装了所有特定于Go的依赖项,工具和编译器。

现在,添加以下行:

〜/ MUX-GO-API / Dockerfile.golang
FROM golang:1.10

WORKDIR /go/src/api
COPY . .

RUN \
    go get -d -v \
    && go install -v \
    && go build

EXPOSE 3000
CMD ["./api"]

从顶部开始,您会发现FROM语句现在是golang: 1.10 这意味着Docker将从Docker Hub获取预先构建的Go图像,该图像已经安装了所有需要的Go工具。

现在,再次使用以下命令构建Docker镜像:

docker build -f Dockerfile.golang -t golang .

使用以下命令检查图像的最终大小:

docker images

这将产生类似于以下的输出:

REPOSITORY  TAG     IMAGE ID        CREATED         SIZE
golang      latest  eaee5f524da2    40 seconds ago  744MB
. . .

尽管Dockerfile本身效率更高,构建时间更短,但总图像大小实际上增加了。 预先构建的Golang图像大约为744MB ,这是一个很大的数字。

这是构建Docker镜像的首选方法。 它为您提供了一个基本映像,社区已批准该映像作为指定语言的标准,在本例中为Go。 但是,要使映像为生产做好准备,您需要删除正在运行的应用程序不需要的部分。

请记住,当您不确定自己的需求时,使用这些沉重的图像是很好的。 随意使用它们作为一次性容器以及构建其他图像的基础。 出于开发或测试目的,您不需要考虑通过网络发送图像,使用繁重的图像非常好。 但是,如果您想优化部署,那么您需要尽力使图像尽可能小。

现在您已经测试了特定于语言的图像,您可以继续下一步,在该步骤中,您将使用轻量级Alpine Linux发行版作为基本映像,以使Docker镜像更轻松。

第4步 - 建立基本高山图像

优化Docker镜像的最简单步骤之一是使用较小的基本图像。 Alpine是一款轻量级Linux发行版,旨在提高安全性和资源效率。 Alpine Docker镜像使用musl libcBusyBox来保持紧凑,在容器中运行不超过8MB。 小尺寸是由于二进制包被稀释和分割,使您可以更好地控制安装的内容,从而使环境尽可能小而有效。

创建Alpine映像的过程与在第2步中创建Ubuntu映像的过程类似。首先,创建一个名为Dockerfile.alpine的新文件:

nano ~/mux-go-api/Dockerfile.alpine

现在添加以下代码段:

〜/ MUX-GO-API / Dockerfile.alpine
FROM alpine:3.8

RUN apk add --no-cache \
    ca-certificates \
    git \
    gcc \
    musl-dev \
    openssl \
    go

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
ENV APIPATH $GOPATH/src/api
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" "$APIPATH" && chmod -R 777 "$GOPATH"

WORKDIR $APIPATH
COPY . .

RUN \
    go get -d -v \
    && go install -v \
    && go build

EXPOSE 3000
CMD ["./api"]

在这里,您将添加apk add命令以使用Alpine的包管理器来安装Go及其所需的所有库。 与Ubuntu映像一样,您也需要设置环境变量。

继续构建图像:

docker build -f Dockerfile.alpine -t alpine .

再次检查图像大小:

docker images

您将收到类似于以下内容的输出:

REPOSITORY  TAG     IMAGE ID        CREATED         SIZE
alpine      latest  ee35a601158d    30 seconds ago  426MB
. . .

尺寸已降至约426MB

Alpine基本图像的小尺寸减小了最终图像尺寸,但是还有一些其他功能可以使它更小。

接下来,尝试使用Go的预制Alpine图像。 这将使Dockerfile更短,并且还将减小最终图像的大小。 因为Go的预构建的Alpine图像是使用源代码编译的Go构建的,所以它的占用空间明显更小。

首先创建一个名为Dockerfile.golang-alpine的新文件:

nano ~/mux-go-api/Dockerfile.golang-alpine

将以下内容添加到文件中:

〜/ MUX-GO-API / Dockerfile.golang高山
FROM golang:1.10-alpine3.8

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
  && go install -v \
  && go build

EXPOSE 3000
CMD ["./api"]

Dockerfile.golang-alpineDockerfile.alpine之间的唯一区别是FROM命令和第一个RUN命令。 现在, FROM命令指定一个带有1.10-alpine3.8标记的golang图像,而RUN只有一个用于安装Git的命令。 你需要Git for go get命令才能在Dockerfile.golang-alpine底部的第二个RUN命令中工作。

使用以下命令构建映像:

docker build -f Dockerfile.golang-alpine -t golang-alpine .

检索您的图片列表:

docker images

您将收到以下输出:

REPOSITORY      TAG     IMAGE ID        CREATED         SIZE
golang-alpine   latest  97103a8b912b    49 seconds ago  288MB

现在图像尺寸下降到大约288MB

即使你已经设法减少了很多尺寸,你还可以做的最后一件事就是让图像准备好投入生产。 它被称为多阶段构建。 通过使用多阶段构建,您可以使用一个映像构建应用程序,同时使用另一个较轻的映像来打包已编译的应用程序以进行生产,这个过程将在下一步中运行。

第5步 - 使用多阶段构建排除构建工具

理想情况下,您在生产中运行的映像不应安装任何构建工具,也不应该为生产应用程序运行多余的依赖项。 您可以使用多阶段构建从最终的Docker镜像中删除它们。 这通过在中间容器中构建二进制文件或其他术语编译的Go应用程序,然后将其复制到没有任何不必要的依赖项的空容器来工作。

首先创建另一个名为Dockerfile.multistage文件:

nano ~/mux-go-api/Dockerfile.multistage

你在这里添加的内容将会很熟悉。 首先添加与Dockerfile.golang-alpine完全相同的代码。 但这一次,还要添加第二个图像,您将从第一个图像复制二进制文件。

〜/ MUX-GO-API / Dockerfile.multistage
FROM golang:1.10-alpine3.8 AS multistage

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
  && go install -v \
  && go build

##

FROM alpine:3.8
COPY --from=multistage /go/bin/api /go/bin/
EXPOSE 3000
CMD ["/go/bin/api"]

保存并关闭文件。 这里有两个FROM命令。 第一个与Dockerfile.golang-alpine相同,只是在FROM命令中有一个额外的AS multistage 这将为它提供一个multistage名称,然后您将在Dockerfile.multistage文件的底部引用它。 在第二个FROM命令中,您将获取基本alpine图像,并将已编译的Go应用程序从multistage图像COPY到其中。 此过程将进一步缩小最终图像的大小,使其可以投入生产。

使用以下命令运行构建:

docker build -f Dockerfile.multistage -t prod .

使用多阶段构建后,立即检查图像大小。

docker images

你会发现两个新图像,而不是只有一个:

REPOSITORY      TAG     IMAGE ID        CREATED         SIZE
prod            latest  82fc005abc40    38 seconds ago  11.3MB
<none>          <none>  d7855c8f8280    38 seconds ago   294MB
. . .

<none>图像是使用FROM golang:1.10-alpine3.8 AS multistage命令构建的multistage图像。 它只是用于构建和编译Go应用程序的中介,而此上下文中的prod图像是仅包含已编译的Go应用程序的最终图像。

从最初的744MB开始 ,您现在已经将图像大小减少到大约11.3MB 跟踪这样的小图像并通过网络将其发送到生产服务器将比使用超过700MB的图像更容易,并且从长远来看将为您节省大量资源。

结论

在本教程中,您使用不同的基本Docker镜像和中间图像优化了Docker镜像以进行生产,以编译和构建代码。 这样,您就可以将样本API打包成尽可能小的样本。 您可以使用这些技术来提高Docker应用程序的构建和部署速度以及您可能拥有的任何CI / CD管道。

如果您有兴趣了解有关使用Docker构建应用程序的更多信息,请查看我们的如何使用Docker构建Node.js应用程序教程。 有关优化容器的更多概念性信息,请参阅为Kubernetes构建优化容器