Terraform:如何配置CloudWatch日志订阅以将数据传递给Lambda?

39

我需要将我的CloudWatch日志发送到一个日志分析服务。

我已经按照这些文章herehere的说明进行了手动设置,并使其正常工作,没有问题。

现在我正在尝试使用Terraform自动化所有这些过程(包括角色/策略、安全组、CloudWatch日志组、Lambda函数以及从日志组触发Lambda函数)。

但是我无法找出如何使用TF来配置AWS从CloudWatch日志触发Lambda函数。

我可以通过手动方式将这两个TF资源链接在一起,方法如下(在Lambda网络控制台UI中):

  • 进入lambda函数的“Triggers”部分
  • 点击“Add Trigger”
  • 从触发器类型列表中选择“cloudwatch logs”
  • 选择我想要触发Lambda的日志组
  • 输入过滤器名称
  • 将过滤器模式保留为空(表示触发所有日志流)
  • 确保选中“enable trigger”
  • 点击提交按钮

完成后,在云监控日志控制台的订阅列中,Lambda函数会显示为“Lambda(cloudwatch-sumologic-lambda)”。

我尝试使用以下TF资源创建订阅:

resource "aws_cloudwatch_log_subscription_filter" "cloudwatch-sumologic-lambda-subscription" {
  name            = "cloudwatch-sumologic-lambda-subscription"
  role_arn        = "${aws_iam_role.jordi-waf-cloudwatch-lambda-role.arn}"
  log_group_name  = "${aws_cloudwatch_log_group.jordi-waf-int-app-loggroup.name}"
  filter_pattern  = "logtype test"
  destination_arn = "${aws_lambda_function.cloudwatch-sumologic-lambda.arn}"
}

但是它出现了以下错误:

aws_cloudwatch_log_subscription_filter.cloudwatch-sumologic-lambda-subscription: InvalidParameterException: vendor lambda的destinationArn不能与roleArn一起使用

我发现这个答案关于设置类似于计划事件的东西,但那似乎不等同于我上述描述的控制台操作所做的事情(控制台UI方法没有创建任何我能看到的事件/规则)。

请问有人可以给我指点一下我做错了什么吗?


很难确定,但看起来AWS在说您给PutSubscriptionFilter的角色没有访问Lambda的权限。您能否也发布aws_iam_role.jordi-waf-cloudwatch-lambda-role.arn资源的定义? - ydaetskcoR
请确保您的 aws_cloudwatch_log_subscription_filter 资源的 destination_arn 不以 ":*" 结尾。我犯了这个错误,浪费了一整天的时间来解决问题。 - Rashmi Jain
2个回答

60

我错误地定义了aws_cloudwatch_log_subscription_filter资源 - 在这种情况下,不应提供role_arn参数。

您还需要添加一个aws_lambda_permission资源(在过滤器上定义depends_on关系或者Terraform可能以错误的顺序执行)。

请注意,AWS Lambda控制台UI会在不可见的情况下为您添加Lambda权限,因此如果您之前在控制台UI中执行了相同的操作,则aws_cloudwatch_log_subscription_filter将在没有权限资源的情况下工作。

必要的Terraform配置如下所示(最后两个资源是配置实际cloudwatch->lambda触发器的相关资源):

// intended for application logs (access logs, modsec, etc.)
resource "aws_cloudwatch_log_group" "test-app-loggroup" {
  name              = "test-app"
  retention_in_days = 90
}

resource "aws_security_group" "cloudwatch-sumologic-lambda-sg" {
  name = "cloudwatch-sumologic-lambda-sg"

  tags {
    Name = "cloudwatch-sumologic-lambda-sg"
  }

  description = "Security group for lambda to move logs from CWL to SumoLogic"
  vpc_id      = "${aws_vpc.dev-vpc.id}"
}

resource "aws_security_group_rule" "https-egress-cloudwatch-sumologic-to-internet" {
  type              = "egress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  security_group_id = "${aws_security_group.cloudwatch-sumologic-lambda-sg.id}"
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_iam_role" "test-cloudwatch-lambda-role" {
  name = "test-cloudwatch-lambda-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "test-cloudwatch-lambda-policy" {
  name = "test-cloudwatch-lambda-policy"
  role = "${aws_iam_role.test-cloudwatch-lambda-role.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CopiedFromTemplateAWSLambdaVPCAccessExecutionRole1",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterface"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CopiedFromTemplateAWSLambdaVPCAccessExecutionRole2",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeNetworkInterfaces",
        "ec2:DeleteNetworkInterface"
      ],
      "Resource": "arn:aws:ec2:ap-southeast-2:${var.dev_vpc_account_id}:network-interface/*"
    },

    {
      "Sid": "CopiedFromTemplateAWSLambdaBasicExecutionRole1",
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:ap-southeast-2:${var.dev_vpc_account_id}:*"
    },
    {
      "Sid": "CopiedFromTemplateAWSLambdaBasicExecutionRole2",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
    "arn:aws:logs:ap-southeast-2:${var.dev_vpc_account_id}:log-group:/aws/lambda/*"
      ]
    },

    {
      "Sid": "CopiedFromTemplateAWSLambdaAMIExecutionRole",
      "Effect": "Allow",
      "Action": [
         "ec2:DescribeImages"
      ],
      "Resource": "*"
    }


  ]
}
EOF
}

resource "aws_lambda_function" "cloudwatch-sumologic-lambda" {
  function_name    = "cloudwatch-sumologic-lambda"
  filename         = "${var.lambda_dir}/cloudwatchSumologicLambda.zip"
  source_code_hash = "${base64sha256(file("${var.lambda_dir}/cloudwatchSumologicLambda.zip"))}"
  handler          = "cloudwatchSumologic.handler"

  role        = "${aws_iam_role.test-cloudwatch-lambda-role.arn}"
  memory_size = "128"
  runtime     = "nodejs4.3"

  // set low because I'm concerned about cost-blowout in the case of mis-configuration
  timeout = "15"

  vpc_config = {
    subnet_ids         = ["${aws_subnet.dev-private-subnet.id}"]
    security_group_ids = ["${aws_security_group.cloudwatch-sumologic-lambda-sg.id}"]
  }
}

resource "aws_lambda_permission" "test-app-allow-cloudwatch" {
  statement_id  = "test-app-allow-cloudwatch"
  action        = "lambda:InvokeFunction"
  function_name = "${aws_lambda_function.cloudwatch-sumologic-lambda.arn}"
  principal     = "logs.ap-southeast-2.amazonaws.com"
  source_arn    = "${aws_cloudwatch_log_group.test-app-loggroup.arn}"
}

resource "aws_cloudwatch_log_subscription_filter" "test-app-cloudwatch-sumologic-lambda-subscription" {
  depends_on      = ["aws_lambda_permission.test-app-allow-cloudwatch"]
  name            = "cloudwatch-sumologic-lambda-subscription"
  log_group_name  = "${aws_cloudwatch_log_group.test-app-loggroup.name}"
  filter_pattern  = ""
  destination_arn = "${aws_lambda_function.cloudwatch-sumologic-lambda.arn}"
}

编辑:请注意,上述 TF 代码是多年前使用版本 0.11.x 编写的 - 它仍然可以工作,但可能有更好的方法来完成任务。具体地,除非需要,否则不要使用内联策略,而是使用 aws_iam_policy_document - 这样处理起来更容易维护。


我认为没有必要创建“aws_cloudwatch_log_group”,因为在推送日志时Lambda函数已经创建了它。 如果您想针对某些特定情况进行推送,那么也许可以这样做。 - Jayesh Dhandha
8
与其让AWS后端创建资源(如日志组、权限等),最好通过TF自行创建。这样,您可以在需要正确配置它们时轻松掌控它们(例如,在处理争用时,您可以轻松控制日志组)。此外,通过自行创建,您可以准确知道配置的细节(由AWS自动创建的资源未经记录并且可能会发生改变)。 - Shorn
好的解决方案,谢谢分享,但我必须在日志组ARN的末尾添加“”:${aws_cloudwatch_log_group.test-app-loggroup.arn}: - user1297406
我希望这个例子可以在这个页面上找到: https://www.terraform.io/docs/providers/aws/r/cloudwatch_log_subscription_filter.html - Ildar Galikov
4
我必须按照RtmY的建议设置 source_arn = "${aws_cloudwatch_log_group.test-app-loggroup.arn}:*"。 我使用的是Terraform 0.14.6版本。 - Gopi Palamalai
@GopiPalamalai,我也是,没有它,我会得到错误消息“无法执行 Lambda 函数。请确保您已经授予 CloudWatch Logs 权限来执行您的函数”。 - allan.simon

9
在使用Terraform v0.12.29和AWS提供程序v3.1.0时,我遇到了一个奇怪的问题,它浪费了我几个小时的调试时间。
为了节省其他人宝贵的时间,我将其作为对已接受答案的补充分享出来。 cloudwatch日志组arn的值:
${aws_cloudwatch_log_group.test-app-loggroup.arn}

插值未正确进行 - 结果末尾缺少 ":*"。

导致以下错误:

创建{the-calling-service}时出错:InvalidCloudWatchLogsLogGroupArnException: 检查日志组ARN:{the-calling-service}无法验证它。

在末尾加上:*即可解决问题:

source_arn = "${aws_cloudwatch_log_group.test-app-loggroup.arn}:*" #<----Notice the :* postfix

我不再运送我的日志(现在使用日志洞察)。我回到我的git历史记录中,查看了我删除它时Lambda权限的source_arn。它只是arn(没有“:*”),尽管它没有使用字符串插值(因为之前升级到TF 0.12.x)。 - Shorn
你能将它(TF arn)与在AWS控制台中看到的arn值进行比较吗? - RtmY
谢谢!我刚刚自己发现了这个问题,然后去寻找确认。 - Aaron M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接