workers-rsでハローワールド + Github Actionで自動デプロイ

2024/12/19に公開

この記事は デジタルキューブ & ヘプタゴン Advent Calendar 2024 の 12月23日分の記事です。

Rust練習したいなーと常々思っていたので、入口としてCloudflare Workers用のRustクレートworkers-rsに入門してみます。

そもそもCloudflare Workersって何?

Cloudflareのエッジロケーションでサーバレスにコードを実行してくれるサービスです。
CDNのエッジで処理するので通信による待ち時間が少なく、V8 isolateによってコールドスタートの影響も非常に少ないことが特徴です。厳密な説明ではありませんが、僕は制約がある代わりに早くて軽量なAWS Lambdaみたいなものだと思ってます。
https://workers.cloudflare.com/
CDNやエッジロケーションの概念から説明している100秒テックさんの動画の解説がわかりやすいです。

やること:

  • workers-rsのhello-worldのテンプレートをCloudflare Workers上で動かす。
  • ついでにGitHub Actionsで自動デプロイしてみる。

Cloudflare Workers上でプラットフォームの標準とされるJavaScript/TypeScript以外の言語を実行する際はWebAssembly (Wasm)として実行する必要があります。そのため、Rustを使って開発する場合にはある程度のWasmの知識やJavaScriptとの連携を意識したコーディングが必要でしたが、公式のworkers-rsクレートを使用することで、これらをあまり意識せずにRustを使った開発ができるようになりました。
https://github.com/cloudflare/workers-rs

下記の公式ドキュメントを参考にしながらテンプレートの手動デプロイと、GitHub Actions を使った自動デプロイの2つを行います。(以降の手順はmacOS上で実行したものです。)
https://developers.cloudflare.com/workers/languages/rust/

Rust環境のセットアップ

まずは、rustのプロジェクト作成とコンパイルをするための下準備を行います。

rustupでWasm用のツールチェーン(コンパイラと関連ツール)をインストール。

rustup target add wasm32-unknown-unknown

テンプレートからプロジェクトを作成するためにcargo-generateをインストール。

cargo install cargo-generate

テンプレートの作成と手動デプロイ

プロジェクトを作成して手動でCloudflare Workersにデプロイしてみます。

テンプレートからプロジェクトを作成

まずはcargo generateでプロジェクトを作成します。

cargo generate cloudflare/workers-rs

テンプレートはtemplates/hello-worldを選択。

? 🤷   Which template should be expanded? ›
  templates/axum
❯ templates/hello-world
  templates/hello-world-http
  templates/leptos

プロジェクト名はworkers-rs-github-actionsとします。(任意の名前でOK)

🤷   Project Name: workers-rs-github-actions

テンプレート内のファイルの確認

次のファイル・ディレクトリが生成されます。

  • Cargo.toml - Cargoの設定ファイル。Workers用のWasmのコンパイルに関する設定と、依存するクレートとの依存関係が記載されています。
  • wrangler.toml - Wranglerの用の設定ファイル。wrangler devとwrangler deployの実行時にworker-buildが呼び出されるよう Custom buildsで設定されています。
  • src - Rustのコード用のディレクトリです。
作成されたプロジェクトのディレクトリ構成
.
├── Cargo.toml
├── src
│   └── lib.rs
└── wrangler.toml

src/lib.rsの中身はこんな感じです。Hello World!とレスポンスするだけのシンプルなものです。

src/lib.rs
use worker::*;

#[event(fetch)]
async fn fetch(
    _req: Request,
    _env: Env,
    _ctx: Context,
) -> Result<Response> {
    console_error_panic_hook::set_once();
    Response::ok("Hello World!")
}

以下、コードの簡単な解説です。
use worker::*でworkerクレートをインポート

use worker::*;

eventマクロ(#[event])で関数に属性を付与します。eventマクロで属性を付与された関数は、イベント発生時に呼び出されるエントリーポイントとなります。#[event(fetch)]の場合、WorkerがHTTPリクエストを受信した際に呼び出されます。(参照:event macro

#[event(fetch)]

#[event(fetch)]でfetchイベントのエントリーポイントに設定された関数は三つのパラメータを受け取ります。(参照:fetch parameters

  • Request:受信したリクエストを表すオブジェクトで、HTTPメソッド、ヘッダー情報、ボディのデータなどを含みます。
  • Env:wrangler.tomlで設定された、環境変数やWorkerにバインドされたリソースへのアクセスするためのオブジェクトです。
  • Context:何を取り扱っているかよくわかりませんでした🙏 原文→Provides access to waitUntil (deferred asynchronous tasks) and passThroughOnException (fail open) functionality.
async fn fetch(
    _req: Request,
    _env: Env,
    _ctx: Context,
) -> Result<Response> {
...
}

console_error_panic_hook::set_once()を使用して、Wasmのパニック発生時にデバッグメッセージを表示するようにします。

console_error_panic_hook::set_once();

fetchイベントのエントリーポイントの関数では、Response型の戻り値に基づいてHTTPリクエストにレスポンスします。このテンプレートではResponse::ok("Hello World!")を使用してステータスコード200Hello World!という文字列をレスポンスします。(参照:Response

Response::ok("Hello World!")

手動デプロイと動作チェック

wrangler deployでデプロイします。(JavaScript/TypeScript用のテンプレートと同じコマンドでデプロイできます)

npx wrangler deploy

worker-buildの実行により、RustコードはWASMにコンパイルされ、WASMモジュールを利用するためのJavaScriptグルーコードと共にCloudflare Workersにデプロイされます。

wrangler deploy

 ⛅️ wrangler 3.92.0 (update available 3.95.0)
-------------------------------------------------------

Running custom build: cargo install -q worker-build && worker-build --release
[INFO]: 🎯  Checking for the Wasm target...
[INFO]: 🌀  Compiling to Wasm...
...

CloudflareのダッシュボードのWorkers & Pagesで確認するとデプロイされていることが確認できます。

先ほどの画像にもあるVisitのリンクから動作を確認。Hello World!が返され、正常に動作していることが確認できました。

GitHub Actionsからの自動デプロイ

GitHub Actionsからプッシュをトリガーにデプロイします。

GitHubにリポジトリを作成

先ほど作成したプロジェクトのコードからGitHubにリポジトリを作成します。

git init 
git add .
git commit -m "first commit"
gh repo create workers-rs-github-actions --private --source=. --remote=origin --push

GitHub Actionsのワークフローを追加

GitHub Actionsのワークフローを記述した.github/workflow/deploy.ymlを作成します。
mainブランチへのpushをトリガーに、公式のアクションのcloudflare/wrangler-action@v3でビルドとデプロイを行います。JS/TSで開発する場合と同じアクションが使えます。

.github/workflow/deploy.yml
name: Deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to Cloudflare Workers
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}

シークレットを登録

次に、GitHub ActionsからCloudflare WorkersへのデプロイをするためにCloudflareのAPI tokenと account IDを取得して、GitHubのリポジトリにシークレットとして登録します。

API Tokenの取得方法
https://developers.cloudflare.com/workers/wrangler/migration/v1-to-v2/wrangler-legacy/authentication/#generate-tokens
account IDの取得方法
https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/#find-account-id-workers-and-pages

GitHubリポジトリの「Settings > Security > Secret and variables」 から Cloudflare のAPI Tokenを CF_API_TOKEN、account IDをCF_ACCOUNT_IDとして登録します。
https://docs.github.com/ja/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository

pushして自動デプロイ

デプロイできたことを確認するためにlib.rs内のResponse::okの引数をHello World from GitHub Actions!に書き換えます。

src/lib.rs
- Response::ok("Hello World!")
+ Response::ok("Hello World from GitHub Actions!")

書き換え後コミットしてプッシュ。

git add .
git commit -m "Workflowを追加"
git push -u origin main

Workflowが動いた!ここから終わるまで5分ほど待つ。

終わった!(途中環境変数の設定ミスでrerunするなどしたので29 minutes agoになってます。)

再度デプロイ先のリンクを叩くとHello World from GitHub Actions!が表示され、反映されてることが確認できます。

GitHub Actionsからのデプロイも成功!

おわりに

workers-rsのhello-worldのテンプレートをデプロイしてみました。
Rustを使った開発でも、JavaScript/TypeScriptの時と変わらず手軽に開発とデプロイが行えていい感じです。これを使ってちょっとしたチャットボットを作ったりしながら、気軽にRustコードを書いていきたい。
今回は「シークレットを登録」で永続的な認証情報を登録して自動デプロイを行いましたが、一時的な認証情報を発行して行うデプロイも試したいです。

Discussion