😎

AWS EC2とAPI gatewayによるAPIサーバをterraformで実装する

2023/03/11に公開

はじめに

私の所属している研究室の業務として、機械学習モデルを用いたAPIを作成して外部に公開しなければ行けないタスクが舞い降ってきました。自分の技術スタックとして、インターンシップ先でAWSのEC2の実験環境を整えるために、terraformを使って立ち上げていたことくらいで、API Gatewayを用いてAPIを立てたことがなかったので、いい機会と思い挑戦しました。

ただ、この記事はやってみたの記事なので、あくまで参考程度に見てください。(実験のために素人が作っているので、特にセキュリティ周りはガバガバだと思います。)

使用技術

初期段階

初期段階では、後々研究室でサービスを運用するとなった場合に備えて、スケーラブルなものにしたかったため、ECS+Fargate or ECS+EC2用いて作ることにしていました。しかし、以下のことが原因で諦めました。

  • ECS+Fargateは小規模で運用する分には割高
  • ECS+EC2 作っている間に、実装するべきことや考えなければいけないことが多く、素人には太刀打ちできなかった。
  • スケーラブルは今回のケースにとってはオーバースペックすぎる
  • 初めてにしてはハードルが高すぎる

最終段階

そこで、以下の技術を使うことにより少しタスクが減り、1~2日程度で作成することができました。

  • AWS

    • API Gateway
    • Lambda
    • VPC
    • EC2
  • APIサーバ

    • Python
    • FastAPI
    • Docker
  • HCL ツール

    • terraform

構成

AWS 構成

構成はこちらの記事を参考にさせていただきました。🙏 そのまま使わせていただきます。

流れとしては、API gateway を介してVPC 内のlambdaにリクエストを送り、そこからEC2のAPIを叩くという構成です。
API gatewayをproxyのように使うので、セキュアなAPIを構築しようとしました。

ただ、参考にした記事や他の記事ではAPIをコンソールを用いて構築していたので、自分はterraformで構築することにしました。

EC2内部の構成

EC2内は以下のとおりです。後々、機械学習モデルや、特殊なNLPモジュールを使う予定だったので、EC2インスタンスでそのまま動かすとConflictが起きそうだったのと、ローカルで動かして確認したかったので、Docker Containerを立ち上げて運用することにしました。

ワークスペース構成

❯ tree .
.
├── README.md
├── api
│   ├── docker
│   │   ├── Dockerfile
│   │   ├── push_ecr.sh
│   │   └── requirements.txt
│   ├── docker-compose.yml
│   ├── src
│   │   └── main.py
│   └── test
│       └── api-test.py
└── terraform
    ├── README.md
    ├── ami-list.txt
    ├── api-gateway.tf
    ├── ec2
    │   └── initialize.sh
    ├── ec2.tf
    ├── keys
    │   ├── example
    │   └── example.pub
    ├── lambda
    │   ├── app
    │   │   └── lambda.py
    │   ├── build_layer.sh
    │   ├── lib
    │   │   └── python
    │   └── requirements.txt
    ├── lambda.tf
    ├── layer.zip
    ├── main.tf
    ├── output.tf
    ├── output.txt
    ├── terraform.tfstate
    ├── terraform.tfstate.backup
    ├── terraform.tfvars
    ├── terraform.tfvars.default
    ├── variable.tf
    └── vpc.tf


APIコンテナの作成

Fast APIを用いたPython API

まずはFastAPIのかんたんなAPIから。

main.py

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Dialog(BaseModel):
    message: str

@app.post("/dialog")
def dialog(dialog: Dialog):
    return {"responce": f"Hello! You said {dialog.message}"}

Dockerfile

Dockerfileも例のごとく作ります.
大事な点として、Docker run したときに8080portを開放するようにしています。

FROM ubuntu:20.04

# aptのサーバを日本にする
RUN sed -i 's@archive.ubuntu.com@ftp.jaist.ac.jp/pub/Linux@g' /etc/apt/sources.list

# 必要そうなものをinstall
RUN apt-get update && apt-get install -y \
    software-properties-common && \
    rm -rf /var/lib/apt/lists/*

RUN add-apt-repository ppa:apt-fast/stable
RUN apt-get update \ 
    && apt install -y apt-fast \
    && apt-fast clean && rm -rf /var/lib/apt/lists/*

RUN apt-fast update \
    && apt-fast install -y --no-install-recommends \
    wget \
    vim \
    build-essential \
    libreadline-dev \
    libncursesw5-dev \
    libssl-dev \
    libsqlite3-dev \
    libgdbm-dev \
    libbz2-dev \
    liblzma-dev \
    zlib1g-dev \
    uuid-dev \
    libffi-dev \
    libdb-dev \
    build-essential \
    libssl-dev \
    libffi-dev \
    python3-dev \
    python3-pip

RUN apt-fast autoremove -y

COPY docker/requirements.txt .
RUN pip install -r requirements.txt

# copy src
COPY src root/src
COPY test root/test

EXPOSE 8080

ENTRYPOINT uvicorn root.src.main:app --reload --host "0.0.0.0" --port "8080"

Docker compose

これをdocker compose を作成します

version: '3'
services:
  chat-server:
    image: chat-server-image
    platform: linux/amd64
    build:
      context: .
      dockerfile: docker/Dockerfile
    container_name: example
    restart: always
    ports:
      - 8080:8080
    tty: true

テスト実行

以下のコマンドでdocker imageをビルド&コンテナ化します。

docker compose build
docker compose up

local 環境ではlocalhost:8080がendpointなので、APIを叩いて確認します。

私のようにtest/api-test.pyにpytestのコードを書いておくと後々便利です。

ECRにpush

EC2にimageを持ってくるためにECRにimageをpushしておきます。

IMAGE_NAME=example
ECR_IMAGE_NAME=<ECR リポジトリURL>/$IMAGE_NAME

aws ecr get-login-password --region ap-northeast-1 |  \
    docker login  \
    --username AWS \
    --password-stdin  \
    <account id>.dkr.ecr.<region>.amazonaws.com

docker tag "${IMAGE_NAME}":latest "${ECR_IMAGE_NAME}":latest
docker push "${ECR_IMAGE_NAME}":latest
#docker image rm "{$ECR_IMAGE_NAME}":latest

これで準備OKです。

EC2の立ち上げ

AWSのためのterraform 準備

veriable.tfなどにaccess_key, secret_key, regionなどを定義しておいて、以下のスクリプトでawsを使うことを宣言します。

AWSのaccess/secret keyをPushしてしまうと、めちゃくちゃ危ないので、terraform.tfvarsなど、pushされないファイルに書いて運用しています。

main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.1.0"
    }
  }
}
locals {
  app_name = "chat-server"
}
provider "aws" {
  region     = var.region
  access_key = var.access_key
  secret_key = var.secret_key
  default_tags {
    tags = {
      application = local.app_name
    }
  }
}

VPC

まずはVPCから作成していきましょう
ここは自分ではなく、他の記事のほうが詳しく書いてくれていると思います。

vpc.tf
# ====================
# VPC
# ====================
resource "aws_vpc" "chat-vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true # DNS解決を有効化
  enable_dns_hostnames = true # DNSホスト名を有効化

  tags = {
    Name = "chat-vpc"
  }
}

# ====================
# Subnet
# ====================
resource "aws_subnet" "chat-subnet" {
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  vpc_id            = aws_vpc.chat-vpc.id

  # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる
  map_public_ip_on_launch = true

  tags = {
    Name = "chat-subnet"
  }
}

# ====================
# Internet Gateway
# ====================
resource "aws_internet_gateway" "chat-ig" {
  vpc_id = aws_vpc.chat-vpc.id

  tags = {
    Name = "chat-ig"
  }
}

# ====================
# Route Table
# ====================
resource "aws_route_table" "chat-rt" {
  vpc_id = aws_vpc.chat-vpc.id

  tags = {
    Name = "chat-rt"
  }
}

resource "aws_route" "chat-route" {
  gateway_id             = aws_internet_gateway.chat-ig.id
  route_table_id         = aws_route_table.chat-rt.id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route_table_association" "chat-rt-ass" {
  subnet_id      = aws_subnet.chat-subnet.id
  route_table_id = aws_route_table.chat-rt.id
}


また、セキュリティグループも記述します。

vpc.tf
# ====================
# Security Group
# ====================
resource "aws_security_group" "chat-sg" {
  vpc_id = aws_vpc.chat-vpc.id
  name   = "chat-sg"

  tags = {
    Name = "chat-sg"
  }
}

# インバウンドルール(ssh接続用)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
}

# インバウンドルール(pingコマンド用)
resource "aws_security_group_rule" "in_icmp" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = -1
  to_port           = -1
  protocol          = "icmp"
}

# インバウンドルール HTTP
resource "aws_security_group_rule" "in_http" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
}
# インバウンドルール HTTPS(必要ないが)
resource "aws_security_group_rule" "in_https" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
}
# インバウンドルール HTTPS(必要ないが)
resource "aws_security_group_rule" "in_app" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 8080
  to_port           = 8080
  protocol          = "tcp"
}

# アウトバウンドルール(全開放)
resource "aws_security_group_rule" "out_all" {
  security_group_id = aws_security_group.chat-sg.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}

最後に、ECRを使えるようにするために、以下を記述します

vpc.tf
#### 
# ECRを使えるようにするため
#####
resource "aws_vpc_endpoint" "ecr-dkr" {
  vpc_id              = aws_vpc.chat-vpc.id
  service_name        = "com.amazonaws.ap-northeast-1.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.chat-subnet.id]
  security_group_ids  = [aws_security_group.chat-sg.id]
  private_dns_enabled = true
  tags = {
    Environment = "ecr-dkr"
  }
}
resource "aws_vpc_endpoint" "ecr-api" {
  vpc_id              = aws_vpc.chat-vpc.id
  service_name        = "com.amazonaws.ap-northeast-1.ecr.api"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.chat-subnet.id]
  security_group_ids  = [aws_security_group.chat-sg.id]
  private_dns_enabled = true
  tags = {
    Environment = "ecr-api"
  }
}

secret keyの作成

まずEC2へアクセスする用のaccess keyを作成します。本当はterraform のaws_key_pairなどで、applyするたびに作成するのが一般的なのですが、今回は簡易的に自分で作る方法を取りました。

ssh-keygen -t ed25519

そのSSH鍵で入れるようにします。

ec2.py
# ====================
# Key Pair
# ====================
resource "aws_key_pair" "chat-server" {
  key_name   = "chat-server-key"
  public_key = file("./keys/example.pub") 
}

EC2立ち上げ

ec2.py
# ====================
# AMI
# ====================
# 最新版のAmazonLinux2 GPU typeのAMI情報
data "aws_ami" "ami-info" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "architecture"
    values = ["x86_64"]
  }
  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*"] #"Deep Learning AMI GPU PyTorch*"
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"] # gp2
  }
  filter {
    name   = "state"
    values = ["available"]
  }
}

# ====================
# EC2 Instance
# ====================
resource "aws_instance" "chat-server" {
  ami                    = data.aws_ami.ami-info.image_id
  vpc_security_group_ids = [aws_security_group.chat-sg.id]
  subnet_id              = aws_subnet.chat-subnet.id
  key_name               = aws_key_pair.chat-server.id
  instance_type          = "t2.large"

  user_data = templatefile(
    "${path.module}/ec2/initialize.sh",
    {
      IMAGE_URL = var.image_url
    }
  )

  iam_instance_profile = "instance_role"

  tags = {
    Name = "chat-server"
  }
}

# ====================
# Elastic IP
# ====================
resource "aws_eip" "chat-eip" {
  instance = aws_instance.chat-server.id
  vpc      = true
}

Elastic IPを指定することで、global IP addressを作成しています。これでできたIPアドレスに、先程作ったガキを使ってssh 接続します。

また、以下のところで、EC2Instanceを初期化するスクリプトを指定しています。

ec2.tf
# ====================
# EC2 Instance
# ====================
resource "aws_instance" "chat-server" {
  ami                    = data.aws_ami.ami-info.image_id
  vpc_security_group_ids = [aws_security_group.chat-sg.id]
  subnet_id              = aws_subnet.chat-subnet.id
  key_name               = aws_key_pair.chat-server.id
  instance_type          = "t2.large"

  user_data = templatefile(
    "${path.module}/ec2/initialize.sh", ### ここ!!
    {
      IMAGE_URL = var.image_url
    }
  )
  ・・・
}

これは、ec2/initialize.shに最初に実行してほしい内容をshellscriptで書いておくと、自動で実行してくれるようになります。

私の場合は、このように書きました。


#!/bin/sh

# S3FS
yum -y update
amazon-linux-extras install -y epel
yum -y install s3fs-fuse

# S3のマウントポイントを作成
mkdir /mnt/s3fs
echo "<backet name> /mnt/s3fs fuse.s3fs _netdev,allow_other,iam_role=auto,umask=022 0 0" | sudo tee -a /etc/fstab
mount -a
df -h


# docker install
sudo yum install -y docker
sudo systemctl start docker
sudo usermod -a -G docker ec2-user
sudo systemctl enable docker

# imageのpull
aws ecr get-login-password --region ap-northeast-1 | sudo docker login --username AWS --password-stdin <accountID>.dkr.ecr.<region>.amazonaws.com
sudo docker pull ${IMAGE_URL}

# run
sudo docker run --rm -p 8080:8080 -d ${IMAGE_URL}

EC2にassume policyを追加

まだこれでは、EC2からECRにアクセスできません。なぜなら、アクセス件を持ってないからです。個人ユーザのaccess keyとsecret keyでaws configureを使ってEC2内でloginするのもいいですが、AWSぽくありません。

そこで、assume policyを付与します。

policy.tf
# #####################
# # IAM role
# #####################
resource "aws_iam_instance_profile" "instance_role" {
  name = "instance_role"
  role = aws_iam_role.instance_role.name
}

resource "aws_iam_role" "instance_role" {
  name               = "instance_role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

# s3とECRのroleを追加
resource "aws_iam_role_policy" "instance_role_policy" {
  name   = "instance_role_policy"
  role   = aws_iam_role.instance_role.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ecr:*",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "ecr-public:*",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::saya-chat-models"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObjectVersion",
        "s3:DeleteObject",
        "s3:GetObjectVersion"
      ],
      "Resource": [
        "arn:aws:s3:::saya-chat-models/*"
      ]
    }
  ]
}
EOF
}

これで、立ち上げることができました。
global IP addressがわかっていて、セキュリティグループで8080を開けているので、直接叩くことができます。
直接叩いて、返答が問題なければ成功しています。

Lambda の作成

ここまでで、EC2を使ってAPIサーバを立ち上げることに成功しました。
ここからはlambdaを介してAPIを公開します。

アーキテクチャの図でいうと、以下の赤い部分になります。

lambda layerの作成

lambdaのデフォルトから、追加してモジュールを使いたい場合(例えば、ライブラリやその他aptで入れたいものなど)、Lambda Layerを使うことで解決できます。

Lambda Layerとは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、カスタムランタイム、データ、または設定ファイルを含めることができます。レイヤーを使用して、デプロイパッケージのサイズを減らし、コードの共有と責任の分離を促進することで、ビジネスロジックの記述をより迅速に繰り返すことができます。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-layers.html

なので、コードを作っていきましょう。
以下のようなディレクトリ構造を取ります。

lambda
├── app
│   └── lambda.py
├── build_layer.sh
└── requirements.txt

app/lambda.pyの中身は以下の通りです。

lambda.py
import os
import requests
import json

def get_responce(statusCode: int, body: str):
    if not isinstance(body, str):
        raise RuntimeError("body is not string") 
    response = {
        "isBase64Encoded": True,
        "statusCode": statusCode,
        "headers": { "Access-Control-Allow-Origin": "*" },
        "body": body
    }
    return response

def handler(event, context):
    response = {}

    endpoint = os.environ["URL"]
    port = os.environ["PORT"]
    path = os.environ["PATH"]
    
    request_url = f"http://{endpoint}:{port}/{path}"

    body = json.loads(event["body"])
    if body is None or "data" in body:
        response = get_responce(400,"body don't have data")

    data = body["data"]
    payload = body
    print(f"data : {data}")

    try:
        ecs_res = requests.post(request_url, data=json.dumps(payload), timeout=(60.0,60.0))
    except ecs_res.exceptions.RequestException as e:
        response = get_responce(400, f"ECS error {e}")
        print("failed") 
    except Exception as e:
        response = get_responce(500, f"lambda error {e}")
        print("failed in lambda") 
    else:
        response = get_responce(200, ecs_res.text)
        print("succeeded!")
    finally:
        return response

以下のコードを使ってlayerを作成します。

#!/usr/bin/env bash

if [ -d lib/ ]; then
  rm -rf lib/
fi

echo "bundle pypi packages"
pip install -r requirements.txt -t lib/python

find lib -type f | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm

ここまでくれば、生成されたlayerをzip化して、プロジェクトルートにおいておけばOKです!

terraform コード

ここまでできたら

terraform のコードは以下の通りです。

lambda.tf
resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

locals {
  function_name = "my_lambda_function"
}

data "archive_file" "function_source" {
  type        = "zip"
  source_dir  = "lambda/app"
  output_path = "lambda/archive/my_lambda_function.zip"
}

data "archive_file" "layer_source" {
  type        = "zip"
  source_dir  = "lambda/lib"
  output_path = "lambda/archive/my_lambda_layer.zip"
}

resource "aws_lambda_layer_version" "lambda_layer" {
  layer_name       = "lambda_layer"
  filename         = data.archive_file.layer_source.output_path
  source_code_hash = data.archive_file.layer_source.output_base64sha256
}


resource "aws_lambda_function" "function" {
  function_name = local.function_name
  handler       = "lambda.handler"
  role          = aws_iam_role.lambda_role.arn
  runtime       = "python3.9"
  layers        = [aws_lambda_layer_version.lambda_layer.arn]

  kms_key_arn = aws_kms_key.lambda_key.arn
  timeout = 60
  filename         = data.archive_file.function_source.output_path
  source_code_hash = data.archive_file.function_source.output_base64sha256

  environment {
    variables = {
      URL  = aws_eip.chat-eip.public_ip,
      PORT = "8080",
      PATH = "dialog",
    }
  }

  depends_on = [aws_iam_role_policy_attachment.lambda_policy, aws_cloudwatch_log_group.lambda_log_group]
}

resource "aws_kms_key" "lambda_key" {
  description             = "My Lambda Function Customer Master Key"
  enable_key_rotation     = true
  deletion_window_in_days = 7
}

resource "aws_kms_alias" "lambda_key_alias" {
  name          = "alias/my-lambda-key"
  target_key_id = aws_kms_key.lambda_key.id
}



######################
# lambda のためのassume role
#######################

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"

    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

data "aws_iam_policy" "lambda_basic_execution" {
  arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

data "aws_iam_policy_document" "lambda_policy" {
  source_json = data.aws_iam_policy.lambda_basic_execution.policy

  statement {
    effect = "Allow"

    actions = [
      "kms:Decrypt"
    ]

    resources = ["*"]
  }
}

resource "aws_iam_policy" "lambda_policy" {
  name   = "MyLambdaPolicy"
  policy = data.aws_iam_policy_document.lambda_policy.json
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = aws_iam_policy.lambda_policy.arn
}

resource "aws_iam_role" "lambda_role" {
  name               = "MyLambdaRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
###########################
# cloud watch role
###########################
resource "aws_cloudwatch_log_group" "lambda_log_group" {
  name = "/aws/lambda/${local.function_name}"
}


###############
# API Gateway からアクセス許可
###############

resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.function.function_name
  principal     = "apigateway.amazonaws.com"

  # The /*/* portion grants access from any method on any resource
  # within the API Gateway "REST API".
  source_arn = "${aws_api_gateway_rest_api.chat-server-api.execution_arn}/*/*/*"
}

立ち上げられたら、AWSコンソールからlambdaの画面に行って直接叩いてみて、デバッグしてみましょう!

API Gatewayの作成

API Gatewayは以下の通りです!

api_gateway.tf
#####################
# API define
####################
resource "aws_api_gateway_rest_api" "chat-server-api" {
  name = "ChatServerAPI"
}

resource "aws_api_gateway_rest_api_policy" "chat-server-api-policy" {
  rest_api_id = aws_api_gateway_rest_api.chat-server-api.id
  policy      = data.aws_iam_policy_document.api-gateway-policy.json
}

data "aws_iam_policy_document" "api-gateway-policy" {
  statement {
    effect = "Allow"
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    actions = ["execute-api:Invoke"]
    resources = [
      "${aws_api_gateway_rest_api.chat-server-api.execution_arn}/*/POST/dialog"
    ]

  }
}

#################
# stage and deployment
#################


resource "aws_api_gateway_deployment" "chat-server-deployment" {
  depends_on = [
    aws_api_gateway_rest_api.chat-server-api,
    aws_api_gateway_integration.dialog
  ]
  rest_api_id = aws_api_gateway_rest_api.chat-server-api.id
  stage_name  = "dev"

  triggers = {
    redeployment = filebase64("${path.module}/api-gateway.tf")
  }

  lifecycle {
    create_before_destroy = true
  }
}


###########################################################
# Path  : /dialog
# Method: POST
###########################################################
resource "aws_api_gateway_resource" "dialog" {
  path_part   = "dialog"
  parent_id   = aws_api_gateway_rest_api.chat-server-api.root_resource_id
  rest_api_id = aws_api_gateway_rest_api.chat-server-api.id
}

resource "aws_api_gateway_method" "dialog" {
  rest_api_id      = aws_api_gateway_rest_api.chat-server-api.id
  resource_id      = aws_api_gateway_resource.dialog.id
  http_method      = "POST"
  authorization    = "NONE"
  api_key_required = false
}

resource "aws_api_gateway_method_response" "dialog" {
  rest_api_id = aws_api_gateway_rest_api.chat-server-api.id
  resource_id = aws_api_gateway_resource.dialog.id
  http_method = aws_api_gateway_method.dialog.http_method
  status_code = "200"
  response_models = {
    "application/json" = "Empty"
  }
  depends_on = [aws_api_gateway_method.dialog]
}


resource "aws_api_gateway_integration" "dialog" {
  rest_api_id = aws_api_gateway_rest_api.chat-server-api.id
  resource_id = aws_api_gateway_resource.dialog.id
  http_method = aws_api_gateway_method.dialog.http_method

  content_handling = "CONVERT_TO_TEXT"

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.function.invoke_arn
}

ここまでくればterraform applyで立ち上げて、実行できるはずです!

まとめ

最後の方は力尽きて解説が疎かになってしまいましたが、今回terraformを使ってAPI構築を行い、デプロイしてみました。

policyとか、必要な情報が欠落していると思うので、適当に埋めて使ってみてください!

今後はCI/CDを使って、適切にデプロイできるようにしてみたいです。

Discussion