Sbercloud.Advanced. Разворачиваем инфраструктуру при помощи Terraform

В одной из наших предыдущих статей мы уже делали обзор на Terraform в облаке Sbercloud.Advanced.
В рамках данной статьи рассмотрим на практике пример того, как можно развернуть инфраструктуру при помощи Terraform в Sbercloud.Advanced.
Задача
Задача, развернуть инфраструктуру при помощи Terraform в Sbercloud.Advanced.
Требования к инфраструктуре:
  • Подсеть для ресурсов опубликованных в интернет (public subnet).
  • Подсеть для ресурсов без публикации в интернет (private subnet).
  • Подсеть для NAT и VPN Gateway.
  • NAT Gateway для доступа хостов в интернет.
  • Network ACL для каждой подсети.
  • Два севера на базе ОС Windows Server 2019 в приватной подсети в разных зонах доступности.
  • Два сервера на базе Ubuntu 20.04 в публичной подсети в разных зонах доступности.
  • Security group для хостов.
  • Load balancer для балансировки http трафика между серверами в публичной подсети.
  • VPN Gateway для создания IPSEC туннеля с офисом компании.
  • Схема выглядит следующим образом:
В данном сценарии сервера pub-srv1 и pub-srv2 являются web серверами, опубликованными в интернет, входящий web трафик будет балансироваться при помощи load balancer.
На момент написания данной статьи нельзя создать VPN Gateway и IPSEC туннель при помощи Terraform в Sbercloud, поэтому VPN Gateway и IPSEC туннель будут созданы через консоль.
Требования к сетевым доступам для подсетей (правила будут созданы в Network ACL):
  • Исходящий трафик из публичной подсети разрешен только в интернет.
  • Входящий трафик до публичной подсети разрешен только из офисной подсети 172.16.0.0/24.
  • Исходящий трафик из приватной подсети разрешен в интернет и во все офисные подсети (172.16.0.0/20).
  • Входящий трафик до приватной подсети разрешен из всех офисных подсетей (172.16.0.0/20).
Требования к сетевым доступам для хостов (правила будут созданы в Security group):
  • К хостам pri-srv1 и pri-srv2 нет специфичных требований по доступам и применяются общие доступа для всей подсети, в security group 2 будет создано правило permit any.
  • К хостам pub-srv1 и pub-srv2 входящий доступ разрешен по icmp и tcp 22, 80.
Приступим к созданию конфигурации в Terraform
Примечание: В данном примере, для упрощения, вся конфигурация будет находиться в одном файле, в реальном сценарии хранить конфигурацию в одном файле не рекомендуется.
Первым делом укажем Terraform с каким провайдером мы будем работать:
# Конфигурируем провайдера Sbercloud
#
terraform {
  required_providers {
    sbercloud = {
      source  = "sbercloud-terraform/sbercloud"
      version = "1.4.0"
    }
  }
}
provider "sbercloud" {
  region      = "ru-moscow-1"
  project_name = "ru-moscow-1_test"
  access_key  = var.access_key
  secret_key  = var.secret_key
}
Мы указали, что нам требуется провайдер sbercloud версии 1.4.0 и что мы будем использовать регион ru-moscow-1 и проект с именем ru-moscow-1_test.
В нашем примере backends будет храниться локально, в продакшн сценарии хранить bakends локально не рекомендуется и в коде следует указать как и где его хранить. Подробнее об этом можно почитать на официальном сайте.

Далее объявляем необходимые переменные:
# Объявляем необходимые переменные
#
variable "access_key" {
  description = "Access Key to access SberCloud"
  sensitive  = true
}
variable "secret_key" {
  description = "Secret Key to access SberCloud"
  sensitive  = true
}
variable "srv_admin_pass" {
  description = "Default admin password for Windows servers"
  sensitive  = true
}
access_key и secret_key - это ключи для доступа к sbercloud.
srv_admin_pass - пароль, который будет установлен для учетной записи администратора на создаваемых нами Windows серверах.
Значения данных переменных лучше всего хранить в отдельном файле иначе любой, кто имеет доступ к конфигурационному файлу Terraform, будет знать ключи доступа и пароли.
Если не указывать значения в файле конфигурации и не создавать отдельный файл с указанием переменных, то при каждом применении конфигурации Terraform попросить ввести значения вручную.

Получаем список зон доступности и flavors (тип конфигурации вычислительной мощности) для ecs:
# Получаем список зон доступности
#
data "sbercloud_availability_zones" "az_list" {}
# Получаем имя типа инстанса для наших ECS
#
data "sbercloud_compute_flavors" "flavor_n_2_8" {
  availability_zone = data.sbercloud_availability_zones.az_list.names[0]
  performance_type  = "normal"
  cpu_core_count    = 2
  memory_size      = 8
}
data "sbercloud_compute_flavors" "flavor_n_4_16" {
  availability_zone = data.sbercloud_availability_zones.az_list.names[0]
  performance_type  = "normal"
  cpu_core_count    = 4
  memory_size      = 16
}
Мы будем использовать два типа виртуальных машин, для серверов на базе ubuntu: 2 CPU и 8 GB RAM, для серверов на базе Windows server: 4 CPU и 16 GB RAM.
Посмотреть какие типы виртуальным машин есть в Sbercloud можно на данной странице.

Определяем локальные переменные:
# Определяем локальные переменные
#
locals {
  number_of_az = length(data.sbercloud_availability_zones.az_list.names)
  ecs_count    = "2"
}
Данные переменные нам понадобятся позже при создании виртуальных машин.

Создаем VPC и подсети:
# Создаем VPC
#
resource "sbercloud_vpc" "vpc_01" {
  name = "vpc-01"
  cidr = "10.0.0.0/16"
}
# Создаем подсети
#
resource "sbercloud_vpc_subnet" "subnet_public" {
  name      = "snet-public"
  cidr      = "10.0.0.0/24"
  gateway_ip = "10.0.0.1"
  primary_dns  = "100.125.13.59"
  secondary_dns = "8.8.8.8"
  vpc_id = sbercloud_vpc.vpc_01.id
}
resource "sbercloud_vpc_subnet" "subnet_private" {
  name      = "snet-private"
  cidr      = "10.0.1.0/24"
  gateway_ip = "10.0.1.1"
  primary_dns  = "100.125.13.59"
  secondary_dns = "8.8.8.8"
  vpc_id = sbercloud_vpc.vpc_01.id
}
resource "sbercloud_vpc_subnet" "subnet_nat_vpn" {
  name      = "snet-nat_vpn_gw"
  cidr      = "10.0.2.0/28"
  gateway_ip = "10.0.2.1"
  primary_dns  = "100.125.13.59"
  secondary_dns = "8.8.8.8"
  vpc_id = sbercloud_vpc.vpc_01.id
}
Мы создадим VPC с именем vpc-01, а также создадим три подсети в данном VPC. Вы можете заметить, что в качестве primary dns сервера указан 100.125.13.59 - это dns сервер, который рекомендует использовать Sbercloud.

Создаем Network ACL и прописываем нужные нам доступа:
resource "sbercloud_network_acl_rule" "acl_rule_1" {
  name                  = "allow-snet-office_out"
  description            = "allow access to subnet 172.16.0.0/20"
  action                = "allow"
  protocol              = "any"
  source_ip_address      = "0.0.0.0/0"
  destination_ip_address = "172.16.0.0/20"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_2" {
  name                  = "allow-snet-office_in"
  description            = "allow access from subnet 172.16.0.0/20"
  action                = "allow"
  protocol              = "any"
  source_ip_address      = "172.16.0.0/20"
  destination_ip_address = "0.0.0.0/0"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_3" {
  name                  = "allow-snet-admins_in"
  description            = "allow access from subnet 172.16.0.0/24"
  action                = "allow"
  protocol              = "any"
  source_ip_address      = "172.16.0.0/24"
  destination_ip_address = "0.0.0.0/0"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_4" {
  name                  = "deny-rfc1918_out_1"
  description            = "deny access to subnet 10.0.0.0/8"
  action                = "deny"
  protocol              = "any"
  source_ip_address      = "0.0.0.0/0"
  destination_ip_address = "10.0.0.0/8"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_5" {
  name                  = "deny-rfc1918_out_2"
  description            = "deny access to subnet 172.16.0.0/12"
  action                = "deny"
  protocol              = "any"
  source_ip_address      = "0.0.0.0/0"
  destination_ip_address = "172.16.0.0/12"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_6" {
  name                  = "deny-rfc1918_out_3"
  description            = "deny access to subnet 192.168.0.0/16"
  action                = "deny"
  protocol              = "any"
  source_ip_address      = "0.0.0.0/0"
  destination_ip_address = "192.168.0.0/16"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_7" {
  name                  = "allow-any_out"
  description            = "Allow access to any"
  action                = "allow"
  protocol              = "any"
  source_ip_address      = "0.0.0.0/0"
  destination_ip_address = "0.0.0.0/0"
  enabled                = "true"
}
resource "sbercloud_network_acl_rule" "acl_rule_8" {
  name                  = "allow-heath_check_in"
  description            = "Allow access for health check"
  action                = "allow"
  protocol              = "tcp"
  source_ip_address      = "100.125.0.0/16"
  destination_ip_address = "0.0.0.0/0"
  destination_port      = "80"
  enabled                = "true"
}
resource "sbercloud_network_acl" "nacl_01" {
  name          = "acl-public"
  subnets        = [sbercloud_vpc_subnet.subnet_public.id]
  inbound_rules  = [sbercloud_network_acl_rule.acl_rule_3.id,sbercloud_network_acl_rule.acl_rule_8.id]
  outbound_rules = [sbercloud_network_acl_rule.acl_rule_4.id,sbercloud_network_acl_rule.acl_rule_5.id,sbercloud_network_acl_rule.acl_rule_6.id,
  sbercloud_network_acl_rule.acl_rule_7.id]
}
resource "sbercloud_network_acl" "nacl_02" {
  name          = "acl-private"
  subnets        = [sbercloud_vpc_subnet.subnet_private.id]
  inbound_rules  = [sbercloud_network_acl_rule.acl_rule_2.id]
  outbound_rules = [sbercloud_network_acl_rule.acl_rule_1.id,sbercloud_network_acl_rule.acl_rule_4.id,sbercloud_network_acl_rule.acl_rule_5.id,
  sbercloud_network_acl_rule.acl_rule_6.id,sbercloud_network_acl_rule.acl_rule_7.id]
}
Сначала мы прописываем нужные правила, после чего указываем какие правила в каком ACL нужно использовать.
Поскольку у нас на каждую подсеть своя ACL, а некоторые правила пересекаются, то для того чтобы одно правило можно было применить на несколько ACL мы указываем 0.0.0.0/0 вместо подсети на которую мы хотим применить данное правило.
Можно использовать одну ACL на несколько подсетей, тогда мы бы указывали точный адрес подсети.
Правило номер 8 разрешает входящий трафик на порт tcp 80 с подсети 100.125.0.0/16, данного доступа не было в требованиях, без этого доступа load balancer не сможет проверить доступность наших серверов и будет считать, что они недоступны.

Создаем группы безопасности и правило для них:
# Создаем Security groups
#
resource "sbercloud_networking_secgroup" "sg_01" {
  name        = "sg-01"
  description = "Security group allows tcp 22,80 and icmp"
}
resource "sbercloud_networking_secgroup" "sg_02" {
  name        = "sg-02"
  description = "Security group allows any traffic"
}
# Создаем правила для Security groups
#
resource "sbercloud_networking_secgroup_rule" "sg_rule_01" {
  direction        = "ingress"
  ethertype        = "IPv4"
  protocol          = "icmp"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = sbercloud_networking_secgroup.sg_01.id
}
resource "sbercloud_networking_secgroup_rule" "sg_rule_02" {
  direction        = "ingress"
  ethertype        = "IPv4"
  protocol          = "tcp"
  port_range_min    = "22"
  port_range_max    = "22"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = sbercloud_networking_secgroup.sg_01.id
}
resource "sbercloud_networking_secgroup_rule" "sg_rule_03" {
  direction        = "ingress"
  ethertype        = "IPv4"
  protocol          = "tcp"
  port_range_min    = "80"
  port_range_max    = "80"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = sbercloud_networking_secgroup.sg_01.id
}
resource "sbercloud_networking_secgroup_rule" "sg_rule_04" {
  direction        = "ingress"
  ethertype        = "IPv4"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = sbercloud_networking_secgroup.sg_02.id
}
Создаем два внешних IP адреса(EIP) для NAT gateway и load balancer:
resource "sbercloud_vpc_eip" "eip_nat" {
  publicip {
    type = "5_bgp"
  }
  bandwidth {
    name        = "nat_bandwidth"
    size        = 50
    share_type  = "PER"
    charge_mode = "bandwidth"
  }
}
resource "sbercloud_vpc_eip" "eip_elb" {
  publicip {
    type = "5_bgp"
  }
  bandwidth {
    name        = "elb_bandwidth"
    size        = 50
    share_type  = "PER"
    charge_mode = "bandwidth"
  }
}
Значение size означает величину пропускной способности в Мбит/с, в данном примере пропускная способность у NAT и load balancer будет 50 Мбит/с

Создаем NAT gateway и правила для SNAT:
# Создаем NAT Gateway
#
resource "sbercloud_nat_gateway" "nat_01" {
  name        = "nat-gw-01"
  spec        = "1"
  vpc_id      = sbercloud_vpc.vpc_01.id
  subnet_id  = sbercloud_vpc_subnet.subnet_nat_vpn.id
}
# Создаем SNAT правила
#
resource "sbercloud_nat_snat_rule" "snat_public_subnet" {
  nat_gateway_id = sbercloud_nat_gateway.nat_01.id
  subnet_id      = sbercloud_vpc_subnet.subnet_public.id
  floating_ip_id = sbercloud_vpc_eip.eip_nat.id
}
resource "sbercloud_nat_snat_rule" "snat_private_subnet" {
  nat_gateway_id = sbercloud_nat_gateway.nat_01.id
  subnet_id      = sbercloud_vpc_subnet.subnet_private.id
  floating_ip_id = sbercloud_vpc_eip.eip_nat.id
}
Мы сконфигурировали два SNAT правила, которые нужны private и public подсетям для выхода в интернет.
Значение spec указывает какое количество SNAT соединений будет поддерживать NAT gateway. В данный момент можно указать значения от 1 до 4, кол-во соединений в зависимости от значений следующие: 1-10к, 2-50к, 3-200к, 4-1kk.

Создаем виртуальные машины:
# Получаем ID для Ubuntu образа
#
data "sbercloud_images_image" "img_ubuntu_20_04" {
  name        = "Ubuntu 20.04 server 64bit"
  most_recent = true
}
#Получаем ID для Windows образа
#
data "sbercloud_images_image" "img_winsrv_2019" {
  name        = "Windows Server 2019 Datacenter 64bit English"
  most_recent = true
}
#Создаем ключевую пару
#
resource "sbercloud_compute_keypair" "keypair_01" {
  name      = "keypair_01"
  public_key = "Тут нужно указать свой публичный ключ"
}
# Создаем 2 сервера на Ubuntu 20.04 в публичной подсети
#
resource "sbercloud_compute_instance" "ecs_pub_srv" {
  count = local.ecs_count
  name              = "pub-srv${count.index+1}"
  image_id          = data.sbercloud_images_image.img_ubuntu_20_04.id
  flavor_id        = data.sbercloud_compute_flavors.flavor_n_2_8.ids[0]
  security_groups  = [sbercloud_networking_secgroup.sg_01.name]
  availability_zone = data.sbercloud_availability_zones.az_list.names[count.index % local.number_of_az]
  system_disk_type  = "SAS"
  system_disk_size  = 40
  user_data        = "#!/bin/bash\napt-get update && apt-get -y install nginx && sed -i.bak \"s/nginx\\!/$(hostname)/\" /var/www/html/index.nginx-debian.html"
  key_pair          = sbercloud_compute_keypair.keypair_01.name
  network {
    uuid = sbercloud_vpc_subnet.subnet_public.id
  }
  depends_on = [
    sbercloud_nat_snat_rule.snat_public_subnet
  ]
}
# Создаем 2 сервера на Windows Server 2019 в приватной подсети
#
resource "sbercloud_compute_instance" "ecs_pri_srv" {
  count = local.ecs_count
  name              = "pri-srv${count.index+1}"
  image_id          = data.sbercloud_images_image.img_winsrv_2019.id
  flavor_id        = data.sbercloud_compute_flavors.flavor_n_4_16.ids[0]
  security_groups  = [sbercloud_networking_secgroup.sg_02.name]
  availability_zone = data.sbercloud_availability_zones.az_list.names[count.index % local.number_of_az]
  system_disk_type  = "SAS"
  system_disk_size  = 60
  admin_pass        = var.srv_admin_pass
  network {
    uuid = sbercloud_vpc_subnet.subnet_private.id
  }
  depends_on = [
    sbercloud_nat_snat_rule.snat_private_subnet
  ]
}
Вначале мы получаем ID нужных нам образов и создаем ключевую пару для Ubuntu серверов.
Значение count ссылается на переменную которую мы создавали выше, в нашем случае оно равно 2, следовательно, будет создано по два Windows и Ubuntu сервера в разных зонах доступности.
В значение user_data указан код который будет выполнен при развертывании, будет установлен nginx и имя хоста будет добавлено на web страницу.
При использование user_data доступ до сервера может быть только по ключевой паре, если использовать пароль, то пароль установлен не будет.

Создаем Load Balancer и настраиваем балансировку:
# Создаем Load Balancer
#
resource "sbercloud_lb_loadbalancer" "elb_01" {
  name          = "elb-01"
  vip_subnet_id = sbercloud_vpc_subnet.subnet_public.subnet_id
}
# Привязываем публичный IP к Load Balancer
#
resource "sbercloud_networking_eip_associate" "eip_elb_associate" {
  public_ip = sbercloud_vpc_eip.eip_elb.address
  port_id  = sbercloud_lb_loadbalancer.elb_01.vip_port_id
}
# Создаем ELB listener
#
resource "sbercloud_lb_listener" "web_listener_80" {
  name            = "HTTP listener"
  protocol        = "HTTP"
  protocol_port  = 80
  loadbalancer_id = sbercloud_lb_loadbalancer.elb_01.id
}
# Создаем ECS пул для ELB
#
resource "sbercloud_lb_pool" "lb_pool_01" {
  name        = "Servers group for ELB"
  protocol    = "HTTP"
  lb_method  = "ROUND_ROBIN"
  listener_id = sbercloud_lb_listener.web_listener_80.id
}
# Создаем ELB health check политику
#
resource "sbercloud_lb_monitor" "elb_health_check_01" {
  name          = "Health check for Web Servers"
  type          = "HTTP"
  url_path      = "/"
  expected_codes = "200-202"
  delay          = 10
  timeout        = 5
  max_retries    = 3
  pool_id        = sbercloud_lb_pool.lb_pool_01.id
}
# Добавляем ВМ в сервер группу для балансировки
#
resource "sbercloud_lb_member" "lb_servers" {
  count = local.ecs_count
  address = sbercloud_compute_instance.ecs_pub_srv[count.index].access_ip_v4
  protocol_port = 80
  pool_id      = sbercloud_lb_pool.lb_pool_01.id
  subnet_id    = sbercloud_vpc_subnet.subnet_public.subnet_id
  depends_on = [
    sbercloud_lb_monitor.elb_health_check_01
  ]
}
Наши сервера будут проверяться по протоколу HTTP и если на одном из них будет недоступна web страница, то весь трафик будет перенаправляться на рабочий сервер.
Метод балансировки выбран Roud Robin, помимо него есть еще Least Connections(трафик будет идти на сервер где меньше соединений) и Source IP(трафик будет балансироваться в зависимости от IP источника)

Конфигурация готова.
После ее применения проверяем, что все соответствует требованиям.
Мы видим, что созданы 4 сервера, все в нужных нам подсетях и зонах доступности.
Создан Load Balancer с внешних адресом 45.9.25.81, зайдем на данный адрес по http и проверим, что наши web сервера работают и трафик между нами балансируется.
Открылась веб страница на сервере pub-srv1, обновим страницу несколько раз
Открылась веб страница на сервере pub-srv2.
Балансировщик работает и балансирует трафик.

За кадром был создан VPN Gateway и построен IPSEC туннель с офисом, в роли офиса в данном примере выступает Яндекс.Облако.
Проверим, что все сетевые доступа соответствуют нашим требованиям.
Подключимся к серверу в офисе с IP 172.16.0.5, согласно нашим требованиям с данного сервера должны быть доступны все сервера в Sbercloud.
Все сервера доступны.

Попробуем с данного сервера подключиться по ssh к pub-srv1.
Подключение прошло успешно, с pub-srv1 доступен интернет, но не доступны сервера в офисной подсети, все согласно требованиям.

Далее подключимся к серверу в офисе с IP 172.16.1.5, согласно требованиям с данного сервера должны быть доступны только сервера в private подсети.
Все соответствует требованиям, сервера в private подсети доступны, сервера в public подсети нет.

Теперь подключимся к серверу pri-srv2 (10.0.1.10) и проверим, что с него доступны сервера в офисе.
Офисные сервера доступны, все доступа сконфигурированы согласно требованиям.

В данной статье был показан пример того, как при помощи Terraform можно развернуть инфраструктуру в облаке Sbercloud.Advanced.
Использование Terraform является очень удобным средством для развертывания инфраструктуры, буквально одной командой вы можете развернуть всю необходимую вашей компании инфраструктуру в облаке.
К сожалению на данный момент не всё в Sbercloud можно настроить при помощи Terraform, будем надеяться, что разработчики в будущем добавят недостающие возможности.
Остались вопросы? Напишите нам