2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Terraform] AWSサービスでREST APIを構築する

Posted at

はじめに

この記事では、AWSサービスを使ってREST APIをTerraformで構築する手順について解説します。
具体的には以下の構成を構築します。

ブラウザからエンドポイントにアクセスし、DynamoDBにあるデータが取得されて、ブラウザにJson形式で返される。といった構成になっています。
Frame 1 (1)

前提条件

  • 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"
  }
}

スクリーンショット 2024-03-19 7 52 46

エンドポイントにアクセス

① API Gatewayのコンソール画面からデプロイされたAPIのエンドポイントをコピーして、ブラウザに貼り付けます。
スクリーンショット 2024-03-19 7 44 18

② エンドポイントにアクセスし、以下の画像のようにレスポンスが返されれば、正常に処理が行われたことになります。
動作確認に「Talend API Tester - free Edition」というGoogleの拡張機能を使用しています。

以上で、AWSサービスを使用しREST APIをTerraformで構築する手順についての解説は終わりとなります。

さいごに

この記事が何かの参考になれば幸いです。また、内容に誤りなどがあった際に、ぜひコメントをいただければと思います。

参考記事

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?