如何为生产优化Docker镜像

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Code.org 来接受捐赠。

介绍

在生产环境中,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,您也可以将此过程应用于几乎任何编程语言。


先决条件

在开始之前,您需要:

  • 具有 sudo 权限的非 root 用户帐户的 Ubuntu 18.04 服务器。 按照我们的 Initial Server Setup with Ubuntu 18.04 教程获取指导。 尽管本教程在 Ubuntu 18.04 上进行了测试,但您可以在任何 Linux 发行版上执行许多步骤。
  • Docker 安装在您的服务器上。 请按照 如何在 Ubuntu 18.04 上安装和使用 Docker 的步骤 1 和 2 进行安装说明。

第 1 步 — 下载示例 Go API

在优化您的 Docker 镜像之前,您必须首先下载 示例 API,您将从中构建您的 Docker 镜像。 使用简单的 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 文件中找到用 Go 编写的 API 的源代码。 尽管此 API 很小且只有几个端点,但它适用于模拟生产就绪 API 以用于本教程的目的。

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

第 2 步 — 构建基础 Ubuntu 映像

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

首先编写一个 Dockerfile,指示 Docker 创建一个 Ubuntu 映像、安装 Go 并运行示例 API。 确保在克隆存储库的目录中创建 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 所在目录获取代码并将其复制到图像中。 最后的 RUN 命令安装源代码编译和运行 API 所需的 Go 依赖项。

注意: 使用 && 操作符串起来 RUN 命令对于优化 Dockerfile 很重要,因为每个 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

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

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

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

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

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

预建镜像是普通的基础镜像,用户已经修改以包含特定情况的工具。 然后,用户可以将这些镜像推送到 Docker Hub 镜像存储库,允许其他用户使用共享镜像,而不必编写自己的个人 Dockerfile。 这是生产环境中的常见过程,您可以在 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

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

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

尽管 Dockerfile 本身效率更高且构建时间更短,但总映像大小实际上增加了。 预构建的 Golang 映像大约为 744MB,数量可观。

这是构建 Docker 镜像的首选方式。 它为您提供了一个基础图像,社区已批准将其作为指定语言(在本例中为 Go)使用的标准。 但是,要使映像准备好用于生产,您需要切掉正在运行的应用程序不需要的部分。

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

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

第 4 步 - 构建基本 Alpine 图像

优化 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

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

OutputREPOSITORY  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-alpine

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 的命令。 go get 命令需要 Git 才能在 Dockerfile.golang-alpine 底部的第二个 RUN 命令中工作。

使用以下命令构建映像:

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

检索您的图像列表:

docker images

您将收到以下输出:

OutputREPOSITORY      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 命令中,您将从 multistage 图像中获取基本的 alpine 图像和 COPY 以覆盖已编译的 Go 应用程序。 这个过程将进一步缩小最终图像的大小,使其为生产做好准备。

使用以下命令运行构建:

docker build -f Dockerfile.multistage -t prod .

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

docker images

您会发现两张新图片,而不仅仅是一张:

OutputREPOSITORY      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 构建优化的容器