- 1. 背景
- 2. 前提
- 3. 作業ディレクトリの作成
- 4. CDKプロジェクト作成
- 5. Stackを書いていく
- 6. Lambdaを書く
- 7. デプロイ準備
- 8. デプロイする
- 9. 動作確認
- 10. 最後に
1. 背景
AWSコンソール上でポチポチするのは面倒なので、CDKを使ってAWSリソースの作成します。 Rustについては速度に関してLambdaと相性が良いことを期待しているのと、開発体験がどう変わるのか、という実験的部分もありつつ採用しました。 ※Lambdaの中身については、最低限の動作確認が出来るレベルのものなので、この点は悪しからず。
CDKは内部的にCloudFormationが展開されるわけですが、言語の選択権もあり、書き心地や見通しも非常に良いです。
AWSリソースをプロビジョニングするならCDK一択で問題ない、という点から選択しています。
また、所々でURLを掲載していますが完成品は以下となります。
2. 前提
インストールがされている前提で進めますが、インストール時に参考としたページは次項へ記載。 使用する各種ツール群のVersionは以下のとおり。
# MacOS Monterey 12.5 Intel chip # 各種ツール群 aws --version && \ cdk --version && \ rustc --version && \ cargo lambda -V; aws-cli/2.4.11 Python/3.8.8 Darwin/21.6.0 exe/x86_64 prompt/off 2.49.0 (build 793dd76) rustc 1.63.0 (4b91a6ea7 2022-08-08) cargo-lambda 0.11.2 (f9cead5 2022-10-08Z)
2.1. インストール時の参考
3. 作業ディレクトリの作成
ディレクトリを作成します。 この中にCDKプロジェクトとLambdaのプロジェクトを作成します。
mkdir rust_backend_cdk cd rust_backend_cdk
4. CDKプロジェクト作成
init
コマンドで作成します。今回はTypeScriptを使用します。
cdk init --language typescript
5. Stackを書いていく
yaml
やjson
で書くのに比べるとかなり楽です。
公式のAPI ReferenceはSampleが豊富なので、こちらを見ながら進めました。
APIGatewayからLambdaを呼び出す前提ですが、パプリックな状態なのでAPIキーを必須にしています。
// https://github.com/Tanabebe/rust_backend_cdk/blob/main/lib/rust_backend_cdk-stack.ts import * as cdk from 'aws-cdk-lib'; import {Construct} from 'constructs'; import {ApiKeySourceType, LambdaIntegration, RestApi} from "aws-cdk-lib/aws-apigateway"; import {Architecture, Code, Function as LambdaFunction, Runtime} from "aws-cdk-lib/aws-lambda"; import {join} from "path"; import {Duration} from "aws-cdk-lib"; export class RustBackendCdkStack extends cdk.Stack { // API Gatewayの作成 private api = new RestApi(this, 'ExampleRustApi', { apiKeySourceType: ApiKeySourceType.HEADER }); constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // APIキーの作成 const apiKey = this.api.addApiKey('apiKey', { apiKeyName: 'ExampleRustApiKey' }); // 使用量プランの作成 const usagePlan = this.api.addUsagePlan('ExampleRustApiUsagePlan'); usagePlan.addApiKey(apiKey); usagePlan.addApiStage({ stage: this.api.deploymentStage }) // Lambdaの設定 const exampleRustLambda = new LambdaFunction(this, 'ExampleRustLambda', { runtime: Runtime.PROVIDED_AL2, code: Code.fromAsset(join(__dirname, '..', 'functions', 'target', 'lambda', 'functions', 'bootstrap.zip')), architecture: Architecture.ARM_64, memorySize: 128, handler: 'bootstrap', timeout: Duration.seconds(20), }); // LambdaをAPIGatewayに統合する const exampleLambdaIntegration = new LambdaIntegration(exampleRustLambda); // API Gatewayのリソース定義 const exampleLambdaResource = this.api.root.addResource('example'); exampleLambdaResource.addMethod('POST', exampleLambdaIntegration, { apiKeyRequired: true }); } }
5.1. Lambdaを作成する
CDKのプロジェクトと同ディレクトリにLambdaのプロジェクトを作成します。
cargo lambda new functions # 対話時の回答 ? Is this function an HTTP function? Yes ? Which service is this function receiving events from? Amazon Api Gateway REST Api
Cargo.toml
に追記する。
※crateのダウンロードに時間がかかるので待ちます。
# https://github.com/Tanabebe/rust_backend_cdk/blob/main/functions/Cargo.toml serde = "1.0.148" serde_json = "1.0.89" openssl = { version = '0.10.42', features = ["vendored"] } openssl-sys = "0.9.76" lambda-apigateway-response = "0.1.1"
6. Lambdaを書く
awslabsのサンプルとほぼ同じですが、参考にしながら最低限の動作確認が出来るレベルで書いていきます。 これくらいの内容なら問題ないですが、もう少し混み合ってくると大体1発でコンパイルが通らないので、よく怒られます。
// https://github.com/Tanabebe/rust_backend_cdk/blob/main/functions/src/main.rs use lambda_runtime::{service_fn, LambdaEvent, Error}; use serde_json::{json, Value}; use lambda_apigateway_response::http::StatusCode; use lambda_apigateway_response::{Headers, MultiValueHeaders, Response}; type LambdaResult<T> = Result<T, Error>; #[tokio::main] async fn main() -> Result<(), Error> { let func = service_fn(func); lambda_runtime::run(func).await?; Ok(()) } async fn func(event: LambdaEvent<Value>) -> LambdaResult<Response<Value>> { let (event, _context) = event.into_parts(); let first_name = event["body"]["firstName"].as_str().unwrap_or("world"); let res = Response { status_code: StatusCode::OK, body: json!({ "message": format!("Hello, {}!", first_name) }), headers: Headers::new(), multi_value_headers: MultiValueHeaders::new(), is_base64_encoded: false, }; Ok(res) }
7. デプロイ準備
awsのアカウント設定をします。
# aws設定 aws configure AWS Access Key ID [****************]: your access key AWS Secret Access Key [****************]: your secret access key Default region name [ap-northeast-1]: ap-northeast-1 Default output format [json]:json # cdkをデプロイする時に必要なので実行(1回のみ) cdk bootstrap
毎度複数のコマンドを叩くのは面倒なので簡易的ではありますが、デプロイ用のMakefileを作成します。
# https://github.com/Tanabebe/rust_backend_cdk/blob/main/Makefile RUST_DIR = functions BUILD_ARTIFACT_DIR = target/lambda/functions .PHONY: build build: cd $(RUST_DIR) && \ cargo lambda build --release --arm64 && \ zip -j $(BUILD_ARTIFACT_DIR)/bootstrap.zip $(BUILD_ARTIFACT_DIR)/bootstrap && \ cd ../ ; .PHONY: run-dev run-dev: cd $(RUST_DIR) && \ cargo lambda watch .PHONY: deploy deploy: make build cdk deploy
8. デプロイする
make deloy
を実行します。
Do you wish to deploy these changes (y/n)?
にはy
でOKです。
CloudFormationのStackが展開されるので、完了するまで待ちます。
待機中にAWSコンソール上でCloudFormationを眺めてみるのも良いでしょう。
9. 動作確認
デプロイが完了すると、API Gatewayエンドポイントがコンソールに表示されるのでcURLで確認します。
※APIキーはAPI Gatewayに移動し、メニューのAPIキーから確認できます
※x-api-key
とurl
は適宜読み替えてください。
curl -X POST -H "Content-Type: application/json" \ -d '{"firstName": "tanabebe"}' \ --header 'x-api-key:your api key' \ https://your.lambda.endpoint.url/prod/example
削除したい場合はcdk destroy
もしくは、CloudFormationのStack削除をしましょう。
10. 最後に
最初はSAMで同じ事を行いましたが、拡張していった場合にjson
やyaml
だとツラいよね、という事もあり、CDKで作り直しました。
今回の例はサンプル的な内容ではありますが、慣れればプロジェクト作成から動作確認まで30分もかからず行えます。
Rustを利用するためにzip
で固める必要があったりerror: failed to run custom build command
のエラーが出たり、Lambdaでデプロイ出来たけど動かないとかそこそこ苦労しましたので、ハマりどころはまた別の機会に。
準備は面倒でしたが、一度作成してしまえば作成、破棄が簡単なので楽ですし、CDKならコードをクリーンにしていけば拡張するときもそこまで苦しくないですね。
プロジェクト初期段階で、ある程度コード化しておくことで、実装するプログラムにも集中出来ます。
とはいえ、この後にRustでひいひい言ってたわけですが。可能なら是非取り組んでみてはいかがでしょうか。