Задание:

  • a) Требования к виртуальным машинам:
    • 1. Основные характеристики:
      • i. Операционная система: Альт p10 StarterKit/Альт Сервер p10-cloud
      • ii. Количество vCPU: 1.
      • iii. Объём оперативной памяти: 1024 МБ.
      • iv. Объём диска: 10 ГБ/30 ГБ
      • v. Тип диска: HDD.
  • b) Подготовьте сценарий автоматизации развёртывания облачной инфраструктуры:
    • 1. Создание виртуальных машин и сетей:
      • i. Виртуальные машины и сети должны быть созданы строго в соответствии с предложенной топологией (см. Топология ниже).
      • ii. Имена виртуальных машин, сетей, подсетей и маршрутизаторов должны соответствовать именованиям, указанным в Топологии.
      • iii. Обеспечьте правильное подключение виртуальных машин к соответствующим сетям в рамках заданной топологии.
  • 2. Безопасность и доступ:
    • i. Разрешите трафик по протоколу ICMP для всех виртуальных машин для диагностики сетевых подключений.
    • ii. Назначьте IP-адреса всем машинам. Сохраните внешние IP-адреса всех машин в файле /home/altlinux/white.ip на машине ControlVM.
    • iii. Настройте аутентификацию на основе открытых ключей для SSH.
    • iv. В случае предоставления внешнего доступа к виртуальным машинам, разрешите его только по протоколу SSH (публичный ключ, пароль отключён) и только с соответствующих IP-адресов.
  • 3. Балансировка нагрузки:
    • i. Создайте балансировщик нагрузки и распределите трафик между серверами Web1 и Web2 (см. Топология).
    • ii. Ограничьте внешний доступ к балансировщику только протоколами HTTP и HTTPS. Все остальные порты должны быть закрыты.
    • iii. Балансировка нагрузки должна использовать алгоритм round robin.
    • iv. При обращении на внешний адрес балансировщика нагрузки должен выводиться ответ от приложения, работающего на внутренних серверах Web1 и Web2.
  • 4. Настройка подключения:
    • i. Настройте машину WebAdm так, чтобы она могла подключаться по SSH с использованием пользователя altlinux и пароля «P@ssw0rd» к серверам Web1 и Web2 с помощью VPN туннеля.
    • ii. Убедитесь, что машина ControlVM может подключаться к машине WebAdm используя ключевую пару пользователя altlinux по SSH через её глобальный IP-адрес.

Вариант реализации:

ControlVM

Все файлы создаются в контексте каталога /home/altlinux/bin, если не сказано иное

  • Создаём файл network.tf и описываем последовательно сетевую часть для развёртывания данной инфраструктуры с поэтапным запуском и наблюдением созданных ресурсов:
vim network.tf
    • Помещаем следующее содержимое:
      • Создадим виртуальную сеть с именем INTERNET в соответствие с топологией;
      • В созданной виртуальной сети создадим одноимённую подсеть, т.к. все сетевые параметры задаются на подсеть в рамках сети (с произвольными параметрами);
        • В данном случае подсеть INTERNETимеет IP-адрес 192.168.200.0/24, DHCP-пул адресов для раздачи из данной подсети с 192.168.200.100 по 192.168.200.200, в качестве шлюза по умолчанию в данной подсети будет использоваться IP-адрес 192.168.200.1, в качестве DNS IP-адрес 77.88.8.8;
      • Также данную подсеть INTERNET необходимо добавить в ранее созданный виртуальный маршрутизатор с именем cloud (чтобы не создавать новый), который необходим для работы ControlVM и не должен быть удалён по завершению работы, для того чтобы для всех подключённых инстансов к данной подсети был доступ в сеть Интернет, т.к. на данном виртуальном маршрутизаторе ранее был выставлен чек-бокс для SNAT, и именно через него ControlVM и все остальные будут иметь доступ в сеть Интернет;
# Обращаемся к источнику данных, чтобы узнать ID существующего виртуального маршрутизатора
data "openstack_networking_router_v2" "router" {
  name = "cloud"
}

# Создаём сеть с именем "INTERNET" в соответствие с топологией
resource "openstack_networking_network_v2" "network" {
  name           = "INTERNET"
  admin_state_up = "true"
}

# Создаём подсеть с именем "INTERNET" в ранее созданной сети с именем "INTERNET"
resource "openstack_networking_subnet_v2" "subnet" {
  name            = "INTERNET"
  network_id      = openstack_networking_network_v2.network.id
  cidr            = "192.168.200.0/24"
  ip_version      = 4
  gateway_ip      = "192.168.200.1"
  dns_nameservers = [ "77.88.8.8" ]
  enable_dhcp     = true

  allocation_pool {
    start = "192.168.200.100"
    end   = "192.168.200.200"
  }
}

# Добавляем в существующий маршрутизатор созданную подсеть с именем "INTERNET", чтобы в дальнейшем ВМ подключённые к данной подсети имели доступ в Интернет
resource "openstack_networking_router_interface_v2" "router_interface" {
    router_id = data.openstack_networking_router_v2.router.id
    subnet_id = openstack_networking_subnet_v2.subnet.id
}
  • Выполняем проверку синтаксиса и структуры файлов конфигурации Terraform
terraform validate
    • Результат:

  • Проверяем конфигурацию перед развёртыванием, смотрим план развёртывания ресурсов:
terraform plan
    • Результат:
      • По плану будет создано (добавлено) 3 ресурса: Сеть INTERNET, Подсеть INTERNET и добавление в виртуальный маршрутизатор cloud подсети INTERNET:

  • Запускаем развёртывание данных ресурсов:
terraform apply
    • Подтверждаем развёртывание введя yes:

    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • Сеть с подсетью:

  • Проверяем наличие созданных ресурсов средствами веб-интерфейса:
    • Сеть с подсетью:

    • Подсеть добавленная в существующий маршрутизатор (для доступа в сеть Интернет из данной подсети):

 

  • В файл network.tf добавляем следующий код:
    • Создаём порты в сети INTERNET в одноимённой подсети для каждого инстанса и балансировщика нагрузки
    • Обращаясь уже к ранее созданным ресурсам, а именно к идентификаторам id ресурсов с именемами network и subnet
# Создадим порт для инстанса WebADM
resource "openstack_networking_port_v2" "port_webadm" {
  name       = "webadm"
  network_id = openstack_networking_network_v2.network.id

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.subnet.id
    ip_address = "192.168.200.20"
  }
}

# Создадим порт для инстанса WEB1
resource "openstack_networking_port_v2" "port_web1" {
  name       = "web1"
  network_id = openstack_networking_network_v2.network.id

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.subnet.id
    ip_address = "192.168.200.21"
  }
}

# Создадим порт для инстанса WEB2
resource "openstack_networking_port_v2" "port_web2" {
  name       = "web2"
  network_id = openstack_networking_network_v2.network.id

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.subnet.id
    ip_address = "192.168.200.22"
  }
}

# Создадим порт для балансировщика нагрузки Load Balancer
resource "openstack_networking_port_v2" "port_loadbalancer" {
  name       = "Load Balancer"
  network_id = openstack_networking_network_v2.network.id

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.subnet.id
    ip_address = "192.168.200.23"
  }
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов:
terraform apply
    • Подтверждаем развёртывание введя yes:

    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • в веб-интерфейсе нет возможности посмотреть созданные порты;

 

  • В файл network.tf добавляем следующий код:
    • Создаём плавающие IP-адреса ("публичные") для каждого инстанса и балансировщика нагрузки;
    • Плавающие IP-адреса должны браться из "Публичной" сети указав её имя, в данном случае public;
# Создадим плавающий IP для инстанса WebADM
resource "openstack_networking_floatingip_v2" "floatingip_webadm" {
  pool = "public"
}

# Создадим плавающий IP для инстанса WEB1
resource "openstack_networking_floatingip_v2" "floatingip_web1" {
  pool = "public"
}

# Создадим плавающий IP для инстанса WEB2
resource "openstack_networking_floatingip_v2" "floatingip_web2" {
  pool = "public"
}

# Создадим плавающий IP для балансировщика нагрузки Load Balancer
resource "openstack_networking_floatingip_v2" "floatingip_loadbalancer" {
  pool = "public"
}
    • Имя "публичной" сети можно посмотреть через openstack-cli:
      • Та сеть, которую ранее не создавали в ручную для работы ControlVM (cloud) и средствами Terraform в соответствие с топологией (INTERNET);

    • Имя "публичной" сети можно посмотреть через веб-интерфейс:

  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • Плавающие IP-адреса (4 шт.):

  • Проверяем наличие созданных ресурсов средствами веб-интерфейса:
    • Плавающие IP-адреса (4 шт.):

 

  • В файл network.tf добавляем следующий код:
    • Создавая ассоциацию ранее созданного плавающего IP-адреса с ещё ранее созданным портов;
    • Для каждого инстанса и балансировщика нагрузки;
# Создадим для WebADM ассоциацию плавающего IP и порт (публичного и приватного IP адресов)
resource "openstack_networking_floatingip_associate_v2" "association_webadm" {
  port_id     = openstack_networking_port_v2.port_webadm.id
  floating_ip = openstack_networking_floatingip_v2.floatingip_webadm.address
}

# Создадим для WEB1 ассоциацию плавающего IP и порт (публичного и приватного IP адресов)
resource "openstack_networking_floatingip_associate_v2" "association_web1" {
  port_id     = openstack_networking_port_v2.port_web1.id
  floating_ip = openstack_networking_floatingip_v2.floatingip_web1.address
}

# Создадим для WEB2 ассоциацию плавающего IP и порт (публичного и приватного IP адресов)
resource "openstack_networking_floatingip_associate_v2" "association_web2" {
  port_id     = openstack_networking_port_v2.port_web2.id
  floating_ip = openstack_networking_floatingip_v2.floatingip_web2.address
}

# Создадим для Load Balancer ассоциацию плавающего IP и порт (публичного и приватного IP адресов)
resource "openstack_networking_floatingip_associate_v2" "association_loadbalancer" {
  port_id     = openstack_networking_port_v2.port_loadbalancer.id
  floating_ip = openstack_networking_floatingip_v2.floatingip_loadbalancer.address
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем в веб-интерфейсе статус плавающих IP-адресов сменился с Неактивен на Запущена:
    • Также появились IP-адреса ВМ (но сами ВМ (инстансы) ещё не созданы) из подсети INTERNET (192.168.200.0/24 созданной ранее);

 

  • Создаём файл security.tf и описываем последовательно часть касающуюся сетевой безопасности (с точки зрения требования задания) для развёртывания данной инфраструктуры с поэтапным запуском и наблюдением созданных ресурсов:
vim network.tf
    • Помещаем следующее содержимое:
      • Создаём две группы безопасности для будущего контроля ICMP и SSH
# Создаём группу безопасности для ICMP
resource "openstack_networking_secgroup_v2" "secgroup_1" {
  name = "ICMP"
  description = "ICMP"
}

# Создаём группу безопасности для SSH
resource "openstack_networking_secgroup_v2" "secgroup_2" {
  name = "SSH"
  description = "SSH"
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • Группы безапасности:

  • Проверяем наличие созданных ресурсов средствами веб-интерфейса:
    • Группы безапасности:

 

  • В файл security.tf добавляем следующий код:
    • Создавая правило, в ранее созданную группу безопасности обращаясь по идентификатору id созданного ресурса secgroup_1  для фильтрации по протоколу icmp с любых IP-адресов для входящего трафика;
    • Создавая правило, в ранее созданную группу безопасности обращаясь по идентификатору id созданного ресурса secgroup_2  для фильтрации по протоколу tcp на порт 22 (SSH) с любых IP-адресов для входящего трафика;
    • Ограничивая тем самом доступ к будущим инстансам по ICMP и SSH из внешних сетей;
# Добавляем правило в группу безопасности для ICMP
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_icmp" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "icmp"
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.secgroup_1.id
}

# Добавляем правило в группу безопасности для SSH
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_ssh" {
  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 = openstack_networking_secgroup_v2.secgroup_2.id
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • Правила в группах безопасности:

  • Проверяем наличие созданных ресурсов средствами веб-интерфейса:
    • Правила в группах безопасности:

 

  • В файл security.tf добавляем следующий код:
    • Поскольку в дальнейшем по заданию на инстансах WEB1 и WEB2 должно быть развёрнуто веб-приложение, а значит они должны принимать входящий трафик на порты 80/tcp и 443/tcp (HTTP и HTTPS)
    • Поскольку в дальнейшем по заданию необходимо реализовать туннельное соединение между инстансами WebADM и WEB1, WEB2, а значит WebADM должен принимать входящий трафик на порт реализуемого VPN-соединения, например WireGuard (51820/udp);
    • Таким образом необходимо создать ещё 1 или 2 группы безопасности с соответствующими правилами;
    • В данном примере создаются две группы безопасности с именами WEB и VPN и три правила: два для группы WEB и одно для группы VPN;
# Создаём группу безопасность для WEB-трафика (HTTP/HTTPS)
resource "openstack_networking_secgroup_v2" "secgroup_3" {
  name = "WEB"
  description = "WEB for HTTP/HTTPS"
}

# Добавляем первое правило в группу безопасности для WEB (HTTP)
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_http" {
  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 = openstack_networking_secgroup_v2.secgroup_3.id
}

# Добавляем второе правило в группу безопасности для WEB (HTTPS)
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_https" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.secgroup_3.id
}

# Создаём группу безопасность для VPN (WireGuard)
resource "openstack_networking_secgroup_v2" "secgroup_4" {
  name = "VPN"
  description = "VPN (Wireguard)"
}

# Добавляем правило в группу безопасности для VPN (WireGuard)
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_vpn" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "udp"
  port_range_min    = 51820
  port_range_max    = 51820
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.secgroup_4.id
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданных ресурсов средствами openstack-cli:
    • Правила в группах безопасности:

  • Проверяем наличие созданных ресурсов средствами веб-интерфейса:
    • Правила в группах безопасности:

 

  • В файл network.tf добавляем следующий код:
    • Назначая ранее созданные группы безопасности на ранее созданные порты для каждого инстанса, обращаясь по идентификаторам id созданных ранее ресурсов;
    • На порт для инстанса WebADM назначаем группы безопасности ICMP, SSH (явное требование задания) и VPN, т.к. в дальнейшем данный инстанс будет выступать в роле VPN-сервера и должен принимать соединения на порт указанный в правиле для данной группы безопасности;
    • На порт для инстансов WEB1  и WEB2 назначаем группы безопасности ICMP, SSH (явное требование задания) и WEB, т.к. в дальнейшем на данных инстансах будет развёрнуто веб-приложение и должны приниматься соединения на порты указанные в правилах для данной группы безопасности;
# Назначим группы (ICMP, SSH, VPN) безопасности на порт для инстанса WebADM
resource "openstack_networking_port_secgroup_associate_v2" "security_group_associate_webadm" {
    port_id            = openstack_networking_port_v2.port_webadm.id
    enforce            = true
    security_group_ids = [
      openstack_networking_secgroup_v2.secgroup_1.id,
      openstack_networking_secgroup_v2.secgroup_2.id,
      openstack_networking_secgroup_v2.secgroup_4.id
    ]
}

# Назначим группы (ICMP, SSH, WEB) безопасности на порт для инстанса WEB1
resource "openstack_networking_port_secgroup_associate_v2" "security_group_associate_web1" {
    port_id            = openstack_networking_port_v2.port_web1.id
    enforce            = true
    security_group_ids = [
      openstack_networking_secgroup_v2.secgroup_1.id,
      openstack_networking_secgroup_v2.secgroup_2.id,
      openstack_networking_secgroup_v2.secgroup_3.id
    ]
}

# Назначим группы (ICMP, SSH, WEB) безопасности на порт для инстанса WEB2
resource "openstack_networking_port_secgroup_associate_v2" "security_group_associate_web2" {
    port_id            = openstack_networking_port_v2.port_web2.id
    enforce            = true
    security_group_ids = [
      openstack_networking_secgroup_v2.secgroup_1.id,
      openstack_networking_secgroup_v2.secgroup_2.id,
      openstack_networking_secgroup_v2.secgroup_3.id
    ]
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

 

  • Создаём файл instance.tf и описываем последовательно часть для развёртывания необходимых инстансов (виртуальных машин) с поэтапным запуском и наблюдением созданных ресурсов:
vim instance.tf
    • Помещаем следующее содержимое:
      • Т.к. все ресурсы и сети должны быть созданы в соответствие с топологией по условиям задания, а на топологии ControlVM также должна быть подключена к сети INTERNET;
      • То добавляем к инстансу ControlVM ещё один сетевой интерфейс из подсети INTERNET и назначаем фиксированный IP-адрес;
      • Необходимо указать идентификатор id виртуальной машины ControlVM
# Подключаем в ControlVM интерфейс из подсети "INTERNET" в соответствие с топологией
resource "openstack_compute_interface_attach_v2" "controlvm" {
  instance_id = "96306ddf-a488-4e35-8a8f-25696821efe5"
  network_id  = openstack_networking_network_v2.network.id
  fixed_ip = "192.168.200.10"
  depends_on = [ openstack_networking_subnet_v2.subnet ]
}
    • instance_id (идентификатор) можно получить средствами openstack-cli:

    • instance_id (идентификатор) можно получить средствами веб-интерфейса:

  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

    • Проверяем наличие второго сетевого интерфейса из подсети INTERNET средствами openstack-cli:

    • Проверяем наличие второго сетевого интерфейса из подсети INTERNET средствами веб-интерфейса:

 

  • В файл instance.tf добавляем следующий код:
    • Описывая конфигурацию инстансов WebADM, WEB1 и WEB2;
    • См. ниже информацию о переменных var.* и файле cloud-init.yml;
# Создаём инстанс с именем "WebADM"
resource "openstack_compute_instance_v2" "webadm" {
  name            = "WebADM"
  flavor_id       = var.flavor_id
  key_pair        = var.key_pair
  user_data       = file("cloud-init.yml")

  block_device {
    uuid                  = var.image_id
    source_type           = "image"
    volume_size           = 10
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.port_webadm.id
  }
}

# Создаём инстанс с именами "WEB1"
resource "openstack_compute_instance_v2" "web1" {
  name            = "WEB1"
  flavor_id       = var.flavor_id
  key_pair        = var.key_pair
  user_data       = file("cloud-init.yml")

  block_device {
    uuid                  = var.image_id
    source_type           = "image"
    volume_size           = 10
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.port_web1.id
  }
}

# Создаём инстанс с именами "WEB2"
resource "openstack_compute_instance_v2" "web2" {
  name            = "WEB2"
  flavor_id       = var.flavor_id
  key_pair        = var.key_pair
  user_data       = file("cloud-init.yml")

  block_device {
    uuid                  = var.image_id
    source_type           = "image"
    volume_size           = 10
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.port_web2.id
  }
}

 

  • В файл variables.tf добавляем следующий код:
    • Определяя значение переменных указывая идентификаторы id для соответствующих (существующих) ресурсов;
    • См. ниже где брать идентификаторы;
    • Учитывая основные требования задания для создаваемых инстансов:

# ID для образа "alt-p10-cloud-x86_64.qcow2" (Starterkit)
variable "image_id" {
	type = string
	default = "92e78753-4f88-40eb-a10a-5a7fb9bfc106"
}

# ID для шаблона 1 vCPU 1 RAM (openstack --insecure flavor list)
variable "flavor_id" {
	type = string
	default = "1f64883c-1cdd-45e4-ac8a-82ab57a12fdf"
}

# Имя ssh-ключа
variable "key_pair" {
	type = string
	default = "cloud"
}

 

  • Для переменной image_id значение можно получить:
    • Средствами openstack-cli:

    • Средствами веб-интерфейса:

  • Для переменной flavor_id значение можно получить:
    • Средствами openstack-cli:

    • Средствами веб-интерфейса нет возможности;
  • Для переменной key_pair значение можно получить:
    • Средствами openstack-cli:

    • Средствами веб-интерфейса:

  • Средствами Terraform передаём именно тот ключ, на основе которого осуществляется доступ с рабочего места до ControlVM
    • Чтобы при необходимости можно было подключиться по SSH к каждому инстансу по "публичному" (Плавающему) IP-адресу, что также является требованиями задания;
  • Создаём файл cloud-init.yml:
vim cloud-init.yml
    • Помещаем следующее содержимое:
      • Задавая пароль по требованиям задания P@ssw0rd для каждого создаваемого инстанса;
      • Также передаём значение публичной части SSH-ключа (генерация ssh-ключей см. ниже), чтобы в дальнейшем был доступ с ControlVM до каждого инстанса по SSH;
      • И именно по ключам, т.к. по условиям задания подключение по SSH на основе открытых ключей;
      • Доступ по SSH с ControlVM до каждого инстанса необходим для дальнейшей работы Ansible;
#cloud-config
chpasswd:
  expire: false
  users:
  - {name: altlinux, password: P@ssw0rd, type: text}
  ssh_pwauth: false

users:
  - name: altlinux
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: wheel
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFzYMn859ToNndaRRTlXoDnBDgME7rTPNvPlh9hmo4QsvmMdF3lKgQTR6p53cP1zUgxfstkKCfvMlAfNCE3iJWGxaY8B8tM4fO6jC8eMKe93cIK74lFk1cZnrHHUJSvM4dsD+hsoZ06VPOWSoUtipi9B7AgOF0EsL63GTx0EeajJCOerQUlA6qJM785NfcRNS23vSHhlRyV84xrfBhP/0w2jiHWy+jNCVtpO/M7lAQ5EtcVegSZupzDoJ6K/4GpgjLoP4eUfr8EVNMLGZ5AJlJmlFL5hTsOwa/42CN+TwFSC4G6PFXtWLdZJebPJ2nlcKpydmwSDEeXTy4vPAZWXBD altlinux@controlvm
  • Генерируем на ControlVM ключевую пару и смотрим содержимое публичного (открытого) ключа, которое необходимо указать в файле cloud-init.yml:

  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданных инстансов средствами openstack-cli:

  • Проверяем наличие созданных инстансов средствами веб-интерфейса:

  • Также проверяем что:
    • Каждый инстанс подключён к сети INTERNET с неоходимыми группами безопасности:

    • Каждый Плавающий IP-адрес назначем на инстансы (за исключением 1-го для балансировки нагрузки):

 

  • Создаём файл loadbalancer.tf и описываем последовательно часть для развёртывания балансировщика нагрузки с поэтапным запуском и наблюдением созданных ресурсов:
vim loadbalancer.tf
    • Помещаем следующее содержимое:
      • Создадим балансировщик нагрузки, который будет подключён в соответствие с топологией к подсети INTERNET;
# Создаём балансировщик нагрузки с именем "Load Balancer"
resource "openstack_lb_loadbalancer_v2" "loadbalancer" {
  name          = "Load Balancer"
  vip_subnet_id = openstack_networking_subnet_v2.subnet.id

  depends_on = [ 
    openstack_compute_instance_v2.web1,
    openstack_compute_instance_v2.web2
   ]
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие созданного балансировщика нагрузки средствами openstack-cli:

  • Проверяем наличие созданного балансировщика нагрузки средствами веб-интерфейса:

 

  • В файл loadbalancer.tf добавляем следующий код:
    • Определяя правила в ранее созданном балансировщике нагрузки для HTTP и HTTPS трафика;
# Создаём правило в балансировщике нагрузки для HTTP
resource "openstack_lb_listener_v2" "listener_http" {
  name            = "HTTP"
  protocol        = "TCP"
  protocol_port   = "80"
  loadbalancer_id = openstack_lb_loadbalancer_v2.loadbalancer.id
}

# Создаём правило в балансировщике нагрузки для HTTPS
resource "openstack_lb_listener_v2" "listener_https" {
  name            = "HTTPS"
  protocol        = "TCP"
  protocol_port   = "443"
  loadbalancer_id = openstack_lb_loadbalancer_v2.loadbalancer.id
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

 

  • В файл loadbalancer.tf добавляем следующий код:
    • Создавая целевые группы для HTTP и HTTPS указывая соответствующий протокол и алгоритм балансировки в соответствие с заданием round robin;
# Создаём целевую группу для HTTP с указанием алгоритма балансировки "ROUND ROBIN"
resource "openstack_lb_pool_v2" "pool_http" {
  name        = "HTTP"
  protocol    = "HTTP"
  lb_method   = "ROUND_ROBIN"
  listener_id = openstack_lb_listener_v2.listener_http.id
}

# Создаём целевую группу для HTTPS с указанием алгоритма балансировки "ROUND ROBIN"
resource "openstack_lb_pool_v2" "pool_https" {
  name        = "HTTPS"
  protocol    = "HTTPS"
  lb_method   = "ROUND_ROBIN"
  listener_id = openstack_lb_listener_v2.listener_https.id
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем созданные целевые группы средствами openstack-cli:

  • Проверяем созданные целевые группы средствами веб-интерфейса:

 

  • В файл loadbalancer.tf добавляем следующий код:
    • Добавляя инстансы WEB1 и WEB2 в ранее созданные целевые группы для HTTP и HTTPS;
# Добавляем инстанс WEB1  в целевую группу HTTP
resource "openstack_lb_member_v2" "member_web1_http" {
  name          = "WEB1"
  subnet_id     = openstack_networking_subnet_v2.subnet.id
  pool_id       = openstack_lb_pool_v2.pool_http.id
  address       = "192.168.200.21"
  protocol_port = "80"
}

# Добавляем инстанс WEB2  в целевую группу HTTP
resource "openstack_lb_member_v2" "member_web2_http" {
  name          = "WEB2"
  subnet_id     = openstack_networking_subnet_v2.subnet.id
  pool_id       = openstack_lb_pool_v2.pool_http.id
  address       = "192.168.200.22"
  protocol_port = "80"
}

# Добавляем инстанс WEB1  в целевую группу HTTPS
resource "openstack_lb_member_v2" "member_web1_https" {
  name          = "WEB1"
  subnet_id     = openstack_networking_subnet_v2.subnet.id
  pool_id       = openstack_lb_pool_v2.pool_https.id
  address       = "192.168.200.21"
  protocol_port = "80"
}

# Добавляем инстанс WEB2  в целевую группу HTTPS
resource "openstack_lb_member_v2" "member_web2_https" {
  name          = "WEB2"
  subnet_id     = openstack_networking_subnet_v2.subnet.id
  pool_id       = openstack_lb_pool_v2.pool_https.id
  address       = "192.168.200.22"
  protocol_port = "80"
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие инстансов в целевых группах средствами openstack-cli:

  • Проверяем наличие инстансов в целевых группах средствами веб-интерфейса:

 

  • В файл loadbalancer.tf добавляем следующий код:
    • Добавляя проверку доступности инстансов в каждой целевой группе;
# Создаём проверку доступности инстансов в целевой группе HTTP
resource "openstack_lb_monitor_v2" "monitor_http" {
  name        = "monitor HTTP"
  pool_id     = openstack_lb_pool_v2.pool_http.id
  type        = "PING"
  delay       = "10"
  timeout     = "4"
  max_retries = "5"
}

# Создаём проверку доступности инстансов в целевой группе HTTPS
resource "openstack_lb_monitor_v2" "monitor_https" {
  name        = "monitor HTTPS"
  pool_id     = openstack_lb_pool_v2.pool_https.id
  type        = "PING"
  delay       = "10"
  timeout     = "4"
  max_retries = "5"
}
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем доступность мониторинга инстансов в целевых группах средствами openstack-cli:

  • Проверяем доступность мониторинга инстансов в целевых группах средствами веб-интерфейса:

 

  • Создаём файл output.tf:
vim output.tf
    • Помещаем следующее содержимое:
      • Для печати на экран "внешний (публичные)" плавающие IP-адреса всех инстансов;
      • Понадобится в дальнейшем для сохранения вывода в файл по требованиям задания;
output "WebADM" {
  value = openstack_networking_floatingip_v2.floatingip_webadm.address
  depends_on = [ openstack_compute_instance_v2.webadm ]
}

output "WEB1" {
  value = openstack_networking_floatingip_v2.floatingip_web1.address
  depends_on = [ openstack_compute_instance_v2.web1 ]
}

output "WEB2" {
  value = openstack_networking_floatingip_v2.floatingip_web2.address
  depends_on = [ openstack_compute_instance_v2.web2 ]
}

output "LoadBalancer" {
  value = openstack_networking_floatingip_v2.floatingip_loadbalancer.address
}

 

  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

 

  • На текущий момент вся инфраструктура развёрнута в соответствие с требованиями задания, за исключением настройки VPN-туннеля и SSH по паролю в рамках туннельного соединения
  • Реализуем недостающий функционал средствами связки Terraform и Ansible

 

  • Создаём файл templates.tf:
vim templates.tf
    • Помещаем следующее содержимое:
      • Поскольку нельзя предугадать какие IP-адреса будут выданы инстансам в роли "внешних" (Плавающих) реализуем возможность генерации автоматического инвентаря для Ansible средствами Terraform;
      • Т.к. в конечном варианте ожидается запуск единого скрипта который развёрнёт инфраструктуру а затем её настроит;
data "template_file" "inventory" {
    template = file("/home/altlinux/bin/_templates/inventory.tpl")
  
    vars = {
        user = "altlinux"
        webadm = join("", [openstack_compute_instance_v2.webadm.name, " ansible_host=", openstack_networking_floatingip_v2.floatingip_webadm.address])
        web1 = join("", [openstack_compute_instance_v2.web1.name, " ansible_host=", openstack_networking_floatingip_v2.floatingip_web1.address])
        web2 = join("", [openstack_compute_instance_v2.web2.name, " ansible_host=", openstack_networking_floatingip_v2.floatingip_web2.address])
    }
}

resource "local_file" "save_inventory" {
   content  = data.template_file.inventory.rendered
   filename = "/home/altlinux/bin/ansible/inventory"
}
  • Создаём директорию "_templates" где будет хранить шаблоны:
mkdir _templates
  • Создаём сам файл шаблона для автоматической генерации инвентаря для Ansible:
vim _templates/inventory.tpl
    • Помещаем в него следующее содержимое:
      • В результате Terraform должен будет генерировать инвентарь для Ansible по пути /home/altlinux/bin/ansible/inventory описанный в ini-формате;
${webadm}
${web1}
${web2}

[all:vars]
ansible_ssh_user = ${user}
ansible_ssh_extra_args = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
ansible_python_interpreter = /usr/bin/python3
  • Создаём директорию для Ansible:
mkdir ansible
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие файла /home/altlinux/bin/ansible/inventory и его содержимое:

  • Устанавливаем ansible:
sudo apt-get update && sudo apt-get install -y ansible
  • Проверяем корректность сгенерированного инвентарного файла:
ansible -i ansible/inventory -m ping all
    • Результат:
      • Ansible успешно может подключиться с ControlVM до всех инстансов для дальнейшей их настройки;

 

  • В файл templates.tf добавляем следующий код:
    • Добавляя возможность Terraform автоматически генерировать конфигурационные файлы для WireGuard;
    • Поскольку нет информации о "внешнем" (Плавающим) IP-адресе который будет назначем WebADM при новом развёртывании проекта;
    • Он необходим для конфигурационного файла WireGuard для параметра Endpoint;
data "template_file" "web1_wg0" {
    template =  file("/home/altlinux/bin/_templates/web1_wg0.tpl")

    vars = {
      floatingip_webadm = join("",[openstack_networking_floatingip_v2.floatingip_webadm.address])
    }
}

resource "local_file" "save_web1_wg0" {
   content  = data.template_file.web1_wg0.rendered
   filename = "/home/altlinux/bin/ansible/wireguard/web1_wg0.conf"
}

data "template_file" "web2_wg0" {
    template =  file("/home/altlinux/bin/_templates/web2_wg0.tpl")

    vars = {
      floatingip_webadm = join("",[openstack_networking_floatingip_v2.floatingip_webadm.address])
    }
}

resource "local_file" "save_web2_wg0" {
   content  = data.template_file.web2_wg0.rendered
   filename = "/home/altlinux/bin/ansible/wireguard/web2_wg0.conf"
}
  • Создаём сам файл шаблона конфигурационного файла туннельного соединения для инстанса WEB1:
vim _templates/web1_wg0.tpl
    • Помещаем в него следующее содержимое:
[Interface]
Address = 10.20.30.2/29
PrivateKey = +OoP8y4QuTZL7P5Esr7YV7p/GqleqSTE5Mf6R1E6y38=

# WebAdm
[Peer]
PublicKey = Pn1bzrpZoBob/VeAvWDQfazwrZh18WarKSKzid2K1wc=
AllowedIPs = 10.20.30.0/29
PersistentKeepalive = 10
Endpoint = ${floatingip_webadm}:51820
  • Создаём сам файл шаблона конфигурационного файла туннельного соединения для инстанса WEB2:
vim _templates/web2_wg0.tpl
    • Помещаем в него следующее содержимое:
[Interface]
Address = 10.20.30.3/29
PrivateKey = UEN3Lbpm+NyXsw+XMEZPKHAaMocFRP0KfDZvIiu+BUk=

# WebAdm
[Peer]
PublicKey = Pn1bzrpZoBob/VeAvWDQfazwrZh18WarKSKzid2K1wc=
AllowedIPs = 10.20.30.0/29
PersistentKeepalive = 10
Endpoint = ${floatingip_webadm}:51820
  • Создаём директорию для куда Terraform сохранит сгенерированные конфигурационные файлы:
mkdir ansible/wireguard
  • Проверяем конфигурацию и план terraform validate и terraform plan и запускаем развёртывание ресурсов terraform apply:
    • Результат:

  • Проверяем наличие конфигурационных файлов для туннельных соединений и их содержимое:
    • Должен корректно подставляться параметр Endpoint:

 

  • Создаём конфигурационный файл туннельного соединения для инстанса WebADM:
vim ansible/wireguard/webadm_wg0.conf
    • Помещаем в него следующее содержимое:
      • Т.к. с точки зрения WireGuard инстанс WebADM будет выступать сервером, то данный файл может бьть статическим и не требуем какой-либо динамически получаемой информации;
[Interface]
Address = 10.20.30.1/29
ListenPort = 51820
PrivateKey = wHpaZtEfAYGxVXhwzUa8OS1EbBXqrjDW8E1Z73pzTUI=

# WEB1
[Peer]
PublicKey = dZuyzPZVpJ4gyvMpJRCOI3PBGEIYe1x1MZuYyYHjfT0=
AllowedIPs = 10.20.30.2/32

# WEB2
[Peer]
PublicKey = ZSTT8CxQsxJ1KU9bO5PoSG6iLFBb8hQtIc4Kyox4AnU=
AllowedIPs = 10.20.30.3/32

 

  • Создаём playbook который будет реализовывать настройку VPN-соединения средствами WireGuard между WebADM и WEB1, WEB2:
vim ansible/wireguard_playbook.yml
    • Помещаем в него следующее содержимое:
---
- name: Install packages
  hosts: all
  become: true

  tasks:
    - name: Install Wireguard
      community.general.apt_rpm:
        name:
          - wireguard-tools
          - wireguard-tools-wg-quick
        state: latest
        update_cache: true

- name: Settings WebAdm Wireguard server
  hosts: WebADM
  become: true

  tasks:
    - name: Create directory '/etc/wireguard'
      ansible.builtin.file:
        path: /etc/wireguard
        state: directory
        mode: '0755'

    - name: Copy file 'wg0.conf'
      ansible.builtin.copy:
        src: wireguard/webadm_wg0.conf
        dest: /etc/wireguard/wg0.conf

    - name: Started and enabled wg0
      ansible.builtin.systemd:
        name: wg-quick@wg0
        state: started
        enabled: true

- name: Settings WEB1 Wireguard client
  hosts: WEB1
  become: true

  tasks:
    - name: Create directory '/etc/wireguard'
      ansible.builtin.file:
        path: /etc/wireguard
        state: directory
        mode: '0755'

    - name: Copy file 'wg0.conf'
      ansible.builtin.copy:
        src: wireguard/web1_wg0.conf
        dest: /etc/wireguard/wg0.conf

    - name: Started and enabled wg0
      ansible.builtin.systemd:
        name: wg-quick@wg0
        state: started
        enabled: true

- name: Settings WEB2 Wireguard client
  hosts: WEB2
  become: true

  tasks:
    - name: Create directory '/etc/wireguard'
      ansible.builtin.file:
        path: /etc/wireguard
        state: directory
        mode: '0755'

    - name: Copy file 'wg0.conf'
      ansible.builtin.copy:
        src: wireguard/web2_wg0.conf
        dest: /etc/wireguard/wg0.conf

    - name: Started and enabled wg0
      ansible.builtin.systemd:
        name: wg-quick@wg0
        state: started
        enabled: true
  • Устанавливаем коллекцию необходимую для работы модуля community.general.apt_rpm:
ansible-galaxy collection install community.general
    • Результат:

  • Запускаем playbook-сценарий для настройки VPN-туннеля:
ansible-playbook -i ansible/inventory ansible/wireguard_playbook.yml
    • Результат:

  • Проверяем наличие VPN-соединения:
    • Подключаемся по SSH  с ControlVM на WebADM по Плавающему IP-адресу;
    • Смотрим туннельные соединения;
    • Проверяем связность;

 

  • Создаём playbook который будет реализовывать:
    • WebADM так, чтобы она могла подключаться по SSH с использованием пользователя altlinux и пароля «P@ssw0rd» к инстансам WEB1 и WEB2 с помощью VPN туннеля
vim ansible/ssh_playbook.yml
    • Помещаем в него следующее содержимое:
---
- hosts: WEB1 WEB2
  become: true
  
  tasks:
    - name: Setting 'sshd_config' file
      ansible.builtin.lineinfile:
        line: "{{ item }}"
        path: /etc/openssh/sshd_config
        state: present
      with_items:
        - "PasswordAuthentication no"
        - "Match address 10.20.30.0/29"
        - "    PasswordAuthentication yes"

    - name: Restarted sshd
      ansible.builtin.systemd:
        name: sshd
        state: restarted
  • Запускаем playbook-сценарий для настройки SSH по VPN-туннелю:
ansible-playbook -i ansible/inventory ansible/ssh_playbook.yml
    • Результат:

  • Проверяем:
    • Подключение с WebADM по туннельным IP-адресам на основе пароля:

  • Также проверяем что подключение по SSH из внешних сете (не по туннелю) доступно только по ключу:

 

  • Таким образом, на текущий момент получаем следующую структуру файлов и каталогов в рамках каталога /home/altlinux/bin на ControlVM:

Последнее изменение: среда, 15 января 2025, 16:18