🔨

Rustのデプロイ用イメージを作成したら苦労した話

2021/12/02に公開

この記事は物工/計数 Advent Calendar 2021 2日目の記事です。
1日目はこちら:研究室選びの豆知識

先日某所で開発しているRustのプロジェクトをECSに載せることになり、デプロイ用のDockerイメージを作成したらそれなりに苦労したので備忘録としてまとめます。

TL;DR

まずは普通にやってみる

定石通りマルチステージビルドにして、buildステージではreleaseオプションをつけてコンパイル cargo build --release し、できたバイナリファイルをdeployステージで実行します

FROM rust:1.54.0 as build
WORKDIR /app
COPY Cargo.toml Cargo.toml
RUN mkdir src
RUN echo "fn main(){}" > src/main.rs
RUN cargo build --release
COPY . .
RUN rm -f target/release/deps/app*
RUN cargo build --release

FROM debian:10.4 as deploy
COPY --from=build /app/target/release/main /usr/local/bin/myapp
CMD ["myapp"]

すると次のようなエラーが表示されます。

error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory

私はこれまで任意の言語はコンパイルしたら完全な実行可能ファイルを作ってくれるものだと勝手に思い込んでいましたが、どうやら世の中にはshared objectなるものが存在し、ランタイムに動的リンクを構成して実行するものも存在しているらしい。

静的リンクのみでコンパイルする

軽く調べたところmuslというライブラリを用いてビルドすると静的リンクのみでバイナリを書き出してくれるようです。そこで次のようにビルドターゲットを変更して再挑戦(事前に x86_64-unknown-linux-musl ターゲットを追加しておく必要がある)

rustup target add x86_64-unknown-linux-musl
cargo build --release --target=x86_64-unknown-linux-musl

これで行けるかと思いきや新たに次のようなエラーが発生。

error: failed to run custom build command for `openssl-sys v0.9.54`
--- stderr
thread 'main' panicked at '

どうやら openssl を使用しているとmuslでのビルドがデフォルトでは上手くいかないらしい
https://www.reddit.com/r/rust/comments/fc1k8x/how_can_i_compile_rust_project_that_uses_openssl/

巨人の肩に乗る

大変ありがたいことに先人がmuslビルド用の専用イメージを用意してくださっていました。このイメージを使ってビルドするとよしなに openssl まわりのリンクを解決してくれます。
https://github.com/emk/rust-musl-builder

というわけでこれを使用して Dockerfile を次のように更新します。私のプロジェクトでは mysqlclient が必要だったので追加していますが、不要な場合は消してもらって大丈夫です。

FROM ekidd/rust-musl-builder:1.48.0 AS builder
# install mysql client
USER root
RUN sudo apt-get update && sudo apt-get install -y libmysqlclient-dev
# build
USER rust
ADD --chown=rust:rust . ./
RUN cargo build --release

FROM alpine:3.14.3 AS deploy
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder \
    /home/rust/src/target/x86_64-unknown-linux-musl/release/main \
    ./main
CMD ["/app/main"]

READMEには openssl 使用時はopenssl-probeを入れるよう書いてあったのですが、いまのところ入れなくても動いているのでこの点については後日調べようと思います。

MySQLを使用する場合はもうひと踏ん張り

libmysqlclient-dev に静的リンクを貼るためにさらに次のクレートを追加する必要があります。
https://github.com/pzmarzly/mysqlclient-sys

Cargo.toml に以下を追加

[patch.crates-io]
mysqlclient-sys = { git = "https://github.com/pzmarzly/mysqlclient-sys", rev = "acd1b2b" }

私の場合は以上の手順で正常に動作することが確認できました。

【おまけ】AWSならDockerHubでなくECRを使おう

DockerHubはanonymousからのPullに上限がかけられているのでCodeBuild等でビルドしているとリクエスト上限で失敗することがそれなりの頻度であります。従来はDockerHubに登録してCodeBuild内でユーザー情報を指定する方法がよくとられていたようですが、最近はECR Public Galleryなるものが登場したのでこちらを利用する方が良いかと思います。ただ、現状はまだDockerHubほど公式イメージが充実していないので、そういったものは自前でビルドしてprivateリポジトリに保存しておくのが良いかもしれません。

おわりに

コンパイル奥が深い....

Discussion