Terraform: инфраструктура на уровне кода — страница 25 из 65

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"

}

Ваш кластер веб-серверов почти готов к развертыванию. Осталось только принять во внимание несколько подводных камней.


Подводные камни

При создании модулей обращайте внимание:

• на файловые пути;

• вложенные блоки.