💡

Terraformで翻訳Web APIを構築する

2023/05/19に公開

AWSが公開している AWS Hands-on for Beginners サーバーレスアーキテクチャで翻訳 Web API を構築する をTerraformで構築してみました。

アジェンダ

  1. 想定読者
  2. 構築イメージ
  3. 環境
  4. 変数の定義
  5. DynamoDBの構築
  6. Lambdaの構築
  7. Lambdaに付与するIAMロールの作成
  8. API Gatewayの構築
  9. API Gatewayに付与するIAMロールの作成
  10. 翻訳実行結果の確認

想定読者

GUIで「サーバーレスアーキテクチャで翻訳 Web API を構築する」を実施済みの方
ハンズオンを実施してからTerraformで構築する方が理解しやすいと思います。

なお、本記事では、Terraformのインストール方法やCLI環境の準備方法、各AWSリソースの説明等は行なっていません。
Terraformのインストールについてはこちらの記事
AWS CLIの設定方法はこちらをご参照ください。

構築イメージ

構成はハンズオンと同様です。
API Gateway を経由して Lambda を呼び出した後、
Lambda では受け取った値を Translate に渡して翻訳します。
さらに、DynamoDB に入力と出力結果を記録するアーキテクチャーです。

作成するサービス

  • Lambda
  • API Gateway
  • DynamoDB
  • Translate

環境

Terraformバージョンとディレクトリ構成は以下の通りです。

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ファイルで使用する変数を定義します。

variables.tf

variable "test_name" {
  type        = string
  default     = "handson-serverless"
  description = "instance name"
}

DynamoDBの構築

API Gatewayから送信されたデータと翻訳されたデータを格納するためにDynamoDBを構築します。

main.tf

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 を作成します。

main.tf

# コンテンツ、ファイル、またはファイルのディレクトリからアーカイブを生成
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 ファイルに書いていきます。

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 に付与するロールを作成します。

main.tf

# 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 と連携します。

main.tf
# プロバイダーに設定されている 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 に付与するロールを作成します。

main.tf

# 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