【Rust】【Docker】Cargoプロジェクトで素直に書いたDockerfileをdocker buildするとソースが書き換わるたびにフルビルドが走って滅茶苦茶遅いのを対策する方法を改良してもっとキャッシュさせる。
目次
- 目次
- ソース
- 問題点
- おさらい
- 対策:ダミーのCargo.toml Cargo.lockを作ってDockerfileに仕込んでおく。
- dummy-cargo-toml-createrは何をしている?
- 最後に
ソース
問題点
記事内でもあるが、
この方法を使っても,自分のアプリケーションは差分ビルドすることは出来ません またライブラリのバージョンを1つだけ上げるような行為を行っても当然フルビルドになってしまいます.
これがめっちゃしんどいのに対する対策が今回の議題。
おさらい
元々、任意のRustプロジェクトに対し、
COPY . . RUN cargo build --release
素直にこれをやるとフルビルドが走るよねってことで、
# プログラムの依存関係だけをコピー COPY Cargo.toml Cargo.lock /work-dir/ # 何もプログラムが無いとビルドエラーになるのでダミーのものを用意する RUN mkdir -p /work-dir/src/ && touch /work-dir/src/lib.rs # キャッシュのために依存ライブラリだけをビルドする RUN cargo build --release # リポジトリ全体をコピー COPY . . # 本物のビルドを行う RUN cargo build --release
これをやると依存関係のビルドだけ行われた・・・かに思われるが、 これには罠があり、Cargo.toml Cargo.lockが書き変わると2行目からやり直しになる。
これが例えばCIとかであれば独自のキャッシュ機構とかを使ったりして問題無いかもしれないが、dockerのシンプルなキャッシュを使ってローカルでビルドする事はできない。
これは面倒だ。
対策:ダミーのCargo.toml Cargo.lockを作ってDockerfileに仕込んでおく。
これは泥臭いアプローチである。簡単に言えば、docker(正確にはbuild kitか?)を騙す的なアプローチ。
- dummy-cargo-toml-createrを実行してCargo.toml Cargo.lockのパッケージのバージョンだけを0.1.0に固定したDummyVersion.tomlとDummyVersion.lockに作る。
$ cargo install dummy-cargo-toml-creater $ ~/.cargo/bin/dummy-cargo-toml-creater $ ls ./DummyVersion.toml ./DummyVersion.toml $ ls ./DummyVersion.lock ./DummyVersion.lock
この操作は、Makefileとか使ってるなら
dummy-cargo-toml-create: ~/.cargo/bin/dummy-cargo-toml-creater docker-build: dummy-cargo-toml-create docker build dummy .
とかしておけば忘れない。
依存パッケージのビルドの時にそのDummyVersion.tomlとDummyVersion.lockをCargo.toml Cargo.lockとしてDockerに入れ込んでビルドする。
最後に ./src、そしてCargo.tomlとCargo.lockを入れ込んでビルドする。
最終的に ekidd/rust-musl-builder
を使ったりした時はこうなる。
FROM ekidd/rust-musl-builder:nightly-2019-04-25 as builder ## Build Cache Dependency Library RUN mkdir /tmp/app WORKDIR /tmp/app ## Build Dependency Library with DummyVersion.toml/lock COPY DummyVersion.toml ./Cargo.toml COPY DummyVersion.lock ./Cargo.lock RUN mkdir -p src/ && \ touch src/lib.rs RUN sudo chown -R rust:rust . RUN cargo build --release ## Build Base Library with Cargo.toml/lock COPY ./src/ ./src/ COPY Cargo.toml ./Cargo.toml COPY Cargo.lock ./Cargo.lock RUN sudo chown -R rust:rust . RUN cargo build --release
これでdocker buildをすれば、例え Cargo.tomlのpackage.versionを書き換えても依存パッケはキャッシュされ続けるし、それ以外が書き換わった時はしっかりそのキャッシュは使われなくなる。
dummy-cargo-toml-createrは何をしている?
fn ctoml_creater() { let manifest_str = match File::open("./Cargo.toml") { Ok(file) => { let mut buf_file = BufReader::new(file); do_cat(&mut buf_file) }, Err(e) => panic!("{}", e), }; let mut doc = manifest_str.parse::<Document>().expect("invalid doc"); doc["package"]["version"] = value("0.1.0"); let mut file = std::fs::File::create("./DummyVersion.toml").unwrap(); file.write_all(doc.to_string().as_bytes()).expect("Could not write to file!"); } fn clock_creater() { let manifest_str = match File::open("./Cargo.lock") { Ok(file) => { let mut buf_file = BufReader::new(file); do_cat(&mut buf_file) }, Err(e) => panic!("{}", e), }; let mut doc = manifest_str.parse::<Document>().expect("invalid doc"); let mut counter = 0; let mut idx = 0; let tables = doc["package"].as_array_of_tables_mut().unwrap(); for t in tables.iter() { for v in t.iter() { if v.0 == "name" { if let Some(s) = v.1.as_str() { if s == env!("CARGO_PKG_NAME") { idx = counter; break } } } } counter += 1; } doc["package"][idx]["version"] = value("0.1.0"); let mut file = std::fs::File::create("./DummyVersion.lock").unwrap(); file.write_all(doc.to_string().as_bytes()).expect("Could not write to file!"); }
https://github.com/nnao45/dummy-cargo-toml-creater/blob/master/src/main.rs
やってることは、
- Cargo.tomlとCargo.lockを読み込んでStringで持つ。
manifest_str.parse::<toml_read::Document>()
を使ってtoml_read::Document
型にパースする。- 2で作ったオブジェクトの持つtoml::Valueに頑張ってたどり着いて、書き換える
- 書き換えたオブジェクトをDummyVersion.tomlとDummyVersion.lockとして保存する。
3の時の Cargo.lock
だが、正直めっちゃイケてない・・・が、どうやってスマートにpackageというtomlの配列テーブルから自分のパッケージを探し出して書き込むのか考えるのが面倒だったから途中でやめた・・・・機能は満たしているからいいだろ感。
もっと言えば、別にrustでこの機能を書く必要さえ無いと言えば無い・・・笑 が、今回のrustのビルドが目的なので、まぁrustで書くかとなった。
最後に
正直綺麗な実装とは言えないが、キャッシュも依存の更新も、パッケージ自体のバージョンアップにも対応していて上手くいく。dummy-cargo-toml-creater
も大したことやってないので、一つのアプローチとして是非使いたい人はカスタマイズして各々の開発環境に即してキャッシュを効かせてみて欲しい。
流石に、cargo1.40くらいには対応されそうなもんだが・・・。