RustでのAWS Lambda開発に必要なツールを揃えてみた

RustでAWS Lambda関数を開発する時に必要なツールを自分なりにまとめてみました。複数のツールを組み合わせることでストレスなく開発できる環境がセットアップできます。
2021.03.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

最近RustでちょっとしたAWS Lambdaの関数を書く機会があって開発に必要なものを色々試したので紹介します。

RustでのLambda 関数の開発に必要そうなもの

今回は以下のツールや設定を準備しました。この辺りはだいたいどんなケースでも必要になるのではないかと思います。

これらの設定を順番にみていきます。また今回の設定はGitHubで公開しています

カスタムランタイム

素直にaws-lambda-rust-runtimeを使っています。開発が活発になってきて嬉しいです。

クロスコンパイル環境

私はmacOS上で開発しているのでAWS上にデプロイするにはクロスコンパイルを行う必要があります。muslのビルド環境を整えるのに挫折したのでsoftprops/lambda-rust を使わせてもらいます。開発中は下記のようなcargo makeのタスクを使ってビルドしています。

ポイントは

  • READMEにある通りcargoのキャッシュを設定する
  • 開発中のビルドタスクではdevプロファイルを指定する

Makefile.toml

[env]
LAMBDA_RUST_TAG = "0.3.0-rust-1.45.0"

[tasks.build-debug]
description = "Build for debug"
script = ['''
  docker run --rm \
    -v ${PWD}:/code \
    -v ${PWD}/.cache/cargo/registry:/root/.cargo/registry \
    -v ${PWD}/.cache/cargo/git:/root/.cargo/git \
    -e PROFILE=dev \
    softprops/lambda-rust:${LAMBDA_RUST_TAG}
'''
]

環境変数の設定 direnv

各自の開発環境の設定はdirenvで行います。AWS上にデプロイした時に環境ごとの設定を環境変数で行うことも多いと思います。direnvはenvrcではなくdotenv形式で設定するようにします。dotenv形式で設定を記述することでdockerの実行時にも設定を流用できます。

今回は以下のように設定しました。

envrc

dotenv

.env

# localstackのホスト側ポート番号
# 各自の環境で使いたいポートが違うので環境変数で設定できるようにする(後述)
LOCAL_STACK_PORT=4567
# ダミーのAWSクレデンシャル
# localstackを使う場合でも何かしらクレデンシャルは必要なので
# AWSデプロイ時には関数に設定したロールが使われる
AWS_ACCESS_KEY_ID=XXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYY

# 実行時にrusotoの設定をオーバーライドするための設定(後述)
override_endpoint=http://localhost:${LOCAL_STACK_PORT}

AWS APIクライアント

AWSのクライアントにはrusotoを使います。以下のような設定を作って開発環境ではlocalstackを向くようにします。設定自体はconfig crate で環境変数からロードします。

#[derive(Deserialize, Debug)]
pub struct AwsConfig {
    override_endpoint: Option<String>,
}

impl AwsConfig {
    pub fn to_region(&self) -> Region {
        self.override_endpoint.clone().map_or_else(
            || Default::default(),
            |e| Region::Custom {
                name: "us-east-1".to_string(),
                endpoint: e.to_string(),
            },
        )
    }
}

AWSリソースのスタブ localstack

AWSリソースのスタブには使い慣れているlocalstackを使いました。docker-compose.ymlはこんな感じです。ポイントは下記の通りです。

  • ホスト側のポートを各自好きに設定できるように環境変数を使う
  • リソースの作成を行うスクリプトを/docker-entry-initaws.d にマッピングする

docker-compose.yml

version: "3.9"
services:
  localstack:
    image: localstack/localstack:0.12.8
    ports:
      - "${LOCAL_STACK_PORT}:4566"
    environment:
     - SERVICES=dynamodb
    volumes:
      - ./localstack/init_scripts:/docker-entrypoint-initaws.d

ローカル実行 lambci/lambda

こちらもMakefile.tomlに実行用のタスクを定義しておきます。先ほどのビルドタスクを依存関係に入れてその成果物を使います。

ポイントは下記の通りです。

  • テスト用のイベントを標準入力から渡す(JSONファイルやスクリプトで自由に生成できる)
  • 環境変数を.envファイルから参照する
  • —network オプションでコンテナはlocalstackと同じネットワークで起動する
    • ※docker-composeのデフォルトのネットワーク名は<プロジェクト名>-default
  • localstackと同じネットワークなので環境変数override_endpointにはサービス名を使うようにする
[tasks.run-local]
description = "run lambda function locally"
script = [
  '''
 unzip -o \
    target/lambda/debug/${CARGO_MAKE_CRATE_NAME}.zip \
    -d /tmp/lambda
  ''',
  '''
  cat event.json | \
  docker run \
    -i \
    --env-file=.env \
    -e DOCKER_LAMBDA_USE_STDIN=1 \
    -e override_endpoint=http://localstack:${LOCAL_STACK_PORT} \
    --rm \
    -v /tmp/lambda:/var/task \
    --network ${CARGO_MAKE_CRATE_NAME}_default \
    lambci/lambda:provided 
  '''
]
dependencies = ['build-debug']

デプロイ

aws-lambda-rust-runtimeのREADMEにawscliでのデプロイ例があります。

serverless-rustプラグイン

ここまで触れてこなかったですが、AWS環境に作成するリソースや環境ごとの環境変数の設定を管理するにはserverless frameworkのserverless-rustプラグインを使った方が楽だと思います。

その場合はプラグインがデフォルトで使うビルド用イメージのRustツールチェインが古いのでタグを明示すると新しめのイメージを使えます。

custom:
  # ツールチェインのバージョンを指定するためにタグを上書き
  rust:
    dockerTag: '0.3.0-rust-1.45.0'
    dockerImage: 'softprops/lambda-rust'

まとめ

RustでAWS Lambda関数を開発する時に必要なツールを自分なりにまとめてみました。複数のツールを組み合わせることでストレスなく開発できる環境がセットアップできたと思います。コンテナ上でのビルドが少し遅いのでその点も改善できたらいいなと思います。