🕌

RustとAWS Lambda・Amazon API GatewayでサーバレスWeb APIを作る方法

2021/03/31に公開

先日、RustでサーバレスWeb APIを作ったという記事を公開しました。

https://zenn.dev/uedayou/articles/06f744ac1c4737

同時期に公開されていたこちらの記事を見て、私の方法と似ていますが若干違うところもあるので、私が行った作り方も事例の一つとして紹介したいと思います。

GitHub にこれから紹介するコードのサンプルを公開しました。手順通りに作業すればAWS上にRustで動くサーバレスWeb APIがデプロイできます。RustとLambdaで何か作られるときのひな型として使ってください。

https://github.com/uedayou/rust-aws-serverless-api-sample

概要

ここで紹介するコードは以下を利用しています。

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 はこのようになっています。

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を有効しています。

src/main.rs
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を使って行います。

Dockerfile.build
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で行っています。

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用の設定ファイルは以下のようになっています。必要に応じて書き換えてください。

template.yaml
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事例として見れもらえればと思います。

https://github.com/uedayou/oxigraph-sparql-api-serverless

Discussion