RustとAWS Lambda・Amazon API GatewayでサーバレスWeb APIを作る方法
先日、RustでサーバレスWeb APIを作ったという記事を公開しました。
同時期に公開されていたこちらの記事を見て、私の方法と似ていますが若干違うところもあるので、私が行った作り方も事例の一つとして紹介したいと思います。
GitHub にこれから紹介するコードのサンプルを公開しました。手順通りに作業すればAWS上にRustで動くサーバレスWeb APIがデプロイできます。RustとLambdaで何か作られるときのひな型として使ってください。
概要
ここで紹介するコードは以下を利用しています。
- Docker (ビルド用)
- AWS CLI (デプロイ用)
- AWS SAM CLI (デプロイ用)
Docker コンテナで Rustプログラムをビルドして、AWS SAM CLI でAWS上にデプロイします。
コード
まず、githubリポジトリからサンプルコードをcloneしてください。
$ git clone https://github.com/uedayou/rust-aws-serverless-api-sample.git
$ cd rust-aws-serverless-api-sample
Cargo.toml
はこのようになっています。
[package]
name = "rust-aws-serverless-api-sample"
version = "0.1.0"
authors = ["uedayou <yooueda@gmail.com>"]
edition = "2018"
[[bin]]
name = "bootstrap"
path = "src/main.rs"
[dependencies]
lambda_http = "0.3.0"
tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] }
src/main.rs
は以下になります。サンプルはname=hoge
みたいなパラメータを送ると{"name":"hoge"}
のようなJSONを返すだけのプログラムです。一応、CORS
を有効しています。
use lambda_http::{
handler,
lambda_runtime::{self, Context},
IntoResponse, Request, RequestExt, Response,
};
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
#[tokio::main]
async fn main() -> Result<(), Error> {
lambda_runtime::run(handler(func)).await?;
Ok(())
}
async fn func(event: Request, _: Context) -> Result<impl IntoResponse, Error> {
Ok(match event.query_string_parameters().get("name") {
Some(name) => {
let json = format!(r#"{{"name": "{}"}}"#, name);
Response::builder()
.status(200)
.header("Content-Type", "application/json")
.header("Access-Control-Allow-Methods", "OPTIONS,POST,GET")
.header("Access-Control-Allow-Credential", "true")
.header("Access-Control-Allow-Origin", "*")
.body(json)
.expect("failed to render response")
},
_ => Response::builder()
.status(400)
.body("error".into())
.expect("failed to render response"),
})
}
このmain.rs
を元にやりたいことを追加していけば、開発も楽かなと思います。
ビルド
Rustコードのビルドは、Amazon Linux 2のイメージとDockerを使って行います。
FROM public.ecr.aws/lambda/provided:al2
RUN yum install -y gcc zip
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
ENV PATH $PATH:/root/.cargo/bin
RUN rustup install stable
WORKDIR /code
ADD build.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/build.sh
ENTRYPOINT ["/usr/local/bin/build.sh"]
実際のビルドはbuild.sh
で行っています。
#!/bin/bash
cd /code
cargo build --release
rm -f lambda.zip
cd target/release/
zip lambda.zip bootstrap
mv lambda.zip ../../
cd ../../
ビルドコマンドは以下の通りです。ビルドが成功したら、lambda.zip
というファイルが作成されると思います。
$ docker image build -t rust-lambda-build -f Dockerfile.build .
$ docker container run --rm -v $PWD:/code -v $HOME/.cargo/registry:/root/.cargo/registry -v $HOME/.cargo/git:/root/.cargo/git rust-lambda-build
Dockerでのビルドについてはlambda-rustというものが公開されていますが、これをビルドしたらエラーが出たので、上記の方法で行っています。
デプロイ
事前にAWS CLI
でアカウント設定してから、AWS SAM CLI
を使ってAWS Lambda
関数とAmazon API Gateway
のAPIをデプロイします。AWS SAM CLI用の設定ファイルは以下のようになっています。必要に応じて書き換えてください。
AWSTemplateFormatVersion: 2010-09-09
Description: Rust AWS Serverless API
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 30
Api:
OpenApiVersion: 3.0.2
Cors:
AllowMethods: "'OPTIONS,POST,GET'"
AllowHeaders: "'Origin, Authorization, Accept, Content-Type'"
AllowOrigin: "'*'"
Resources:
SampleServerFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-lambda-function
Description: Sample Server Lambda Function
CodeUri: lambda.zip
Runtime: provided.al2
Handler: bootstrap.is.real.handler
MemorySize: 1024
Environment:
Variables:
RUST_BACKTRACE: 1
Events:
api:
Type: Api
Properties:
Path: /sample
Method: any
Outputs:
SampleServerApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
SampleServerFunction:
Value: !GetAtt SampleServerFunction.Arn
SampleServerFunctionIamRole:
Value: !GetAtt SampleServerFunctionRole.Arn
まずパッケージ化コマンドを実行します。{S3バケット名}
を自身のアカウントのデプロイしたいリージョンと同一リージョンにあるバケット名を指定してください。
$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket {S3バケット名}
その後、デプロイコマンドを実行します。{スタック名}
にこのAPIの名前を指定します。
$ sam deploy --template-file packaged.yaml --stack-name {スタック名} --capabilities CAPABILITY_IAM
実行が完了したら、以下のような表示が出ると思います。
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key SampleServerFunctionIamRole
Description -
Value arn:aws:iam::{アカウントID}:role/{スタック名}-SampleServerFunctionRole-ABCDEFGHIJKL
Key SampleServerFunction
Description -
Value arn:aws:lambda:{リージョン}:{アカウントID}:function:{スタック名}-
lambda-function
Key SampleServerApi
Description -
Value https://abcdefghijkl.execute-api.{リージョン}.amazonaws.com/Prod/
-------------------------------------------------------------------------------------------------
https://abcdefghijkl.execute-api.{リージョン}.amazonaws.com/Prod/
がWeb API のルートURLです。template.yaml
で/sample
パスでアクセスできるように設定していますので、
https://abcdefghijkl.execute-api.{リージョン}.amazonaws.com/Prod/sample
でアクセスできます。ここにパラメータをつけて
https://abcdefghijkl.execute-api.{リージョン}.amazonaws.com/Prod/sample?name=hoge
としてアクセスすると
{"name":"hoge"}
という結果が返ってきます。
まとめ
Docker と AWS SAM CLI を使った Rust製サーバレスWeb API の作り方を紹介しました。RustをAWS Lambda上で使っている事例が他の言語よりは少なく、このようなサンプルもまだまだ数が多くはないと思いますので、何かの参考になればうれしいです。
このサンプルは、実際に運用中の以下のコードを編集して作ったものです。もし興味がある方おられたら、このサンプルをどう使っているかの1事例として見れもらえればと思います。
Discussion