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
で検知しています。
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の変数定義の例です。
aws_profile="default"
aws_region="ap-northeast-1"
resource_name="lambda-container-sample"
Docker build & pushしている build-docker.sh
は前回の記事とまったく同じです。
よく似たスクリプトとしてLambdaにコンテナイメージの更新を強制させるスクリプトとして 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" ]
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;
});
};
{
"name": "lambda-container-sample",
"version": "0.1.0",
"dependencies": {}
}
以上。
追記 2023/06/19
この記事のコードは、以下の記事でも活用しました。