Faster Rust Docker Builds, Rust in Linux, Mocking in Rust, and More
Topics:
- Minimal Dockerfile for Rust via @JesusGuzmanJr
- Mocking in Rust and Rust in the Linux Kernel via @mysteriouspants
- Conversations around WebAssembly via @jacobrosenthal, @BlaineBublitz, and @PeterKehl
- Some Blockchain Stuff via @jacobrosenthal
Minimal Dockerfile for Building Rust via @JesusGuzmanJr
Repo: https://github.com/JesusGuzmanJr/CodeSnippets/blob/main/Dockerfile
Jesus shared a minimal Dockerfile for building Rust in a CI pipeline. It utilizes cargo-chef which is a cargo-subcommand to speed up Rust Docker builds using Docker layer caching. It does this in following steps:
-
Compute a recipe file, or a set of information required to build dependencies. The relevant command is
cargo chef prepare --recipe-path /build/recipe.json
-
Cache dependencies using the recipe (
cargo chef cook --release --recipe-path recipe.json
) -
Build the application (using cargo-make and wasm-bindgen). The final command is
cargo make build --profile production
-
Run the application.
FROM fedora:33 as planning_layer
WORKDIR /build
RUN curl --proto '=https' --tlsv1.3 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN dnf -y install gcc-c++
RUN cargo install cargo-chef
COPY . .
RUN cargo chef prepare --recipe-path /build/recipe.json
FROM fedora:33 as dependency_cache_layer
WORKDIR /build
RUN curl --proto '=https' --tlsv1.3 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN dnf -y install gcc-c++ pkg-config openssl-static
COPY --from=planning_layer /build/recipe.json recipe.json
RUN cargo install cargo-chef
RUN cargo chef cook --release --recipe-path recipe.json
FROM fedora:33 as build_layer
WORKDIR /build
RUN curl --proto '=https' --tlsv1.3 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN dnf -y install gcc-c++ pkg-config musl-gcc git perl-core
RUN git clone git://git.openssl.org/openssl.git
RUN cd openssl && git checkout OpenSSL_1_1_1-stable
RUN cd openssl && ./config -fPIC no-weak-ssl-ciphers no-async --prefix=/usr/local/ssl --openssldir=/usr/local/ssl
RUN cd openssl && make && make install
ENV OPENSSL_STATIC true
ENV OPENSSL_DIR /usr/local/ssl
RUN cargo install trunk
RUN cargo install cargo-make
RUN cargo install wasm-bindgen-cli --version 0.2.70
RUN rustup target add wasm32-unknown-unknown
RUN rustup target add x86_64-unknown-linux-musl
RUN dnf -y install nodejs npm
RUN npm install -g sass
COPY . .
COPY --from=dependency_cache_layer /build/target target
COPY --from=dependency_cache_layer $CARGO_HOME $CARGO_HOME
RUN cargo make build --profile production
FROM scratch
COPY --from=build_layer /build/target/x86_64-unknown-linux-musl/release/server /
EXPOSE 80/tcp
ENTRYPOINT ["/server"]
Another Hand-Rolled Example
Jacob showed an example Dockerfile with a similar process of caching except done manually (not using cargo-chef):
FROM rust as dependencies
WORKDIR /build
# Create new fake project ($USER is needed by `cargo new`)
RUN USER=root cargo new app
WORKDIR /build/app
# Copy real app dependencies
COPY Cargo.* ./
# Build fake project with real dependencies
RUN cargo build --release
# Remove the fake app build artifacts
#
# NOTE If your application name contains `-` (`foo-bar` for example)
# then the correct command to remove build artifacts looks like:
#
# RUN rm -rf target/release/foo-bar target/release/deps/foo_bar-*
# ^ ^
RUN rm -rf target/release/hello* target/release/deps/hello-*
Yet Another Similar Example
Chris also had found a similar example in a blog post on How to Package Rust Applications Into Minimal Docker Containers
# Dockerfile for creating a statically-linked Rust application using docker's
# multi-stage build feature. This also leverages the docker build cache to avoid
# re-downloading dependencies if they have not changed.
FROM rust:1.35.0 AS build
WORKDIR /usr/src
# Download the target for static linking.
RUN rustup target add x86_64-unknown-linux-musl
# Create a dummy project and build the app's dependencies.
# If the Cargo.toml or Cargo.lock files have not changed,
# we can use the docker build cache and skip these (typically slow) steps.
RUN USER=root cargo new url-shortener
WORKDIR /usr/src/url-shortener
COPY Cargo.toml Cargo.lock ./
RUN cargo build --release
# Copy the source and build the application.
COPY src ./src
RUN cargo install --target x86_64-unknown-linux-musl --path .
# Copy the statically-linked binary into a scratch container.
FROM scratch
COPY --from=build /usr/local/cargo/bin/url-shortener .
USER 1000
CMD ["./url-shortener"]
Mocking in Rust and Rust in the Linux Kernel via @mysteriouspants
Mocking with Mockall
Chris was looking at mockall for isolating some code and making it more testable. Mocking is tradionally done in more dynamic languages, but that doesn't stop people from trying to do it in Rust. There are two ways to use Mockall. The easiest is to use #[automock]. It can mock most traits, or structs that only have a single impl block. For things it can't handle, there is mock!.
Linux Kernel
Chris was also excited by news that Linus Torvalds, responding to an RFC that adds support for Rust to the Linux kernel, said:
on the whole I don't hate it.
The conversation went on to other developments in the Linux kernel space. A few links from the discussion are below:
- The Rust for Linux website: Work to support Rust that will be eventually submitted for review to the LKML.
- kernel crate: Crate containing kernel APIs that have been ported or wrapped for usage by Rust code in the kernel
- Modern storage is plenty fast. It is the APIs that are bad: A post about building better APIs for high performance I/O systems. In particular it mentions io_uring, an interface for performing I/O with the kernel, and introduces a crate that leverages it.
- Introducing Glommio: The introduction blog post for Glommio, a Thread-per-Core Crate for Rust & Linux
Conversations around WebAssembly via @jacobrosenthal, @BlaineBublitz, and @PeterKehl
Wizer
Jacob was watching the WebAssembly Summit. He thought that the presentation on Wizer, a WebAssembly Pre-Initializer, was interesting. From the Wizer README:
Wizer instantiates your WebAssembly module, executes its initialization function, and then snapshots the initialized state out into a new WebAssembly module. Now you can use this new, pre-initialized WebAssembly module to hit the ground running, without making your users wait for that first-time set up code to complete.
OCaml-rs
Another presentation from the WebAssembly Summit was on Grain, a WebAssembly-First Programming Language. Blaine contributes to Grain and was looking at ocaml-rs as part of his work on the project as a way to avoid writing more C bindings to OCaml.
Watt
Peter brought up Watt, a crate providing a runtime for executing Rust procedural macros compiled as WebAssembly. There is a cargo subcommand, cargo watt, which aims to improve the Watt tooling by:
- Compiling existing proc-macro crates without manual intervention for the watt runtime
- Verifying that a wasm file is compiled from a particular source
Some Blockchain Stuff via @jacobrosenthal
Jacob also shared some links on Blockchain:
-
Fe: Fe is a statically typed language for the Ethereum Virtual Machine (EVM)inspired by Rust.
-
uint is a crate providing facilities to construct big unsigned integer types which use no allocations (stack-based, fixed bit length). They also have primitive types and implement math on top of it. This is what Jacob used to get an advantage playing the Blockchain-based game, Dark Forest, building a miner that was 10x faster than the competition.
Crates You Should Know
- mockall: A powerful mock object library for Rust
- glommio: A set of utilities to allow one to write thread per core applications
- ocaml: OCaml bindings for Rust
- watt: Runtime for executing Rust procedural macros compiled as WebAssembly
- uint: Large fixed-size integer arithmetic
- rmp: Pure Rust MessagePack serialization implementation