Docker + Rust 的第一步
TL;DR:我们将安装 Docker 并为 Rust 程序创建五个不同的容器,每个容器都比另一个复杂一点。
你好!在这篇文章中,我将向你展示如何 dockerize 你的 Rust 程序。我们将从一个非常简单的容器开始,然后从它构建到更复杂的容器,我们负责编译时间和图像大小。
我考虑直接跳到最后一个而不是细化这么多,但我更喜欢清晰而不是简洁地使用这些#beginners帖子。随意跳任何对你来说太明显的东西,问我还有什么太模糊。
安装Docker
首先,安装Docker。 getting started 指南包含Mac OS、Linux和Windows的说明。
安装后,通过发出以下命令运行并测试它:
$ docker run hello-world
进入全屏模式 退出全屏模式
它应该从Docker Hub中提取hello-world映像,并返回一个文本块,详细解释幕后发生的事情。
词汇表
如果您对 Docker 完全陌生,那么当我使用以下术语时,有助于清楚地理解我的意思:
-
Docker:我们刚刚安装的应用程序(或者更准确地说,是我们用来处理图像和容器的 Docker 守护程序);
-
Dockerfile:名为
Dockerfile的文件,其中包含 Docker 将运行以构建映像的命令。在 Rust 项目中,它位于 manifest 旁边,即Cargo.toml文件; -
Image:当我们运行命令
build时,我们会创建一个包含我们在Dockerfile中指定的所有内容的图像。在容器中运行图像会产生结果。例如,如果我们的Dockerfile有构建和运行 Web 服务器的指令,则镜像将包含程序(即 Web 服务器本身),当我们_运行_镜像时可以访问该程序,从而创建容器; -
基础镜像:这是我们用来作为基础的镜像。例如,对于我们来说,Rust 将是一个基础镜像(我们不手动构建它;我们下载它以供使用);
-
Container:运行镜像的结果。容器是在您的计算机上运行的进程,其中包含运行应用程序所需的一切。为了更好地理解,我推荐这个 Liz Rice 的演讲。
示例项目
我将使用我使用warp构建的 REST API。如果您想自己构建它,您可以查看本指南;如果没有,请在此处免费下载甚至使用您自己的项目;我相信您映射命令不会有问题。
您在指南中找到的代码与我在此处使用的代码之间存在一些差异:
- 我现在使用 IP 0.0.0.0 而不是 127.0.0.1。我改变了这个,所以我不必告诉 Docker 要绑定哪个 IP;
- 旧版本有两个 crate:二进制(
main.rs)和库(lib.rs)。这让 Docker 变得更加困难(并且会使我在这里的解释过于复杂),所以我只维护了二进制 crate 并将库 crate 内容移动到一个模块中。
第一个Dockerfile
此文件和所有其他文件均可用此处。您将通过它们的编号来识别它们。
我将从一个非常简单的版本开始,并对其进行几次改进。在这里,我正在处理我上面提到的名为holodeck的项目(因此,如果您正在使用您的项目,则必须更改该名称):
# 1. This tells docker to use the Rust official image
FROM rust:1.49
# 2. Copy the files in your machine to the Docker image
COPY ./ ./
# Build your program for release
RUN cargo build --release
# Run the binary
CMD ["./target/release/holodeck"]
进入全屏模式 退出全屏模式
有了这个,我只需在Dockerfile所在的位置运行下面的命令。
$ docker build -t holodeck .
进入全屏模式 退出全屏模式
这将创建图像。要查看这一点,您可以查看 Docker 应用程序或使用命令docker images。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
holodeck latest aad6ff7c3b4d 47 seconds ago 2.42GB
进入全屏模式 退出全屏模式
要运行它,我们所要做的就是发出这样的命令:
$ docker run -p 8080:3030 --rm --name holodeck1 holodeck
Warp 6, Engage!
进入全屏模式 退出全屏模式
让我扩展参数:
-
-p映射端口,那么3030 inside Docker(我们的warp服务器正在使用的端口)将可以通过8080 _outside_访问,即您的机器(如果您不这样做,Docker将映射一个随机端口); -
--rm在这里是为了在我们关闭它后移除容器(为了形象化,您可以在没有--rm的情况下运行,然后运行docker ps -a列出所有容器,然后使用docker rm containername将其移除)。
现在可以在localhost:8080中测试它。
$ curl --location --request POST 'localhost:8080/holodeck' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
"id": 2,
"name": "Bride Of Chaotica!"
}'
Simulation #1 created.
进入全屏模式 退出全屏模式
停止容器:
$ docker stop holodeck1
holodeck1
进入全屏模式 退出全屏模式
如果你像我一样运行你的图像,你的终端将被冻结。为避免这种情况,请在分离模式下运行,只需添加参数-d:
$ docker run -dp 8080:3030 --rm --name holodeck1 holodeck
进入全屏模式 退出全屏模式
伟大的!这很好......直到你不得不一次又一次地构建(甚至可能再次构建,因为你正在编写这样的指南并且必须经常调整内容)。为什么这是个问题? 编译时间。每次我们运行docker build时,Rust 都会重新执行整个构建过程;而我们正在为发布而构建的事实只会让情况变得更糟。
让我们修复它。
第二个Dockerfile
这是一个更详细的替代方案:
# Rust as the base image
FROM rust:1.49
# 1. Create a new empty shell project
RUN USER=root cargo new --bin holodeck
WORKDIR /holodeck
# 2. Copy our manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# 3. Build only the dependencies to cache them
RUN cargo build --release
RUN rm src/*.rs
# 4. Now that the dependency is built, copy your source code
COPY ./src ./src
# 5. Build for release.
RUN rm ./target/release/deps/holodeck*
RUN cargo install --path .
CMD ["holodeck"]
进入全屏模式 退出全屏模式
我使用的是
install,但这与build相同,只是它将二进制文件放在指定的路径上,在本例中为WORKDIR。
这就是我们避免编译器_aeon_的方式。通过仅构建附加到新程序的依赖项(步骤 1 到 3),然后使用Dockerfile中的新命令构建我们的程序(命令 4 和 5),我们可以阻止 Docker 忽略缓存。为什么它有效?因为我们Dockerfile中的每个命令都会创建一个新的层,这是对图像的修改。当我们运行docker build时,只有修改过的层被更新,其余的从本地缓存中检索。实际上,只要我们不更改清单,就不必重新构建依赖项。
就我而言,在第一次构建耗时 323.6 秒后,第二次构建(我刚刚更改了main.rs)只用了 33.9 秒。伟大的!
但是...还有另一个问题:图像大小。例如,我的有 1.65 GB,这比第一个有 2.42 GB 更好,但仍然太大。让我们对其进行一些修复。
第三个Dockerfile
如果您访问了 Isaac 的帖子,您会看到他通过使用两个基本映像“链接”构建来管理此问题。他在第一个FROM rust之后做了所有事情,这是构建所有内容的构建,然后调用另一个FROM rust,仅从第一个构建复制所需的文件。这允许最终图像仅保留这些最后复制的文件,从而减小图像大小。
这是我们的做法:
FROM rust:1.49 as build
# create a new empty shell project
RUN USER=root cargo new --bin holodeck
WORKDIR /holodeck
# copy over your manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# this build step will cache your dependencies
RUN cargo build --release
RUN rm src/*.rs
# copy your source tree
COPY ./src ./src
# build for release
RUN rm ./target/release/deps/holodeck*
RUN cargo build --release
# our final base
FROM rust:1.49
# copy the build artifact from the build stage
COPY --from=build /holodeck/target/release/holodeck .
# set the startup command to run your binary
CMD ["./holodeck"]
进入全屏模式 退出全屏模式
这将图像大小从 1.66 GB 减少到 1.26 GB。但是我承诺了Dockerfile的五个版本,所以你已经知道我们可以做得更好。
第四个Dockerfile
我们现在要做的是更改我们正在使用的 Rust 图像标签。标签是“图像变体”的另一种说法,即为满足特定目标而设计的替代图像。因此,如果我们想要一个节省空间的图像,它是我们正在寻找的标签。
这意味着只需将第二个FROM替换为:
FROM rust:1.49-slim-buster
进入全屏模式 退出全屏模式
此图像标签使用基于 Debian 标签的 Rust 构建,称为buster slim以创建更紧凑的图像。现在,我们有 642.3 MB 而不是 1.26 GB。
有些人可能想知道我为什么不使用 Alpine。好吧,我做到了,而且 buster-slim 小了 10MB。但我避开 Alpine 的真正原因将在下一步中明确。
第五个(也是最后一个)Dockerfile
我不认为总是需要寻找最小的图像尺寸。你必须有一个理由。
因为我_do_有理由(向你展示),所以我将做最后一个更改,这将提供一个非常小的 Docker 映像。为此,我们需要一个没有任何 Rust 的图像,只有将要执行的二进制文件(毕竟这是编译语言的优点之一)。
为了实现这一点,我们使用 Rust 基础镜像来构建我们的二进制文件,然后将二进制文件移动到没有 Rust 的 Linux 镜像中。为此,我们将使用debian:buster-slim本身。
再次,关于 Alpine。我也没有在这里使用它,原因有两个:
- 要在 Alpine 中运行 Rust 代码,必须使用 MUSL](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/musl-support-for-fully-static-binaries.html)编译为[,这为这篇面向初学者的帖子增加了一层额外的复杂性;
2.我不确定 MUSL 是不是一个不错的选择。
结果:75.39 MB。这距离2.42 GB 还差得很远。
我们来做一个整体比较:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--OBfCWq4x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/75hj6rfqxhwtvocx0rbl.png)
而已!感谢您到目前为止的阅读。再见!
封面图片来自Aron Yigin
更多推荐



所有评论(0)