首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Rust日报】 2019-11-10 - 为Rust应用构建小而快速的镜像

【Rust日报】 2019-11-10 - 为Rust应用构建小而快速的镜像

作者头像
MikeLoveRust
发布2019-11-12 09:54:58
1.8K0
发布2019-11-12 09:54:58
举报

为Rust应用构建小而快速的镜像

原文地址

这篇文章我会介绍如何为Rust应用创建小且快速的Docker镜像。 我将会从创建一个小的测试应用开始,然后不断构建迭代Dockerfile。

环境要求

确保你已经安装了下面的应用:

  • rustup v1.14.0+
  • docker v17.06.2+

起步: 创建demo应用

使用rustup进行设置,确保你使用了最新的稳定版Rust。

rustup default stable
rustup update

创建一个myapp的新项目

cargo new myapp
cd myapp/

起步: 初始化dockerfile

以下是我们用于docker构建的起点,在当前目录中创建一个名为Dockerfile的文件:

FROM rust:latest

WORKDIR /usr/src/myapp
COPY . .
RUN cargo build --release
RUN cargo install --path .
CMD ["/usr/local/cargo/bin/myapp"]

同样创建一个.dockerignore的文件写入以下内容:

target/
Dockerfile

你可以尝试构建并运行应用:

docker build -t myapp .
docker run --rm -it myapp

如果一切都能工作的话,你可以在控制台看到Hello, world!

我们初次构建的问题

当我写这篇文章的时候, Rust 包管理器 cargo 有一个issue是 它还没有 一个dependencies-only的选项,来单独构建依赖。

cargo缺少这样单独构建依赖的选项使得我们在每次改动src下面的内容时都会对重新构建依赖项,但我们只想在Cargo.toml或者Cargo.lock文件改变是重新构建依赖项,比方说添加或者更新依赖时。

另一个问题是,虽然rust:latest Docker映像非常适合构建,但它的映像相当大,容量超过1.6GB。

改进构建流程避免src改动重新构建依赖项

为了避免这些问题并且开启docker构建缓存让构建变得更快,首先我们开始改动Cargo.toml来添加一个依赖:

[package]
name = "myapp"
version = "0.1.0"
[dependencies]
rand = "0.5.5"

我们添加rand包作为项目依赖,它提供了方便地生成随机数的工具。

现在如果运行:

docker build -t myapp .

它将构建rand依赖关系并将其添加到缓存,但是更改src / main.rs将使下一次生成的缓存无效:

cat src/main.rs
fn main() {
    println!("I've been updated!");
}

docker build -t myapp .

请注意,此次构建必须再次重建rand依赖项。

在等待Cargo的only-dependencies构建选项时,在将任何代码复制到构建环境之前,我们可以通过将Dockerfile更改为默认的src/main.rs来克服此问题:

FROM rust:latest

WORKDIR /usr/src/myapp
COPY Cargo.toml Cargo.toml
RUN mkdir src/
RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs
RUN cargo build --release
RUN rm -f target/release/deps/myapp*
COPY . .
RUN cargo build --release
RUN cargo install --path .
CMD ["/usr/local/cargo/bin/myapp"]

上面的Dockerfile中的以下行将导致Cargo构建时仅重建我们的应用程序:

RUN rm -f target/release/deps/myapp*

所以如果我们编译的话:

docker build -t myapp .

然后我们对src/main.rs做一点小小的改动:

cat  src/main.rs
fn main() {
    println!("I've been updated yet again!");
}

我们将会发现接下来docker构建应用只会在我们的应用逻辑改变时重新构建,而依赖项目则被缓存起来用来快速构建。

减小镜像体积

rust:latest镜像具有构建项目所需的所有工具,但大小超过1.6GB。我们可以使用Alpine Linux(一种出色的小型Linux发行版)来改善镜像大小。

Alpine团队提供了一个只有几兆字节大小的docker映像,并且仍然具有一些用于调试的shell功能,并且可以用作Rust构建的小型基础映像。

使用多阶段docker构建,我们可以使用rust:latest来完成构建工作,但是只需将应用复制到基于alpine:latest的最终构建阶段即可:

# ------------------------------------------------------------------------------
# Cargo Build Stage
# ------------------------------------------------------------------------------

FROM rust:latest as cargo-build

WORKDIR /usr/src/myapp
COPY Cargo.toml Cargo.toml
RUN mkdir src/
RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs
RUN cargo build --release
RUN rm -f target/release/deps/myapp*
COPY . .
RUN cargo build --release
RUN cargo install --path .
# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------

FROM alpine:latest

COPY --from=cargo-build /usr/local/cargo/bin/myapp /usr/local/bin/myapp
CMD ["myapp"]

现在如果你运行:

docker build -t myapp .
docker images |grep myapp

你可以看到这些东西:

myapp               latest              03a3838a37bc        7 seconds ago       8.54MB

下一步:跟进、修复并进一步完善我们的构建

如果你尝试使用docker run --rm -it myapp运行以上示例,则可能会遇到类似以下错误:

standard_init_linux.go:187: exec user process caused "no such file or directory"

如果您熟悉[ldd](https://en.wikipedia.org/wiki/Ldd_(Unix)则可以运行以下命令,以查看我们缺少应用程序的共享库:

docker run --rm -it myapp ldd /usr/local/bin/myapp

在上面的例子中我演示了如何通过避免每次src/main.rs改动重新构建依赖提升构建速度,以及如何将镜像大小从1.6GB+减少到几兆字节,然而我们的构建还是不能生效,因为我们需要针对MUSL Libc进行构建,这是一个轻量级、快速的标准库,在alpine:latest中是默认库。

除此之外,我们还希望确保我们的应用程序以容器内的非特权用户身份运行,从而遵守最小特权原则。

为MUSL Libc构建

要针对MUSL libc进行构建,我们需要安装x86_64-unknown-linux-musl 构建目标,以便可以将Cargo标记为使用--target为其构建。我们还需要标记Rust以使用musl-gcc链接器。

rust:latest镜像预安装rustup。 rustup允许我们使用rustup target add $NAME安装新的构建目标,因此我们可以像这样修改Dockerfile:

# ------------------------------------------------------------------------------
# Cargo Build Stage
# ------------------------------------------------------------------------------

FROM rust:latest as cargo-build

RUN apt-get update
RUN apt-get install musl-tools -y
RUN rustup target add x86_64-unknown-linux-musl
WORKDIR /usr/src/myapp
COPY Cargo.toml Cargo.toml
RUN mkdir src/
RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
RUN rm -f target/x86_64-unknown-linux-musl/release/deps/myapp*
COPY . .
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------

FROM alpine:latest

COPY --from=cargo-build /usr/src/myapp/target/x86_64-unknown-linux-musl/release/myapp /usr/local/bin/myapp
CMD ["myapp"]

请注意以下行,它显示了我们为MUSL Libc构建应用程序的新方式:

RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl

重新构建应用程序并运行它:

docker build -t myapp .
docker run --rm -it myapp

如果一切正常,你应该再次看到应用已被更新了!

以非特权用户身份运行

为了遵循最小特权原则,我们创建一个名为myapp的用户,避免用户以root用户的身份运行应用。

将Final Stage docker build阶段更改为以下内容:

# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------

FROM alpine:latest

RUN addgroup -g 1000 myapp
RUN adduser -D -s /bin/sh -u 1000 -G myapp myapp
WORKDIR /home/myapp/bin/
COPY --from=cargo-build /usr/src/myapp/target/x86_64-unknown-linux-musl/release/myapp .
RUN chown myapp:myapp myapp
USER myapp

CMD ["./myapp"]

更新src/main.rs:

cat src/main.rs
use std::process::Command;

fn main() {
    let mut user = String::from_utf8(Command::new("whoami").output().unwrap().stdout).unwrap();
    user.pop();
    println!("I've once more been updated, and now I run as the user {}!", user)
}

现在构建并运行应用:

docker build -t myapp .
docker run --rm -it myapp

如果一切正常,你应该会看到应用已再次更新,现在应用以用户myapp运行。

最后

现在我们构建应用程序的完整Dockerfile如下所示:

# ------------------------------------------------------------------------------
# Cargo Build Stage
# ------------------------------------------------------------------------------

FROM rust:latest as cargo-build

RUN apt-get update
RUN apt-get install musl-tools -y
RUN rustup target add x86_64-unknown-linux-musl
WORKDIR /usr/src/myapp
COPY Cargo.toml Cargo.toml
RUN mkdir src/
RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
RUN rm -f target/x86_64-unknown-linux-musl/release/deps/myapp*
COPY . .
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------

FROM alpine:latest

RUN addgroup -g 1000 myapp
RUN adduser -D -s /bin/sh -u 1000 -G myapp myapp
WORKDIR /home/myapp/bin/
COPY --from=cargo-build /usr/src/myapp/target/x86_64-unknown-linux-musl/release/myapp .
RUN chown myapp:myapp myapp
USER myapp

CMD ["./myapp"]

从这里观看我的演示,有关使用Skaffold在DC / OS上将Rust部署到Kubernetes。利用该演示中的一些技术,你可以将应用程序自动部署到Kubernetes,以使用Skaffold在本地minikube系统上进行测试。

Happy coding!


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为Rust应用构建小而快速的镜像
  • 环境要求
  • 起步: 创建demo应用
  • 起步: 初始化dockerfile
  • 我们初次构建的问题
    • 改进构建流程避免src改动重新构建依赖项
      • 减小镜像体积
      • 下一步:跟进、修复并进一步完善我们的构建
        • 为MUSL Libc构建
          • 以非特权用户身份运行
          • 最后
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档