はじめに
このリポジトリは、S3、API Gateway、Lambda、RDS、IAMを利用したサーバレス構築の概要を説明します。この構築は、Terraformを使用してIaC (Infrastructure as Code) を実現し、静的ウェブサイトのホスティング、APIゲートウェイによるリバースプロキシ、Lambda関数の実行、RDSデータベースの利用を実現します。
以下がレポジトリです。
参考記事
構成
サーバレスアーキテクチャ構成図:
Terraform によるインフラストラクチャの構築
Terraform は、インフラストラクチャをコードとして定義および管理するためのオープンソースツールです。Terraform を使用して、S3 バケット、API Gateway、Lambda 関数、RDS インスタンス、IAM ポリシーなどの AWS リソースを宣言的に作成できます。
このアーキテクチャの利点
このアーキテクチャには、以下のような利点があります。
- スケーラビリティ: トラフィックが増加すると、自動的にスケールアップできます。
- コスト効率: 使用した分だけ支払う従量課金制モデルです。
- 運用管理: サーバーを管理する必要はありません。
- 高可用性: 冗長構成により、高い可用性を提供します。
コード
API Gateway
API Gateway は、フルマネージドのサービスで、API を作成、公開、管理、保護、監視するための単一の統合インターフェースを提供します。このアーキテクチャでは、API Gateway をリバースプロキシとして使用し、クライアントからのリクエストを S3 または Lambda に振り分けています。
resource "aws_api_gateway_rest_api" "main" {
body = jsonencode({
openapi = "3.0.1"
info = {
title = "api"
version = "1.0"
}
paths = {
get = {
x-amazon-apigateway-integration = {
payloadFormatVersion = "1.0"
type = "HTTP_PROXY"
uri = "${var.s3_url}"
}
}
post = {
x-amazon-apigateway-integration = {
payloadFormatVersion = "1.0"
type = "AWS_PROXY"
uri = "${var.lambda_invoke_arn}"
credentials = "${var.iam_role_lambda}"
}
}
}
})
name = "main"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_integration" "MyIntergration" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_rest_api.main.root_resource_id
http_method = aws_api_gateway_method.MyMethod.http_method
type = "AWS_PROXY"
cache_namespace = aws_api_gateway_rest_api.main.root_resource_id
content_handling = "CONVERT_TO_TEXT"
integration_http_method = "POST"
passthrough_behavior = "WHEN_NO_MATCH"
uri = var.lambda_invoke_arn
}
resource "aws_api_gateway_integration" "MyIntergrationS3" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_rest_api.main.root_resource_id
http_method = aws_api_gateway_method.MyMethods3.http_method
type = "HTTP_PROXY"
cache_namespace = aws_api_gateway_rest_api.main.root_resource_id
content_handling = "CONVERT_TO_TEXT"
integration_http_method = "GET"
passthrough_behavior = "WHEN_NO_MATCH"
uri = var.s3_url
}
resource "aws_api_gateway_method" "MyMethod" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_rest_api.main.root_resource_id
http_method = "POST"
authorization = "NONE"
depends_on = [aws_api_gateway_rest_api.main]
}
resource "aws_api_gateway_method" "MyMethods3" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_rest_api.main.root_resource_id
http_method = "GET"
authorization = "NONE"
depends_on = [aws_api_gateway_rest_api.main]
}
data "aws_iam_policy_document" "api_gateway_policy" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = ["*"]
}
actions = ["execute-api:Invoke"]
resources = ["${aws_api_gateway_rest_api.main.execution_arn}/*"]
}
}
resource "aws_api_gateway_rest_api_policy" "policy" {
rest_api_id = aws_api_gateway_rest_api.main.id
policy = data.aws_iam_policy_document.api_gateway_policy.json
}
resource "aws_api_gateway_deployment" "main" {
rest_api_id = aws_api_gateway_rest_api.main.id
stage_name = "stage"
triggers = {
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.main.body))
}
depends_on = [aws_api_gateway_method.MyMethod, aws_api_gateway_method.MyMethods3,
aws_api_gateway_integration.MyIntergration, aws_api_gateway_integration.MyIntergrationS3]
lifecycle {
create_before_destroy = true
}
}
Lambda
Lambda は、サーバーをプロビジョニングしたり管理したりすることなく、コードを実行できるイベント駆動型コンピューティングサービスです。このアーキテクチャでは、Lambda を使用して動的な処理を実装しています。
data "archive_file" "lambda" {
type = "zip"
source_dir = "./modules/lambda/src"
output_path = "./modules/lambda/src/lambda_function_payload.zip"
}
resource "aws_lambda_function" "main" {
filename = "./modules/lambda/src/lambda_function_payload.zip"
function_name = "lambda_function"
description = "lambda_function"
role = var.iam_role_lambda
architectures = ["x86_64"]
handler = "index.lambda_handler"
source_code_hash = data.archive_file.lambda.output_base64sha256
timeout = 30
runtime = "python3.9"
vpc_config {
subnet_ids = [var.subnet_public_subnet_1a_id]
security_group_ids = [var.sg_lambda_id]
}
environment {
variables = {
db_host = var.db_address
db_user = var.db_username
db_pass = var.db_password
db_name = var.db_name
}
}
tags = {
Name = "${var.app_name}-lamdba"
}
}
resource "aws_lambda_permission" "apigateway-to-lambda" {
statement_id = "AllowAPIGatewayGetTrApi"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.main.arn
principal = "apigateway.amazonaws.com"
source_arn = "${var.api_execution_arn}/*"
}
import base64
import json
import os
import pymysql
ENDPOINT=os.environ["db_host"]
PORT=3306
USER=os.environ["db_user"]
PASS=os.environ["db_pass"]
DBNAME=os.environ["db_name"]
os.environ['LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN'] = '1'
def execute_sql(cur):
try:
sql = """
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) DEFAULT NULL,
email VARCHAR(255) DEFAULT NULL,
password VARCHAR(255) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
"""
cur.execute(sql)
except pymysql.Error as e:
print(f"エラーが発生しました: {e}")
cur.rollback()
conn = pymysql.connect(host=ENDPOINT, user=USER, passwd=PASS, port=PORT, database=DBNAME)
cur = conn.cursor()
def lambda_handler(event, context):
execute_sql(cur)
try:
username = event.get('username')
email = event.get('email')
password = event.get('password')
sql = "INSERT INTO users (username, email, password) VALUES (%s, %s, %s)"
cur.execute(sql, (username, email, password,))
return {
'statusCode': 200,
'body': json.dumps({}),
'headers': {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
},
}
except:
import traceback
err = traceback.format_exc()
print(err)
return {
'statusCode' : 500,
'headers' : {
'context-type' : 'text/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
},
'body' : json.dumps({
'error' : '内部エラーが発生しました'
})
}