LoginSignup
2
0

More than 1 year has passed since last update.

TerraformでAPI Gateway + コンテナのLambdaを作成してみる

Last updated at Posted at 2023-05-25

terraform apply コマンドだけでコンテナイメージを作成して、API GatewayとLambdaを作成できるようにします。コンテナイメージの更新時も terraform apply だけで済むように内部で aws lambda update-function-code コマンドを呼び出しています。前回の記事と前々回の記事の内容の合体プラスアルファです。

前回:TerraformでECRのリポジトリを作成しdocker build & pushする - Qiita
前々回:TerraformでAPI Gateway + Lambdaの最小構成テンプレート - Qiita

Terraformのファイル本体は以下の通りです。

コンテナイメージのソースとなるファイルが更新されたらdocker build & pushされるようにするのと、 aws lambda update-function-code コマンドを呼ぶようにしています。ソースファイルの更新は docker_source_file_sha1 で検知しています。

main.tf
variable aws_profile {}
variable aws_region {}
variable resource_name {}

terraform {
  backend "s3" {
  }
}

provider "aws" {
   profile = var.aws_profile
   region = var.aws_region
}

################################################################################
# ECR
################################################################################

resource "aws_ecr_repository" "default" {
  name = var.resource_name
  force_delete = true # terraform destroyしたときにリポジトリも削除されるように
}

################################################################################
# docker push
################################################################################

locals {
  # Dockerイメージのソースとなるファイルのハッシュ値
  docker_source_file_sha1 = sha1(join("", [for f in ["build-docker.sh", "Dockerfile", "app.js", "package.json"]: filesha1(f)]))
}

resource "null_resource" "image" {
  depends_on = [
    aws_ecr_repository.default
  ]

  triggers = {
    file_content_sha1 = local.docker_source_file_sha1
  }

  provisioner "local-exec" {
    # docker build & push
    command = "sh ./build-docker.sh"
  }
}

################################################################################
# Lambda
################################################################################

resource "aws_iam_role" "lambda_role" {
  name               = "${var.resource_name}-lambda-role"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}

resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

data "aws_iam_policy_document" "lambda_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

resource "aws_lambda_function" "api" {
  depends_on       = [
    aws_iam_role.lambda_role,
    null_resource.image,
  ]
  function_name    = "${var.resource_name}-api"
  role             = aws_iam_role.lambda_role.arn
  package_type     = "Image"
  image_uri        = "${aws_ecr_repository.default.repository_url}:latest"
  timeout          = 30
}

resource "null_resource" "refresh_lambda" {
  depends_on = [
    aws_lambda_function.api,
    null_resource.image,
  ]

  triggers = {
    # イメージを更新したときに新しいイメージでLambdaを更新するためのトリガー
    file_content_sha1 = local.docker_source_file_sha1
  }

  provisioner "local-exec" {
    command = "sh ./refresh-lambda.sh"
    # スクリプトの中身は aws lambda update-function-code を呼び出している
  }
}

################################################################################
# API Gateway
################################################################################

resource "aws_iam_role" "api_gateway_role" {
  name               = "${var.resource_name}-apigateway-role"
  assume_role_policy = data.aws_iam_policy_document.api_gateway_assume_role.json
}

resource "aws_iam_role_policy_attachment" "api_gateway_policy_logs" {
  role       = aws_iam_role.api_gateway_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
}

resource "aws_iam_role_policy_attachment" "api_gateway_policy_lambda" {
  role       = aws_iam_role.api_gateway_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaRole"
}

data "aws_iam_policy_document" "api_gateway_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["apigateway.amazonaws.com"]
    }
  }
}

resource "aws_api_gateway_rest_api" "api" {
  name = "${var.resource_name}-api"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
  
  body = jsonencode({
    openapi = "3.0.1"
    info = {
      title   = "api"
      version = "1.0"
    }
    paths = {
      "/" = {
        get = {
          x-amazon-apigateway-integration = {
            httpMethod           = "POST"
            payloadFormatVersion = "1.0"
            type                 = "AWS_PROXY"
            uri                  = aws_lambda_function.api.invoke_arn
            credentials          = aws_iam_role.api_gateway_role.arn
          }
        }
      }
      "/{proxy+}" = {
        get = {
          x-amazon-apigateway-integration = {
            httpMethod           = "POST"
            payloadFormatVersion = "1.0"
            type                 = "AWS_PROXY"
            uri                  = aws_lambda_function.api.invoke_arn
            credentials          = aws_iam_role.api_gateway_role.arn
          }
        }
      }
    }
  })
}

resource "aws_api_gateway_deployment" "deployment" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  depends_on  = [aws_api_gateway_rest_api.api]
  stage_name  = "prod"
  triggers = {
    redeployment = sha1(jsonencode(aws_api_gateway_rest_api.api))
  }
}

output "invoke_url" {
  value = aws_api_gateway_deployment.deployment.invoke_url
}

################################################################################

Terraformの変数定義の例です。

terraform.tfvars
aws_profile="default"
aws_region="ap-northeast-1"
resource_name="lambda-container-sample"

Docker build & pushしている build-docker.sh は前回の記事とまったく同じです。

よく似たスクリプトとしてLambdaにコンテナイメージの更新を強制させるスクリプトとして refresh-lambda.sh を作成しています。

refresh-lambda.sh
#!/bin/bash

. ./terraform.tfvars

aws_account_id=$(aws --profile $aws_profile sts get-caller-identity --query 'Account' --output text)

lambda_function_name=${resource_name}-api

aws --profile $aws_profile --region $aws_region lambda update-function-code --function-name $lambda_function_name --image-uri $aws_account_id.dkr.ecr.$aws_region.amazonaws.com/$resource_name:latest

Dockerfile等、コンテナイメージを構成するファイルには、サンプルとして最低限のことだけ書いています。

FROM public.ecr.aws/lambda/nodejs:18

COPY app.js package.json  ${LAMBDA_TASK_ROOT}/

RUN npm install

CMD [ "app.lambda_handler" ]
app.js
exports.lambda_handler = async (event, context) => {
  return new Promise(function(resolve, reject) {
    resolve({
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json",
        },
        "body": JSON.stringify({message: "OK", event: event}),
    });
    return;
  });
};
package.json
{
  "name": "lambda-container-sample",
  "version": "0.1.0",
  "dependencies": {}
}

以上。

追記 2023/06/19

この記事のコードは、以下の記事でも活用しました。

2
0
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
0