如何为Rust创建小型Docker镜像? - DEV


构建最小的 Docker 镜像来部署 Rust 带来了很多好处:它不仅有利于安全性(减少攻击面),而且可以缩短部署时间、降低成本(减少带宽和存储),并降低依赖冲突的风险。

我们的 "应用程序 "相当简单:我们将建立一个命令行工具,调用https://api.myip.com,并打印出结果。

进行HTTPS调用很有意思,因为它需要一个与TLS交互的库,通常是openssl。但为了建立尽可能小的Docker镜像,我们需要静态链接我们的程序,而静态链接openssl并不那么容易。这就是为什么我们要避免使用openssl,而使用rustls。

$ cargo new myip

Cargo.toml:
[package]
name = "myip"
version =
"0.1.0"
edition =
"2018"

# See more keys and their definitions at https:
//doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version =
"1", features = ["derive"] }
reqwest = { version =
"0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] }


[target.'cfg(all(target_env =
"musl", target_pointer_width = "64"))'.dependencies.jemallocator]
version =
"0.3"

main.rs:

use serde::Deserialize;
use std::error::Error;

// Use Jemalloc only for musl-64 bits platforms
#[cfg(all(target_env =
"musl", target_pointer_width = "64"))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

#[derive(Deserialize, Debug)]
struct ApiRes {
    ip: String,
}

fn main() -> Result<(), Box<dyn Error>> {
    let res = reqwest::blocking::get(
"https://api.myip.com")?.json::<ApiRes>()?;

    println!(
"{}", res.ip);

    Ok(())
}

$ cargo run
     Running `target/debug/myip`
127.0.0.1

FROM scratch
Size: 15.9 MB

为了使用FROM scratch作为基础镜像,我们必须将我们的程序静态链接到musl libc,因为glibc在scratch中是不可用的。这可以通过使用x86_64-unknown-linux-musl目标来实现。

这种方法的一个问题是,musl的内存分配器没有进行速度优化,可能会降低你的应用程序的性能,特别是在处理高吞吐量的应用程序时。

这就是为什么我们使用 jemalloc,一个为高并发应用设计的内存分配器。

Dockerfile.scratch

####################################################################################################
## Builder
####################################################################################################
FROM rust:latest AS builder

RUN rustup target add x86_64-unknown-linux-musl
RUN apt update && apt install -y musl-tools musl-dev
RUN update-ca-certificates

# Create appuser
ENV USER=myip
ENV UID=10001

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home
"/nonexistent" \
    --shell
"/sbin/nologin" \
    --no-create-home \
    --uid
"${UID}" \
   
"${USER}"


WORKDIR /myip

COPY ./ .

RUN cargo build --target x86_64-unknown-linux-musl --release

####################################################################################################
## Final image
####################################################################################################
FROM scratch

# Import from builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

WORKDIR /myip

# Copy our build
COPY --from=builder /myip/target/x86_64-unknown-linux-musl/release/myip ./

# Use an unprivileged user.
USER myip:myip

CMD [
"/myip/myip"]

运行:

$ docker build -t myip:scratch -f Dockerfile.scratch .
# ...
$ docker run -ti --rm myip:scratch
127.0.0.1

FROM alpine
Size: 21.6MB

Alpine Linux是一个以安全为导向,基于musl libc和busybox的轻量级Linux发行版。

当FROM scratch不够用,而你需要一个软件包管理器来安装chromium或ssh等依赖项时,就应该使用它。

由于它是基于musl libc的,因此具有与FROM scratch相同的限制,我们需要使用x86_64-unknown-linux-musl静态链接我们的Rust程序。
Dockerfile.alpine:

####################################################################################################
## Builder
####################################################################################################
FROM rust:latest AS builder

RUN rustup target add x86_64-unknown-linux-musl
RUN apt update && apt install -y musl-tools musl-dev
RUN update-ca-certificates

# Create appuser
ENV USER=myip
ENV UID=10001

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home
"/nonexistent" \
    --shell
"/sbin/nologin" \
    --no-create-home \
    --uid
"${UID}" \
   
"${USER}"


WORKDIR /myip

COPY ./ .

RUN cargo build --target x86_64-unknown-linux-musl --release

####################################################################################################
## Final image
####################################################################################################
FROM alpine

# Import from builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

WORKDIR /myip

# Copy our build
COPY --from=builder /myip/target/x86_64-unknown-linux-musl/release/myip ./

# Use an unprivileged user.
USER myip:myip

CMD [
"/myip/myip"]

运行:

$ docker build -t myip:alpine -f Dockerfile.alpine .
# ...
$ docker run -ti --rm myip:alpine
127.0.0.1

更多:你可以在 GitHub 上找到代码:github.com/skerkour/kerkour.com