コンテナイメージを使用したLambdaのTerraformテンプレートを考えてみた
こんにちは!コンサルティング部のくろすけです!
コンテナイメージを使用したLambdaについて、Terraformでスマートに構築できないかなと以前から思っておりました。
現状で思い至ったTerraformテンプレートについて、記事にしたいと思います。
経緯
実はあまり覚えてないんですが、下記のような経緯だったと思います。
- なぜTerraform?
- IaCでLambda周りのインフラを再利用したい
- IaCにおいて個人的に好きなTerraformで
- なぜコンテナイメージを使用したLambda?
- zipを使用したLambda(以降zipLambda)の250MB制限厳しいな...
- ECSでコンテナをすでに使ってるし、Lambdaもコンテナにしたら保守のキャパが空くかな...
今回のメインではないですが、下記についても考慮しています。
- ECRリポジトリも同一Terraformテンプレートで管理
- ただし、下記を満たすためにコンテナLambdaの初期イメージにはダミーのECRリポジトリを指定
- コンテナLambdaの構築には、イメージURLの指定が必須
- 同一Terraformテンプレートで作成したECRには作成直後はイメージが空
- ただし、下記を満たすためにコンテナLambdaの初期イメージにはダミーのECRリポジトリを指定
- CI/CDの追加拡張も想定(というよりほぼ前提(今回はスコープ外))
- アプリのデプロイは基本的にCI/CDで行う
- アプリの更新による差分ではTerraformは更新しない
- TerraformでのイメージのBuild・Pushはしない
- local-exec Provisionarを使用することで、Terraformテンプレート内でイメージのBuild・Pushが可能
- が、Terraformコマンド実行の際にDockerコマンド等に依存することになるため不採用
前提
使用バージョン
バージョン | |
---|---|
Terraform | 1.10.4 |
Terraform AWS Provider | v5.87.0 |
構成
構築のスコープは下記の通りになります。
- Lambda
- ECR
- IAM Role
- CloudWatch Logs
経緯にも記載しましたが、今回のポイントは下記です。
- ECRリポジトリも同一Terraformテンプレートで管理
- CI/CDの追加拡張も想定(というよりほぼ前提(今回はスコープ外))
- TerraformでのイメージのBuild・Pushはしない
やってみる
ディレクトリ構成
├── services
│ └── lambda
│ └── ${ENV}
│ ├── main.tf
│ ├── provider.tf
│ ├── terraform.tfvars
│ └── variables.tf
└── modules
└── lambda
├── main.tf
├── outputs.tf
└── variables.tf
Terraformテンプレート
services/lambda/main.tf
module "lambda" {
source = "../../../modules/lambda"
ecr_repository_name = "${var.sys_name}-${var.env}-repository"
dummy_ecr_repository_name = "dummy-${var.env}-repository"
dummy_ecr_repository_tag = "latest"
lambda_function_name = "${var.sys_name}-${var.env}-lambda"
lambda_memory_size = 128
lambda_timeout = 3
lambda_environment = {
ENV = var.env
}
lambda_alias_name = var.env
lambda_role_name = "${var.sys_name}-${var.env}-lambda-role"
lambda_policy_name = "${var.sys_name}-${var.env}-lambda-policy"
env = var.env
}
services/lambda/variables.tf
variable "sys_name" {
description = "The name of the System"
type = string
}
variable "env" {
description = "The environment for the VPC"
type = string
default = "dev"
}
modules/lambda/main.tf
################################################################################
# Common #
################################################################################
data "aws_caller_identity" "self" {}
data "aws_region" "self" {}
################################################################################
# ECR #
################################################################################
resource "aws_ecr_repository" "lambda" {
name = var.ecr_repository_name
image_tag_mutability = "IMMUTABLE"
tags = {
Name = var.ecr_repository_name
}
} # アプリケーション用のECRを作成する(こちらはダミーではない)
################################################################################
# Lambda #
################################################################################
resource "aws_lambda_function" "lambda" {
function_name = var.lambda_function_name
architectures = ["arm64"]
package_type = "Image"
image_uri = (
"${data.aws_caller_identity.self.account_id}.dkr.ecr.${data.aws_region.self.name
}.amazonaws.com/${var.dummy_ecr_repository_name}:${var.dummy_ecr_repository_tag}"
) # ダミーのイメージURIを指定
role = aws_iam_role.lambda.arn
publish = true
memory_size = var.lambda_memory_size
timeout = var.lambda_timeout
lifecycle {
ignore_changes = [
image_uri, last_modified
] # アプリケーションの更新は無視する(イメージURI等の変更を無視する)
}
environment {
variables = var.lambda_environment
}
tracing_config {
mode = "Active"
}
depends_on = [
aws_cloudwatch_log_group.lambda
]
tags = {
Name = var.lambda_function_name
}
}
resource "aws_lambda_alias" "lambda" {
name = var.env
function_name = aws_lambda_function.lambda.arn
function_version = aws_lambda_function.lambda.version
lifecycle {
ignore_changes = [
function_version
] # アプリケーションの更新は無視する(イメージURI等の変更を無視する)
}
}
################################################################################
# IAM Role for Lambda #
################################################################################
resource "aws_iam_role" "lambda" {
name = var.lambda_role_name
assume_role_policy = data.aws_iam_policy_document.lambda_principal.json
tags = {
Name = var.lambda_role_name
}
}
data "aws_iam_policy_document" "lambda_principal" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"lambda.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy_attachment" "lambda" {
role = aws_iam_role.lambda.name
policy_arn = aws_iam_policy.lambda.arn
}
resource "aws_iam_policy" "lambda" {
name = var.lambda_policy_name
policy = jsonencode(var.lambda_policy_document)
tags = {
Name = var.lambda_policy_name
}
}
################################################################################
# CloudWatch Logs #
################################################################################
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/${var.lambda_function_name}"
retention_in_days = var.cloudwatch_loggrp_retention_in_days
tags = {
Name = "/aws/lambda/${var.lambda_function_name}"
}
}
modules/lambda/variables.tf
variable "ecr_repository_name" {
description = "The name of the ECR repository"
type = string
}
variable "dummy_ecr_repository_name" {
description = "The name of the dummy ECR repository"
type = string
}
variable "dummy_ecr_repository_tag" {
description = "The tag for the ECR dummy repository"
type = string
default = "latest"
}
variable "lambda_function_name" {
description = "The name of the Lambda function"
type = string
}
variable "lambda_memory_size" {
description = "The amount of memory for the Lambda function"
type = number
}
variable "lambda_timeout" {
description = "The timeout for the Lambda function"
type = number
}
variable "lambda_environment" {
description = "The environment variables for the Lambda function"
type = object({})
default = {
ENV = "pvt1"
}
}
variable "lambda_alias_name" {
description = "The name of the Lambda function alias"
type = string
}
variable "lambda_role_name" {
description = "The name of the IAM role for the Lambda function"
type = string
}
variable "lambda_policy_name" {
description = "The name of the IAM policy for the Lambda function"
type = string
}
variable "lambda_policy_document" {
description = "The IAM policy document for the Lambda function"
type = object({
Version = string
Statement = list(object({
Effect = string
Action = list(string)
Resource = list(string)
}))
})
default = {
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
Resource = [
"arn:aws:logs:*:*:*",
]
}
]
}
}
variable "cloudwatch_loggrp_retention_in_days" {
description = "The retention period for the CloudWatch log group"
type = number
default = 3
}
variable "env" {
description = "The environment"
type = string
}
結果
Warningが出ていますが、意図する挙動に対するものなので無視して問題ありません。
あとがき
あとはCI/CDなどでイメージURIを更新するようにしてあげれば、OKかなと思います!
(これはこれで後でAWS CodePipelineを用いて記事にしようと思います。)
CI/CDもないと全体最適化できているか判定できないですが、明日の自分がやってくれる!はず!
心残りはvariables.tfを作り込んでいないので、Lambdaの柔軟性が低いかなと思っております。(これも明日の自分に任せよう。)
ということでやりたいことは尽きないですが、ひとまず自分なりに考えたコンテナLambdaのTerraformテンプレートの概要は以上になります!それでは!