function computeのTableStore Triggerを試してみる
Alibaba Cloudの記事になりますが、TableStore(NoSQL)のデータに変更があった際にサーバーレス関数のFunction Computeを起動する構成を試してみたいと思います。
AlibabaでTerraformを使ってリソース作成するのはやったことないので、一度コンソールで作成してから各リソースをimportすることで理解を深めて、その後リソース作り直しました。
RAMはコンソールから作った際のものを使い回しているので、もしかしたら最初からTerraformだとエラーになる可能性もありますが、ご了承ください。
Terraformのソースコードも貼っておきますので参考にしてみてください。
前提条件
- Terraformのインストール
- Terraform実行するためのAlibaba Cloudのアクセスキーとシークレットキー
Terraformで作成
Terraformで作成します。必要なリソースは以下です。
- Function Compute v3:サーバーレス関数
- ソースコード
- Function Compute Trigger:実行トリガー
- TableStore:NoSQL
- RAM Role / Policy:権限管理ロールとポリシー
- Simple Log Service(SLS):ログサービス
ディレクトリ構成は以下になります。
.
├── Makefile // コマンド定義(Option)
├── README.md
├── function_compute.tf // Function Compute
├── local.tf
├── log_service.tf // Simple Log Service
├── main.tf
├── ram.tf // RAM
├── src // Function Compute用のソースコード
│ └── tablestore-trigger-func-code.zip
├── table_store.tf // TableStore
├── terraform.tfvars
└── variables.tf
Function Compute v3
ざっくり説明:AWSのLambdaのようなサービス
Terraformのコード
# Function Computeの設定
resource "alicloud_fcv3_function" "table_store" {
function_name = "tablestore-trigger-func"
memory_size = "512"
timeout = "60"
runtime = "nodejs18"
handler = "index.handler"
cpu = "0.35"
disk_size = "512"
code {
zip_file = filebase64("${path.module}/src/tablestore-trigger-func-code.zip")
}
role = alicloud_ram_role.fc_default.arn
log_config {
log_begin_rule = "DefaultRegex"
logstore = alicloud_log_store.main.logstore_name
project = alicloud_log_project.main.project_name
}
environment_variables = {
"TZ" = "Asia/Tokyo"
}
timeouts {}
internet_access = "true"
}
# TableStoreのトリガーを設定
resource "alicloud_fcv3_trigger" "trigger" {
trigger_type = "tablestore"
trigger_name = "test-tablestore-trigger"
qualifier = "LATEST"
trigger_config = jsonencode({})
source_arn = local.trigger_table_store_arn
invocation_role = alicloud_ram_role.trigger_role.arn
function_name = alicloud_fcv3_function.table_store.function_name
timeouts {}
}
コードはOSSというストレージに配置しておくこともできますが、今回はコードをzipにしてあげるようにしています。
TableStoreのトリガーをTerraformで作成するときには、TableStoreのテーブルのコンソール画面でStreamを有効にする必要がありました。
ソースコード
TableStoreのイベントを受け取り、PKとemailをコンソールに吐き出します。
'use strict';
// ロガーの設定
const logger = {
info: console.log,
error: console.error
};
import cbor from 'cbor';
/**
* ハンドラ関数
* @param {Buffer} event - イベントデータ
* @param {Object} context - コンテキスト
* @param {Function} callback - コールバック関数
*/
export const handler = async (event, context, callback) => {
try {
logger.info("イベントの処理を開始します");
// イベントデータの解析
const records = await cbor.decode(event);
for (const record of records.Records || []) {
logger.info("レコードを処理: %j", record);
const pk = getPkValue(record, "PK");
const email = getAttributeValue(record, "email");
// PK と email の値を処理
logger.info("PK: %s, email: %s", pk, email);
}
callback(null, 'OK');
} catch (e) {
logger.error("イベントの処理中にエラーが発生しました: %s", e.message);
callback(null, 'Error');
}
};
/**
* 属性値を取得する
* @param {Object} record - レコード
* @param {string} column - 列名
* @returns {any} 属性値
*/
const getAttributeValue = (record, column) => {
const attrs = record.Columns || [];
for (const x of attrs) {
if (x.ColumnName === column) {
return x.Value;
}
}
return null;
};
/**
* 主キー値を取得する
* @param {Object} record - レコード
* @param {string} column - 列名
* @returns {any} 主キー値
*/
const getPkValue = (record, column) => {
const attrs = record.PrimaryKey || [];
for (const x of attrs) {
if (x.ColumnName === column) {
return x.Value;
}
}
return null;
};
ソースコードはFunction Computeのコンソールでも編集できます。
TableStore
ざっくり説明:AWSのDynamoDBのようなサービス
# TableStoreのインスタンス
resource "alicloud_ots_instance" "test_instance" {
name = "test-fc"
accessed_by = "Any"
instance_type = "Capacity"
network_type_acl = [
"VPC",
"CLASSIC",
"INTERNET" # Terraformで操作のために追加しました
]
resource_group_id = var.resource_group_id
tags = {
env = "test"
}
}
# TableStoreのテーブル
resource "alicloud_ots_table" "event_forms" {
instance_name = alicloud_ots_instance.test_instance.name
table_name = "event_forms"
time_to_live = -1
max_version = 1
primary_key {
name = "PK"
type = "String"
}
depends_on = [
alicloud_ots_instance.test_instance
]
}
PrimaryKeyは PK として、その他のカラムは email
と allowCampaignSend
と age
を追加しますが、Terraformのコード内では特に指定する必要がありません。
network_type_acl
に "INTERNET" を追加していないとテーブルの操作ができませんでした。
RAM
ざっくり説明:AWSのIAMのようなサービス
# Function Computeのデフォルトロール
resource "alicloud_ram_role" "fc_default" {
name = "AliyunFcDefaultRole"
document = jsonencode({
"Statement" : [
{
"Action" : "sts:AssumeRole",
"Effect" : "Allow",
"Principal" : {
"Service" : [
"fc.aliyuncs.com"
]
}
}
],
"Version" : "1"
})
description = "Default Service Role for FC to operate other resource"
}
# デフォルトのポリシーをアタッチ
resource "alicloud_ram_role_policy_attachment" "fc_default_policy" {
policy_name = "AliyunFCDefaultRolePolicy"
policy_type = "System"
role_name = alicloud_ram_role.fc_default.name
depends_on = [
alicloud_ram_role.fc_default
]
}
// TableStore Stream から Function Compute サービス関数をトリガーするためのロールとポリシーを作成
resource "alicloud_ram_role" "trigger_role" {
name = "AliyunTableStoreStreamNotificationRole"
document = jsonencode({
Version = "1"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = [
"ots.aliyuncs.com"
]
RAM = [
"acs:ram::${var.alicloud_account_id}:root" // Alibaba Cloud Account ID(自分のとは異なる)
]
}
}
]
})
description = "trigger role for OTS to invoke functions of {serviceName}"
timeouts {}
}
# TableStore Stream から Function Compute サービス関数をトリガーするためのポリシー
resource "alicloud_ram_policy" "trigger_policy" {
policy_name = "AliyunTableStoreStreamNotificationRolePolicy"
policy_document = jsonencode({
"Version" : "1",
"Statement" : [
{
"Action" : [
"ots:BatchGet*",
"ots:Describe*",
"ots:Get*",
"ots:List*"
],
"Resource" : "*",
"Effect" : "Allow"
},
{
"Action" : [
"fc:InvokeFunction"
],
"Resource" : "*",
"Effect" : "Allow"
}
]
})
description = "Function Compute サービス関数をトリガーするための TableStore Stream の認可ポリシー"
timeouts {}
}
# TableStore Stream から Function Compute サービス関数をトリガーするためのロールにポリシーをアタッチ
resource "alicloud_ram_role_policy_attachment" "trigger_policy_attach" {
policy_name = alicloud_ram_policy.trigger_policy.policy_name
policy_type = "Custom"
role_name = alicloud_ram_role.trigger_role.name
timeouts {}
depends_on = [
alicloud_ram_role.trigger_role
]
}
AliyunTableStoreStreamNotificationRole
を作成するときに、コンソールで確認した自分のAccountIDと異なっていたので、コンソールからトリガーを作成したときに作られたAccountIDをvariablesにセットしています。
Simple Log Service(SLS)
ざっくり説明:AWSのCloudWatch Logsのようなサービス
# SLSのプロジェクト
resource "alicloud_log_project" "main" {
project_name = "function-compute-log"
}
# SLSのログストア
resource "alicloud_log_store" "main" {
project_name = alicloud_log_project.main.id
logstore_name = "function-log"
shard_count = 3
auto_split = true
max_split_shard_count = 64
retention_period = 90
enable_web_tracking = true
append_meta = false
timeouts {}
depends_on = [
alicloud_log_project.main
]
}
検証
上記で作成したリソースでTableStoreにデータを追加したときに、Function Computeが実行されることを確認します。
画面からTableStoreにデータを追加
追加するデータは、email
が test@gmail.com
で、allowCampaignSend
が false
、age
が26
とします。
OKを押すとデータが追加されます。
Function Computeで実行ログを確認
Function Computeの Logs
から実行ログを確認すると、先ほど追加したデータが表示されていることがわかります。
これでTableStoreにデータを追加したときに、Function Computeが実行されることを確認できました!
まとめに行く前に検証してみて良かったところと詰まったところを書いておきます。
良かったところ
- Function Computeのコンソール上でコードを編集する時にVSCodeを使える
- コンソールのVSCodeで編集してdeployまで、が早くて開発体験として良かった
-
npm install cbor
して利用するときもターミナル開いて出来て簡単
-
- VSCodeの拡張機能として標準でTONGYI Lingmaが入っている
- TONGYI Lingmaとは、Qwenを活用したAIコーディングアシスタント
- ※ ただしデフォルト中国語なので日本語で回答するように指示してあげないといけない
- Function ComputeのLogsタブでTraceで処理時間確認できるの便利そう
詰まったところ
- TableStoreからのeventがCBOR形式で送られてくる
- Testsタブから作成したイベントはJSON形式だったので混乱した
- TerraformでTableStoreのトリガーを作成しようと思ったが、サポートされていないようで、コンソールからTableのStreamをEnableにする必要がある
- 最初からTerraformでチャレンジしようと思ったが、Terraformのドキュメントがあまり細かいパラメータまで書いていないのと馴染みがなくて断念した→やはり最初コンソールで作ってimportしていくのが初学者には良さそう
まとめ
TableStoreの変更をトリガーにFunction Compute 3.0を実行することができました。
初めてやってみたので大変なところはありましたが、Terraform化する過程で公式ドキュメントとか読んで少しは理解が深まったのかなと思います。
Function Compute2.0にはAsparaDB RDS for MySQLのトリガーもありましたが、廃止されてしまったようです。ただコンソールの画面を見ているとcoming soonとなっていたのでそのうち対応されるかもしれないですね。現在はEventBridgeを使うことで出来るとのこと。
調べてみたらAWSのAuroraにもRDSのトリガーはありましたが、Aurora MySQLのネイティブ関数というものを使うらしい。(詳しくはこちら)
参考記事
- alicloud_fcv3_function | Resources | aliyun/alicloud | Terraform | Terraform Registry
- alicloud_ots_table | Resources | aliyun/alicloud | Terraform | Terraform Registry
- EventBridge を経由した、Function Compute (Alibaba Cloud) の RDS トリガー検証|クラウドテクノロジーブログ|ソフトバンク
- Aurora MySQL ネイティブ関数を使用した Lambda 関数の呼び出し - Amazon Aurora
Discussion