在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。

顺利导出了!
试用一下导出的资源
接下来,让我们创建一个类似的堆栈并尝试进行导入操作。
通过设置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函数时,

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

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