actix-webのホットリロードされるDocker開発環境を作る

# 編集履歴
|日付|内容|
|:-:|:--|
|2021/05/10|actix-webのドキュメントに記載のAuto-Relodingの手順が変わった旨を追記|

# はじめに

本記事では表題の通り、actix-webの開発環境について検討します。
actix-webはRust製のweb frameworkで、LL製の有名フレームワークより高速に動作することで知られています。
https://www.techempower.com/benchmarks/

一方で、修正を確認するためには都度コンパイルをする必要があり、RubyやPythonのような軽量言語に比べると開発効率が下がるのでは、という懸念があるかと思います。
（実際には型安全でコンパイラのチェックが優秀なRustは結果として開発効率が良い可能性もありますが。）

そこで、本記事ではLL製の有名フレームワークと同等の効率でRust製のAPI Serverを開発するための環境を構築する方法を検討します。

# 理想の開発環境

現代のWebアプリ開発はコンテナベースで行われることが多いかと思います。
そこで、本記事では

- `docker-compose up`とか打ったら開発環境が動く
- 開発用と本番用のDockerfileが共通になっており、本番用のイメージが小さい
- 修正がすぐに確認できる

のような条件を満たす開発環境を目指します。

# 想定する構成

本記事では、Vue.jsで作られたSPAのフロントエンドからactix-webで作られたAPI Serverへリクエストする構成を考えます。
本記事の主な対象ではないのですが、DBにはPostgreSQLを想定しています。

- フロントエンド：Vue.js
- バックエンド：actix-web
- DB：PostgreSQL

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314315/b453f6ff-85c9-9699-e8ef-bce2a0aa15e3.png)


# 本題

本記事ではRustに関係する部分のみを紹介します。
上記構成を作る全体像が必要であれば、[成果物のリポジトリ](https://gitlab.com/yamamoto_kazumasa/docker-actix-web)をご参照ください。

## Dockerfile

Dockerfileの作成には[Dockerfileを改善するためのBest Practice 2019年版](https://www.slideshare.net/zembutsu/dockerfile-bestpractices-19-and-advice)を非常に参考にしています。

まず、マルチステージビルド機能を使用して開発用のステージとビルド用、本番用のステージを分けています。本番用には開発用に比べて軽量なベースイメージを使用しており、例えばFargateなどにデプロイする際にかかる時間を減らしています。

なお、今回は本番用のベースイメージに`rust:1.43.1-slim-stretch`を使用していますが、開発環境は不要なのでもっと小さなイメージを使用することが可能です。
ベースイメージについては以下のリンクが参考になります。
参考：[RustのLinux muslターゲット （その2：極小Dockerイメージを実現）](https://blog.rust-jp.rs/tatsuya6502/posts/2019-12-small-docker-image/)

```dockerfile:Dockerfile
# 開発環境
FROM rust:1.43.1 as develop-stage
WORKDIR /app
RUN cargo install cargo-watch
COPY . .

# ビルド環境
FROM develop-stage as build-stage
RUN cargo build --release

# 本番環境
FROM rust:1.43.1-slim-stretch
COPY --from=build-stage /app/target/release/api .
EXPOSE 8088
CMD ["/usr/local/bin/api"]
```


ちなみに、このDockerfileと同じパスに

```:.dockerignore
target
```

というファイルを置いています。
これは本番用のイメージをビルドする際に、`COPY . .`で時間をかけないようにする意図があります。

## docker-compose.yml

以下のyamlはRust部分のみを抽出したdocker-compose.ymlです。
`build`には先ほどのDockerfileを指定し、`target`には開発用のステージを指定します。

また、中間ファイルのディレクトリをキャッシュすることで`docker-compose up`するたびに不要なビルドが実行されることを回避しています。

commandには`cargo watch -x run`を指定しており、これによってホットリローディングを実現しています。
~~なお、actix-webのドキュメントには[Auto-Reloading Development Server](https://actix.rs/docs/autoreload/)という項目が存在するのですが、ここで紹介されている`systemfd`が`systemd`を必要としており、docker上で動かすのが難しいと思い断念しています。~~
（2021/05/10追記：現在は[actix-webのドキュメント](https://actix.rs/docs/autoreload/)でも `cargo watch` を使用する手順となっています）

`cargo watch`では修正するたびにプロセスを再実行するのだと思いますが、ステートレスなAPI Serverを開発する分には影響が少ないと判断しています。

```yaml:docker-compose.yml
version: '3.7'

services:
  backend:
    build:
      context: ./backend
      target: 'develop-stage'
    ports:
      - "8088:8088"
    depends_on:
      - db
    volumes:
      - ./backend:/app
      - cargo-cache:/usr/local/cargo/registry
      - target-cache:/app/target
    command: /bin/sh -c "cargo watch -x run"

volumes:
  cargo-cache:
  target-cache:
```


# 成果物

上記構成で作られた開発環境のサンプルは以下の通りです。
本記事では紹介しなかったフロントエンドのDockerfileなども含まれます。

https://gitlab.com/yamamoto_kazumasa/docker-actix-web


# まとめ
API Serverの開発には十分なactix-webの開発環境を構築することが出来ました。
修正するたびにコンパイルする状況に比べれば良い環境ではないかと思います。

今回は開発時の構成にのみ着目しましたが、実際の開発ではユニットテストの実行やCI/CDの考慮が必要だと思います。
その場合には、本記事で紹介したDockerfileに適切にステージを追加することで必要なイメージを効率よく作ることができると思います。

将来的には開発の全体をカバーしたサンプルプロジェクトを作れたらと考えています。