[2022年版] RustをつかってAWS Lambdaを実装&AWS CDKでデプロイする

2022.06.14

Introduction

この記事ではCargo-lambdaというツールを使って、
Lambdaをcargoサブコマンドでビルド&デプロイしました。
今回は、rust-aws-cdk-lambdaというライブラリを使用して、
AWS CDK v2でAWS Lambda on Rustのデプロイをしてみます。

以前のRustをつかってAWS Lambdaを実装&AWS CDKでデプロイするという記事で
やった手順よりだいぶ楽にできました。

Environment

  • OS : MacOS 12.4

AWSアカウントはセットアップ済みと仮定します。

Try it now!

とにかくすぐAWS Lanmd on Rust(複数モジュール) + CDKを動かしたい人のための手順。
rust-aws-cdk-lambdaのサンプルをCDKでデプロイします。

セットアップ

Rustをインストールします。

#cargo-lambdaに必要
%brew install zig

#Rustのインストール
% curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

・・・

RustとCargoのコマンドが使えればOK。

% rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.61.0 (fe5b13d68 2022-05-18)`

% cargo --version
cargo 1.61.0 (a028ae42f 2022-04-29)

aarch64-unknown-linux-gnuツールチェインのインストール。

% rustup target add aarch64-unknown-linux-gnu

Nodeのインストール。 ここの方法でも
HomebrewでもいいのでNodeのv16以降をインストールします。

% node -v
v18.2.0

ここで解説している、
cargo-lambdaもインストール。

% brew tap cargo-lambda/cargo-lambda
% brew install cargo-lambda

cdk-examplesのサンプルをデプロイ

ここにあるcdk-examples/rust-binsをCDKでデプロイしてみましょう。
このプロジェクトは、2つのLambda(my_lambda1,my_lambda2)モジュールと
共通モジュール(shared)から構成されています。
これらのRustモジュールをCDKアプリでビルド&デプロイします。

なお、my_lambda1のLambdaはS3へのアクセスを行っています。
my_lambda2は、reqwestクレートを使って、
外部へのアクセスをしています。

git cloneしてnpm install。

% git clone https://github.com/rnag/rust.aws-cdk-lambda.git
% cd rust.aws-cdk-lambda/cdk-examples/rust-bins
% npm install

・・・

bootstrapします。

# us-east-1を指定
% cdk bootstrap aws://$(aws sts get-caller-identity | jq -r .Account)/us-east-1

🍺  Building Rust code...
🎯  Cross-compile `my_lambda1`: 81.39s
🎯  Cross-compile `my_lambda2`: 0.00s
 ⏳  Bootstrapping environment aws://xxxxxxxxxx/us-east-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
 ✅  Environment aws://xxxxxxxxxx/us-east-1 bootstrapped (no changes).

そしてus-east-1へdeployします。

% export AWS_REGION=us-east-1
% cdk deploy
🍺  Building Rust code...
🎯  Cross-compile `my_lambda1`: 10.20s
🎯  Cross-compile `my_lambda2`: 0.00s

✨  Synthesis time: 13.21s

IAM Statement Changes
┌───┬──────────────────────────────┬────────┬──────────────────────────────┬───────────────────────────────┬───────────┐
│   │ Resource                     │ Effect │ Action                       │ Principal                     │ Condition │
├───┼──────────────────────────────┼────────┼──────────────────────────────┼───────────────────────────────┼───────────┤
│ + │ ${MyRustCdkBucket.Arn}       │ Allow  │ s3:Abort*                    │ AWS:${MyRustLambda1/ServiceRo │           │
│   │ ${MyRustCdkBucket.Arn}/*     │        │ s3:DeleteObject*             │ le}                           │           │
│   │                              │        │ s3:GetBucket*                │                               │           │
│   │                              │        │ s3:GetObject*                │                               │           │
│   │                              │        │ s3:List*                     │                               │           │
│   │                              │        │ s3:PutObject                 │                               │           │
│   │                              │        │ s3:PutObjectLegalHold        │                               │           │
│   │                              │        │ s3:PutObjectRetention        │                               │           │
│   │                              │        │ s3:PutObjectTagging          │                               │           │
│   │                              │        │ s3:PutObjectVersionTagging   │                               │           │
├───┼──────────────────────────────┼────────┼──────────────────────────────┼───────────────────────────────┼───────────┤
│ + │ ${MyRustLambda1/ServiceRole. │ Allow  │ sts:AssumeRole               │ Service:lambda.amazonaws.com  │           │
│   │ Arn}                         │        │                              │                               │           │
├───┼──────────────────────────────┼────────┼──────────────────────────────┼───────────────────────────────┼───────────┤
│ + │ ${MyRustLambda2/ServiceRole. │ Allow  │ sts:AssumeRole               │ Service:lambda.amazonaws.com  │           │
│   │ Arn}                         │        │                              │                               │           │
└───┴──────────────────────────────┴────────┴──────────────────────────────┴───────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬──────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                     │ Managed Policy ARN                                                             │
├───┼──────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${MyRustLambda1/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
├───┼──────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${MyRustLambda2/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴──────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
MyRustCdkStack: deploying...
MyRustCdkStack: creating CloudFormation changeset...

 ✅  MyRustCdkStack

✨  Deployment time: 87.18s

Stack ARN:
arn:aws:cloudformation:us-east-1:xxxxxxxxxxxxxxxx:stack/MyRustCdkStack/xxxxxxxxxxxxxxxx

✨  Total time: 100.39s

デプロイ完了しました。
AWSコンソールでCloudformationをみると、
bin/rust-bins.tsで指定している名前のスタックができています。
また、Lambdaページにはmy_lambda1とmy_lambda2(名前は違うけど)が定義されています。

AWSコンソールやAWS CLIでlambdaを実行できます。

% aws lambda invoke --function-name <Lambda名>  --payload '{"command":"hello"}' --cli-binary-format raw-in-base64-out --region us-east-1 out.json

{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

# out.json
{"req_id":"xxxxxxxxxxxxxxxx","msg":"Hello from Lambda 1! The command hello executed."}

AWS Lambda on RustをCDKでデプロイ〜動作確認までできました。
確認がおわって必要なくなったら、
スタックの削除とS3バケットの削除を忘れずにしておきましょう。

workspace example

とりあえず動かすことはできたので、
各種ファイルをみてみます。
例としてこのリポジトリをもとに解説します。
このリポジトリはcdk-examplesのrust-workspacesをもとに簡略化したものです。
このプロジェクトはlambda_workspaces下に共通ライブラリ1つと
Lambdaプロジェクト2つで出来ており、
それぞれがCargo.tomlを持つ構成になってます。

まずはcdk.json(cdk initで生成されるコンテキストファイル)をみてみます。
ここでは各種設定やinclude/excludeファイルの指定などをしています。

{
  "app": "npx ts-node --prefer-ts-exts bin/rust-lambda-template.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:stackRelativeExports": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-lambda:recognizeVersionProps": true,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
    "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
    "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
    "@aws-cdk/core:target-partitions": [
      "aws",
      "aws-cn"
    ]
  }
}

実際デプロイ時に実行されるコマンドはappの部分にあります。
ts-nodeをつかってbin/rust-lambda-template.tsを実行してますね。

rust-lambda-template.tsでは
rust-lambda-template-stack.tsをimportしてスタックのインスタンス化をしています。

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { RustLambdaTemplateStack } from '../lib/rust-lambda-template-stack';

const app = new cdk.App();
new RustLambdaTemplateStack(app, 'RustLambdaTemplateStack');

lib/rust-lambda-template-stack.tsで
各AWSのサービスを設定。
Lambdaワークスペースの設定や、
rust.aws-cdk-lambdaライブラリのRustFunctionを使って、
各Lambdaの設定をしています。

・・・

export class RustLambdaTemplateStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {

                ・・・・・・・・・・

        let myLambda1 = new RustFunction(this, 'MyRustLambda1', {
            package: 'my_lambda1',
            setupLogging: true,
            environment: {
                ENV_STR: "bar",
            },
        });

        let _myLambda2 = new RustFunction(this, 'MyRustLambda2', {
            package: 'my_lambda2',
            setupLogging: true,
        });
    }
}

このサンプルも、先程と同じく、npm install後にbootstrapしてcdk deploy
すればデプロイできます。  

Lambda関数を追加する

このプロジェクト(rust-workspaces)に対してLambda関数を追加したい場合、
cargolambda newで新しいパッケージを作ります。

% cd /path/your/rust-workspaces/lambda_workspaces
% cargo lambda new add_mylambda3

・・・

lambda_workspaces/Cargo.tomlのmembersに
追加したいLambdaプロジェクトを記述。

[workspace]
members = [
    "my_lambda1",
    "my_lambda2",
    "add_mylambda3", #←追記
    "common_libs",
]

lib/rust-lambda-template-stack.ts
でCDK定義の追加。

export class RustLambdaTemplateStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {

        ////// 省略 ////////

        let _myLambda3 = new RustFunction(this, 'MyRustLambda3', {
            package: 'add_mylambda3',
            setupLogging: true,
        });

    }
}

あとはそのままcdkデプロイコマンドでデプロイできます。

% cdk deploy

・・・

Summary

今回はRustで実装したAWS LambdaをCDKでデプロイしてみました。
rust.aws-cdk-lambdaにはそのまま動作するサンプルがあるので、
それをもとに必要な部分を書き換えて設定するのが手っ取り早いと思われます。