はじめに
お仕事でAWS Lambdaを使って実装する処理が出てきました。
この処理はクリティカルな部分ではない、補助的な処理だったため日頃から使う機会を窺っていたRustで実装してみることにしました。
まずはRust+Lambdaの肩慣らしのために、プライベートなアカウントで利用している毎日のAWSの料金をSlackに通知する処理をRustに移植してみることにしました。
元ネタ: LambdaでAWSの料金を毎日Slackに通知する(Python3) qiita.com
今回移植したコード一式はこちらにおきました。 github.com
Rustへの移植
基本的には元のPythonの処理をそのまま移植していく方針としました。
関数の作成やIAMロールへのポリシーのアタッチなども元ネタとほぼ同じやり方にしています。
関数の作成はこんな感じでカスタムランタイムを選択しておきます。アーキテクチャはx86_64
にしました。
コードはローカルでzipファイルを作ってアップロードする形にします。
プロジェクト構成
全体的な構成はこんな感じにしました。
$ tree . . ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── bootstrap // エントリーポイント用のバイナリクレート │ ├── Cargo.toml │ └── src │ └── main.rs └── event.json // テスト用のダミーインプット
Lambdaのカスタムランタイムの仕様としてエントリーポイントはbootstrap
という実行可能ファイルにする必要があるとのことでした。
docs.aws.amazon.com
今回はCargoのワークスペースを作成しその中にバイナリクレートとしてbootstrap
というクレートを配置する形にしました。
ビルド〜zip作成
こちらに従ってビルドしていくようなMakefileを用意しました。
あらかじめrustup target add x86_64-unknown-linux-gnu
しておいた上でmake zip
するとDocker上でクロスコンパイル〜zip作成が行われるようになっています。
Makefileはこんな感じです。
DOCKER_PLATFORM ?= linux/amd64 RUST_VERSION ?= 1.57 RUST_ARCH ?= x86_64-unknown-linux-gnu DEPS ?= bootstrap/src/*.rs bootstrap/Cargo.toml Cargo.toml Cargo.lock RELEASE_DIR := ${PWD}/target/${RUST_ARCH}/release TARGET_BIN := ${RELEASE_DIR}/bootstrap TARGET_ZIP := lambda.zip build: cargo build --release --target ${RUST_ARCH} .PHONY: buildx buildx: $(TARGET_BIN) $(TARGET_BIN): $(DEPS) docker run -it --rm --platform ${DOCKER_PLATFORM} \ -v "$${PWD}":/usr/src/myapp -w /usr/src/myapp rust:${RUST_VERSION} \ make build .PHONY: zip zip: $(TARGET_ZIP) $(TARGET_ZIP): $(TARGET_BIN) zip -j "$(TARGET_ZIP)" "$(TARGET_BIN)"
PythonからRustへの移植
移植したコード全体はこちらです。 github.com
ロギング
pythonではloggerをこんな感じで用意していました。
logger = logging.getLogger() logger.setLevel(logging.INFO)
print!
やprintln!
でも良かったのですが、ログレベルをサポートするためにこちらを使うことにしました。
こんな感じで使います。
// 初期化 simplelog::SimpleLogger::init(LevelFilter::Info, Config::default()).unwrap(); // ログ出力 log::info!("hello {}", "world");
AWS SDK
boto3の代わりにrusoto
を使います。
⚠️ Rusoto is in maintenance mode. ⚠️
と書かれてますが今回は気にせず使うことにしました。
こんな感じになります。
let client = CloudWatchClient::new(Region::UsEast1); client.get_metric_statistics(GetMetricStatisticsInput { namespace: String::from("AWS/Billing"), metric_name: String::from("EstimatedCharges"), dimensions: Some(vec![Dimension { name: String::from("Currency"), value: String::from("USD"), }]), start_time: (Utc::today().and_hms(0, 0, 0) - Duration::days(1)) .format("%+") .to_string(), end_time: Utc::today().and_hms(0, 0, 0).format("%+").to_string(), period: 86400, statistics: Some(vec![String::from("Maximum")]), ..GetMetricStatisticsInput::default() }).await
SlackへのPost(WebHook)
requestsの代わりにreqwest
を使います。
こんな感じにになりました。
let client = reqwest::Client::new(); client.post(slack_post_url.to_string()).body(body.to_string()).send().await?;
実行!
ということでmake zip
し、作成されるzipファイルをマネジメントコンソールからアップロードしテスト実行してみます。
いい感じですね!
残課題
Dockerでのビルド周りが遅いのは要改善です。
また、LocalStackやlambci/lambda
を用いてローカル実行できるようにしてあげる必要もあると思います。
この辺は追々対応していきます。
ということで今回は以上です。