📌
[Terraform] AWSサービスでREST APIを構築する
はじめに
この記事では、AWSサービスを使ってREST APIをTerraformで構築する手順について解説します。
具体的には以下の構成を構築します。
ブラウザからエンドポイントにアクセス し、DynamoDBにあるデータが取得されて、ブラウザにJson形式で返される。といった構成になっています。
前提条件
- MacOS環境
- Terraformがインストール済み
- AWS CLIのProfileがセットアップ済み
ディレクトリ構成
.
├── .gitignore
├── .terraform.lock.hcl
├── README.md
├── main.tf
├── terraform.tfstate
├── src
│ └── lambda_function.py
└── upload
└── lambda_function.zip
Step1: main.tfの作成
まずは main.tf を作成し、Terraformの設定、プロバイダーの設定、必要なリソースを定義します。
プロバイダーとは、雑いうと、TerraformとAWS等のサービスを連携させるモジュールのようなものになります。
terraform {
required_version = "~> 1.7"
required_providers {
# AWSを利用するためのプロバイダー
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
# Lambda関数のスクリプトをzip化するためのプロバイダー
archive = {
source = "hashicorp/archive"
version = "~> 2.4"
}
}
# terraform.tfstate(AWS構成の状態管理)ファイルの管理場所
backend "local" {
path = "./terraform.tfstate"
}
}
## DynamoDB
# DynamoDBにテーブルを作成
resource "aws_dynamodb_table" "gang_of_straw" {
name = "gang_of_straw"
billing_mode = "PROVISIONED"
read_capacity = 20
write_capacity = 20
hash_key = "name"
range_key = "bounty"
attribute {
name = "name"
type = "S"
}
attribute {
name = "bounty"
type = "S"
}
}
## IAM
# [*] lambda関数がDynamoDBにアクセスするためのロールを作成
resource "aws_iam_role" "dynamodb_read_only" {
name = "dynamodb_read_only"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "sts:AssumeRole"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# Lambda関数のログをCloudWatchに書き込むための権限を [*] にアタッチ
resource "aws_iam_role_policy_attachment" "cloudwatch_log_writing_permit" {
role = aws_iam_role.dynamodb_read_only.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Lambda関数がDynamoDBのテーブルを読み込み可能にするための権限を [*] にアタッチ
resource "aws_iam_role_policy_attachment" "dynamodb_reading_permit" {
role = aws_iam_role.dynamodb_read_only.name
policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess"
}
## Lambda
# Lambda関数のスクリプトをzip化
data "archive_file" "lambda_function_file" {
type = "zip"
source_dir = "./src/"
output_path = "./upload/lambda_function.zip"
}
# Lambda関数を作成
resource "aws_lambda_function" "get_gang_info" {
filename = data.archive_file.lambda_function_file.output_path
function_name = "get_gang_info"
role = aws_iam_role.dynamodb_read_only.arn
handler = "lambda_function.lambda_handler"
source_code_hash = data.archive_file.lambda_function_file.output_base64sha256
runtime = "python3.12"
timeout = 29
environment {
variables = {
TABLE_NAME = aws_dynamodb_table.gang_of_straw.name
}
}
}
# API GatewayからLambda関数を呼び出せるようにするための権限を設定
resource "aws_lambda_permission" "tr_lambda_permit" {
statement_id = "get_gang_info_api"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.get_gang_info.arn
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.get_gang_info_api.execution_arn}/*/GET/gang"
}
## API Gateway
# API GatewayのREST APIを作成
resource "aws_api_gateway_rest_api" "get_gang_info_api" {
name = "get_gang_info_api"
}
# ルート「/」にリソース「/リソース名/」を作成
resource "aws_api_gateway_resource" "gang_api_resource" {
rest_api_id = aws_api_gateway_rest_api.get_gang_info_api.id
parent_id = aws_api_gateway_rest_api.get_gang_info_api.root_resource_id
path_part = "gang"
}
# 作成したリソースに対してHTTPメソッドのGETを作成
resource "aws_api_gateway_method" "gang_get_method" {
authorization = "NONE"
http_method = "GET"
resource_id = aws_api_gateway_resource.gang_api_resource.id
rest_api_id = aws_api_gateway_rest_api.get_gang_info_api.id
}
# Lambdaプロキシ統合を設定
resource "aws_api_gateway_integration" "get_gang_lambda_integration" {
http_method = aws_api_gateway_method.gang_get_method.http_method
resource_id = aws_api_gateway_resource.gang_api_resource.id
rest_api_id = aws_api_gateway_rest_api.get_gang_info_api.id
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.get_gang_info.invoke_arn
}
# 作成したREST APIをデプロイ(公開)
resource "aws_api_gateway_deployment" "tr_api" {
depends_on = [
aws_api_gateway_integration.get_gang_lambda_integration
]
rest_api_id = aws_api_gateway_rest_api.get_gang_info_api.id
stage_name = "dev"
# triggers = {
# redeployment = filebase64("main.tf")
# }
}
Step2: Lambda関数の作成
次にLambda関数のスクリプトを「src/lambda_function.py」に書いていきます。
このスクリプトでは、https://・・・/dev/gang?パラメータ=値
のような形式で渡されたAPIからパラメータを抽出し、DBから抽出したパラメータに該当するデータを取得してJson形式で返す処理を行っています。
import boto3
import json
import logging
from botocore.exceptions import ClientError
dynamodb = boto3.resource('dynamodb')
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()
def get_item(params):
if params == None:
logger.warn('パラメータを入力してください。足りないパラメータ:name, bounty')
return None
else:
if 'name' in params.keys():
if 'bounty' in params.keys():
try:
table = dynamodb.Table('gang_of_straw')
response = table.get_item(Key=params)
print(f'レスポンスの内容:{response}')
if response.get('Item') == None:
return 0
else:
return response.get('Item')
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationException':
logger.warn('指定されたキー要素がスキーマと一致しません')
return 0
else:
raise e
else:
logger.warn('パラメータが足りません。足りないパラメータ:bounty')
return 'bounty'
else:
logger.warn('パラメータが足りません。足りないパラメータ:name')
return 'name'
def lambda_handler(event, context):
item = get_item(event.get('queryStringParameters'))
if item == None:
return {
'statusCode': 400,
'body': 'パラメータを入力してください。足りないパラメータ:name, bounty'
}
elif (item == 'name') or (item == 'bounty'):
return {
'statusCode': 400,
'body': f'パラメータに{item}を入力してください。'
}
elif item == 0:
return {
'statusCode': 400,
'body': '該当する情報はありませんでした。'
}
else:
return {
'statusCode': 200,
'body': json.dumps(item)
}
Step3: コマンド実行
# Terraformの初期化
terraform init
# 構築されるインフラ情報を確認
terraform plan
# 確認した内容でインフラを構築
terraform apply
Step4: 動作確認
データ投入
以下のデータをDynamoDBのコンソール画面に貼り付けて項目を作成します。
{
"name": {
"S": "Monkey・D・Luffy"
},
"bounty": {
"S": "1500000000"
}
}
エンドポイントにアクセス
① API Gatewayのコンソール画面からデプロイされたAPIのエンドポイントをコピーして、ブラウザに貼り付けます。
② エンドポイントにアクセスし、以下の画像のようにレスポンスが返されれば、正常に処理が行われたことになります。
動作確認に「Talend API Tester - free Edition」というGoogleの拡張機能を使用しています。
以上で、AWSサービスを使用しREST APIをTerraformで構築する手順についての解説は終わりとなります。
さいごに
この記事が何かの参考になれば幸いです。また、内容に誤りなどがあった際に、ぜひコメントをいただければと思います。
参考記事
Discussion