今日のTerraform loopネタはLambda関数作成。ログ監視の一貫なので、CloudWatchLogsのロググループとサブスクリプションフィルタ作成も一緒にやる。
この例でのディレクトリ構成は以下の通り。lambda/upload配下のzipファイルはTerraformにより生成されたもので、初回は空である。
work_dir
├── config.tf #初期化ファイル
├── lambda
│ ├── code
│ │ ├── func001
│ │ │ └── lambda-func001.py
│ │ ├── func002
│ │ │ └── lambda-func002.py
│ │ └── func003
│ │ └── lambda-func003.py
│ └── upload
│ ├── lambda-func001.zip
│ ├── lambda-func002.zip
│ └── lambda-func003.zip
├── lambda.auto.tfvars
├── lambda_cwl.tf
├── terraform.tfvars #regionのみ定義
└── variables.tf
最初に、すべて定数で記述したパターン。
lambda_logs.tf(定数バージョン)
#################################################
# Lambda archive data
#################################################
data "archive_file" "data-lambda-func001" {
type = "zip"
source_dir = "lambda/code/func001"
output_path = "lambda/upload/lambda-func001.zip"
}
#################################################
# Lambda function
#################################################
resource "aws_lambda_function" "lambda-func001" {
filename = data.archive_file.data-lambda-func001.output_path
function_name = "lambda-func001"
role = "arn:aws:iam::012345678910:role/send-log-filter-role"
handler = "lambda-func001.lambda_handler"
source_code_hash = base64sha256("lambda/upload/lambda-func001.zip")
timeout = 60
runtime = "python3.9"
environment {
variables = {
"SNS_TOPIC_ARN" = "arn:aws:sns:ap-northeast-1:012345678910:log-monitor-topic"
}
}
}
#################################################
# Lambda Permission
#################################################
resource "aws_lambda_permission" "func-perm001" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda-func001.arn
principal = "logs.ap-northeast-1.amazonaws.com"
source_arn = "arn:aws:logs:ap-northeast-1:012345678910:log-group:*:*"
}
#################################################
# CloudWatchLogs group
#################################################
resource "aws_cloudwatch_log_group" "cwl-group001" {
name = "log-group001"
retention_in_days = "7"
}
#################################################
# CloudWatchLogs subscription filter
#################################################
resource "aws_cloudwatch_log_subscription_filter" "cwl-subscription001" {
name = "cwl-filter001"
log_group_name = aws_cloudwatch_log_group.cwl-group001.name
filter_pattern = "[( msg=\"*error*\" || msg=\"*Error*\" ) && ( msg!=\"*test*\" && msg!=\"*Test*\" && msg!=\"*TEST*\" )]"
destination_arn = aws_lambda_function.lambda-func001.arn
}
これをapplyするとそれぞれ単体でリソースが作成される。次にloop処理の例。
lambda_logs.tf(loopバージョン)
#################################################
# Lambda archive data
#################################################
data "archive_file" "data-lambda-func" {
for_each = var.lambda_param_list
source_dir = lookup(each.value, "src_dir")
output_path = lookup(each.value, "out_path")
type = "zip"
}
#################################################
# Lambda function
#################################################
resource "aws_lambda_function" "lambda-func" {
for_each = var.lambda_param_list
filename = lookup(each.value, "out_path")
function_name = lookup(each.value, "func_name")
role = var.lambda_role
handler = "${lookup(each.value, "func_name")}.lambda_handler"
source_code_hash = base64sha256("${lookup(each.value, "out_path")}")
timeout = 60
runtime = "python3.9"
environment {
variables = {
"SNS_TOPIC_ARN" = var.topic_arn
}
}
}
#################################################
# Lambda Permission
#################################################
resource "aws_lambda_permission" "my-func-perm" {
for_each = var.lambda_param_list
action = "lambda:InvokeFunction"
function_name = "arn:aws:lambda:ap-northeast-1:012345678910:function:${lookup(each.value, "func_name")}"
principal = "logs.ap-northeast-1.amazonaws.com"
source_arn = var.src_arn
}
#################################################
# CloudWatchLogs group
#################################################
resource "aws_cloudwatch_log_group" "cwl-group" {
for_each = var.logs_param_list
name = lookup(each.value, "log_grp")
retention_in_days = "7"
}
#################################################
# CloudWatchLogs subscription filter
#################################################
resource "aws_cloudwatch_log_subscription_filter" "cwl-subscription" {
for_each = var.logs_param_list
name = lookup(each.value, "filter")
log_group_name = lookup(each.value, "log_grp")
filter_pattern = lookup(each.value, "pattern")
destination_arn = aws_lambda_function.lambda-func["param1"].arn
}
archive_fileによりTerraformがLambda用のzipファイルを生成してくれる。source_code_hashがzipのハッシュ値の差分をチェックして変更があればデプロイする。と、いうことだがこの記述だとコードを変更しなくても都度zipファイルが生成されてタイムスタンプが更新され、変更していなくても変更したと判断されてデプロイされる。デプロイされてもコードは同じだから害はないんだが、どうも納得がいかん。変更扱いにしたくなければarchive_fileのブロックを全部コメントしておくか、source_code_hashをコメントするか。どういう運用がいいんだろう?
ちなみに最初のapplyは比較対象のzipファイルがなくてエラーになるため、source_code_hashは初回のみコメントアウトしておく。
subscription filterで、Lambdaをloopで作ったのにここで単体で指定しているのは、こういう例もあるということで。この参照の場合は、logs_param_listにLambdaのARNの変数が存在しなくても問題ない。
lambda.auto.tfvars(tfコードが参照する変数)
#################################################
# Lambda function loop vars
#################################################
lambda_param_list = {
param1 = {
src_dir = "lambda/code/func001"
out_path = "lambda/upload/lambda-func001.zip"
func_name = "lambda-func001"
}
param2 = {
src_dir = "lambda/code/func002"
out_path = "lambda/upload/lambda-func002.zip"
func_name = "lambda-func002"
}
param3 = {
src_dir = "lambda/code/func003"
out_path = "lambda/upload/lambda-func003.zip"
func_name = "lambda-func003"
}
}
#################################################
# Lambda function vars
#################################################
lambda_role = "arn:aws:iam::012345678910:role/send-log-filter-role"
topic_arn = "arn:aws:sns:ap-northeast-1:012345678910:log-monitor-topic"
#################################################
# Lambda Permission vars
#################################################
src_arn = "arn:aws:logs:ap-northeast-1:012345678910:log-group:*:*"
#################################################
# CloudWatchLogs group & filter loop vars
#################################################
logs_param_list = {
param1 = {
log_grp = "log-group001"
filter = "logs-filter001"
pattern = "[( msg=\"*error*\" || msg=\"*Error*\" ) && ( msg!=\"*test*\" && msg!=\"*Test*\" && msg!=\"*TEST*\" )]"
}
param2 = {
log_grp = "log-group002"
filter = "logs-filter002"
pattern = "[( msg=\"*error*\" || msg=\"*ERROR*\" ) && ( msg!=\"*test*\" && msg!=\"*Test*\" && msg!=\"*TEST*\" )]"
}
param3 = {
log_grp = "log-group003"
filter = "logs-filter003"
pattern = "[( msg=\"*FAIL*\" || msg=\"*fail*\" || msg=\"*Fail*\" ) && ( msg!=\"*test*\" && msg!=\"*Test*\" && msg!=\"*TEST*\" )]"
}
}
フィルタパターンが汚なくて見るに耐えないがこればかりはどうしようもない…
variables.tf(宣言のみ)
##################################
# main
##################################
variable "region" {
type = string
description = ""
}
##################################
# lambda loop
##################################
variable "lambda_param_list" {
type = map(map(string))
description = ""
}
##################################
# lambda role
##################################
variable "lambda_role" {
type = string
description = ""
}
##################################
# topic
##################################
variable "topic_arn" {
type = string
description = ""
}
##################################
# lambda parmission
##################################
variable "src_arn" {
type = string
description = ""
}
##################################
# logs loop
##################################
variable "logs_param_list" {
type = map(map(string))
description = ""
}
これによりログ監視に必要なリソースが一気に作成される。最初にコード書くのは面倒だけど、やっぱり自動化の仕組み作っておくと楽だな。