Работа с AWS VPC и Terraform в Unix/Linux

Работа с 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:

Созданная 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» завершена.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *