Параметр count, который вы видели ранее, позволяет создавать простые циклы. Но если проявить смекалку, тот же механизм можно использовать и для условных выражений. Мы начнем с рассмотрения конструкций if в пункте «Выражения if с использованием параметра count» ниже, а затем перейдем к выражениям if-else.
Выражения if с использованием параметра count
В главе 4 вы написали модуль Terraform, который можно применить в качестве «чертежа» для развертывания кластеров с веб-серверами. Этот модуль создавал группу автомасштабирования (ASG), балансировщик нагрузки (ALB), группы безопасности и ряд других ресурсов. Чего он не создавал, так это запланированного действия. Поскольку кластер нужно масштабировать только в промышленных условиях, вы определили ресурсы aws_autoscaling_schedule непосредственно в промышленной конфигурации в файле live/prod/services/webserver-cluster/main.tf. Можно ли их определить в модуле webserver-cluster и затем создавать только для определенных пользователей?
Попробуем это сделать. Для начала добавим булеву входную переменную в файл modules/services/webserver-cluster/variables.tf, чтобы иметь возможность включать и выключать автомасштабирование в этом модуле:
variable "enable_autoscaling" {
description = "If set to true, enable auto scaling"
type = bool
}
Теперь, если бы вы использовали язык программирования общего назначения, вы бы могли применить эту входную переменную в выражении if:
# Это просто псевдокод. Он не будет работать в Terraform.
if var.enable_autoscaling {
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "${var.cluster_name}-scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "${var.cluster_name}-scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
}
Terraform не поддерживает выражения if, поэтому данный код работать не будет. Но того же результата можно добиться с помощью параметра count и двух особенностей языка.
• Если внутри ресурса параметру count присвоить значение 1, вы получите копию этого ресурса; если указать 0, этот ресурс вообще не будет создан.
• Terraform поддерживает условные выражения в формате
Объединив эти две идеи, мы можем обновить модуль webserver-cluster следующим образом:
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
count = var.enable_autoscaling ? 1 : 0
scheduled_action_name = "${var.cluster_name}-scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
count = var.enable_autoscaling ? 1 : 0
scheduled_action_name = "${var.cluster_name}-scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
Если var.enable_autoscaling равно true, параметру count для каждого из ресурсов aws_autoscaling_schedule будет присвоено значение 1, поэтому оба они будут созданы в единственном экземпляре. Если var.enable_autoscaling равно false, параметру count для каждого из ресурсов aws_autoscaling_schedule будет присвоено значение 0, поэтому ни один из них создан не будет. Это именно та условная логика, которая нам нужна!
Теперь мы можем обновить использование этого модуля в тестовой среде (в файле live/stage/services/webserver-cluster/main.tf): выключим масштабирование, присвоив enable_autoscaling значение false:
module "webserver_cluster" {
source = "../../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
instance_type = "t2.micro"
min_size = 2
max_size = 2
enable_autoscaling = false
}
Аналогичным образом обновим использование этого модуля в промышленной среде (в файле live/prod/services/webserver-cluster/main.tf). Включим масштабирование, присвоив enable_autoscaling значение true (не забудьте также убрать пользовательские ресурсы aws_autoscaling_schedule, которые остались в промышленной среде после выполнения примеров главы 4):
module "webserver_cluster" {
source = "../../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
instance_type = "m4.large"
min_size = 2
max_size = 10
enable_autoscaling = true
custom_tags = {
Owner = "team-foo"
DeployedBy = "terraform"
}
}
Этот подход хорошо работает в случае, если пользователь передает вашему модулю явное булево значение. Но если вместо этого передается результат более сложного сравнения, такого как проверка равенства строк? Рассмотрим более замысловатый пример.
Представьте, что вы хотите создать в рамках модуля webserver-cluster набор оповещений CloudWatch. Оповещение CloudWatch может доставляться с помощью разных механизмов (скажем, в виде электронного письма или текстового сообщения), если достигается заранее заданный порог. Например, ниже мы используем ресурс aws_cloudwatch_metric_alarm в файле modules/services/webserver-cluster/main.tf, чтобы создать оповещение, которое срабатывает, если загруженность процессора превышает 90 % на протяжении пяти минут:
resource "aws_cloudwatch_metric_alarm" "high_cpu_utilization" {
alarm_name = "${var.cluster_name}-high-cpu-utilization"
namespace = "AWS/EC2"
metric_name = "CPUUtilization"
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.example.name
}
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
period = 300
statistic = "Average"
threshold = 90
unit = "Percent"
}
Это хорошо работает для показателя CPUUtilization. Но если нужно добавить еще одно оповещение, которое срабатывает, когда заканчиваются кредиты для процессора?46 Ниже это демонстрируется на примере нашего кластера веб-серверов:
resource "aws_cloudwatch_metric_alarm" "low_cpu_credit_balance" {
alarm_name = "${var.cluster_name}-low-cpu-credit-balance"
namespace = "AWS/EC2"
metric_name = "CPUCreditBalance"
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.example.name
}
comparison_operator = "LessThanThreshold"
evaluation_periods = 1
period = 300
statistic = "Minimum"
threshold = 10
unit = "Count"
}
Но есть одна загвоздка: кредиты для процессора распространяются только на серверы типа tXXX (как t2.micro, t2.medium и т. д.). Более крупные типы серверов (вроде m4.large) эти кредиты не поддерживают и не отчитываются о показателе CPUCreditBalance. Поэтому, если вы создадите подобное оповещение для таких серверов, оно никогда не выйдет из состояния INSUFFICIENT_DATA. Возможно ли создавать оповещения только в случае, если var.instance_type начинается с буквы t?
Вы могли бы создать новую булеву входную переменную с именем var.is_t2_instance, но тогда бы она дублировала var.instance_type, а вы, скорее всего, забудете обновлять их вместе. Лучшая альтернатива — использование условного выражения:
resource "aws_cloudwatch_metric_alarm" "low_cpu_credit_balance" {
count = format("%.1s", var.instance_type) == "t" ? 1 : 0
alarm_name = "${var.cluster_name}-low-cpu-credit-balance"
namespace = "AWS/EC2"
metric_name = "CPUCreditBalance"
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.example.name
}
comparison_operator = "LessThanThreshold"
evaluation_periods = 1
period = 300
statistic = "Minimum"
threshold = 10
unit = "Count"
}
Код оповещения остается прежним, если не считать относительно сложного параметра count:
count = format("%.1s", var.instance_type) == "t" ? 1 : 0
Здесь используется функция format, которая извлекает первый символ из var.instance_type. Если это символ t (как в случае с t2.micro), параметру count присваивается значение 1; в противном случае параметр count будет равен 0. Таким образом, оповещение создается только для серверов, у которых действительно есть показатель CPUCreditBalance.
Выражения if-else с использованием параметра count
Теперь вы знаете, как создавать выражения if. Но что насчет if-else?