Terraformで翻訳Web APIを構築する
AWSが公開している AWS Hands-on for Beginners サーバーレスアーキテクチャで翻訳 Web API を構築する をTerraformで構築してみました。
アジェンダ
- 想定読者
- 構築イメージ
- 環境
- 変数の定義
- DynamoDBの構築
- Lambdaの構築
- Lambdaに付与するIAMロールの作成
- API Gatewayの構築
- API Gatewayに付与するIAMロールの作成
- 翻訳実行結果の確認
想定読者
GUIで「サーバーレスアーキテクチャで翻訳 Web API を構築する」を実施済みの方
ハンズオンを実施してからTerraformで構築する方が理解しやすいと思います。
なお、本記事では、Terraformのインストール方法やCLI環境の準備方法、各AWSリソースの説明等は行なっていません。
Terraformのインストールについてはこちらの記事、
AWS CLIの設定方法はこちらをご参照ください。
構築イメージ
構成はハンズオンと同様です。
API Gateway を経由して Lambda を呼び出した後、
Lambda では受け取った値を Translate に渡して翻訳します。
さらに、DynamoDB に入力と出力結果を記録するアーキテクチャーです。
作成するサービス
- Lambda
- API Gateway
- DynamoDB
- Translate
環境
Terraformバージョンとディレクトリ構成は以下の通りです。
$ tf -v
Terraform v1.4.4
Terraform-Test
├── main.tf
├── variables.tf
└── lambda-contents
├── src
| └──lambda_function.py
└── zip
└──lambda.zip
変数の定義
variables.tf
ファイルにmain.tf
ファイルで使用する変数を定義します。
variable "test_name" {
type = string
default = "handson-serverless"
description = "instance name"
}
DynamoDBの構築
API Gatewayから送信されたデータと翻訳されたデータを格納するためにDynamoDBを構築します。
resource "aws_dynamodb_table" "my-dynamodb-table" {
name = "handson-translate-history"
billing_mode = "PROVISIONED"
read_capacity = 1
write_capacity = 1
hash_key = "timestamp" # パーティションキー
attribute {
name = "timestamp"
type = "S" # hash_keyのtypeの指定
}
tags = {
name = "my-dynamodb-table"
}
}
Lambdaの構築
API から受け取ったデータを処理する Lambda を作成します。
# コンテンツ、ファイル、またはファイルのディレクトリからアーカイブを生成
data "archive_file" "my_function" {
type = "zip"
source_file = "${path.module}/lambda-contents/src/lambda_function.py" # アーカイブするファイル
output_path = "${path.module}$/lambda-contents/zip/lambda.zip" # アーカイブファイルの出力先
}
# Lambda関数の作成
resource "aws_lambda_function" "my_lambda" {
filename = data.archive_file.my_function.output_path
function_name = "handson-lambda-function"
role = aws_iam_role.lambda_function_role.arn
handler = lambda_function.lambda_handler publish = true
source_code_hash = data.archive_file.my_function.output_base64sha256
runtime = "python3.9"
memory_size = 128
timeout = 3
}
Lambda関数のコード
プログラムコードを lambda_function.py
ファイルに書いていきます。
import json
import boto3
import datetime
translate = boto3.client(service_name='translate')
dynamodb_translate_history_tbl = boto3.resource('dynamodb').Table('translate-history')
def lambda_handler(event, context):
input_text = event['queryStringParameters']['input_text']
response = translate.translate_text(
Text=input_text,
SourceLanguageCode="ja",
TargetLanguageCode="en"
)
output_text = response.get('TranslatedText')
dynamodb_translate_history_tbl.put_item(
Item = {
"timestamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
"input_text": input_text,
"output_text": output_text
}
)
return {
'statusCode': 200,
'body': json.dumps({
'output_text': output_text
}),
'isBase64Encoded': False,
'headers': {}
}
Lambdaに付与するIAMロールの作成
Lambda に付与するロールを作成します。
# Lambdaに関するロール作成
# 信頼ポリシーを作成
data "aws_iam_policy_document" "AWSLambdaTrustPolicy" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
# IAMロール作成
# 作成済みの信頼ポリシーを紐付け
resource "aws_iam_role" "lambda_function_role" {
name = "handson-function-role"
assume_role_policy = data.aws_iam_policy_document.AWSLambdaTrustPolicy.json
}
# IAMロールにIAMポリシーをアタッチ
# CloudWatchLogsに対しての書き込み権限をLambdaに付与
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_function_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# 翻訳のためにTranslateにアクセスする権限をLambdaに付与
resource "aws_iam_role_policy_attachment" "translate_policy" {
role = aws_iam_role.lambda_function_role.name
policy_arn = "arn:aws:iam::aws:policy/TranslateFullAccess"
}
# データを保存するためにDynamoDBにアクセスする権限をLambdaに付与
resource "aws_iam_role_policy_attachment" "dynamodb_policy" {
role = aws_iam_role.lambda_function_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
}
# Lambda関数を呼び出す外部ソースを許可するLambdaパーミッションを作成
# CloudWatchログを記録するためにLambda関数へのアクセスを許可
resource "aws_lambda_permission" "allow_cloudwatch" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_lambda.function_name
principal = "events.amazonaws.com"
}
IAMポリシーについて
今回はAWS管理ポリシーをpolicy_arn
に指定していますが、カスタマー管理ポリシーの付与も可能です。
カスタマー管理ポリシーの場合は以下のようにaws_iam_policy_document
でポリシードキュメントを定義し、
aws_iam_policy
で作成したIAMポリシーとポリシードキュメントを紐付けます。
# ポリシードキュメントの定義
data "aws_iam_policy_document" "example"
statement {
actions = ["logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"]
effect = "Allow"
resource = ["arn:aws:logs:*:*:*"]
}
# IAMポリシー作成とポリシードキュメントの紐付け
resource "aws_iam_policy" "example"{
name = "example"
policy = data.aws_iam_policy_document.example.json
}
# IAMロールとIAMポリシーの関連付け
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.function_role.name
policy_arn = aws_iam_policy.example.arn
}
API Gatewayの構築
API Gateway を作成して Lambda と連携します。
# プロバイダーに設定されている AWS リージョン名を取得するために、リソースを使用する方法を示す
data "aws_region" "current" {}
# Terraformが認可されている有効なアカウントID、ユーザーID、およびARNへのアクセスを取得
data "aws_caller_identity" "current" {}
# プロトコルがRESTのAPI Gateway作成
resource "aws_api_gateway_rest_api" "my_api_gateway" {
name = "${var.project}-api-gateway"
}
# リソースの作成
resource "aws_api_gateway_resource" "my_api_gateway_resource" {
parent_id = aws_api_gateway_rest_api.my_api_gateway.root_resource_id
path_part = "translate"
rest_api_id = aws_api_gateway_rest_api.my_api_gateway.id
}
# メソッドの作成
resource "aws_api_gateway_method" "my_api_gateway_method" {
authorization = "NONE"
http_method = "GET"
resource_id = aws_api_gateway_resource.my_api_gateway_resource.id
rest_api_id = aws_api_gateway_rest_api.my_api_gateway.id
request_parameters = {
"method.request.querystring.input_text" = true
}
}
# LambdaとAPI Gatewayの紐付け
resource "aws_api_gateway_integration" "lambda_api_gateway_intergration" {
http_method = aws_api_gateway_method.my_api_gateway_method.http_method
resource_id = aws_api_gateway_resource.my_api_gateway_resource.id
rest_api_id = aws_api_gateway_rest_api.my_api_gateway.id
type = "AWS_PROXY"
integration_http_method = "POST"
uri = aws_lambda_function.my_lambda.invoke_arn
}
# HTTPメソッドレスポンスのステータスコードを設定
resource "aws_api_gateway_method_response" "api_gateway_response_200" {
rest_api_id = aws_api_gateway_rest_api.my_api_gateway.id
resource_id = aws_api_gateway_resource.my_api_gateway_resource.id
http_method = aws_api_gateway_method.my_api_gateway_method.http_method
status_code = "200"
}
# API Gatewayのデプロイを宣言
resource "aws_api_gateway_deployment" "my_api_gateway_deployment" {
rest_api_id = aws_api_gateway_rest_api.my_api_gateway.id
stage_name = "development"
}
API Gateway に付与するIAMロールの作成
API Gateway に付与するロールを作成します。
# API Gatewayに関するロール作成
# 信頼ポリシーの作成
data "aws_iam_policy_document" "AWSAPIGatewayTrustPolicy" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
type = "Service"
identifiers = ["apigateway.amazonaws.com"]
}
}
}
# IAMロールの作成
resource "aws_iam_role" "apigateway_role" {
name = "${var.project}-apigateway-role"
assume_role_policy = data.aws_iam_policy_document.AWSAPIGatewayTrustPolicy.json
}
# IAMロールにIAMポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "apigateway_policy" {
role = aws_iam_role.apigateway_role.name
policy_arn = "arn:aws:iam::aws:policy/TranslateFullAccess"
}
# APIの内容を処理するためにLambda関数へのアクセスを許可
resource "aws_lambda_permission" "allow_apigateway" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.my_api_gateway.id}/*/${aws_api_gateway_method.my_api_gateway_method.http_method}${aws_api_gateway_resource.my_api_gateway_resource.path}"
}
翻訳実行結果の確認
terraform init
terraform plan
terraform apply
の順でコマンドを実行し、
各リソースのデプロイが完了した後、
今回は Postman で実行結果を確認します。
まず、 マネジメントコンソールから API Gateway のエンドポイントを確認し、
クエリパラメーター ?input_text=ありがとう
を含むURLを Postman で実行します。
「ありがとう」という日本語が翻訳され output_text:thanks
と表示されていれば成功です。
さらに、DynamoDBのテーブルに実行結果が保存されていることを確認します。
Discussion