今日の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 = ""
}

 

これによりログ監視に必要なリソースが一気に作成される。最初にコード書くのは面倒だけど、やっぱり自動化の仕組み作っておくと楽だな。

 

Spain


関連がありそうな記事