
Работа с AWS VPC и Terraform в Unix/Linux
Amazon Virtual Private Cloud (Amazon VPC) – это логически изолированный раздел облака AWS, в котором можно запускать ресурсы AWS в созданной пользователем виртуальной сети. Пользователь полностью контролирует свою среду виртуальной сети, в том числе может выбирать собственный диапазон IP-адресов, создавать подсети, а также настраивать таблицы маршрутизации и сетевые шлюзы. Для обеспечения удобного и безопасного доступа к ресурсам и приложениям в VPC можно использовать как IPv4, так и IPv6.
Сетевую конфигурацию Amazon VPC можно легко настроить по своему усмотрению. Например, для веб-серверов можно создать публичную подсеть с выходом в Интернет, а внутренние системы, такие как базы данных или серверы приложений, расположить в частной подсети без доступа к Интернету. Можно использовать многоуровневую систему безопасности, состоящую из групп безопасности и сетевых списков контроля доступа (NACL), чтобы контролировать доступ к инстансам Amazon EC2 в каждой подсети.
Кроме того, можно создать подключение между корпоративным центром обработки данных и VPC с помощью аппаратной частной виртуальной сети (VPN) и использовать облако AWS для расширения возможностей корпоративного ЦОД.
Установка terraform в Unix/Linux
Установка крайне примитивная и я описал как это можно сделать тут:
Установка terraform в Unix/Linux
Так же, в данной статье, я создал скрипт для автоматической установки данного ПО. Он был протестирован на CentOS 6/7, Debian 8 и на Mac OS X. Все работает должным образом!
Чтобы получить помощь по использованию команд, выполните:
$ terraform Usage: terraform [--version] [--help] <command> [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by less common or more advanced commands. If you're just getting started with Terraform, stick with the common commands. For the other commands, please read the help and docs before usage. Common commands: apply Builds or changes infrastructure console Interactive console for Terraform interpolations destroy Destroy Terraform-managed infrastructure env Workspace management fmt Rewrites config files to canonical format get Download and install modules for the configuration graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Atlas to run refresh Update local state file against real resources show Inspect Terraform state or plan taint Manually mark a resource for recreation untaint Manually unmark a resource as tainted validate Validates the Terraform files version Prints the Terraform version workspace Workspace management All other commands: debug Debug output management (experimental) force-unlock Manually unlock the terraform state state Advanced state management
Приступим к использованию!
Работа с AWS VPC и Terraform в Unix/Linux
AWS VPC — виртуальное частное облако, которое в основном обеспечивает способ структурирования вашей собственной сети так, как вы хотите. Прежде чем погрузиться, давайте определим некоторые базовые концепции VPC:
- CIDR – Classless Intern-Domain Routing, определяет диапазон адресов, который будет представлять наш VPC .
- Subnets — Подсети, я покажу как можно создать публичные, приватные сети, что будет означать, что общедоступная подсеть может быть доступна из Интернета и частные подсетей не могут быть доступны из Интернета.
- Internet Gateway — Интернет-шлюз — это компонент VPC, который обеспечивает связь между вашим VPC и Интернетом.
- Nat Gateway (Network Address Translation Gateway) — Сетевой шлюз перевода адресов используется, чтобы позволить экземплярам в частных подсетях подключаться к Интернету, но наоборот невозможно, например, это предотвратит запуск интернет-соединения с экземплярами в частных подсетях.
- Route Table — В таблице маршрутов в основном содержатся правила относительно того, куда должны идти пакеты.
- Elastic EIP — EIP будет ассоциироваться с NAT Gateway, поэтому в основном эластичный IP является статическим IP-адресом, связанным с вашей учетной записью AWS, например, вы можете использовать EIP для экземпляра с частным IP-адресом, чтобы экземпляр был доступен из Интернета.
- Route Association Table — Служит для связи своих подсетей с указанными таблицами маршрутов, поэтому правила, определенные в этой таблице маршрутов, будут применяться к подсетям.
Terraform решает проблему взаимодействия с различными инфраструктурными сервисами (AWS, Digital Ocean, OpenStack и т. Д.) И предоставляет для нее унифицированный формат конфигурации.
Вот несколько важных моментов:
- .tf — Расширение для конфигурационных файлов, которые обеспечевают полную конфигурацию. Это дает удобный способ разбить конфигурацию на тематические разделы (Например: vpc, iam, ec2 и тд и тп. Пример для AWS провайдера).
- .tfstate — Содержит последнее известное состояние инфраструктуры. А так же, «.tfstate.backup «служит для его бэкапа.
- .tfvars — Содержат значения для объявленных переменных, обычно называемых: terraform.tfvars.
Чтобы записать все имеющиеся переменные в файл, используем:
$ terraform plan -var-file terraform.tfvars
Чтобы запустить инфраструктуру с созданными переменными, запустим:
$ terraform apply -var-file terraform.tfvars
И чтобы удалить, запустим:
$ terraform destroy -var-file terraform.tfvars
Существует два способа настройки групп безопасности AWS в Terraform. Вы можете определить правила, связанные с ресурсом aws_security_group, или вы можете определить дополнительные дискретные ресурсы aws_security_group_rule.
Мой первый инстинкт заключался в том, чтобы определить «базовую» группу безопасности, используя встроенные правила, а затем расширить ее с помощью внешних правил. Плохая идея. Об этом позже.
Однако для двух действительных вариантов есть важные последствия, и я обнаружил, что они не были ясны на момент написания (около Terraform v0.9.11). После небольшого исследования и экспериментов у меня появилось гораздо больше пониманий.
Создание VPC с помощью terraform
Давайте создадим VPC, например:
#--------------------------------------------------- # Create VPC #--------------------------------------------------- resource "aws_vpc" "aws_vpc" { cidr_block = "${var.vpc_cidr}" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "true" assign_generated_ipv6_cidr_block = "false" enable_classiclink = "false" tags { Name = "my-vpc" } }
И так, чтоже я тут написал:
- resources — Представляет собой нотацию Terraform для создания ресурсов.
- aws_vpc — Терраформ «функция» которая позволяет работать с AWS VPC. В данном случае служит для создания нового VPC.
- aws_vpc — это имя, присвоенное VPC, к которому можно будет обратится, чтобы получить такую вещь, как vpc_id или main_route_table_id.
- cidr_block — Диапазон адресов для VPC.
- instance_tenancy — Если указать «default», то ваш экземпляр будет работать на общем оборудовании. Если указать «dedicated», то ваш экземпляр будет работать на однопользовательском оборудовании.
- Если указать «host», то ваш экземпляр будет работать на выделенном хосте, который является изолированным сервером с конфигурациями, которые вы можете контролировать.
- enable_dns_support — Если установлено значение true, будет включена поддержка DNS для VPC, поэтому это укажет на то, что DNS будет поддерживаться для VPC.
- enable_dns_hostname — Указывает, будут ли экземпляры, запущенные в VPC, получить имена хостов DNS.
- assign_generated_ipv6_cidr_block — Сгенерировать ipv6 для VPC.
- enable_classiclink — Позволяет связать ваш экземпляр EC2-Classic с VPC в вашей учетной записи в том же регионе.
- tags — Теги полезны, если вы хотите классифицировать свои ресурсы.
Создание security group с помощью terraform
Вот как выглядит определение встроенной группы безопасности:
resource "aws_security_group" "allow_all" { name = "allow_all" description = "Allow all inbound traffic" ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 0 to_port = 0 protocol = "-1" ipv6_cidr_blocks = ["::/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" ipv6_cidr_blocks = ["::/0"] } }
В примере что выше, есть два правила: правило для входящего (ingress) и исходящего (egress) соединения, определенное внутри блока ресурсов aws_security_group.
Расмотрим что тут написано более подробно:
- resource — Представляет собой нотацию Terraform для создания ресурсов.
- aws_security_group — Так званая функция, которая позволяет работать с SG и управлять ими через terraform.
- allow_all — Название (так званый алиас) для aws_security_group функции. Служит для полуение дополнительной информации.
- name — Используется для имени SG.
- description — Просто описание данного блока.
- ingress/egress — Входящее/Исходящее соединение для указанных портов.
- from_port — Входящее/Исходящее соединение для указанного порта с хоста.
- to_port — Входящее/Исходящее соединение для указанного порта на хоста.
- protocol — Указываем протокол которые будет использоваться для входящего/исходящего соединения.
- cidr_blocks — Диапазон адресов для SG.
Вот как одна и та же идея может быть выражена с использованием внешних правил через ресурс aws_security_group_rule:
resource "aws_security_group" "allow_all" { name = "allow_all" description = "Allow all inbound traffic" } resource "aws_security_group_rule" "ingress_ipv4" { type = "ingress" from_port = 0 to_port = 0 protocol = -1 cidr_blocks = ["0.0.0.0/0"] security_group_id = "${aws_security_group.allow_all.id}" } resource "aws_security_group_rule" "ingress_ipv6" { type = "ingress" from_port = 0 to_port = 0 protocol = -1 ipv6_cidr_blocks = ["::/0"] security_group_id = "${aws_security_group.allow_all.id}" } resource "aws_security_group_rule" "egress_ipv4" { type = "egress" from_port = 0 to_port = 0 protocol = -1 cidr_blocks = ["0.0.0.0/0"] security_group_id = "${aws_security_group.allow_all.id}" } resource "aws_security_group_rule" "egress_ipv6" { type = "egress" from_port = 0 to_port = 0 protocol = -1 ipv6_cidr_blocks = ["::/0"] security_group_id = "${aws_security_group.allow_all.id}" }
Группа безопасности и каждое ее правило определяются как дискретный ресурс и тесно связанные друг с другом через security_group_id атрибут. Для каждого порта прописывать правила — нормально и можно, но если у вам имеется 10 портов, — это уже не прикольно! По этому, делаем следующее:
variable "allowed_ports" { description = "Allowed ports from/to host" type = "list" default = ["80", "443", "8080", "8443"] } #--------------------------------------------------- # Create security group #--------------------------------------------------- resource "aws_security_group" "aws_security_group" { name = "aws_security_group" description = "Security Group aws_security_group" lifecycle { create_before_destroy = true } # allow traffic for TCP 22 ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # allow traffic from TCP 22 egress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } } #--------------------------------------------------- # Add security group rules (one more way) #--------------------------------------------------- resource "aws_security_group_rule" "ingress_ports" { type = "ingress" count = "${length(var.allowed_ports)}" security_group_id = "${aws_security_group.aws_security_group.id}" from_port = "${element(var.allowed_ports, count.index)}" to_port = "${element(var.allowed_ports, count.index)}" protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } resource "aws_security_group_rule" "egress_ports" { type = "egress" count = "${length(var.allowed_ports)}" security_group_id = "${aws_security_group.aws_security_group.id}" from_port = "${element(var.allowed_ports, count.index)}" to_port = "${element(var.allowed_ports, count.index)}" protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
PS: Я на данном этапе, не выношу переменные в отдельный файл. В самом конце этой темы, я приведу полную конфигурацию всех файлов….
Создание private network с помощью terraform
Создадим приватную сеть:
#--------------------------------------------------- # Add AWS subnet (private) #--------------------------------------------------- resource "aws_subnet" "aws_subnet_private" { cidr_block = "172.31.64.0/20" vpc_id = "${aws_vpc.aws_vpc.id}" map_public_ip_on_launch = "false" availability_zone = "us-east-1a" tags { Name = "aws_subnet_private" } }
Расмотрим что тут имеется:
- resource — Представляет собой нотацию Terraform для создания ресурсов.
- aws_subnet — Так званая функция, которая позволяет работать с subnet и управлять ими через terraform.
- aws_subnet_private — Название (так званый алиас) для aws_subnet функции. Служит для полуение дополнительной информации.
- cidr_block — Диапазон адресов.
- vpc_id — Идентификационный номер созданной VPC.
- map_public_ip_on_launch — (Необязательно) Укажите true, чтобы указать, что экземплярам, запущенным в подсети, должен быть присвоен общедоступный IP-адрес. Значение по умолчанию — false.
- availability_zone — Зона (регион) для разварацивания subnet-а.
- tags — Теги полезны, если вы хотите классифицировать свои ресурсы.
Идем дальше….
Создание public network с помощью terraform
Создадим публичную сеть:
#--------------------------------------------------- # Add AWS subnet (public) #--------------------------------------------------- resource "aws_subnet" "aws_subnet_public" { cidr_block = "172.31.80.0/20" vpc_id = "${aws_vpc.aws_vpc.id}" map_public_ip_on_launch = "false" availability_zone = "us-east-1a" tags { Name = "aws_subnet_public" } }
Расмотрим что тут имеется:
- resource — Представляет собой нотацию Terraform для создания ресурсов.
- aws_subnet — Так званая функция, которая позволяет работать с subnet и управлять ими через terraform.
- aws_subnet_public — Название (так званый алиас) для aws_subnet функции. Служит для полуение дополнительной информации.
- cidr_block — Диапазон адресов.
- vpc_id — Идентификационный номер созданной VPC.
- map_public_ip_on_launch — (Необязательно) Укажите true, чтобы указать, что экземплярам, запущенным в подсети, должен быть присвоен общедоступный IP-адрес. Значение по умолчанию — false.
- availability_zone — Зона (регион) для разварацивания subnet-а.
- tags — Теги полезны, если вы хотите классифицировать свои ресурсы.
Продолжаем….
Создание internet gateway с помощью terraform
Так же, создам интернет гетвей:
#--------------------------------------------------- # Add AWS internet gateway #--------------------------------------------------- resource "aws_internet_gateway" "aws_internet_gateway" { vpc_id = "${aws_vpc.aws_vpc.id}" tags { Name = "internet-gateway" } }
Где:
- aws_internet_gateway: Обозначение Terraform для создания ресурса интернет-шлюза в AWS.
Создание route для internet с помощью terraform
Следующим щагом будет создание роута для интернета:
resource "aws_route_table" "aws_route_table" { vpc_id = "${aws_vpc.aws_vpc.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.aws_internet_gateway.id}" } tags { Name = "aws_internet_gateway-default" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } } resource "aws_route_table_association" "aws_route_table_association" { subnet_id = "${aws_subnet.aws_subnet_private.id}" route_table_id = "${aws_route_table.aws_route_table.id}" }
Или, можно еще сделать следующее:
resource "aws_route" "internet_access" { route_table_id = "${aws_vpc.aws_vpc.main_route_table_id}" destination_cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.aws_internet_gateway.id}" }
Где:
- aws_route — Обозначение Terraform для создания Route ресурса в AWS.
- internet_access — Название, присвоенное маршруту (route)
- route_table_id — Идентификатор таблицы маршрута, который содержит правила, указывающие, как должны быть маршрутизированы пакеты, мы назначим основной идентификатор таблицы маршрутизации vpc, в AWS, когда вы создали VPC, у вас будет таблица основных маршрутов по умолчанию.
- destination_cidr_block — Диапазон адресов.
- gateway_id — Идентификатор шлюза, где все пакеты в Интернете должны быть маршрутизированы, если вы помните, что интернет-шлюз позволяет VPC взаимодействовать с Интернетом.
Создание Elastic IP (EIP) с помощью terraform
Мы создадим этот IP-адрес, чтобы назначить ему NAT-шлюз:
#--------------------------------------------------- # CREATE EIP #--------------------------------------------------- resource "aws_eip" "aws_eip" { vpc = true depends_on = ["aws_internet_gateway.aws_internet_gateway"] }
Пояснения:
- aws_eip — Обозначение Terraform для создания ресурса EIP в AWS.
- aws_eip — Имя, присвоенное ресурсу EIP.
- vpc — Если значение true, то EIP присвоится к VPC.
- depend_on — Условная переменная, которая говорит, что в этом случае ресурс EIP должен быть создан только после того, как Интернет-шлюз уже создан.
Создание NAT Gateway с помощью terraform
Убедитесь, что вы создали nat в подсети, подключенной к Интернету (общедоступная подсетьб — public network):
#--------------------------------------------------- # CREATE NAT #--------------------------------------------------- resource "aws_nat_gateway" "aws_nat_gateway" { allocation_id = "${aws_eip.aws_eip.id}" subnet_id = "${aws_subnet.aws_subnet_public.id}" depends_on = ["aws_internet_gateway.aws_internet_gateway"] }
Где:
- aws_nat_gateway — Обозначение Terraform для создания ресурса NAT Gateway в AWS.
- aws_nat_gateway — Имя, присвоенное ресурсу NAT.
- allocation_id — NAT должен иметь EIP-адрес (статический IP-адрес).
- subnet_id — Идентификатор подсети, в котором будет создан ресурс NAT.
Создание private route table и the route to the internet с помощью terraform
Это позволит всем трафику из частных подсетей (privat network) ходить в интернет через NAT-шлюз (трансляция сетевых адресов, — NAT):
#--------------------------------------------------- # Create private route table and the route to the internet #--------------------------------------------------- resource "aws_route_table" "aws_route_table" { vpc_id = "${aws_vpc.aws_vpc.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.aws_internet_gateway.id}" } tags { Name = "aws_internet_gateway-default" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } }
Создание Route Table Associations с помощью terraform
Теперь свяжем подсети с разными таблицами маршрутов:
#--------------------------------------------------- # Route Table Associations #--------------------------------------------------- # private resource "aws_route_table_association" "aws_route_table_association_private" { subnet_id = "${aws_subnet.aws_subnet_private.id}" route_table_id = "${aws_route_table.aws_route_table_private.id}" } # public resource "aws_route_table_association" "aws_route_table_association_public" { subnet_id = "${aws_subnet.aws_subnet_public.id}" route_table_id = "${aws_vpc.aws_vpc.main_route_table_id}" }
Это я привел некоторые примеры по использованию. Я придерживаюсь того, чтобы все и всегда сортировать (стараюсь так делать), т.е придерживатся так званого стандарта (который придумываю я сам для своих нужд), по этому я расскажу как я делаю.
У меня есть папка terraform, в ней у меня будут лежать провайдеры с которыми я буду работать. Т.к в этом примере я буду использовать AWS, то создам данную папку и перейду в нее. Далее, в этой папке, стоит создать:
$ mkdir examples modules
В папке examples, я буду хранить так званые «плейбуки» для разварачивания различных служб, например — zabbix-server, grafana, web-серверы и так далее. В modules директории, я буду хранить все необходимые модули.
Начнем писать модуль, но для этой задачи, я создам папку:
$ mkdir modules/vpc
Переходим в нее:
$ cd modules/vpc
Открываем файл:
$ vim vpc.tf
В данный файл, вставляем:
#--------------------------------------------------- # Create VPC #--------------------------------------------------- resource "aws_vpc" "vpc" { cidr_block = "${cidrsubnet(var.vpc_cidr, 0, 0)}" #cidr_block = "${var.vpc_cidr}" instance_tenancy = "${var.instance_tenancy}" enable_dns_support = "${var.enable_dns_support}" enable_dns_hostnames = "${var.enable_dns_hostnames}" assign_generated_ipv6_cidr_block = "${var.assign_generated_ipv6_cidr_block}" enable_classiclink = "${var.enable_classiclink}" tags { Name = "${lower(var.name)}-vpc-${lower(var.environment)}" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } } #--------------------------------------------------- # Create security group #--------------------------------------------------- resource "aws_security_group" "sg" { name = "${var.name}-sg-${var.environment}" description = "Security Group ${var.name}-sg-${var.environment}" vpc_id = "${aws_vpc.vpc.id}" tags { Name = "${var.name}-sg-${var.environment}" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } lifecycle { create_before_destroy = true } # allow traffic for TCP 22 to host ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # allow traffic for TCP 22 from host egress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } depends_on = ["aws_vpc.vpc"] } #--------------------------------------------------- # Add security group rules (one more way) #--------------------------------------------------- resource "aws_security_group_rule" "ingress_ports" { count = "${length(var.allowed_ports)}" type = "ingress" security_group_id = "${aws_security_group.sg.id}" from_port = "${element(var.allowed_ports, count.index)}" to_port = "${element(var.allowed_ports, count.index)}" protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] depends_on = ["aws_security_group.sg"] } resource "aws_security_group_rule" "egress_ports" { count = "${var.enable_all_egress_ports ? 0 : length(var.allowed_ports)}" type = "egress" security_group_id = "${aws_security_group.sg.id}" from_port = "${element(var.allowed_ports, count.index)}" to_port = "${element(var.allowed_ports, count.index)}" protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] depends_on = ["aws_security_group.sg"] } resource "aws_security_group_rule" "icmp-self" { security_group_id = "${aws_security_group.sg.id}" type = "ingress" protocol = "icmp" from_port = -1 to_port = -1 self = true depends_on = ["aws_security_group.sg"] } resource "aws_security_group_rule" "default_egress" { count = "${var.enable_all_egress_ports ? 1 : 0}" type = "egress" security_group_id = "${aws_security_group.sg.id}" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] depends_on = ["aws_security_group.sg"] } #--------------------------------------------------- # Add AWS subnets (private) #--------------------------------------------------- resource "aws_subnet" "private_subnets" { count = "${length(var.private_subnet_cidrs)}" cidr_block = "${element(var.private_subnet_cidrs, count.index)}" vpc_id = "${aws_vpc.vpc.id}" map_public_ip_on_launch = "false" #count = "${length(var.availability_zones)}" #availability_zone = "${element(var.availability_zones, count.index)}" availability_zone = "${element(var.availability_zones, 0)}" #availability_zone = "${element(split(",", var.availability_zones), count.index)}" tags { Name = "private_subnet-${element(var.availability_zones, count.index)}" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } #--------------------------------------------------- # Add AWS subnets (public) #--------------------------------------------------- resource "aws_subnet" "public_subnets" { count = "${length(var.public_subnet_cidrs)}" cidr_block = "${element(var.public_subnet_cidrs, count.index)}" vpc_id = "${aws_vpc.vpc.id}" map_public_ip_on_launch = "${var.map_public_ip_on_launch}" #count = "${length(var.availability_zones)}" #availability_zone = "${element(var.availability_zones, count.index)}" availability_zone = "${element(var.availability_zones, 0)}" tags { Name = "public_subnet-${element(var.availability_zones, count.index)}" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } #--------------------------------------------------- # Add AWS internet gateway #--------------------------------------------------- resource "aws_internet_gateway" "internet_gw" { count = "${length(var.public_subnet_cidrs) > 0 ? 1 : 0}" vpc_id = "${aws_vpc.vpc.id}" tags { Name = "internet-gateway to ${var.name}-vpc-${var.environment}" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } resource "aws_route_table" "public_route_tables" { count = "${length(var.public_subnet_cidrs) > 0 ? 1 : 0}" vpc_id = "${aws_vpc.vpc.id}" propagating_vgws = ["${var.public_propagating_vgws}"] tags { Name = "public_route_tables" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } resource "aws_route" "public_internet_gateway" { count = "${length(var.public_subnet_cidrs) > 0 ? 1 : 0}" route_table_id = "${aws_route_table.public_route_tables.id}" destination_cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.internet_gw.id}" depends_on = ["aws_internet_gateway.internet_gw", "aws_route_table.public_route_tables"] } #--------------------------------------------------- # CREATE EIP #--------------------------------------------------- resource "aws_eip" "nat_eip" { count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0}" vpc = true depends_on = ["aws_internet_gateway.internet_gw"] } #--------------------------------------------------- # CREATE NAT #--------------------------------------------------- resource "aws_nat_gateway" "nat_gw" { count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0}" allocation_id = "${element(aws_eip.nat_eip.*.id, (var.single_nat_gateway ? 0 : count.index))}" subnet_id = "${element(aws_subnet.public_subnets.*.id, (var.single_nat_gateway ? 0 : count.index))}" depends_on = ["aws_internet_gateway.internet_gw", "aws_subnet.public_subnets"] } #--------------------------------------------------- # Create private route table and the route to the internet #--------------------------------------------------- resource "aws_route_table" "private_route_tables" { count = "${length(var.availability_zones)}" vpc_id = "${aws_vpc.vpc.id}" propagating_vgws = ["${var.private_propagating_vgws}"] tags { Name = "private_route_tables" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } resource "aws_route" "private_nat_gateway" { count = "${var.enable_nat_gateway ? length(var.availability_zones) : 0}" route_table_id = "${element(aws_route_table.private_route_tables.*.id, count.index)}" destination_cidr_block = "0.0.0.0/0" nat_gateway_id = "${element(aws_nat_gateway.nat_gw.*.id, count.index)}" depends_on = ["aws_nat_gateway.nat_gw", "aws_route_table.private_route_tables"] } #--------------------------------------------------- # CREATE VPN #--------------------------------------------------- ############################### # VPN Gateway ############################### resource "aws_vpn_gateway" "vpn_gw" { count = "${var.enable_vpn_gateway ? 1 : 0}" vpc_id = "${aws_vpc.vpc.id}" tags { Name = "vpn_gateway" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } depends_on = ["aws_vpc.vpc"] } #--------------------------------------------------- # CREATE DHCP #--------------------------------------------------- resource "aws_vpc_dhcp_options" "vpc_dhcp_options" { count = "${var.enable_dhcp_options ? 1 : 0}" domain_name = "${var.dhcp_options_domain_name}" domain_name_servers = "${var.dhcp_options_domain_name_servers}" ntp_servers = "${var.dhcp_options_ntp_servers}" netbios_name_servers = "${var.dhcp_options_netbios_name_servers}" netbios_node_type = "${var.dhcp_options_netbios_node_type}" tags { Name = "dhcp" Environment = "${var.environment}" Orchestration = "${var.orchestration}" Createdby = "${var.createdby}" } } #--------------------------------------------------- # Route Table Associations #--------------------------------------------------- ############################## # private ############################## resource "aws_route_table_association" "private_route_table_associations" { count = "${length(var.private_subnet_cidrs)}" subnet_id = "${element(aws_subnet.private_subnets.*.id, count.index)}" route_table_id = "${element(aws_route_table.private_route_tables.*.id, count.index)}" depends_on = ["aws_route_table.private_route_tables", "aws_subnet.private_subnets"] } ############################## # public ############################## resource "aws_route_table_association" "public_route_table_associations" { count = "${length(var.public_subnet_cidrs)}" subnet_id = "${element(aws_subnet.public_subnets.*.id, count.index)}" route_table_id = "${aws_route_table.public_route_tables.id}" depends_on = ["aws_route_table.public_route_tables", "aws_subnet.public_subnets"] } ############################### # DHCP Options Set Association ############################### resource "aws_vpc_dhcp_options_association" "vpc_dhcp_options_association" { count = "${var.enable_dhcp_options ? 1 : 0}" vpc_id = "${aws_vpc.vpc.id}" dhcp_options_id = "${aws_vpc_dhcp_options.vpc_dhcp_options.id}" depends_on = ["aws_vpc.vpc", "aws_vpc_dhcp_options.vpc_dhcp_options"] }
Открываем файл:
$ vim variables.tf
И прописываем:
#----------------------------------------------------------- # Global or/and default variables #----------------------------------------------------------- variable "name" { description = "Name to be used on all resources as prefix" default = "TEST" } variable "instance_tenancy" { description = "instance tenancy" default = "default" } variable "enable_dns_support" { description = "Enabling dns support" default = "true" } variable "enable_dns_hostnames" { description = "Enabling dns hostnames" default = "true" } variable "assign_generated_ipv6_cidr_block" { description = "Generation IPv6" default = "false" } variable "enable_classiclink" { description = "Enabling classiclink" default = "false" } variable "environment" { description = "Environment for service" default = "STAGE" } variable "orchestration" { description = "Type of orchestration" default = "Terraform" } variable "createdby" { description = "Created by" default = "Vitaliy Natarov" } #--------------------------------------------------------------- # Custom variables #--------------------------------------------------------------- variable "allowed_ports" { description = "Allowed ports from/to host" type = "list" } variable "enable_all_egress_ports" { description = "Allows all ports from host" default = false } variable "vpc_cidr" { description = "CIDR for the whole VPC" #type = "list" #default = [] } variable "public_subnet_cidrs" { description = "CIDR for the Public Subnet" type = "list" default = [] } variable "private_subnet_cidrs" { description = "CIDR for the Private Subnet" type = "list" default = [] } variable "availability_zones" { description = "A list of Availability zones in the region" type = "list" default = [] } #variable "availability_zones" { # type = "map" # # default = { # us-east-1 = ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d", "us-east-1e", "us-east-1f"] # us-east-2 = ["us-east-2a", "eu-east-2b", "eu-east-2c"] # us-west-1 = ["us-west-1a", "us-west-1c"] # us-west-2 = ["us-west-2a", "us-west-2b", "us-west-2c"] # ca-central-1 = ["ca-central-1a", "ca-central-1b"] # eu-west-1 = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] # eu-west-2 = ["eu-west-2a", "eu-west-2b"] # eu-central-1 = ["eu-central-1a", "eu-central-1b", "eu-central-1c"] # ap-south-1 = ["ap-south-1a", "ap-south-1b"] # sa-east-1 = ["sa-east-1a", "sa-east-1c"] # ap-northeast-1 = ["ap-northeast-1a", "ap-northeast-1c"] # ap-southeast-1 = ["ap-southeast-1a", "ap-southeast-1b"] # ap-southeast-2 = ["ap-southeast-2a", "ap-southeast-2b", "ap-southeast-2c"] # ap-northeast-1 = ["ap-northeast-1a", "ap-northeast-1c"] # ap-northeast-2 = ["ap-northeast-2a", "ap-northeast-2c"] # } #} variable "enable_internet_gateway" { description = "Allow Internet GateWay to/from public network" default = "false" } variable "private_propagating_vgws" { description = "A list of VGWs the private route table should propagate." type = "list" default = [] } variable "public_propagating_vgws" { description = "A list of VGWs the public route table should propagate." default = [] } variable "enable_vpn_gateway" { description = "Should be true if you want to create a new VPN Gateway resource and attach it to the VPC" default = false } variable "enable_nat_gateway" { description = "Allow Nat GateWay to/from private network" default = "false" } variable "single_nat_gateway" { description = "should be true if you want to provision a single shared NAT Gateway across all of your private networks" default = "false" } variable "enable_eip" { description = "Allow creation elastic eip" default = "false" } variable "map_public_ip_on_launch" { description = "should be false if you do not want to auto-assign public IP on launch" default = "true" } variable "enable_dhcp_options" { description = "Should be true if you want to specify a DHCP options set with a custom domain name, DNS servers, NTP servers, netbios servers, and/or netbios server type" default = false } variable "dhcp_options_domain_name" { description = "Specifies DNS name for DHCP options set" default = "" } variable "dhcp_options_domain_name_servers" { description = "Specify a list of DNS server addresses for DHCP options set, default to AWS provided" type = "list" default = ["AmazonProvidedDNS"] } variable "dhcp_options_ntp_servers" { description = "Specify a list of NTP servers for DHCP options set" type = "list" default = [] } variable "dhcp_options_netbios_name_servers" { description = "Specify a list of netbios servers for DHCP options set" type = "list" default = [] } variable "dhcp_options_netbios_node_type" { description = "Specify netbios node_type for DHCP options set" default = "" }
Собственно в этом файле храняться все переменные. Спасибо кэп!
Открываем последний файл:
$ vim outputs.tf
и в него вставить нужно следующие строки:
output "instance_tenancy" { value = "${aws_vpc.vpc.instance_tenancy}" } output "vpc_id" { value = "${aws_vpc.vpc.id}" } output "vpc_cidr_block" { value = "${aws_vpc.vpc.cidr_block}" } output "default_network_acl_id" { value = "${aws_vpc.vpc.default_network_acl_id}" } output "security_group_id" { value = "${aws_security_group.sg.id}" } output "default_security_group_id" { value = "${aws_vpc.vpc.default_security_group_id}" } output "public_route_table_ids" { value = ["${aws_route_table.public_route_tables.*.id}"] } output "private_route_table_ids" { value = ["${aws_route_table.private_route_tables.*.id}"] } output "vpc-publicsubnets" { value = "${aws_subnet.public_subnets.*.cidr_block}" } output "vpc-publicsubnet-id_0" { value = "${aws_subnet.public_subnets.0.id}" } output "vpc-publicsubnet-ids" { value = "${aws_subnet.public_subnets.*.id}" } output "vpc-privatesubnets" { value = "${aws_subnet.private_subnets.*.cidr_block}" } output "vpc-privatesubnet-ids" { value = "${aws_subnet.private_subnets.*.id}" } output "nat_eips" { value = ["${aws_eip.nat_eip.*.id}"] } output "nat_eips_public_ips" { value = ["${aws_eip.nat_eip.*.public_ip}"] } output "natgw_ids" { value = ["${aws_nat_gateway.nat_gw.*.id}"] } # Internet Gateway output "gateway_id" { description = "The ID of the Internet Gateway" value = "${element(concat(aws_internet_gateway.internet_gw.*.id, list("")), 0)}" } # VPN Gateway output "vgw_id" { description = "The ID of the VPN Gateway" value = "${element(concat(aws_vpn_gateway.vpn_gw.*.id, list("")), 0)}" }
Данный файл служит для того, чтобы выводить нужные значения на экран вашего экрана.
Переходим теперь в папку aws/examples и создадим еще одну папку для проверки написанного чуда:
$ mkdir vpc && cd $_
Внутри созданной папки открываем файл:
$ vim main.tf
И вставим в него следующий код:
# # MAINTAINER Vitaliy Natarov "vitaliy.natarov@yahoo.com" # terraform { required_version = "> 0.9.0" } provider "aws" { region = "us-east-1" # alias = "us-east-1" shared_credentials_file = "${pathexpand("~/.aws/credentials")}" # access_key = "${var.aws_access_key}" # secret_key = "${var.aws_secret_key}" } module "vpc" { source = "../../modules/vpc" name = "TEST-VPC" environment = "PROD" # VPC instance_tenancy = "dedicated" enable_dns_support = "true" enable_dns_hostnames = "true" assign_generated_ipv6_cidr_block = "false" enable_classiclink = "false" vpc_cidr = "172.31.0.0/16" private_subnet_cidrs = ["172.31.64.0/20"] public_subnet_cidrs = ["172.31.80.0/20"] availability_zones = ["us-east-1a", "us-east-1b"] allowed_ports = ["80", "3306", "80", "443"] #Internet-GateWay enable_internet_gateway = "true" #NAT enable_nat_gateway = "false" single_nat_gateway = "true" #VPN enable_vpn_gateway = "false" #DHCP enable_dhcp_options = "false" # EIP enable_eip = "false" }
В этом файле, имеется:
- required_version = «> 0.9.0» — служит для указания версии ПО с которой будет работать ваш модуль.
- provider — Собственно, — это провайдер с которым вы будете работать и разварачивать структуру. Сушествует довольно большое количество. В моем примере, используется AWS.
- region — Представляет собой географическое местоположение aws, в котором будут созданы ваши ресурсы.
- access_key — Ваш access_key будет использоваться Terraform для выполнения какой-либо задачи на вашей учетной записи AWS от вашего имени.
- secret_key — Ваш secret_key, будет использоваться Terraform для выполнения какой-либо задачи на вашей учетной записи AWS от вашего имени.
Нашел прикольную фишку, можно прописать:
provider "aws" { region = "us-east-1" alias = "us-east-1" shared_credentials_file = "${pathexpand("~/.aws/credentials")}" }
Где:
- region — Регион для разварачивания структуры.
- alias — Алиас на данный (указанный) провайдер.
- shared_credentials_file — Креды для подключения к AWS через терраформ.
И в своих модулях прописывать (например):
resource "aws_vpc" "us-east-1" { provider = "aws.us-east-1" enable_dns_support = true enable_dns_hostnames = true assign_generated_ipv6_cidr_block = true cidr_block = "10.0.0.0/16" }
Тоже облегчает жизнь…. Идем дельше….
Все уже написано и готово к использованию. Ну что, начнем тестирование. В папке с вашим плейбуком, выполняем:
$ terraform init
Этим действием я инициализирую проект. Затем, подтягиваю модуль:
$ terraform get
PS: Для обновление изменений в самом модуле, можно выполнять:
$ terraform get -update
Запускем прогон:
$ terraform plan
ИЛИ:
$ terraform plan -out tera
Мне вывело что все у меня хорошо и можно запускать деплой:
$ terraform apply
Открываем AWS аккаунт и смотрим что вышло.
Созданная AWS VPC через terraform:
Созданные подсети через терраформ:
И так далее….
Чтобы визуализировать представление плана выполнения, запустите следующую команду:
$ terraform graph | dot -Tpng > graph.png
Данная команда сохранит вывод в PNG файл, чтобы просто посмотреть вывод, выполните:
$ terraform graph
Чтобы удалить созданное творение, можно выполнить:
$ terraform destroy
Полезные ссылки:
Работа с AWS IAM и Terraform в Unix/Linux
Работа с AWS ELB и Terraform в Unix/Linux
Работа с AWS ASG(auto scaling group) и Terraform в Unix/Linux
Работа с AWS EC2 и Terraform в Unix/Linux
Работа с AWS Route53 и Terraform в Unix/Linux
Работа с AWS RDS и Terraform в Unix/Linux
Работа с AWS S3 и Terraform в Unix/Linux
Весь материал аплоаджу в github аккаунт для удобства использования:
$ git clone https://github.com/SebastianUA/terraform.git
Вот и все на этом. Данная статья «Работа с AWS VPC и Terraform в Unix/Linux» завершена.