🚘

Actixの開発 (Rustrover on Windows)

2023/12/10に公開

概要

前回の記事で、Windows上のRust開発環境として、RustroverとGNU-Cツールチェーンをインストールしました。
クライアント、スタンドアローンの開発であればこれで良いのですが、サーバサイドの開発であれば、DBやProxyといった他のサーバ機能と同時に扱いたくなります。
しかし、Windows上でこれらのサーバと同時に開発を進めるのは、少々面倒くささが伴います。対処方法はいろいろありますが、その一つがDockerを使って、コンテナ上にRustや他のサーバ群をまとめることです。

ここで問題があります。Visual Studioであれば、LLVMを使い、Docker上で動作するRustアプリケーションを直接ブレークポイントを貼るなどのデバグが可能です (と聞いています。すみません、直接確認してません)。しかし、現状のRustroverでは、Docker内のRustコンテナを直接デバグすることは少々面倒です (一応、リモートデバグを使ってやれないことはない)。

そこで、直接Rustアプリケーションでデバグするために、RustによるサーバをWindows上で動作させ、その他のサーバ群をDocker内で動作させることにします。
ここに、その方法を残します。

なお、将来的にはRustrover側で良い感じでデバグできるようになると思いますので、あくまで過渡期的なノウハウになるでしょう。

準備

前提条件

前回の記事にある通り、RustRover、Chocolatey、Rustup、cargo-generateはインストール済みとします。

名前 リビジョン 確認方法
RustRover EAP (2023/11/25build) ヘルプ → バージョン情報
Chocolatey 2.2.2 > choco --version
Rustup 1.26.0 > rustup --version
cargo-generate 0.18.5 > cargo-generate -V

おしながき

  • WSLへのUbuntuインストール
  • UbuntuへのDocker環境構築
  • WindowsへのDocker環境構築
  • RustRoverの環境設定
  • RustRover上のプロジェクト作成

環境一覧

今回環境構築する主なソフトウェアの執筆時のバージョンの一覧です。
バージョンの違いにより動作が異なる場合がありますので、その場合は頑張ってください。

名前 リビジョン 確認方法
Windows 11 22H2 設定アプリ → システム → バージョン情報
WSL 2.0.9.0 > wsl --version
Ubuntu 22.04.3 LTS $ lsb_release -a
Docker Server (Linux) 24.0.7 $ docker --version
Docker Client (Windows) 24.0.7 > \ProgramData\chocolatey\bin\docker --version

WSLへのUbuntuインストール

まず、Windows上にWSLをセットアップし、Ubuntuをインストールします。

WSLのセットアップ

DockerをLinux上で稼働させるため、WSL(Windows Subsystem for Linux)をインストールします。
"Windowsのその他の機能"設定アプリを起動(Windowsキー+Rで、"optionalfeatures"を入力して実行で起動できます)し、以下の機能を有効にします。

  • Hyper-V
  • Linux用Windowsサブシステム
  • 仮想マシンプラットフォーム

WSLのアップデート

WSLを最新版に更新するために、コマンドプロンプトまたはPowerShellを管理者モードで起動します。
そして、wslの更新コマンドを実行します。

PowerShell(管理者モード)
> wsl --update
更新プログラムを確認しています。
Linux 用 Windows サブシステムを更新しています。

Windows Terminal

Windows Terminalがインストールされていない場合は、PowerShellやUbuntuのターミナル起動を簡単に行うためにWindows Terminalをインストールします。
Microsoft Storeアプリから、Windows Terminalをインストール、または、最新版への更新を行います。

Microsoft Storeアプリ --> アプリ --> Windows Terminalを検索 --> インストールまたは更新

Ubuntuインストール

Ubuntuをインストールするために、コマンドプロンプトまたはPowerShellを管理者モードで起動します。
そして、wslのインストールコマンドを実行します。

PowerShell(管理者モード)
> wsl --install Ubuntu-22.04

インストール途中、管理者となるUNIXユーザアカウントとパスワードを尋ねられますので入力します。

Ubuntu
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: <<<管理者UNIXユーザアカウント>>>
New password: *** (上記UNIXユーザアカウント用のパスワード)
Retype new password: *** (確認のためもう一度入力)

wsl.conf

インストールが完了した後、WSL上でUbuntuを起動するための設定ファイル(wsl.conf)を作成するために、Ubuntu内でエディタを起動します。

Ubuntu
$ sudo nano /etc/wsl.conf
[sudo] password for <<<管理者UNIXユーザアカウント>>>

sudoは、管理者権限でコマンドを実行するためのコマンドです。sudoを実行するとき、パスワード入力を求められる場合があります。
nanoは、Ubuntuに標準でインストールされているエディタです。

wsl.confの内容を、以下の通り入力します(<<<管理者UNIXユーザアカウント>>>は、先ほど実際に登録したUNIXユーザアカウントを入力します)。

/etc/wsl.conf
[boot]
systemd = true

[interop]
appendWindowsPath = false

[user]
default = <<<管理者UNIXユーザアカウント>>>
  • systemd(true): 起動時の最初のプロセスとしてsystemdというコマンドを実行させるためのものです。これが無いと、動作しないコマンドが多いため指定します。
  • appendWindowsPath(false): WSL内ではWindowsのコマンドを直接実行することができます。しかし、Ubuntu内でうっかりWindowsのコマンドを実行させると不便な場合もあるため、実行パスからWindowsのコマンドへのパスを取り除きます。
  • default(UNIXユーザアカウント): UbuntuにログインするときのデフォルトのUNIXユーザアカウントを指定します。このユーザアカウントは、インストール時にレジストリに書き込まれますが、バックアップ等でレジストリが消えたときに困るため、明示的に指定しておきます。

wsl.confの内容を反映させるために、Windows Terminalで開いているUbuntu端末を全て閉じます。

最新状態に更新

Ubuntu内のパッケージをいったん最新版に更新するために、以下のコマンドを実行します。

Ubuntu
$ sudo apt update
$ sudo apt upgrade

UbuntuへのDocker環境構築

インストールしたUbuntu上にDocker環境を構築します。
Docker社公式マニュアルにしたがってインストールしていきます。

競合ソフトウェアを削除します

Ubuntu
$ sudo apt remove docker.io docker-doc docker-compose podman-docker containerd runc

Dockerをインストールするのに必要なソフトウェアをインストールします

Ubuntu
$ sudo apt install ca-certificates curl gnupg

Dockerリポジトリの正当性を確認するためのGPGを取得します

Ubuntu
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg

Dockerリポジトリをインストール用ソースに追加します

Ubuntu
$ echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | sudo tee /etc/apt/sources.list.d/docker.list

Dockerリポジトリから、必要なソフトウェアをインストールします

Ubuntu
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli docker-ce-rootless-extras containerd.io docker-buildx-plugin docker-compose-plugin

dockerグループへのユーザを追加します

Ubuntu
$ sudo adduser $USER docker
$ sudo usermod -aG docker $USER

dockerグループへの追加を反映させるためにいったんコンソールを終了します

Ubuntu
$ exit

WindowsへのDocker環境構築

Windows側にもDocker(クライアント)等の環境構築を行う必要があります。

Docker(CLI)

Chocolateyを使って、Windows用のdockerコマンドをインストールします。
なお、Chocolateyがインストールされていない場合は、前回の記事を参照してください。

PowerShellを管理者モードで起動し、以下のコマンドを実行します。

PowerShell(管理者モード)
> choco install docker-cli -y
> choco install docker-compose -y

RustRoverの環境設定

接続するDocker Engineの設定

設定 --> ビルド、実行、デプロイ --> Docker --> (追加)
設定
名前 (好きな名前)
Dockerデーモンへの接続方法 WSL
WSL Ubuntu-22.04

Docker CLIのパス指定

設定 --> ビルド、実行、デプロイ --> Docker --> ツール
設定
Docker実行可能ファイル C:\ProgramData\chocolatey\bin\docker.exe
Docker Compose 実行可能ファイル C:\ProgramData\chocolatey\bin\docker.exe

RustRover上のプロジェクト作成

プロジェクトの概要

現在のRustRoverのバージョンでは、Dockerまたはdocker composeを前提としたプロジェクトをテンプレートから作成することはできません。
したがって、プロジェクトディレクトリを作って、そのディレクトリをプロジェクトとして読み込みます。

今回のサンプルは、RustのActix Webを使ったAPIサーバと、Nginxを使ったリバースプロキシ兼Webサーバの構成にします。

今回作成したサンプルは、このリポジトリにあります。
ただし、説明に無いファイルや一部内容が違うものがありますが、うまく解釈してください。
cargo-generateを使って、サンプルをテンプレートとしてプロジェクトを作成することができます。

サーバー構成

冒頭に書きましたが、APIサーバをWSLホストであるWindows上(ポート9080)、WebサーバをWSL内のDockerコンテナ(ポート8080)で動かすことにします。

なお、api-serverは、Docker内から見たWSLホスト(Windows)のアドレスを指すこととします。

ディレクトリ構成

適当なところにプロジェクトディレクトリを作成し、その下にapiserverとwebserverの二つのサブディレクトリを作成します。

webapp\
  apiserver\
    src\
      main.rs
    Cargo.toml
  webserver\

APIサーバ

Actix Webを使ったAPIサーバを構築します。

webapp\
  apiserver\
    src\
      main.rs
    Cargo.toml
  webserver\
    public\
      index.html
    default.conf
    Dockerfile
  compose.yml
  compose.debug.yml
  createoverride.bat

9080ポートの"/"リクエストを受けると、"Hello World! from RUST"を返すだけのAPIサーバです。

main.rs
use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello World! from RUST")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(hello)
    }).bind(("0.0.0.0", 9080))?
    .run().await
}
Cargo.toml
[package]
name = "api-server"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"

Webサーバ

nginxを使ったプロキシおよびWebサーバを構築します。

webapp\
  webserver\
    public\
      index.html
    default.conf
    Dockerfile

Webサーバとしては、サンプルのデフォルトページだけです。

index.html
<html>
	<p>Hello, World</p>
</html>

Nginxの設定ファイルは、以下の通りです。
"/"へのアクセスはWebサーバのindex.htmlを表示し、"/api"以下のアクセスはAPIサーバにリクエストをフォワードするような設定です。

server {
  listen 8080 default_server;
  server_name _;
  access_log /dev/stdout;
  error_log /dev/stderr;
  
  location / {
    root /var/www/public;
  }
  
  location /api/ {
    rewrite /api/(.*) /$1 break;
    proxy_pass http://api-server:9080;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}  

Nginx用のDockerファイルです。SSL関連の設定は省いています。

FROM nginx:1.25.1-alpine

COPY default.conf /etc/nginx/conf.d/default.conf
COPY public /var/www/public

EXPOSE 8080

Compose

docker compose用のファイルを作成します。

compose.ymlには、Webサーバ用の設定しか含まれていませんが、DBサーバなどを追加する場合は、このファイルに追記することになります。

また、compose.debug.ymlには、WSLのDockerコンテナ内のWebサーバから、Windows上のAPIサーバにアクセスするための設定を記述します。

webapp\
  compose.yml
  compose.debug.yml

compose.ymlは以下の通りです。

services:
  web-server:
    hostname: web-server
    build:
      context: webserver
      dockerfile: ./Dockerfile
    ports:
      - "8080:8080"
    networks:
      - private_net

networks:
  private_net:

Nginxの設定で、/apiパス以下は、ホスト名"api-server"の9080ポートにフォワードするように記載しています。
"api-server"のままでは名前解決できないので、WSLのDockerコンテナ内から見た、Windows上のIPを"api-server"に割り振るように、docker.debug.ymlに設定を記述します。

services:
  web-server:
    extra_hosts:
      - "api-server:172.18.160.1"

ここでは、172.18.160.1になっていますが、実際に割り振られるIPが何になるかは分かりません。このIPを調べるには、WSL側のデフォルトゲートウェイまたはネームサーバー(デフォルトではデフォルトゲートウェイがネームサーバになっています)を調べます。

以下は、Docker側から見たWindowsのIPアドレスを調べるコマンドの実行例です(出力は一部省略しています)。

PowerShell
> wsl -- ip route
default via 172.18.160.1 dev eth0 proto kernel
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.160.0/20 dev eth0 proto kernel scope link src 172.18.175.12

> wsl -- cat /etc/resolv.conf
nameserver 172.18.160.1

api-serverのIP更新バッチ

毎回IPを調べてcompose.debug.ymlを更新するのは面倒なので、コマンドを作成します。

webapp\
  createorverride.bat
createoverride.bat
@echo off
cd /d %~dp0

@setlocal enabledelayedexpansion

for /f "usebackq" %%i in (`wsl -- cat /etc/resolv.conf ^| grep nameserver ^| awk '{print $2}'`) do set WSL_HOST_IP=%%i

set OVERRIDE_FILE=compose.debug.yml
echo.services:>!OVERRIDE_FILE!
echo.  web-server:>>!OVERRIDE_FILE!
echo.    extra_hosts:>>!OVERRIDE_FILE!
echo.      - "api-server:%WSL_HOST_IP%">>!OVERRIDE_FILE!

このバッチは、手動で呼び出すか、ビルドプロセス内で呼び出して自動で更新するようにします。

RustRoverの設定

プロジェクトディレクトリができたら、RustRoverからそれをオープンします。

Dockerの設定

DockerEngineを指定します。
ビルド、実行、デプロイメニューのDockerを選び、Dockerデーモンとして"WSL (Ubuntu-22.04)"を選んで追加します。
WSLを選択したときに、少し経つと設定ダイアログの下の方に"接続完了"とでるはずです。出ない場合は、どこかセットアップが間違えているかもしれません。

Docker/ServersForDebug

次に、Webサーバを起動するための設定を行います。

実行/デバッグ構成メニューから、Docker -> DockerCompose新規構成を選びます。
そして、以下の設定を行います。

  • 名前 - 適当な名前(例えばServersForDebug)
  • サーバー - 上記Dockerの設定で追加したものをメニューから選択する
  • Composeファイル - compose.debug.ymlとcompose.ymlを指定します。GitHubからダウンロードした直後だとcompose.debug.ymlが無いので、手動でcreateoverride.batを起動します。
  • 起動前 - 外部ツールとしてcreateoverride.batを指定しておくと、手動でバッチを起動する手間が省けます

Cargo/Run

次に、APIサーバを起動するための設定を行います。

実行/デバッグ構成メニューから、Cargoを選び、以下の設定を行います。

  • 名前 - 適当な名前
  • 実行場所 - ローカルマシン
  • コマンド - run
  • 作業ディレクトリ - プロジェクト内のapiserverパスを指定

動作確認

  1. Docker/ServersForDebugを実行し、その後、Cargo/Runをデバッグ実行します
  2. main.rsのhello()メソッドにブレークポイントを設定します
  3. 任意のWebブラウザから"http:// localhost:8080/"にアクセスし、"Hello, World"が表示されるのを確認します
  4. 次に"http:// localhost:9080/"にアクセスし、ブレークポイントが動作することを確認します。確認したら再び実行します。
  5. 次に"http:// localhost:8080/api/"にアクセスし、同様にブレークポイントが動作することを確認します。

リリース版

ここまでは、Windows上でAPIサーバを動作させる設定にしていましたが、以下のファイルと設定を追加すると、Docker内でAPIサーバとWebサーバの両方を動作させることができます。

webapp\
  apiserver\
    Dockerfile
  compose.release.yml

APIServer用Dockerfile

FROM rust:1.74-alpine as builder

ARG APPNAME=api-server

RUN apk add --no-cache alpine-sdk build-base

WORKDIR /build
COPY Cargo.toml .
COPY Cargo.lock .
COPY src src
RUN cargo build --release --target x86_64-unknown-linux-musl
RUN cp /build/target/x86_64-unknown-linux-musl/release/$APPNAME /build/target/myapp

FROM alpine:3.18
WORKDIR /app
COPY --from=builder /build/target/myapp /app/myapp
EXPOSE 9080
CMD ["/app/myapp"]

APIServer用docker-compose設定

compose.release.yml
services:
  api-server:
    hostname: api-server
    build:
      context: apiserver
      dockerfile: ./Dockerfile
    ports:
      - "9080:9080"
    networks:
      - private_net

Docker/ServersForRelease

次に、Webサーバを起動するための設定を行います。

実行/デバッグ構成メニューから、Docker -> DockerCompose新規構成を選びます。
そして、以下の設定を行います。

  • 名前 - 適当な名前(例えばServersForRelease)
  • サーバー - 上記Dockerの設定で追加したものをメニューから選択する
  • Composeファイル - compose.release.ymlとcompose.ymlを指定します。

Discussion