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
}
В то же время в промышленной среде можно использовать более крупный тип серверов (например, m4.large) с большим количеством серверов и памяти. Имейте в виду, что этот тип не входит в бесплатный тариф AWS, поэтому, если кластер вам нужен только в образовательных целях и вы не хотите платить, оставьте в поле instance_type значение "t2.micro". Параметру max_size можно присвоить 10, что позволит кластеру расширяться и сжиматься в зависимости от нагрузки (не волнуйтесь, изначально он запустится с двумя серверами):
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
}
Локальные переменные модулей
Определение параметров модуля с помощью входных переменных — отличный подход, но что, если вам нужно определить переменную внутри модуля для каких-то промежуточных вычислений или просто для того, чтобы не дублировать код, но при этом вы не хотите делать ее доступной в качестве конфигурируемого ввода? Например, балансировщик нагрузки в модуле webserver-cluster (modules/services/webserver-cluster/main.tf) прослушивает стандартный для HTTP порт под номером 80. Сейчас нам приходится копировать и вставлять этот номер в разных местах, в том числе и в прослушивателе балансировщика:
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = 80
protocol = "HTTP"
# По умолчанию возвращает простую страницу с кодом 404
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
А вот группа безопасности балансировщика:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Значения в этой группе безопасности, включая блок CIDR 0.0.0.0/0 (любые IP-адреса), номер порта 0 (любой порт) и произвольный протокол "-1", тоже копируются и вставляются на нескольких участках модуля. Явное и многократное задание этих «магических» значений усложняет чтение и поддержку кода. Их можно вынести во входные переменные, но в таком случае ваш модуль может (случайно) переопределить эти значения, что может быть нежелательным. Вместо этого можно определить локальные значения в блоке locals:
locals {
http_port = 80
any_port = 0
any_protocol = "-1"
tcp_protocol = "tcp"
all_ips = ["0.0.0.0/0"]
}
Локальные значения позволяют назначить любому выражению Terraform имя, которое затем можно использовать в коде модуля. Такие имена видны только в самом модуле, поэтому они не имеют никакого влияния на внешний код, при этом вы не можете перезаписать их извне. Чтобы прочитать локальное значение, нужна локальная ссылка со следующим синтаксисом:
local.
Используйте этот синтаксис для обновления прослушивателя балансировщика нагрузки:
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = local.http_port
protocol = "HTTP"
# По умолчанию возвращает простую страницу с кодом 404
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
и всех групп безопасности в модуле, включая ту, которая относится к балансировщику:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
egress {
from_port = local.any_port
to_port = local.any_port
protocol = local.any_protocol
cidr_blocks = local.all_ips
}
}
Локальные переменные упрощают чтение и поддержку кода, поэтому используйте их как можно чаще.
Выходные переменные модуля
Мощной особенностью групп ASG является возможность сконфигурировать их для увеличения и уменьшения количества запущенных серверов в зависимости от нагрузки. Для этого можно воспользоваться запланированным действием, которое будет менять размер кластера в заданное время суток. Например, если ваш кластер испытывает повышенную нагрузку в рабочее время, вы можете запланировать увеличение и уменьшение количества серверов на 9 утра и 5 вечера соответственно.
Запланированное действие, определенное в модуле webserver-cluster, относится как к тестовой, так и к промышленной среде. Поскольку вам не нужно подобного рода масштабирование во время тестирования, можете пока определить график автомасштабирования прямо в промышленной конфигурации. В главе 5 вы познакомитесь с условным определением ресурсов, что позволит вам переместить запланированное действие в модуль webserver-cluster.
Чтобы определить запланированное действие, добавьте следующих два ресурса aws_autoscaling_schedule в файл prod/services/webserver-cluster/main.tf:
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
}
Первый ресурс aws_autoscaling_schedule используется для увеличения количества серверов до десяти в утреннее время (в параметре recurrence используется синтаксис cron, поэтому "09***" означает «в 9 утра каждый день»), а второй уменьшает этот показатель на ночь ("017***" значит «в 5 вечера каждый день»). Однако в обоих случаях не хватает параметра autoscaling_group_name, который задает имя ASG. Сама группа ASG определяется внутри модуля webserver-cluster. Как же получить доступ к ее имени? В языках общего назначения, таких как Ruby, функции могут возвращать значения:
def example_function(param1, param2)
return "Hello, #{param1} #{param2}"
end
# Другие участки вашего кода
return_value = example_function("foo", "bar")
В Terraform модули тоже могут возвращать значения. Для этого используется уже знакомый вам механизм: выходные переменные. Вы можете добавить имя ASG в качестве выходной переменной в файле modules/services/webserver-cluster/outputs.tf, как показано ниже:
output "asg_name" {
value = aws_autoscaling_group.example.name
description = "The name of the Auto Scaling Group"
}
Для обращения к выходным переменным модуля используется следующий синтаксис:
module.
Например:
module.frontend.asg_name
Этот синтаксис можно использовать в файле prod/services/webserver-cluster/main.tf, чтобы установить параметр autoscaling_group_name в каждом из ресурсов aws_autoscaling_schedule:
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
Возможно, вам следует сделать доступным еще одно выходное значение в модуле webserver-cluster: доменное имя ALB. Так вы будете знать, какой URL-адрес нужно проверить после развертывания кластера. Для этого в файле /modules/services/webserver-cluster/outputs.tf необходимо еще раз добавить выходную переменную:
output "alb_dns_name" {
value = aws_lb.example.dns_name
description = "The domain name of the load balancer"
}
После этого данный вывод можно «пропустить через» файлы stage/services/webserver-cluster/outputs.tf и prod/services/webserver-cluster/outputs.tf:
output "alb_dns_name" {
value = module.webserver_cluster.alb_dns_name
description = "The domain name of the load balancer"
}
Ваш кластер веб-серверов почти готов к развертыванию. Осталось только принять во внимание несколько подводных камней.
Подводные камни
При создании модулей обращайте внимание:
• на файловые пути;
• вложенные блоки.