ami = "ami-0c55b159cbfafe1f0"
~ availability_zone = "us-east-2c" -> (known after apply)
~ instance_state = "running" -> (known after apply)
instance_type = "t2.micro"
(...)
+ user_data = "c765373..." # forces replacement
~ volume_tags = {} -> (known after apply)
~ vpc_security_group_ids = [
- "sg-871fa9ec",
] -> (known after apply)
(...)
}
# aws_security_group.instance will be created
+ resource "aws_security_group" "instance" {
+ arn = (known after apply)
+ description = "Managed by Terraform"
+ egress = (known after apply)
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = ""
+ from_port = 8080
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 8080
},
]
+ name = "terraform-example-instance"
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ vpc_id = (known after apply)
}
Plan: 2 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Символы -/+ в выводе плана означают «заменить». Чтобы понять, чем продиктовано то или иное изменение, поищите в выводе плана словосочетание forces replacement. Изменение многих аргументов ресурса aws_instance приводит к замене. Это означает, что имеющийся сервер EC2 будет удален, а его место займет совершено новый сервер. Это пример парадигмы неизменяемой инфраструктуры, которую мы обсуждали в подразделе «Средства шаблонизации серверов» на с. 31. Стоит отметить, что, несмотря на замену веб-сервера, ни один из его пользователей не заметит перебоев в работе; в главе 5 вы увидите, как с помощью Terraform выполнять развертывания с нулевым временем простоя.
Похоже, с планом все в порядке, поэтому введите yes, и вы увидите, как развертывается новый сервер EC2 (рис. 2.8).
Если щелкнуть на новом сервере, внизу страницы, на панели с описанием, можно увидеть его публичный IP-адрес. Дайте ему минуту или две, чтобы он загрузился, и затем сделайте HTTP-запрос по этому адресу на порте 8080, используя браузер или утилиту вроде curl:
$ curl http://
Hello, World
Ура! Теперь у вас есть рабочий веб-сервер, запущенный в AWS!
Рис. 2.8. Вместо старого сервера EC2 мы получаем новый, с кодом веб-сервера
Сетевая безопасность
Чтобы не усложнять примеры в этой книге, развертывание происходит не только в VPC по умолчанию (как упоминалось ранее), но и в стандартные подсети этого VPC. VPC состоит из одной или нескольких подсетей, каждая из которых имеет собственные IP-адреса. Все подсети в VPC по умолчанию являются публичными — их IP-адреса доступны из Интернета. Благодаря этому вы можете проверить работу своего сервера EC2 на домашнем компьютере.
Размещение сервера в публичной подсети подходит для быстрого эксперимента, но в реальных условиях это потенциальный риск безопасности. Хакеры со всего мира постоянно сканируют IP-адреса случайным образом в надежде найти какие-нибудь уязвимости. Если ваши серверы доступны снаружи, достаточно лишь оставить незащищенным один порт или воспользоваться устаревшим кодом с известной уязвимостью — и кто-то сможет проникнуть внутрь.
Таким образом, в промышленных системах все серверы и уж точно все хранилища данных следует развертывать в закрытых подсетях, IP-адреса которых доступны только внутри VPC, но не из публичного Интернета. Все, что должно находиться в публичных подсетях, — это небольшое количество обратных прокси и балансировщиков нагрузки, в которых закрыто все, что только можно (позже в этой главе вы увидите пример того, как развернуть балансировщик нагрузки).
Развертывание конфигурируемого веб-сервера
Вы, наверное, заметили, что код веб-сервера дублирует порт 8080 в группе безопасности и конфигурации пользовательских данных. Это противоречит принципу «не повторяйся» (don’t repeat yourself, или DRY): каждый элемент информации в системе должен иметь единое, однозначное и достоверное представление31. Если номер порта указан в двух местах, легко оказаться в ситуации, когда одно из значений обновлено, а другое — нет.
Чтобы можно было сделать ваш код более конфигурируемым и отвечающим принципу DRY, Terraform позволяет определять входные переменные. Для этого предусмотрен следующий синтаксис:
variable "NAME" {
[CONFIG ...]
}
Тело объявления переменной может содержать три необязательных параметра.
•description. Этот параметр всегда желательно указывать для документирования того, как используется переменная. Ваши коллеги смогут просмотреть это описание не только при чтении кода, но и во время выполнения команд plan или apply (пример этого показан чуть ниже).
• default. Вы можете присвоить значение переменной несколькими способами, в том числе через командную строку (с помощью параметра -var), файл (указывая параметр -var-file) или переменную среды (Terraform ищет переменные среды вида TF_VAR_<имя_переменной>). Если переменная не инициализирована, ей присваивается значение по умолчанию. Если такого нет, Terraform запросит его у пользователя в интерактивном режиме.
•type. Позволяет применить к переменным, которые передает пользователь, ограничения типов. Terraform поддерживает ряд ограничений для таких типов, как string, number, bool, list, map, set, object, tuple и any. Если тип не указан, Terraform воспринимает значение как any.
Вот пример входной переменной, которая проверяет, является ли передаваемое значение числом:
variable "number_example" {
description = "An example of a number variable in Terraform"
type = number
default = 42
}
А вот пример переменной, которая проверяет, является ли значение списком:
variable "list_example" {
description = "An example of a list in Terraform"
type = list
default = ["a", "b", "c"]
}
Ограничения типов можно сочетать. Например, вот входная переменная, которая принимает список и требует, чтобы все значения этого списка были числовыми:
variable "list_numeric_example" {
description = "An example of a numeric list in Terraform"
type = list(number)
default = [1, 2, 3]
}
А вот ассоциативный массив, который требует, чтобы все значения были строковыми:
variable "map_example" {
description = "An example of a map in Terraform"
type = map(string)
default = {
key1 = "value1"
key2 = "value2"
key3 = "value3"
}
}
Вы также можете создавать более сложные структурные типы, используя ограничения object и tuple:
variable "object_example" {
description = "An example of a structural type in Terraform"
type = object({
name = string
age = number
tags = list(string)
enabled = bool
})
default = {
name = "value1"
age = 42
tags = ["a", "b", "c"]
enabled = true
}
}
Выше создается входная переменная, которая требует, чтобы значение было объектом с ключами name (строка), age (число), tags (список строк) и enabled (булево значение). Если попытаться присвоить такой переменной значение, которое не соответствует этому типу, Terraform немедленно вернет ошибку типизации. В следующем примере демонстрируется попытка присвоить enabled строку вместо булева значения:
variable "object_example_with_error" {
description = "An example of a structural type in Terraform with an error"
type = object({
name = string
age = number
tags = list(string)
enabled = bool
})
default = {
name = "value1"
age = 42
tags = ["a", "b", "c"]
enabled = "invalid"
}
}
Вы получите следующую ошибку:
$ terraform apply
Error: Invalid default value for variable
on variables.tf line 78, in variable "object_example_with_error":
78: default = {
79: name = "value1"
80: age = 42
81: tags = ["a", "b", "c"]
82: enabled = "invalid"
83: }
This default value is not compatible with the variable's type constraint: a bool is required.
Для примера с веб-сервером достаточно переменной, которая хранит номер порта:
variable "server_port" {
description = "The port the server will use for HTTP requests"
type = number
}
Обратите внимание, что у входной переменной server_port нет поля default, поэтому, если выполнить команду apply прямо сейчас, Terraform сразу же попросит ввести значение для server_port и покажет вам описание description:
$ terraform apply
var.server_port
The port the server will use for HTTP requests
Enter a value:
Если вы не хотите иметь дело с интерактивной строкой ввода, можете предоставить значение переменной с помощью параметра командной строки -var: