在Terraform和CloudFormation之间协调资源信息

首先

CloudFormation和Terraform都是出色的IaC执行环境,但由于它们之间的资源信息交换不够顺畅,IaC和部署设计变得困难。

为了简化设计,我们考虑了一种将资源信息从Terraform传递到CloudFormation的方法。

此外,为了使示例更易于理解,我们使用CloudFormation。但从根本上说,Terraform几乎可以做到CloudFormation能做的事情,所以它并没有太多意义。请大家考虑到实际的使用场景,比如在使用SAM进行CI/CD时不得不使用CloudFormation。

试试看

我试试做以下的事情。

    • Terraform で S3 バケットと、この後出てくる CloudFormation で作成する Lambda 関数に付与する IAM ロールを作成する

 

    • CloudFormation で、↑で作った S3 バケットから GetObject してきてファイルの中身をログ出力する Lambda 関数を作る

 

    ついでに、もう一度 Terraform に戻って、↑で作成した Lambda のイベントソースマッピングを作る

为 CloudFormation 准备传递信息

准备资源的步骤如下。

请在 test_object.txt 文件中写下一些随意的内容。
给予 IAM 策略 dynamodb:* 权限是为了在最后阶段使用。
从最小权限原则来看并不理想,所以最好再精细些,不过这不是本次讨论的重点,所以暂且不提。

################################################################################
# S3 Bucket                                                                    #
################################################################################
resource "aws_s3_bucket" "test" {
  bucket = local.bucket_name
  acl    = "private"
}

resource "aws_s3_bucket_object" "object" {
  bucket = aws_s3_bucket.test.id
  key    = "test_object"
  source = "${path.module}/test_object.txt"

  etag = filemd5("${path.module}/test_object.txt")
}

################################################################################
# IAM Role                                                                     #
################################################################################
resource "aws_iam_role" "lambda" {
  name               = local.lambda_role_name
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

data "aws_iam_policy_document" "lambda_assume" {
  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_custom.arn
}

resource "aws_iam_policy" "lambda_custom" {
  name   = local.lambda_policy_name
  policy = data.aws_iam_policy_document.lambda_custom.json
}

data "aws_iam_policy_document" "lambda_custom" {
  statement {
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
      "dynamodb:*",
    ]

    resources = [
      "*",
    ]
  }
}

将资源信息传递给CloudFormation

那么,要实现这个的方法是利用”CloudFormation 的 Outputs”。很简单。
但是,由于无法创建没有资源的 CloudFormation 堆栈,所以只需简单地创建一些虚拟资源如 S3 存储桶即可。

################################################################################
# CloudFormation(Terraform Resource Export)                                    #
################################################################################
resource "aws_cloudformation_stack" "terraform_export" {
  name = local.terraform_export_stack_name

  template_body = data.template_file.terraform_export.rendered
}

data "template_file" "terraform_export" {
  template = "${file("${path.module}/cloudformation_template_terraform_export.yml")}"
  vars = {
    iam_role_arn      = aws_iam_role.lambda.arn
    s3_bucket_arn     = aws_s3_bucket.test.arn
    dummy_bucket_name = local.dummy_bucket_name
  }
}
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Export Terraform resource information

Resources:
  # ------------------------------------------------------------#
  #  Dummy Resource
  # ------------------------------------------------------------#
  DummyBucket: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: ${dummy_bucket_name}

Outputs:
  LambdaRoleARN:
    Description: IAM Role ARN created by Terraform
    Value: ${iam_role_arn}
    Export:
      Name: LambdaRoleARN
  S3BucketARN:
    Description: S3 Bucket ARN created by Terraform
    Value: ${s3_bucket_arn}
    Export:
      Name: S3BucketARN

用这个命令,就可以执行terraform apply。

キャプチャ1.png

顺利导出了!

试用一下导出的资源

接下来,让我们创建一个类似的堆栈并尝试进行导入操作。

通过设置depends_on属性,在Terraform中进行资源协同工作和在CloudFormation中进行资源协同工作时,可以等待依赖关系。然而,Terraform无法感知CloudFormation的导出操作。为了避免在导出完成前使用导入导致错误,建议先创建依赖关系。

################################################################################
# CloudFormation(Terraform Resource Import)                                    #
################################################################################
resource "aws_cloudformation_stack" "cfn_import" {
  depends_on = [aws_cloudformation_stack.terraform_export]

  name = local.cfn_import_stack_name

  template_body = data.template_file.cfn_import.rendered
}

data "template_file" "cfn_import" {
  template = "${file("${path.module}/cloudformation_template_cfn_import.yml")}"
  vars = {
    lambda_function_name = local.lambda_function_name
    s3_bucket_name       = aws_s3_bucket.test.id
  }
}
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Export Terraform resource information

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties: 
      FunctionName: ${lambda_function_name}
      Role: !ImportValue LambdaRoleARN
      MemorySize: 128
      Runtime: python3.7
      Handler: "index.handler"
      Code:
        ZipFile: |
          import boto3
          s3 = boto3.client('s3')

          def handler(event, context):
            body = s3.get_object(Bucket="${s3_bucket_name}",Key="test_object")['Body'].read()
            print(body.decode('utf-8'))

            return {
              'statusCode': 200
            }

Outputs:
  LambdaFunctionArn:
    Description: LambdaFunction name created by CloudFormation
    Value: !GetAtt LambdaFunction.Arn
    Export:
      Name: LambdaFunctionARN

当我们执行这个Lambda函数时,

image.png

S3桶中的文件已顯示在標準輸出上!

用 Terraform 引用 CloudFormation 的资源。

使用Terraform为在↑的 CloudFormation 模板中创建的 Lambda 函数设置事件源映射。由于需要 Lambda 函数的 ARN,请利用 CloudFormation 的 Outputs(实际上,Lambda 函数的 ARN 可以事先推测,所以即使不强制读取 Outputs 也可以实现)。

关于 CloudFormation 的输出,由于 cloudformation_export 有数据源,因此使用它会更简单和方便。

另外,由於CloudFormation堆棧尚未完成,因此無法參考已導出的信息,所以需要使用depends_on來等待堆棧完成。

################################################################################
# Lambda Event Source Mapping                                                  #
################################################################################
resource "aws_lambda_event_source_mapping" "terraform_import" {
  depends_on = [aws_cloudformation_stack.cfn_import]

  event_source_arn  = aws_dynamodb_table.dummy.stream_arn
  function_name     = data.aws_cloudformation_export.lambda_function_arn.value
  starting_position = "LATEST"
}

resource "aws_dynamodb_table" "dummy" {
  name         = local.dynamodb_table_name
  billing_mode = "PAY_PER_REQUEST"

  stream_enabled   = true
  stream_view_type = "NEW_AND_OLD_IMAGES"

  hash_key = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

data "aws_cloudformation_export" "lambda_function_arn" {
  depends_on = [aws_cloudformation_stack.cfn_import]

  name = "LambdaFunctionARN"
}

使用此命令进行terraform apply

image.png

我成功地在由Terraform创建的Lambda函数上,通过CloudFormation设置了事件源!

bannerAds