Skip to content

Commit 9284ad6

Browse files
committed
dynamically link glibc to avoid numerous issues, provide statically linked muslc dockerfile
Signed-off-by: Lance-Drane <[email protected]>
1 parent 02accc2 commit 9284ad6

File tree

8 files changed

+105
-27
lines changed

8 files changed

+105
-27
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
*
22
!proxy-http-client
3+
proxy-http-client/**/*.ya?ml
34
!proxy-http-server
5+
proxy-http-server/**/*.ya?ml
46
!shared-deps
57
!Cargo.lock
68
!Cargo.toml

Cargo.lock

Lines changed: 31 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Dockerfile

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,18 @@ RUN cargo chef prepare --bin ${BIN_NAME} --recipe-path recipe.json
1515

1616
FROM chef AS builder
1717
ARG BIN_NAME
18-
RUN apt-get update -qq && apt install -y --no-install-recommends \
19-
pkg-config \
20-
libssl-dev \
21-
&& apt-get clean && rm -rf /var/lib/apt/lists /var/cache/apt/archives
22-
# Create the user and group files to run the binary as an unprivileged user.
23-
RUN mkdir /user && \
24-
echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
25-
echo 'nobody:x:65534:' > /user/group
2618
COPY --from=planner /app/recipe.json recipe.json
27-
# strictly use static linking, but use glibc
28-
# NOTE: when using these flags, you must explicitly specify a build target
29-
# TODO - realistically we should use MUSL instead of GLIBC to create a static binary, however the MUSL allocator is slow and should be replaced (i.e. https://www.tweag.io/blog/2023-08-10-rust-static-link-with-mimalloc/)
30-
ENV RUSTFLAGS='-C target-feature=+crt-static'
3119
# Build dependencies - this is the caching Docker layer!
3220
RUN cargo chef cook --release --bin ${BIN_NAME} --recipe-path recipe.json --target x86_64-unknown-linux-gnu
3321
# Build application
3422
COPY . .
3523
RUN cargo build --release --bin ${BIN_NAME} --target x86_64-unknown-linux-gnu
3624

3725
# final image, as small as possible
38-
FROM scratch AS runtime
26+
# Rust runtime depends on libgcc
27+
FROM gcr.io/distroless/cc:nonroot AS runtime
3928
ARG BIN_NAME
4029
WORKDIR /app
41-
# Import user and group files from the build stage.
42-
COPY --from=builder /user/group /user/passwd /etc/
4330
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/release/${BIN_NAME} /app/bin
4431
ENV PROXYAPP_PRODUCTION="true"
45-
USER nobody:nobody
4632
ENTRYPOINT ["/app/bin"]

Dockerfile.muslc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# special image which will generate an image with a statically-linked binary and a non-root user
2+
3+
# https://github.com/LukeMathWalker/cargo-chef
4+
# https://www.lpalmieri.com/posts/fast-rust-docker-builds/#cargo-chef for an in-depth explanation
5+
FROM lukemathwalker/cargo-chef:latest-rust-1-alpine AS chef
6+
WORKDIR /app
7+
# Force rustup to sync the toolchain in the base layer, so it doesn't happen more than once.
8+
COPY rust-toolchain.toml .
9+
RUN cargo --version
10+
11+
# the checksum of recipe.json will only change if the dependency tree changes
12+
FROM chef AS planner
13+
ARG BIN_NAME
14+
# does not invalidate cache in builder stage, only planner stage
15+
COPY . .
16+
RUN cargo chef prepare --bin ${BIN_NAME} --recipe-path recipe.json
17+
18+
FROM chef AS builder
19+
ARG BIN_NAME
20+
# make is needed for bundling the jemalloc allocator
21+
RUN apk update && \
22+
apk add --no-cache \
23+
make
24+
# Create the user and group files to run the binary as an unprivileged user.
25+
RUN mkdir /user && \
26+
echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
27+
echo 'nobody:x:65534:' > /user/group
28+
COPY --from=planner /app/recipe.json recipe.json
29+
# Build dependencies - this is the caching Docker layer!
30+
RUN cargo chef cook --release --bin ${BIN_NAME} --recipe-path recipe.json --target x86_64-unknown-linux-musl
31+
# Build application
32+
COPY . .
33+
RUN cargo build --release --bin ${BIN_NAME} --target x86_64-unknown-linux-musl
34+
35+
# final image, as small as possible
36+
FROM scratch AS runtime
37+
ARG BIN_NAME
38+
WORKDIR /app
39+
# Import user and group files from the build stage.
40+
COPY --from=builder /user/group /user/passwd /etc/
41+
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/${BIN_NAME} /app/bin
42+
ENV PROXYAPP_PRODUCTION="true"
43+
USER nobody:nobody
44+
ENTRYPOINT ["/app/bin"]

proxy-http-client/Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ tokio-stream = { workspace = true }
2626
tracing = { workspace = true }
2727
uuid = { workspace = true }
2828
intersect-ingress-proxy-common = { path = "../shared-deps", version = "0.1.0" }
29-
# rustls is only necessary if you don't want to dynamically link to openssl, this specific feature also provides certificates so you don't need ca-certificates
30-
reqwest = { version = "0.12", features = ["rustls-tls"] }
3129
reqwest-eventsource = "0.6"
30+
31+
# when not targeting muslc, we can just dynamically link to the system openssl
32+
[target.'cfg(not(target_env = "musl"))'.dependencies.reqwest]
33+
version = "0.12"
34+
35+
# on musl, use rustls in place of the system openssl and ca-certificates, as we compile a static binary.
36+
[target.'cfg(all(target_env = "musl"))'.dependencies.reqwest]
37+
version = "0.12"
38+
default-features = false
39+
features = ["rustls-tls", "charset", "http2", "system-proxy"]
40+
41+
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
42+
version = "0.5"

proxy-http-client/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ use proxy_http_client::{configuration::Settings, event_source::event_source_loop
1414

1515
const APPLICATION_NAME: &str = "proxy-http-client";
1616

17+
// Muslc has a slow allocator, but we can only use jemalloc on 64-bit systems since jemalloc doesn't support i686.
18+
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
19+
#[global_allocator]
20+
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
21+
1722
#[tokio::main]
1823
pub async fn main() -> anyhow::Result<()> {
1924
let configuration = get_configuration::<Settings>().expect("Failed to read configuration");

proxy-http-server/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ axum = { version = "0.8", features = ["macros"] }
3131
axum-extra = { version = "0.10", features = ["typed-header"] }
3232
tower = "0.5"
3333
tower-http = { version = "0.6", features = ["request-id", "tracing", "trace", "util"] }
34+
35+
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
36+
version = "0.5"

proxy-http-server/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ use proxy_http_server::{
1515

1616
const APPLICATION_NAME: &str = "proxy-http-server";
1717

18+
// Muslc has a slow allocator, but we can only use jemalloc on 64-bit systems since jemalloc doesn't support i686.
19+
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
20+
#[global_allocator]
21+
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
22+
1823
#[tokio::main]
1924
async fn main() -> anyhow::Result<()> {
2025
let configuration = get_configuration::<Settings>().expect("Failed to read configuration");

0 commit comments

Comments
 (0)