ãããã£ã®N1! Machine Learning Product Engineer äžæã§ãã æè¿ã¯RustãæžããŠããŠãTerraformãšRustã®çµã¿åããã§ã®ç¥èŠããããäžã«ãªããªãšæã£ãã®ã§æžãæ®ããŸãã Terraformã§Lambdaã®ãããã€ãå®äºããã æ¥åå©çšãããã©ã€ããŒãéçºã«è³ããŸã§ãèªåèªèº«ã¯Lambdaã®ãããã€ã¯å€ããTerraformã§å®çµãããŠããŸãã Terraformã«ã¢ããªã±ãŒã·ã§ã³ã³ãŒããå«ããããªããšãã声ãããã®ã¯ç¥ã£ãŠããã®ã§ãããLambdaã¯ã¡ãã£ãšããã³ãŒããæžãããšãå€ããç¹ã«ãã©ã€ããŒãã®éçºãªã©ã«ãããŠã¯Lambda1åããããã€ããããã«CI/CDãæŽåãããããã®ã¯å€§è¢è£ããããªãšèããŠããŸããããã§ãèªåèªèº«ã¯Lambdaã®ã¢ããªã±ãŒã·ã§ã³ã³ãŒããã€ã³ãã©ã®äžéšã ãšèããŠãterraform applyã®ã¿ã§ãããã€ãŸã§å®çµãããããã«ããŠããŸãã ä»åã¯ãã®æé ãèšããŸãã ããããã®æé ã¯ä»¥äžã®ã¬ããžããªã«ã³ãŒããæ®ããŠããŸãã®ã§ããã¡ããåç
§ããŠãã ããã inakam/cargo-lambda-terraform-sample Contribute to inakam/cargo-lambda-terraform-sample development by creating an account on GitHub. github.com Cargo Lambdaã§Rustã³ãŒããæºåãã Rustã®ã³ãŒã管çã«ã¯Cargo LambdaãçšããŸãã cargo-lambda/cargo-lambda: Cargo Lambda is a Cargo subcommand to help you work with AWS Lambda. Cargo Lambda is a Cargo subcommand to help you work with AWS Lambda. – cargo-lambda/cargo-lambda github.com Lambdaã«Rustã³ãŒãããããã€ããããã«ã¯ãã¯ãã¹ã³ã³ãã€ã«ãªã©ã§é©åãªèšå®ãããå¿
èŠãããã®ã§ãããcargo-lambdaã¯ãããã®èšå®ããããããããŠãããŸãã ä»åã¯Terraformã§ãããã€ããã®ã§ãå®ã¯cargo-lambdaã䜿ãå¿
èŠæ§ã¯èãïŒèªåã§Rustã®ã³ã³ãã€ã«èšå®ãªã©ã調æŽããã°ããã§ããããã€å¯èœïŒã®ã§ããããããžã§ã¯ãã®ã»ããã£ã³ã°ãªã©ã§ãæéãå°ãªããªãã®ã§ãcargo-lambdaã䜿çšããŸãã cargo lambdaã®ã€ã³ã¹ããŒã« Macã®å Žåã¯ä»¥äžã§cargo-lambdaãã€ã³ã¹ããŒã«ããŸãã brew tap cargo-lambda/cargo-lambda brew install cargo-lambda ãã®ã»ãã®ã·ã¹ãã ã«ãããŠã¯ã以äžãåèã«ã€ã³ã¹ããŒã«ããŠãã ãã https://www.cargo-lambda.info/guide/installation.html cargo lambda new cargo-lambdaã䜿ã£ãŠLambda颿°ã®ãã³ãã¬ãŒããäœæããŸãã cargo lambda new --http rust-lambda ä»åã¯AWS Lambda Function URLsã䜿ãããã®ã§ãHTTP functionãçµ±åããããã«ããŠãã³ãã¬ãŒããäœæããŸããïŒlambda_httpã䜿ããç¶æ
ã§ãã³ãã¬ãŒããäœæãããŸãïŒ ãã«ãããŠã¿ã äœæããããã©ã«ãã«ç§»åããŠããããžã§ã¯ãããã«ãããŠã¿ãŸãããã cd rust-lambda cargo lambda build --release --arm64 ã³ãŒãã®ã³ã³ãã€ã«ãç¡äºã«å®äºããã°ãã³ãŒãã®æºåã¯å®äºã§ãã Finished release [optimized] target(s) in 30.26s Terraformã§Lambdaããããã€ããæºåããã æ¬¡ã«TerraformãæŽåããŠãããŸããä»åæ³å®ããŠãããã©ã«ãæ§æã¯ä»¥äžã®ããã«ãªããŸãã . âââ rust-lambda â  âââ Cargo.lock â  âââ Cargo.toml â  âââ src â  â  âââ main.rs â  âââ target âââ terraform âââ lambda.tf âââ lambda_archive âââ modules â  âââ lambda_rust_module â  âââ main.tf â  âââ outputs.tf â  âââ variables.tf âââ outputs.tf âââ variables.tf å
ã»ã©cargo-lambdaã§äœæããrust-lambdaãã©ã«ããšåãéå±€ã«terraformãã©ã«ããäœæãããããã terraform apply ãè¡ãããšã§ãããã€ã§ããããã«ããŸãã Terraformã®èšå® ããã§ã¯terraformãã©ã«ãå
ã«ãã¡ã€ã«ãäœæããã€ã³ãã©ã®èšå®ãè¡ãªã£ãŠãããŸãã 以äžã®ããã«Terraformãèšå®ããŸããS3 Remote Stateãªã©ãå¿
èŠã§ããã°é©å®èšå®ã远å ããŸããAWSã®ãããã¡ã€ã«ã¯ããŒã«ã«ãã·ã³ãªã©ã«èšå®ãããŠãããããã¡ã€ã«åãæå®ããŠãã ããã terraform/variables.tf provider "aws" { region = "ap-northeast-1" shared_credentials_files = ["~/.aws/credentials"] profile = "[AWSã®ãããã¡ã€ã«å]" } terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } variable "name" { default = "rust-lambda" } Lambdaã®ã¢ãžã¥ãŒã«ãäœæãã Rustã§Lambdaã«ãããã€ããããã®ã¢ãžã¥ãŒã«ãäœæããŸãããã®ããã«ã¢ãžã¥ãŒã«ãäœæããŠããããšã§ãLambdaããšåé¢ãããã»Lambdaãå¥ã®çšéã§å¢ãããããšããå Žåã«æè»œã«å¢ããããšãã§ããŠäŸ¿å©ã§ãã modulesãšãããã©ã«ãã®äžã«lambda_rust_moduleãã©ã«ããäœæãã以äžã®èšè¿°ãããŸãã terraform/modules/lambda_rust_module/variables.tf variable "function_name" { description = "The name of the Lambda function." type = string } variable "role" { description = "The ARN of the IAM role to be used by Lambda function." type = string } variable "environment_variables" { description = "A map of environment variables to pass to the Lambda function." type = map(string) default = {} } variable "rust_src_path" { description = "The path to the Lambda function's Rust project." type = string } variable "cargo_lambda_env_name" { description = "name in cargo lambda new [name]" type = string } variable "lambda_zip_local_path" { description = "The path where the Lambda function's zip archive will be saved." type = string } terraform/modules/lambda_rust_module/main.tf output "lambda_function_url" { value = aws_lambda_function_url.this.function_url } terraform/modules/lambda_rust_module/variables.tf # Lambdaã®å®çŸ© resource "aws_lambda_function" "this" { function_name = var.function_name filename = data.archive_file.this.output_path source_code_hash = data.archive_file.this.output_base64sha256 role = var.role architectures = ["arm64"] handler = "bootstrap" runtime = "provided.al2" timeout = 30 environment { variables = var.environment_variables } } resource "aws_lambda_function_url" "this" { function_name = aws_lambda_function.this.function_name authorization_type = "NONE" } # ããŒã«ã«ç°å¢ã§cargo-lambdaã§ãã«ãããããã®èšå® # ãã¡ã€ã«ã®sha256ããã·ã¥ãå
šãŠé£çµãããé£çµããæååããsha512ããã·ã¥ãèšç®ããå·®åãããã°ãã«ããã resource "null_resource" "rust_build" { triggers = { code_diff = sha512(join("", [ for file in fileset(var.rust_src_path, "**/*.rs") : filesha256("${var.rust_src_path}/${file}") ])) } provisioner "local-exec" { working_dir = var.rust_src_path command = "cargo lambda build --release --arm64" } } # ãã€ããªãzipå data "archive_file" "this" { type = "zip" source_file = "${var.rust_src_path}/target/lambda/${var.cargo_lambda_env_name}/bootstrap" output_path = var.lambda_zip_local_path depends_on = [ null_resource.rust_build ] } ãã®Terraformã³ãŒãã®äžã§ã¯ãããŒã«ã«ç°å¢ã§ãã«ããè¡ãããã€ããªãzipåããFunction URLsä»ãã®Lambda颿°ãšããŠãããã€ãããšããæ§æãå«ãŸããŠããŸãã ãã¡ã€ã«ã«ã€ããŠããã·ã¥ãèšç®ããããšã§ãå·®åããã£ãæã®ã¿ãããã€ãããšããããšãå¯èœã«ããŠããŸãã Lambdaãèšå®ãã ã¢ãžã¥ãŒã«ã宿ãããLambdaãäœæããŸãã terraform/lambda.tf module "api_lambda" { source = "./modules/lambda_rust_module" function_name = "${var.name}-api" role = aws_iam_role.lambda_iam_role.arn rust_src_path = "../rust-lambda" cargo_lambda_env_name = "rust-lambda" lambda_zip_local_path = "../lambda_archive/api.zip" environment_variables = { RUST_BACKTRACE = 1 } } Lambdaã«ä»äžããIAMæš©éãåæã«äœæããŸããLambdaãã䜿ããµãŒãã¹ãå¢ããŠããããããã®æš©éã远å ãããšããã§ãããã terraform/iam.tf # lambdaçšRoleã®èšå® resource "aws_iam_role" "lambda_iam_role" { name = "${var.name}-iam-role" assume_role_policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } POLICY } # lambdaçšPolicyã®äœæ resource "aws_iam_role_policy" "lambda_access_policy" { name = "${var.name}-access-policy" role = aws_iam_role.lambda_iam_role.id policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" } ] } POLICY } å®éã«ãããã€ãè¡ã terraformãã©ã«ãå
ããå®éã«ãããã€ããŠã¿ãŸãããã $ terraform init $ terraform apply æåŸã«åºãŠããURLã«ã¢ã¯ã»ã¹ããŠãHello worldã衚瀺ããããšå®æã§ãã Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: api_lambda_function_url = "<https://4wrzwdaows4h3z7vcdc2xlodou0wogpb.lambda-url.ap-northeast-1.on.aws/>" WebAPIãäœæããããã®Lambdaãã³ãã¬ãŒã Rustã䜿çšããLambdaã®å Žåã«æ¯èŒçéèŠãããã®ã¯WebAPIã®äœæã ãšæããŸããããããLambdaãå©çšããå Žåã«ã¯actix-web, axum, Rocketãªã©ã®ãã¬ãŒã ã¯ãŒã¯ããã®ãŸãŸé©çšããããšãã§ããŸããããŸããlambda_httpã䜿ã£ããµã³ãã«ãWebäžã«å°ãªããæ§ç¯ã«èŠåŽããã±ãŒã¹ãå€ãã§ããïŒèŠåŽããŸããïŒ ããã§ä»¥äžã®ããã«main.rsãèšè¿°ããããšã§ãLambdaäžã§æ¯èŒçæ±ããããWebAPIãæ§ç¯ã§ãããšããäŸãèŒããŠãããŸãã rust-lambda/main.rs use std::future::Future; use std::pin::Pin; use lambda_http::{Body, lambda_runtime::Error, Request, RequestExt, Response, run, service_fn}; use serde_json::{json, Value}; type LambdaResult = Result<Response<String>, Error>; fn build_response(status_code: u16, message: Value) -> LambdaResult { let response = Response::builder() .status(status_code) .header("Content-Type", "application/json") .body(message.to_string()) .map_err(Box::new)?; Ok(response) } async fn echo_query(event: Request) -> LambdaResult { let params = event.query_string_parameters(); let name = params.first("name").unwrap_or("world"); let message = json!({ "message": format!("Hello, {}!", name) }); build_response(200, message) } async fn echo_body(event: Request) -> LambdaResult { let body = event.body(); let body_str = match body { Body::Text(text) => text, _ => return build_response(400, json!({ "error": "Invalid request body" })), }; let data: Value = serde_json::from_str(body_str)?; let response = json!({ "message": "Received POST request", "data": data }); build_response(200, response) } async fn not_found() -> LambdaResult { build_response(404, json!({ "error": "Not Found" })) } async fn hello_world() -> LambdaResult { build_response(200, json!({ "message": "Hello, world!" })) } fn route_request(event: Request) -> Pin<Box<dyn Future<Output = LambdaResult> + Send>> { Box::pin(async move { match (event.method().as_str(), event.uri().path()) { ("GET", "/hello") => hello_world().await, ("GET", "/echo") => echo_query(event).await, ("POST", "/echo") => echo_body(event).await, _ => not_found().await, } }) } #[tokio::main] async fn main() -> Result<(), Error> { run(service_fn(route_request)).await } rust-lambda/Cargo.toml [package] name = "rust-lambda" version = "0.1.0" edition = "2021" [dependencies] lambda_http = "0.11.1" tokio = { version = "1", features = ["macros"] } serde_json = "1.0.115" serde = { version = "1.0.197", features = ["derive"] } ãŸããaxumã¯lambda_httpãšçµ±åããŠå©çšã§ãããããªã®ã§ããã¡ããå©çšããŠããããããããŸãããïŒãã®èšäºã®ã¬ãã¥ãŒäžã«ç€Ÿå
ã§æãããŸããâŠïŒïŒ aws-lambda-rust-runtime/examples/http-axum/src/main.rs at main · awslabs/aws-lambda-rust-runtime A Rust runtime for AWS Lambda. Contribute to awslabs/aws-lambda-rust-runtime development by creating an account on GitHub. github.com ãŸãšã ä»åã¯cargo-lambdaãšTerraformãçšããŠRustã§Lambdaã®ç°å¢ãæè»œã«æ§ç¯ããæ¹æ³ã«ã€ããŠè§£èª¬ããŸããã å匷ããŠãRustã«è§ŠããŠã¿ãã®ã§ãããAWSãµãŒãã¹ãšã®çµã¿åããã«ãããŠãRustã®é床ã¯éåžžã«é«éã§ãåŠç¿ã³ã¹ãã¯é«ããšãããã¡ãªããã¯ãããŸãããé«éã«åäœãããšèšãäœè
ã«ãå€ãé£ãã¡ãªãããåŸãããšãã§ããŸããïŒç¹ã«åŸé課éåã®Lambdaãšããç°å¢ã«ãããŠã¯é«éã«åäœãããšããã®ã¯ééçã«ãã¡ãªããããããšèšãããšæããŸãïŒ ã¿ãªãããè¯ãRustã©ã€ãããéããã ããã