[Rust] dockerコンテナにマウントしたディレクトリでbuildすると遅かった話し

普段の開発時にソースコードを書き換える度にdocker buildしているとめんどくさいので
ホスト側のソースコードのディレクトリをdockerコンテナにマウントしてコンテナ上でコンパイルして開発しているのですが
どうもコンパイルが異常に遅いので、いろいろ試したのでメモ

# 環境

- OS
  - macOS Mojave 10.14
- rustc
  - 1.30.0 (da5f414c2 2018-10-24)
- docker
  - Docker for mac Version 2.0.0.0-beta1-mac75 (27117)

# ビルドするアプリケーション

[yagince/rust-webapp-starter](https://github.com/yagince/rust-webapp-starter/)

ちょっと前にactix-web+vuejsを試してみる為に作ったリポジトリがあるので、こちらで試します。

```docker-compose.yml
version: '3.1'

services:
  postgres:
    container_name: postgres
    image: postgres:11
    restart: always
    environment:
      POSTGRES_DB: webapp
      POSTGRES_USER: dbuser
      POSTGRES_PASSWORD: password
    ports:
     - "5432:5432"
  api:
    container_name: api
    build:
      context: .
    command: cargo run
    volumes:
      - .:/app
      - ./tmp/.cargo/registry:/root/.cargo/registry
      - ./tmp/.cache:/root/.cache
    depends_on:
      - postgres
    ports:
     - "28000:8000"
  web:
    container_name: web
    build:
      context: .
    working_dir: '/app/webapp'
    command: yarn run serve
    ports:
     - "28080:8080"
    volumes:
      - .:/app
    depends_on:
      - api
```

- ルートディレクトリをまるっと `/app` にマウント
- registryはcrate追加した時などにキャッシュしたいのでマウント
- `api` が今回メインとなるRustのアプリケーション

# まずは普通にコンテナ内でビルドしてみる

## 前提

- 一回ビルドしていて、依存ライブラリのコンパイルは終わっている
- アプリケーション内のコードを書き換えて、ビルドする

## 結果

```
root@6fd7810d005f:/app# cargo build
   Compiling rust_webapp_starter v0.1.0 (/app)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 02s
```

`1m 02s` !!!

1分以上かかっています

※今回使っているサンプルアプリケーションは複雑なものでもないので、この程度ですが、今仕事で書いているアプリケーションは5−6分かかってました。。。

# 何が悪いのか？

## registryをマウントしているのが悪いのか？

registryをマウントしないでビルドしてみまず

```docker-compose.yml
...
  api:
    container_name: api
    build:
      context: .
    command: cargo run
    volumes:
      - .:/app
    depends_on:
      - postgres
    ports:
     - "28000:8000"
...
```

```
root@5b82f1de979d:/app# cargo build
   Compiling rust_webapp_starter v0.1.0 (/app)
    Finished dev [unoptimized + debuginfo] target(s) in 57.28s
```

誤差ですね。ほぼ変わらず。

## マウントしているディレクトリ上にtargetがあるのが悪いのか？

cargo buildってtargetディレクトリのpath変えられるのだろうか？

オプションを見てみる

```
root@5b82f1de979d:/app# cargo build -h
cargo-build
Compile a local package and all of its dependencies

USAGE:
    cargo build [OPTIONS]

OPTIONS:
    -p, --package <SPEC>...         Package to build
        --all                       Build all packages in the workspace
        --exclude <SPEC>...         Exclude packages from the build
    -j, --jobs <N>                  Number of parallel jobs, defaults to # of CPUs
        --lib                       Build only this package's library
        --bin <NAME>...             Build only the specified binary
        --bins                      Build all binaries
        --example <NAME>...         Build only the specified example
        --examples                  Build all examples
        --test <NAME>...            Build only the specified test target
        --tests                     Build all tests
        --bench <NAME>...           Build only the specified bench target
        --benches                   Build all benches
        --all-targets               Build all targets (lib and bin targets by default)
        --release                   Build artifacts in release mode, with optimizations
        --features <FEATURES>       Space-separated list of features to activate
        --all-features              Activate all available features
        --no-default-features       Do not activate the `default` feature
        --target <TRIPLE>           Build for the target triple
        --target-dir <DIRECTORY>    Directory for all generated artifacts
        --out-dir <PATH>            Copy final artifacts to this directory
        --manifest-path <PATH>      Path to Cargo.toml
        --message-format <FMT>      Error format [default: human]  [possible values: human, json, short]
        --build-plan                Output the build plan in JSON
    -v, --verbose                   Use verbose output (-vv very verbose/build.rs output)
    -q, --quiet                     No output printed to stdout
        --color <WHEN>              Coloring: auto, always, never
        --frozen                    Require Cargo.lock and cache are up to date
        --locked                    Require Cargo.lock is up to date
    -Z <FLAG>...                    Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
    -h, --help                      Prints help information
...
```

`--target-dir <DIRECTORY>` 発見！

試してみます。
**1回目はtaget-dirが空っぽなので、ライブラリ系のコンパイルも行われるため、2回目以降を比較します。**
※ソースコードを何も変更しないとコンパイルされないので、空行を入れたり消したりして再コンパイルしてます

```
root@5b82f1de979d:/app# cargo build --target-dir /tmp/target
   Compiling rust_webapp_starter v0.1.0 (/app)
    Finished dev [unoptimized + debuginfo] target(s) in 6.14s
```

ビンゴ
10倍くらい速くなりました

# targetのディレクトリを変更する

`target-dir` がマウントしたディレクトリだと遅いことがわかったので、別の場所にします。
が、cargo buildに毎回オプションつけるのは面倒ですし、忘れた時に悲惨です。
じゃあどうするか。
環境変数で設定できないのかな？

[Configuration - The Cargo Book](https://doc.rust-lang.org/cargo/reference/config.html#environment-variables)

ありました。

> For example the build.jobs key can also be defined by CARGO_BUILD_JOBS.

とのこと。

## コンテナの環境変数に設定する

```docker-compose.yml
...
  api:
    container_name: api
    build:
      context: .
    command: cargo run
    environment:
      - CARGO_BUILD_TARGET_DIR=/tmp/target
    volumes:
      - .:/app
    depends_on:
      - postgres
    ports:
     - "28000:8000"
...
```

コンテナを立ち上げ直して、確認

```shell
root@b24c551bd6e4:/app# cargo build
   Compiling rust_webapp_starter v0.1.0 (/app)
    Finished dev [unoptimized + debuginfo] target(s) in 6.09s
```

```shell
root@b24c551bd6e4:/app# env | grep -i cargo_build
CARGO_BUILD_TARGET_DIR=/tmo/target
```
ただ、これだとコンテナ立ち上げ直す度にフルビルドが走るので、イマイチ。。。

## Dockerfileに書いてしまう

今回のDockerfileでは、Cargo.tomlを先にCOPYして依存ライブラリのみキャッシュされるように先にcargo buildしているので、
その時点でtarget-dirが変わっていたほうがなにかと効率が良い気がします

```Dockerfile
FROM rust:1.30.0-stretch

MAINTAINER yagince <straitwalk@gmail.com>

RUN apt-get -y -q update \
  && apt-get install -y -q \
     curl \
     gnupg \
     apt-transport-https\
  && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && curl -sL https://deb.nodesource.com/setup_10.x | bash - \
  && apt-get install -y -q \
     nodejs \
     yarn \
     libpq-dev \
  && cargo install diesel_cli --no-default-features --features postgres

ENV CARGO_BUILD_TARGET_DIR=/tmp/target

RUN USER=root cargo new --bin app
WORKDIR /app
COPY ./Cargo.toml Cargo.toml
COPY ./Cargo.lock Cargo.lock

RUN cargo build --release --color never && \
    rm src/*.rs
```

# まとめ

- コンテナにマウントしたディレクトリでそのままビルドすると大変遅い
  - テスト駆動開発とかとてもじゃないけどできない
- target-dirをコンテナ内のディレクトリでマウントしたディレクトリではないところに変更すると速くなる
- target-dirの変更は３つ方法がある
  - `.cargo/config` 系の設定ファイル
  - コマンドラインオプション
  - 環境変数