有时,使用自定义Dockerfile会产生大量图像,是的,它们是一堆会重现孤立环境的层。
今天,我将向您展示一种用简单而酷的码头技巧来节省许多空间的方法。
Dockerfiles
要创建自定义映像时,您需要编写一个Dockerfile,如您所知,文件中的每个句子代表一个新图层,让我们看到一个快速示例。
FROM ubuntu:latest # Pulls latest ubuntu image
RUN apt update # Updates apt repositories
RUN apt install figlet # Installs figlet
CMD ["figlet", "-c", "Hello from dockerized ubuntu!"]
# Runs!
如果您尝试构建并运行以前的Dockerfile
docker build -t hello .
docker run hello
输出将像
一样好吧,输出很漂亮,但是让我们看看我的hello
图像使用了多少空间。
docker image ls
输出为
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 3d0b79dcdd1f 10 minutes ago 109MB
109MB
要使用Ubuntu在我的控制台中打印一个Hello World,这实际上是巨大的,现在,考虑一下现实生活中的应用程序Docker Image将使用多少空间。
单阶段Dockerfile
在此示例中,我们将使用超级简单的go rest api,代码已经完成,您可以找到它here。
由于此博客文章的范围是如何减少Docker映像的大小,因此我将解释GO代码,但我保证将来会创建有关该帖子的文章。
在repository中,您会找到两个Dockerfiles,让我们看看Dockerfile
首先
FROM golang:latest
WORKDIR /server
COPY . .
RUN go mod download
RUN go build -o server .
CMD ["./server"]
由此Dockerfile构建的图像将
- 拉动最新的
golang
Docker Image - 创建一个名为
/server
的目录并将上下文更改为 - 将当前目录中的所有文件复制到我们的容器
- 执行
go mod download
以安装所有项目的依赖项 - 执行
go build -o server .
构建应用程序并将bin重命名为server
7 - 最后,每次我们从此图像运行一个容器 时执行
server
二进制文件
非常简单,对吗?让我们构建图像并查看大小,为此,您可以运行以下命令。
docker build -t GoServer .
docker image ls
输出应该类似于
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 3d0b79dcdd1f 12 minutes ago 109MB
GoServer latest 34e3ad5acd86 21 minutes ago 1.02GB
1.02GB
用于隔离我的服务器环境,可能会令人震惊,但让我们看一下它的包含。
- 我所有的项目依赖项
- 全部依赖
- 轻巧的Linux发行版
- 我的应用程序的代码
- 我的应用程序的最后二进制
因此,它实际上存储了许多我们只使用一次的东西,例如,由于我已经编制了我的项目,也许存储所有依赖关系实际上是不必要的,就像我的应用程序的所有代码一样,以及所有的依赖性。
然后,我们可以摆脱上述列表的某些项目,并像
一样保持它- 轻巧的Linux发行版
-
我的应用程序的代码 - 我的应用程序的最后二进制
但是,我们如何实现这一目标?
为此,Docker实际上有一个解决方案,是建立Dockerfiles的不同策略,将其分配在STAGES
中以减少层的数量。
多阶段Dockerfiles
在project’s repository中,您还有另一个dockerfile,称为Dockerfile.multi
Dockerfile.multi
扩展名.multi
实际上不是必需的,我只是用它来区分它与以前的dockerfile,让我们看一下
# Build stage
FROM golang:latest AS builder
WORKDIR /build
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
# Run stage
FROM alpine:latest
WORKDIR /server
COPY --from=builder /build/server .
CMD ["./server"]
它看起来真的很复杂,因为它使用FROM
两次,但实际上更小,更易于阅读(使用练习)。
当我们构建图像时,这个Dockerfile将做什么?
- 拉动最新的
golang
docker映像,并给予它builder
别名 - 创建一个名为
/build
的目录并将上下文更改为 - 将当前目录中的所有文件复制到我们的容器
- 执行
go mod download
以安装所有项目的依赖项 - 执行
go build -o server .
构建应用程序并将bin重命名为服务器,其中一些GO FLAGS
使我们的二进制可在不同的环境中可执行。
好吧,我们将在此时休息一下。
- 我们复制了代码
- 安装项目依赖项
- 编译了我们的项目 我们想要摆脱的许多文件都在此第一阶段中使用,名为构建器。 。
好,准备好了吗?让我们看看第二阶段。
- 拉最新的
alpine
Docker Image,轻量级Linux发行版 - 创建一个名为
/server
的目录并将上下文更改为 - 复制从上一个
builder
阶段提取的最终二进制server
,我们使用from=
键参数 进行此操作
- 最后,执行
server
二进制
这是一条更长的道路,但值得,让我们看到图像使用与以前相同的两个命令所使用的空间,并进行了几次修改。
docker build -t GoLight -f Dockerfile.multi .
docker image ls
,输出为
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 3d0b79dcdd1f 12 minutes ago 109MB
GoServer latest 34e3ad5acd86 21 minutes ago 1.02GB
GoLight latest 309467e3d24e 6 minutes ago 17.5MB
它有效!现在,我们的服务器映像仅使用17.5MB
甚至比hello
映像少,但是为什么?
多阶段Dockerfiles允许我们从创建图像的特定点提取文件和数据。
因此,我们可以摆脱许多不必要的东西,并在不牺牲功能的情况下节省很多磁盘空间。
不相信我吗?让我们简要介绍使用每个图像的执行容器的执行。
我们将首先使用较重的图像
Docker Run -P 8080:8080 Goserver:最新
然后,此容器创建Web服务器并开始在:8080
上收听,目前我们的API只有一个端点,所以让我们执行GET
请求到http://127.0.0.1:8080/songs
curl -X GET http://127.0.0.1:8080/songs
,输出应该是下一个
[
{
"id": 1,
"name": "Alabaster",
"artist": "Foals",
"album": "Total Life Forever"
},
{
"id": 2,
"name": "Bravery",
"artist": "Human Tetris",
"album": "River Pt. 1"
},
{
"id": 3,
"name": "Lately",
"artist": "Metronomy",
"album": "Metronomy Forever"
},
{
"id": 4,
"name": "Paranoid Android",
"artist": "Radiohead",
"album": "OK Computer"
}
]
好!重容器正常工作,并使用我的磁盘的1.02GB
,现在让我们使用较轻的图像(请记住要停止当前容器)
docker run -p 8080:8080 GoLight:latest
此命令将创建一个容器,但是这次使用较轻的图像,行为是相同的;因此,我们可以执行相同的GET
请求。
curl -X GET http://127.0.0.1:8080/songs
,输出与以前相同,请尝试一下!
结论
Docker是一个很棒的工具,如今在市场上广泛使用,尽管它有一些缺点。
当然,大型图像仍然比虚拟机轻,但是我们可以更好地利用Docker来利用其全部潜力。
多阶段Dockerfiles在编译语言中的工作效果更好,因为您可以使用阶段进行构建和编译,而另一个可以实际执行二进制的阶段,但这不是一个限制。
请继续关注更多Docker帖子!