Docker로 Rust 앱 배포 (Feat. cargo-chef)

· by 박승재

Rust 프로젝트를 Docker로 배포할 때 발생할 수 있는 문제점해결방안을 모아놓은 문서입니다.

기본적인 프로젝트 빌드 Dockerfile 생성부터 빌드 캐시를 최대한 활용할 수 있게 레이어를 구성하는 방법을 소개합니다.

시작하기

Dockerfile:

# syntax=docker/dockerfile:1
FROM rust:1.55.0-slim

WORKDIR /usr/src/project

COPY . .
RUN cargo build --release

CMD ["./target/release/project"]

프로젝트를 빌드하는 Dockerfile입니다.

Mutli-stage 빌드

Dockerfile:

# syntax=docker/dockerfile:1
FROM rust:1.55.0-slim AS builder

WORKDIR /usr/src/project

COPY . .
RUN cargo build --release

FROM debian:bullseye-slim

WORKDIR /usr/local/bin

COPY --from=builder /usr/src/project/target/release/project .

CMD ["./project"]

Mutli-stage 빌드를 활용한 Dockerfile입니다.

프로젝트 실행에 불필요한 의존성이 없기 때문에 도커 이미지의 크기를 더 줄일 수 있습니다.

Mutli-stage 빌드 + 의존성 컴파일

Dockerfile:

# syntax=docker/dockerfile:1
FROM rust:1.55.0-slim AS builder

WORKDIR /usr/src/project

RUN cargo init .
COPY Cargo* ./
RUN cargo build --release && \
    rm target/release/deps/project*

COPY . .
RUN cargo build --release

FROM debian:bullseye-slim

WORKDIR /usr/local/bin

COPY --from=builder /usr/src/project/target/release/project .

CMD ["./project"]

Rust는 컴파일 속도가 느리기 때문에 최대한 컴파일 분량을 줄이는 것이 중요합니다.

미리 빈 프로젝트를 만들고 의존성만 컴파일해서 레이어를 따로 만들어 둔다면,

프로젝트 코드가 수정되어 빌드 이미지를 다시 생성해야 될 때 의존성은 다시 컴파일하지 않고 미리 컴파일해둔 레이어를 재사용할 수 있습니다.

target/release/deps/project*를 지우는 이유는 현재 프로젝트 컴파일 캐시만 지워서,

COPY 이후 두 번째 cargo build에서 현재 프로젝트만 다시 컴파일 할 수 있게하기 위함입니다.

Mutli-stage 빌드 + cargo-chef

Dockerfile:

# syntax=docker/dockerfile:1
FROM rust:1.55.0-slim AS chef

WORKDIR /usr/src/project

RUN set -eux; \
    cargo install cargo-chef; \
    rm -rf $CARGO_HOME/registry

FROM chef as planner

COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder

COPY --from=planner /usr/src/project/recipe.json .
RUN cargo chef cook --release --recipe-path recipe.json

COPY . .
RUN cargo build --release

FROM debian:bullseye-slim

WORKDIR /usr/local/bin

COPY --from=builder /usr/src/project/target/release/project .

CMD ["./project"]

cargo-chef는 Rust 프로젝트의 의존성을 캐시해서 도커 빌드 속도를 높일 수 있게 도와주는 도구입니다.

cargo install 명령을 통해 설치할 수 있으며, cargo chef preparecargo chef cook으로 의존성을 저장하고 컴파일 할 수 있습니다.

recipe.json 파일은 파이썬의 requirements.txt 파일과 같이 의존성을 기록해두는 파일로, cargo chef cook을 통해 의존성을 다운로드하여 컴파일할 수 있습니다.

Mutli-stage 빌드 + cargo-chef + Alpine Linux

Dockerfile:

# syntax=docker/dockerfile:1
FROM rust:1.55.0-alpine AS chef

WORKDIR /usr/src/project

RUN set -eux; \
    apk add --no-cache musl-dev; \
    cargo install cargo-chef; \
    rm -rf $CARGO_HOME/registry

FROM chef as planner

COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder

COPY --from=planner /usr/src/project/recipe.json .
RUN cargo chef cook --release --recipe-path recipe.json

COPY . .
RUN cargo build --release

FROM alpine:3.14

WORKDIR /usr/local/bin

COPY --from=builder /usr/src/project/target/release/project .

CMD ["./project"]

Debian 대신 Alpine Linux를 이용한다면 이미지 용량을 더욱 줄일 수 있습니다.

musl-dev 패키지는 cargo-chef에서 사용하는 의존성입니다.

참고: Optimizing Docker Images for Rust Projects