作者选择了 Code.org以作为 Write for Donations计划的一部分接受捐款。
介绍
在生产环境中, Docker使在容器内创建、部署和运行应用程序变得容易。 容器允许开发人员将应用程序及其所有核心需求和依赖性收集到一个单一的包中,您可以将其转化为 Docker 图像并复制。
大型 Docker 图像可以延长在集群和云提供商之间构建和发送图像所需的时间,例如,如果您每次开发人员启动构建时都需要推出 gigabyte 大小的图像,则您在网络上创建的传输量将在 CI/CD 流程中增加,使您的应用程序变得缓慢,最终会耗费资源。
首先,这些图像通常不需要构建工具来运行他们的应用程序,所以根本不需要添加它们。使用多阶段构建过程(https://docs.docker.com/develop/develop-images/multistage-build/),你可以使用中间图像来编译和构建代码,安装依赖,并将一切包装到最小的尺寸,然后将最终版本的应用程序复制到一个空的图像,而无需构建工具。
在本教程中,您将以几个简单的步骤优化Docker图像,使它们更小,更快,更适合生产。您将为样本构建图像 Go API在几个不同的Docker容器中,从Ubuntu和语言特定的图像开始,然后转向阿尔卑斯分布。您还将使用多阶段构建来优化您的图像生产。本教程的最终目标是展示使用默认Ubuntu图像和优化对象之间的尺寸差异,并展示多阶段构建的优势。
<$>[注] **注:**本教程以 Go编写的API作为示例。这个简单的API将为您提供如何用Docker图像优化Go微服务的清晰理解。
前提条件
在您开始之前,您将需要:
- 具有
sudo
特权的非根用户帐户的 Ubuntu 18.04 服务器. 请遵循我们的 初始服务器设置与 Ubuntu 18.04] 教程以获取指导。 虽然本教程在 Ubuntu 18.04 上进行了测试,但您可以在任何 Linux 发行版上遵循许多步骤。 - Docker 安装在您的服务器上。 请遵循 如何在 Ubuntu 18.04 上安装和使用 Docker 的步骤 1 和 2的安装说明。
步骤 1 – 下载 Sample Go API
在优化您的Docker图像之前,您必须先下载您将构建Docker图像的样本API(https://github.com/do-community/mux-go-api),使用简单的Go API将展示在Docker容器内构建和运行应用程序的所有关键步骤。
在您的服务器上,开始克隆样本Go API:
1git clone https://github.com/do-community/mux-go-api.git
一旦你克隆了这个项目,你将有一个名为mux-go-api
的目录在你的服务器上。
1cd mux-go-api
这将是您的项目的首页目录. 您将从此目录中构建您的Docker图像。 内部,您将在api.go
文件中找到在Go中写的API的源代码。 虽然这个API是最小的,并且只有几个终点,但它对于本教程的目的,适用于模拟生产准备的API。
现在你已经下载了样本Go API,你已经准备好构建一个基础的Ubuntu Docker图像,你可以与后来的优化Docker图像进行比较。
步骤 2 — 构建一个 Ubuntu 图像基础
对于你的第一个Docker图像,当你开始使用基础Ubuntu图像时,你会看到它是什么样子,这将将你的样本API包装到一个环境中,类似于你已经在Ubuntu服务器上运行的软件。在图像中,你将安装你需要运行应用程序的各种包和模块。
开始写一个 Dockerfile 指示 Docker 创建 Ubuntu 图像,安装 Go 并运行样本 API. 请确保在被克隆的 Repo 目录中创建 Dockerfile. 如果您克隆到主目录,它应该是 $HOME/mux-go-api。
创建一个名为Dockerfile.ubuntu
的新文件,在nano
或您最喜欢的文本编辑器中打开它:
1nano ~/mux-go-api/Dockerfile.ubuntu
在此 Dockerfile 中,您将定义一个 Ubuntu 图像并安装 Golang. 然后您将继续安装所需的依赖并构建二进制。
1[label ~/mux-go-api/Dockerfile.ubuntu]
2FROM ubuntu:18.04
3
4RUN apt-get update -y \
5 && apt-get install -y git gcc make golang-1.10
6
7ENV GOROOT /usr/lib/go-1.10
8ENV PATH $GOROOT/bin:$PATH
9ENV GOPATH /root/go
10ENV APIPATH /root/go/src/api
11
12WORKDIR $APIPATH
13COPY . .
14
15RUN \
16 go get -d -v \
17 && go install -v \
18 && go build
19
20EXPOSE 3000
21CMD ["./api"]
从顶部开始,FROM
命令将指定图像将具有哪个基本操作系统,然后RUN
命令在创建图像时安装Go语言。ENV
设置了Go编译器需要的特定环境变量,以便正确工作。WORKDIR
指定了我们要复制代码的目录,而COPY
命令将代码从Dockerfile.ubuntu
所在的目录中复制到图像中。
<$>[注]
注: 使用 &&
运算符串连接 RUN
命令对于优化 Dockerfiles 至关重要,因为每一个 RUN
命令都会创建一个新的层,每一个新的层会增加最终图像的大小。
现在你可以运行构建
命令,从你刚刚创建的Dockerfile中创建一个Docker图像:
1docker build -f Dockerfile.ubuntu -t ubuntu .
构建
命令将从 Dockerfile 构建一个图像. -f
标志表示您希望从 Dockerfile.ubuntu
文件中构建,而 -t
表示标签,这意味着您将其标记为 ubuntu
。
一旦构建完成,你将有一个Ubuntu图像准备运行你的API,但最终的图像大小可能不是理想的;任何超过几百MB的这个API将被认为是一个过大的图像。
运行以下命令列出所有 Docker 图像并找到您的 Ubuntu 图像的大小:
1docker images
您将看到输出显示您刚刚创建的图像:
1[secondary_label Output]
2REPOSITORY TAG IMAGE ID CREATED SIZE
3ubuntu latest 61b2096f6871 33 seconds ago 636MB
4. . .
正如输出中所强调的那样,这个图像对于基本的 Golang API 的尺寸为 636MB,这个数字可能因机器而异,在多个构建中,这种大尺寸会显著影响部署时间和网络输出量。
在本节中,您构建了一个 Ubuntu 图像,包含所有需要的 Go 工具和依赖,以运行您在步骤 1 中克隆的 API。
步骤 3 – 构建特定语言的基于图像
预建图像是用户修改的常规基图像,以包括特定情况的工具。 然后用户可以将这些图像推到 Docker Hub 图像存储库,允许其他用户使用共享图像,而不是写自己的单个 Dockerfiles。 这在生产情况下是一个常见的过程,您可以在 Docker Hub 上找到各种预建图像,几乎用于任何用例。
有了预先构建的基于图像,其中已经包含构建和运行应用程序所需的工具,您可以大大缩短构建时间. 因为您开始使用具有所有必要的工具预先安装的基于图像,您可以跳过将这些工具添加到您的Dockerfile中,使其看起来更清洁,最终减少构建时间。
继续创建另一个 Dockerfile,并将其命名为Dockerfile.golang
。
1nano ~/mux-go-api/Dockerfile.golang
这个文件比上一个文件要简洁得多,因为它已经预先安装了所有 Go 特定的依赖、工具和编译器。
现在,添加以下几行:
1[label ~/mux-go-api/Dockerfile.golang]
2FROM golang:1.10
3
4WORKDIR /go/src/api
5COPY . .
6
7RUN \
8 go get -d -v \
9 && go install -v \
10 && go build
11
12EXPOSE 3000
13CMD ["./api"]
从顶部开始,你会发现FROM
声明现在是golang:1.10
,这意味着Docker将从Docker Hub中获取预先构建的Go图像,该图像已经安装了所有必要的Go工具。
现在,再一次,用以下方式构建 Docker 图像:
1docker build -f Dockerfile.golang -t golang .
通过以下命令检查图像的最终大小:
1docker images
这将产生类似于以下的输出:
1[secondary_label Output]
2REPOSITORY TAG IMAGE ID CREATED SIZE
3golang latest eaee5f524da2 40 seconds ago 744MB
4. . .
尽管Dockerfile本身更高效,构建时间更短,但总图像大小实际上增加了。
这是构建 Docker 图像的首选方式. 它为您提供了社区批准为指定的语言使用的标准的基本图像,在这种情况下,Go. 但是,要让图像为生产做好准备,您需要切除运行应用程序不需要的部分。
请记住,当您对您的需求不确定时,使用这些重型图像是好的。您可以自由使用它们作为投放容器以及构建其他图像的基础。 为了开发或测试目的,您不需要考虑通过网络发送图像,使用重型图像是完美的。
现在,您已经测试了特定语言的图像,您可以转到下一步,您将使用轻量级的Alpine Linux发行版作为基础图像,以使您的Docker图像更轻。
步骤 4 – 建筑基地 阿尔卑斯山图像
优化您的 Docker 图像的最简单步骤之一是使用较小的基图像。 Alpine是一个轻量级的 Linux 发行版,旨在确保安全性和资源效率。 Alpine Docker 图像使用 musl libc 和 BusyBox 保持紧凑性,在一个容器中运行时不需要超过 8 MB。
创建Alpine图像的过程类似于您在步骤 2 中创建Ubuntu图像的方式:首先,创建一个名为Dockerfile.alpine
的新文件:
1nano ~/mux-go-api/Dockerfile.alpine
现在添加这个剪辑:
1[label ~/mux-go-api/Dockerfile.alpine]
2FROM alpine:3.8
3
4RUN apk add --no-cache \
5 ca-certificates \
6 git \
7 gcc \
8 musl-dev \
9 openssl \
10 go
11
12ENV GOPATH /go
13ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
14ENV APIPATH $GOPATH/src/api
15RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" "$APIPATH" && chmod -R 777 "$GOPATH"
16
17WORKDIR $APIPATH
18COPY . .
19
20RUN \
21 go get -d -v \
22 && go install -v \
23 && go build
24
25EXPOSE 3000
26CMD ["./api"]
在这里,您正在添加apk add
命令以使用Alpine的包管理器来安装Go和所有所需的库。
继续前进并构建图像:
1docker build -f Dockerfile.alpine -t alpine .
再次检查图像大小:
1docker images
您将收到类似于以下的输出:
1[secondary_label Output]
2REPOSITORY TAG IMAGE ID CREATED SIZE
3alpine latest ee35a601158d 30 seconds ago 426MB
4. . .
它的大小已经下降到约 426MB。
阿尔卑斯基基图像的大小减少了最终图像大小,但您可以做一些其他事情来使其更小。
接下来,尝试使用预先构建的 Alpine 图像为 Go. 这将使 Dockerfile 更短,并将缩小最终图像的大小。
首先,创建一个名为Dockerfile.golang-alpine
的新文件:
1nano ~/mux-go-api/Dockerfile.golang-alpine
将以下内容添加到文件中:
1[label ~/mux-go-api/Dockerfile.golang-alpine]
2FROM golang:1.10-alpine3.8
3
4RUN apk add --no-cache --update git
5
6WORKDIR /go/src/api
7COPY . .
8
9RUN go get -d -v \
10 && go install -v \
11 && go build
12
13EXPOSE 3000
14CMD ["./api"]
「Dockerfile.golang-alpine」和「Dockerfile.alpine」之間唯一的區別是「FROM」命令和第一個「RUN」命令.現在,「FROM」命令指定一個「golang」圖像,標籤為「1.10-alpine3.8」,而「RUN」只有一個安裝 Git的命令。您需要Git來使用「go get」命令,以便在「Dockerfile.golang-alpine」底部的第二個「RUN」命令中工作。
用以下命令构建图像:
1docker build -f Dockerfile.golang-alpine -t golang-alpine .
找回你的图片列表:
1docker images
您将获得以下输出:
1[secondary_label Output]
2REPOSITORY TAG IMAGE ID CREATED SIZE
3golang-alpine latest 97103a8b912b 49 seconds ago 288MB
现在图像大小已降至约 288MB。
即使你已经成功缩小了大小很多,你可以做的最后一件事是让图像为生产做好准备. 它被称为多阶段构建. 通过使用多阶段构建,你可以使用一个图像来构建应用程序,而使用另一个更轻的图像来包装编译的应用程序进行生产,这是你将在下一步中进行的过程。
步骤 5 — 使用多阶段构建排除构建工具
理想情况下,您在生产中运行的图像不应该安装任何构建工具或依赖性,这些依赖性对于生产应用程序的运行是多余的. 您可以通过使用多阶段构建将这些从最终的Docker图像中移除。
首先,创建一个名为Dockerfile.multistage
的文件:
1nano ~/mux-go-api/Dockerfile.multistage
您将在这里添加的东西将是熟悉的. 开始添加与Dockerfile.golang-alpine
相同的代码,但这次,还添加第二张图像,您将从第一张图像复制二进制图像。
1[label ~/mux-go-api/Dockerfile.multistage]
2FROM golang:1.10-alpine3.8 AS multistage
3
4RUN apk add --no-cache --update git
5
6WORKDIR /go/src/api
7COPY . .
8
9RUN go get -d -v \
10 && go install -v \
11 && go build
12
13##
14
15FROM alpine:3.8
16COPY --from=multistage /go/bin/api /go/bin/
17EXPOSE 3000
18CMD ["/go/bin/api"]
保存和关闭文件. 在这里你有两个FROM
命令. 第一个命令与Dockerfile.golang-alpine
相同,除了在FROM
命令中有额外的AS multistage
之外。 这将给它一个multistage
的名称,然后你会在Dockerfile.multistage
文件的底部引用。 在第二个FROM
命令中,你将把一个基础alpine
图像和COPY
从multistage
图像中编译的Go应用程序中拿进去。
使用以下命令运行 Build:
1docker build -f Dockerfile.multistage -t prod .
现在检查图像大小,使用多阶段构建后。
1docker images
你会发现两个新的图像,而不是一个:
1[secondary_label Output]
2REPOSITORY TAG IMAGE ID CREATED SIZE
3prod latest 82fc005abc40 38 seconds ago 11.3MB
4<none> <none> d7855c8f8280 38 seconds ago 294MB
5. . .
该<none>
图像是用FROM golang:1.10-alpine3.8 AS multistage
命令构建的多层次
图像,它只是构建和编译 Go 应用程序的中间工具,而在这种情况下,prod 图像是仅包含编译 Go 应用程序的最终图像。
从最初的744MB开始,您现在将图像大小缩小到约11.3MB. 跟踪像这样的小图像并将其通过网络发送到您的生产服务器将比超过700MB的图像更容易,从长远来看,您将节省大量资源。
结论
在本教程中,您将 Docker 图像优化用于生产,使用不同的基本 Docker 图像和中间图像来编译和构建代码. 这样,您将样本 API 包装到最小的尺寸。
如果您有兴趣了解有关使用 Docker 构建应用程序的更多信息,请参阅我们的 如何使用 Docker 构建 Node.js 应用程序教程。