原文地址html
這篇文章我會介紹如何快速爲Rust應用體積小的Docker鏡像。我會從建立一個小的測試應用開始,而後不斷構建迭代Dockerfile。linux
首先確保你已經安裝了下面的應用:git
使用rustup進行設置,確保使用最新穩定版的Rust。github
rustup default stable
rustup update
複製代碼
建立一個myapp
的新項目docker
cargo new myapp
cd myapp/
複製代碼
如下是咱們用於docker構建的起點,在當前目錄中建立一個名爲Dockerfile的文件:shell
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
複製代碼
嘗試構建並運行應用:bash
docker build -t myapp .
docker run --rm -it myapp
複製代碼
若是一切都能工做的話,你能夠在控制檯看到Hello, world!
。app
當我寫這篇文章的時候, Rust的包管理器cargo有一個issue是它尚未一個dependencies-only
的選項,用來單獨構建依賴。工具
cargo缺乏這樣單獨構建依賴的選項使得咱們在每次改動src下面的內容時都會從新構建依賴項,但咱們只想在Cargo.toml或者Cargo.lock文件改變時才從新構建依賴項,比方說添加或者更新依賴。
另外一個問題是,雖然rust:latest Docker鏡像很是適合構建,但它的體積至關大,超過1.6GB。
爲了不這些問題而且開啓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則能夠運行如下命令,以查看咱們缺乏的應用程序共享庫:
docker run --rm -it myapp ldd /usr/local/bin/myapp
複製代碼
在上面的例子中我演示瞭如何經過避免每次src/main.rs
改動從新構建依賴提高構建速度,以及如何將鏡像大小從1.6GB+減小到幾兆字節,然而咱們的構建仍是不能生效,由於咱們須要針對MUSL Libc進行構建,這是一個輕量級、快速的標準庫,在alpine:latest中是默認庫。
除此以外,咱們還但願確保咱們的應用程序以容器內的非特權用戶身份運行,從而遵照最小特權原則。
要針對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-mus
複製代碼
從新構建應用程序並運行它:
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
爲:
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!