当我尝试在Terraform模块中使用for_each时遇到了错误
前提 tí)
在公司的工作中,我们经常使用Terraform来管理资源。但是,当我们尝试使用Terraform的模块将CloudWatch Alarms管理起来,并使用for_each来创建类似的CloudWatch Alarm时,出现了错误。
對於API Gateway + Lambda函數的REST API結構,我們試圖為每個Lambda創建一個CloudWatch Alarm。基本上,我們需要創建一些具有相同指標和命名空間的CloudWatch Alarm,只是函數名稱不同。因此,我嘗試使用module+for_each來簡單實現,但卻遇到了錯誤。
另外,我们假设要监控的Lambda函数具有特定的别名,例如只想监控生产环境使用的Lambda函数。
由于我是一个Terraform初学者,如果有任何错误或不妥之处,我会很感激您提供评论。
结论
如果Terraform的版本是0.12系列,则无法在模块中使用for_each对资源进行定义。而如果版本是0.13系列,则可以实现该功能。下面是参考文献链接:
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each#module-count-and-for_each
文件的结构 de
Terraform的版本为0.12.28。
> terraform --version
Terraform v0.12.28
Your version of Terraform is out of date! The latest version
is 0.13.4. You can update by downloading from https://www.terraform.io/downloads.html
在module/cloudwatch_alarm/lambda/文件夹下,创建main.tf和variables.tf文件,内容如下。
provider "aws" {
region = "ap-northeast-1"
shared_credentials_file = "~/.aws/credentials"
profile = "hogehoge"
}
resource "aws_cloudwatch_metric_alarm" "lambda_alarm" {
alarm_name = var.alarm_name
comparison_operator = var.comparison_operator
evaluation_periods = var.evaluation_periods
metric_name = var.metric_name
namespace = var.namespace
period = var.period
statistic = var.statistic
threshold = var.threshold
insufficient_data_actions = var.insufficient_data_actions
alarm_actions = var.alarm_actions
}
variable "alarm_name" {
default = ""
type = string
description = "The name of CloudWatch Alarm"
}
variable "comparison_operator" {
default = ""
type = string
description = "The operator to evaluate the metrics"
}
variable "evaluation_periods" {
default = "1"
type = string
description = "The number of the most recent periods, or data points, to evaluate when determining alarm state"
}
variable "metric_name" {
default = ""
type = string
description = "metrics which you want to monitor"
}
variable "namespace" {
default = "AWS/Lambda"
type = string
description = "namespace which you want to monitor"
}
variable "period" {
default = "300"
type = string
description = "The length of time to evaluate the metric or expression to create each individual data point for an alarm"
}
variable "statistic" {
default = ""
type = string
description = "statistic to evaluate a metrics"
}
variable "threshold" {
default = ""
type = string
description = "threshold to change the status of an alarm"
}
variable "alarm_description" {
default = ""
type = string
description = "the explanation of an alarm"
}
variable "insufficient_data_actions" {
default = ["specify the actions when the data is not sufficient"]
type = list
}
variable "dimensions" {
default = {}
type = map(string)
description = "If you want to monitor specific resource in namespace, you can specify it"
}
variable "alarm_actions" {
default = []
type = list
description = "Actions which are executed when the status of an alarm is changed"
}
然后,使用module创建CloudWatch Alarms。在config.tf中记录alarm的配置,并在main.tf中使用。
module "cloudwatch_for_lambda" {
source = "./module/cloudwatch_alarm/lambda"
for_each = local.lambda_func_list
alarm_name = "${each.key}-${local.alarm_settings["metric"]}-alarm"
comparison_operator = local.alarm_settings["comparison_operator"]
evaluation_periods = local.alarm_settings["evaluation_periods"]
metric_name = local.alarm_settings["metric"]
namespace = local.alarm_settings["namespace"]
period = local.alarm_settings["period"]
statistic = local.alarm_settings["statistic"]
threshold = local.alarm_settings["threshold"]
alarm_description = "Detect errors from Lambda function ${each.key} "
insufficient_data_actions = []
dimensions = {
FunctionName = "${each.key}",
Resource = "${each.key}:${each.value}"
}
}
locals {
lambda_func_list = {
func1 = "alias1",
func2 = "alias2",
func3 = "alias3"
}
alarm_settings = {
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric = "Errors"
namespace = "AWS/Lambda"
period = "300"
statistic = "Sum"
threshold = "0.0"
}
}
现在进行terraform init和terraform plan。
尝试进行terraform init的时候,遇到了一些问题。。。
> terraform init
Initializing modules...
- cloudwatch_for_lambda in module/cloudwatch_alarm/lambda
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 3.9.0...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 3.9"
Warning: Skipping backend initialization pending configuration upgrade
The root module configuration contains errors that may be fixed by running the
configuration upgrade tool, so Terraform is skipping backend initialization.
See below for more information.
Error: Reserved argument name in module block
on main.tf line 4, in module "cloudwatch_for_lambda":
4: for_each = local.lambda_func_list
The name "for_each" is reserved for use in a future version of Terraform.
Terraform has initialized, but configuration upgrades may be needed.
Terraform found syntax errors in the configuration that prevented full
initialization. If you've recently upgraded to Terraform v0.12, this may be
because your configuration uses syntax constructs that are no longer valid,
and so must be updated before full initialization is possible.
Terraform has installed the required providers to support the configuration
upgrade process. To begin upgrading your configuration, run the following:
terraform 0.12upgrade
To see the full set of errors that led to this message, run:
terraform validate
在调查“for_each”这个名称是保留给未来版本的一个Terraform错误时,我找到了以下文章。
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each#module-count-and-for_each
以下是从文章中摘录的内容。
模块计数和for_each
长期以来,用户一直希望在模块块中能够使用count元参数,以便更轻松地创建多个相同模块的实例。
在Terraform 0.12的开发过程中,我们一直在为此做准备工作,并在以后的版本中完成这项工作。除了count,模块块还会接受上面资源中描述的新的for_each参数,并获得类似的结果。
由于该功能在Terraform现有的架构中实施起来特别复杂,所以我们肯定需要进行更多的工作,以支持此功能。为了避免以后的版本中的进一步破坏性变更,0.12版本将保留模块的输入变量名称count和for_each,为此功能的完成做好准备。
看起来是版本的问题。0.12.xx版本似乎无法对模块使用count和for_each函数。我将尝试升级至0.13.1并再次尝试。
> tfenv list
0.13.1
* 0.12.28 (set by /usr/local/Cellar/tfenv/2.0.0/version)
> tfenv use 0.13.1
Switching default version to v0.13.1
Switching completed
> terraform init
Initializing modules...
There are some problems with the configuration, described below.
The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
Error: Module does not support for_each
on main.tf line 4, in module "cloudwatch_for_lambda":
4: for_each = local.lambda_func_list
Module "cloudwatch_for_lambda" cannot be used with for_each because it
contains a nested provider configuration for "aws", at
module/cloudwatch_alarm/lambda/main.tf:1,10-15.
This module can be made compatible with for_each by changing it to receive all
of its provider configurations from the calling module, by using the
"providers" argument in the calling module block.
出现错误。看起来是因为在模块的main.tf文件中包含了提供程序的信息。我们需要将module/cloudwatch_alarm/lambda/main.tf中的提供程序部分分离到provider.tf中,像下面这样。
provider "aws" {
region = "ap-northeast-1"
shared_credentials_file = "~/.aws/credentials"
profile = "hogehoge"
}
resource "aws_cloudwatch_metric_alarm" "lambda_alarm" {
alarm_name = var.alarm_name
comparison_operator = var.comparison_operator
evaluation_periods = var.evaluation_periods
metric_name = var.metric_name
namespace = var.namespace
period = var.period
statistic = var.statistic
threshold = var.threshold
insufficient_data_actions = var.insufficient_data_actions
alarm_actions = var.alarm_actions
}
请再次执行terraform init。
> terraform init
Initializing modules...
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.9.0...
- Installed hashicorp/aws v3.9.0 (signed by HashiCorp)
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, we recommend adding version constraints in a required_providers block
in your configuration, with the constraint strings suggested below.
* hashicorp/aws: version = "~> 3.9.0"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
我会执行terraform plan。
> terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.cloudwatch_for_lambda["func1"].aws_cloudwatch_metric_alarm.lambda_alarm will be created
+ resource "aws_cloudwatch_metric_alarm" "lambda_alarm" {
+ actions_enabled = true
+ alarm_name = "func1-Errors-alarm"
+ arn = (known after apply)
+ comparison_operator = "GreaterThanThreshold"
+ evaluate_low_sample_count_percentiles = (known after apply)
+ evaluation_periods = 1
+ id = (known after apply)
+ metric_name = "Errors"
+ namespace = "AWS/Lambda"
+ period = 300
+ statistic = "Sum"
+ threshold = 0
+ treat_missing_data = "missing"
}
# module.cloudwatch_for_lambda["func2"].aws_cloudwatch_metric_alarm.lambda_alarm will be created
+ resource "aws_cloudwatch_metric_alarm" "lambda_alarm" {
+ actions_enabled = true
+ alarm_name = "func2-Errors-alarm"
+ arn = (known after apply)
+ comparison_operator = "GreaterThanThreshold"
+ evaluate_low_sample_count_percentiles = (known after apply)
+ evaluation_periods = 1
+ id = (known after apply)
+ metric_name = "Errors"
+ namespace = "AWS/Lambda"
+ period = 300
+ statistic = "Sum"
+ threshold = 0
+ treat_missing_data = "missing"
}
# module.cloudwatch_for_lambda["func3"].aws_cloudwatch_metric_alarm.lambda_alarm will be created
+ resource "aws_cloudwatch_metric_alarm" "lambda_alarm" {
+ actions_enabled = true
+ alarm_name = "func3-Errors-alarm"
+ arn = (known after apply)
+ comparison_operator = "GreaterThanThreshold"
+ evaluate_low_sample_count_percentiles = (known after apply)
+ evaluation_periods = 1
+ id = (known after apply)
+ metric_name = "Errors"
+ namespace = "AWS/Lambda"
+ period = 300
+ statistic = "Sum"
+ threshold = 0
+ treat_missing_data = "missing"
}
Plan: 3 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
看起来可以创建三个名字不同的CloudWatch Alarm,正如所期望的那样。
总结
-
- 0.12系のterraformを使用していて、moduleで定義されたリソース対してfor_eachを使おうとするとエラーが出るようです。0.13系だとうまくいきます。
- 無理やりmoduleの中に持っていく必要性はないかもしれませんが、それはterraformファイルをどうやって管理しているかにもよるのではないかと思います。