0

terraform

Terraform на практике: управляем vDC

24.07.2023

Источник

Прошлая статья была теоретической, в ней мы разбирались с тем, что такое Terraform и для чего он нужен. В этой статье познакомимся с инфраструктурой VMware Cloud Director и рассмотрим инфраструктурный код Terraform для нашего проекта — прототипа интернет магазина на Django.

В следующей публикации мы вспомним основы работы в Ansible и напишем плейбуки для разворачивания ПО на подготовленных терраформом серверах. А сейчас начнем с планирования будущего проекта.

План проекта

План проекта

План реализации проекта и список инструментов, которые мы будем использовать на каждом этапе:

  1. Создание инфраструктуры. В виртуальном ЦОДе VMware будет создано 3 виртуальных сервера, связанных одной маршрутизируемой сетью, и подключенных к виртуальному роутеру, у которого есть публичный IP.
  2. Настройка инфраструктуры. Terraform удаленно развернет и конфигурирует виртуальные серверы и сеть вместе с виртуальным роутером, следуя строгой последовательности, которую мы ему зададим в конфигурационном файле.
  3. Установка и конфигурация ПО. После создания и настройки инфраструктуры, Ansible установит на каждый виртуальный сервер свой набор ПО.

Создавать инфраструктуру будем в VMware Cloud Director, настроим её с помощью Terraform, а устанавливать и конфигурировать ПО будем через Ansible. Графически наш стек можно представить так:

Отлично, план есть, теперь подробнее разберемся в каждом из пунктов. Начнём с облачной среды, в которой будет работать — VMware Cloud Director.

Инфраструктура — VMware Cloud Director

Инфраструктура — VMware Cloud Director

VMware Cloud Director — это система управления виртуальным ЦОДом бизнес-уровня. Одно из основных преимуществ клауд директора перед конкурентами — это низкий порог вхождения за счет интуитивно понятного интерфейса и расположения элементов управления.

VMware Cloud Director: виртуальный ЦОД бизнес-уровня

Если вы ещё не знакомы с VMware Cloud Director, рекомендуем прочесть нашу статью с подробным обзором возможностей Cloud Director и сравнением его с классической инфраструктурой и с Open source-решениями.

Вот базовые сущности, которые есть в Cloud Director:

  • Виртуальная машина (VM) — это удаленный сервер, который можно создать по индивидуальным параметрам или развернуть его из шаблона.
  • Группа виртуальных машин (vAPP) — это логическое объединение виртуальных машин для группового управления ими.
  • Маршрутизируемые и изолированные сети — виртуальные сети, которые связывают виртуальные машины между собой и с виртуальным роутером, если это нужно.
  • Виртуальный роутер (Edge) — это отдельная виртуальная машина со специальным сетевым ПО подключенная к внешней сети.

Кратко рассмотрим, взаимодействие всех сущностей Cloud Director и выразим его графически:

Главное сетевое устройство в Cloud Director — это виртуальный роутер (Edge), он подключен к внешней сети ЦОДа и имеет статический публичный IP. Эджей может быть несколько, и каждому из них может быть присвоен свой IP. В нашем случае нам доступен только 1 edge и 1 публичный IP.

К эджу подключаются маршрутизируемые сети, к которым подключаются виртуальные машины и контейнеры с виртуальными машинами для выхода в Интернет. Все они стоят за натом, поэтому у эджа есть богатый набор инструментов управления сетевым трафиком: Firewall, VPN, NAT.

Если нужно создать пул виртуальных машин с сетевой связанностью, но без доступа к интернету — используются изолированные сети. Они не подключаются к эджу, а значит не имеют доступа в интернет.

Если нужно создать пул виртуальных машин с сетевой связанностью, но без доступа к интернету — используются изолированные сети. Они не подключаются к эджу, а значит не имеют доступа в интернет.

Наша сетевая схема проекта выглядит так:сетевая схема проекта

Мы используем 1 виртуальный роутер, c 1 публичным IP, 1 маршрутизируемую сеть (10.10.0.1/24) и 3 виртуальные машины с доступом в интернет. Вопросы сетевой доступности виртуальных машин решаются с помощью настройки NAT-правил для входящего и исходящего трафиков.

Итак, у нас есть план и архитектура проекта. Можно переходить к коду.

Настройка инфраструктуры — Terraform

Настройка инфраструктуры — Terraform

Для подключения Terraform к Cloud Director мы будем использовать провайдер VCD. Не путайте с vDC! VCD — это сокращение от Virtual Cloud Director, а vDC — расшифровывается как Virtual Data Center. Сам проект Terraform будет состоять буквально из 2 файлов:

  • Settings.tf содержит набор переменных, значение которых задается на основе данных полученных из Cloud Director.
  • Main.tf содержит основной код инфраструктуры, параметры которой подтягиваются из переменных, содержащихся в settings.tf файле.

Позже в проект добавятся ещё Ansible файлы, которые мы рассмотрим в следующей статье, а сейчас разбиремся в логических блоках кода файла settings.tf:#=====Настройки для подключения======
variable "connect" {
 type = map
 default = {
 "organization" = "Cloud_155403_9";
 "url_connect" = "https://one.msk.cloud.mts.ru/api"
 "data_center" = "Cloud_155403_9_VDC"
 "user" = "pseudolukian"
 "password" = "W7JnMEDvHE7n"
 }
}

Здесь мы используем тип данных — map (список ключ:значение, или словарь), который задали в параметре type, а сами данные содержатся в параметре default.

В блоке “connect” мы указали данные для подключения к Cloud Director. Мы их взяли из Панели управления 1cloud:

Далее следует большой логический блок, содержащий сетевые настройки, разделенный на секции:#Настройки виртуального роутера
variable "edge" {
 type = map
 default = {
  "ip" = "89.22.173.56"
  "name" = "Cloud_155403_9_Edge"
 }
}

Данные в этом блоке взяты из раздела управления виртуальным роутером в Панели VMware Cloud Director.

Следующая небольшая секция описывает настройки подключения виртуального роутера к внешней сети:#Настройки внешней сети
variable "main_web" {
 type = map
 default = {
  "name" = "1Cloud-BackBone"
  "type" = "ext"
 }
}

Эти параметры взяты также из раздела управления виртуальным роутером Панели Cloud Director:

Название внешней сети — 1Cloud-BackBone, а IP виртуального роутера: 89.22.173.56. Теперь, когда мы получили все нужные данные для подключения и параметры сетевого устройства, можно создавать маршрутизируемую сеть.

Вот как выглядит код нашей сети:variable "routed_web" {
  type = map

  default =  {
    "edge" = "Cloud_155403_9_Edge" #Название виртуального роутера
    "name" = "routed_web" #Название создаваемой сети
    "gateway" = "10.10.0.1" #Шлюз сети
    "cidr" = "10.10.0.1/24" #CIDR сети
    "dhcp_start" =  "10.10.0.2" #Начало диапазона dhcp-адресов
    "dhcp_end" =  "10.10.0.2" #Конец диапазона dhcp-адресов
    "static_start" = "10.10.0.3" #Начало диапазона static_IP-адресов
    "static_end" = "10.10.0.5" #Конец диапазона static_IP-адресов
    "type" = "org" #Тип сети
    "netmask" = "255.255.255.0" #Маска сети
    "dns_1" = "8.8.8.8" #Адрес 1 DNS сервера
    "dns_2" = "1.1.1.1" #Адрес 2 DNS сервера
  }
}

Что тут происходит? Мы создаём маршрутизируемую сеть 10.10.0.1/24 с подключением к виртуальному роутеру — Cloud_155403_9_Edge, на 3 IP адреса: 10.10.0.3-10.10.0.5. Ещё мы принудительно задаём 2 DNS сервера, это нужно для корректной работы DNS на наших VM.

На этом логический блок кода, описывающий сетевые параметры нашего проекта заканчивается. Теперь рассмотрим блок описывающий параметры виртуальных машин. В этом блоке есть несколько секций. Первая секция — это параметры, которые будут передаваться в шаблон, по которому будут разворачиваться все VM:#===========Настройки VM================
variable "vm_main_template" {
  type = map
  default = {
    "catalog_name" = "OneCloud_Templates_SSD_Producer" #каталог шаблонов     "template_name" = "Ubuntu 18.04 x64 v7 (minimal requirements)" #vAPP шаблон
    "cpus" = 2 #количество ядер
    "memory" = 2048 #объем оперативной памяти
    "network_name" = "routed_web" #название сети, к которой подключена VM
    "ip_mode" = "MANUAL" #тип выдачи IP
  }
}

Здесь указаны данные, единые для всех VM: шаблон ОС, объем ресурсов и способ выдачи IP-адреса. Далее будет серия секций переменных, описывающих индивидуальные параметры, такие как имя VM, IP-адрес и SSH-порт:

Nginx VM

variable "Nginx" {
    type = map
    default = {
    "name" = "Nginx"
    "ip" = "10.10.0.3"
    "ssh_port" = "22"
    }
}

Django VM

variable "Django" {
    type = map
    default = {
    "name" = "Django"
    "ip" = "10.10.0.4"
    "ssh_port" = "23"
    }
}

PosrgreSQL VM

variable "PostgreSQL" {
    type = map
    default = {
    "name" = "PostgreSQL"
    "ip" = "10.10.0.5"
    "ssh_port" = "24"
    }
}

Последним идёт блок кода, содержащий параметры кастомизации гостевой ОС VM:#Параметры кастомизации
variable "guest_properties" {
    type = map
    default = {
    "admin_passwd" = "Pseudokatkov1"
    }
}

Кастомизация — это процесс настройки ОС на VM. Здесь мы задали пароль администратора ОС. Логин по умолчанию — root, а пароль — мы задали переменной admin_passwd.

Итак, параметры сети и VM мы задали. Время переходить к основному коду проекта — файлу main.tf. С полным кодом файла settings.tf можно ознакомиться здесь.

Если расположение блоков кода в settings.tf значения не имело, то в main.tf — это очень значимый параметр. Сначала мы подключаемся к Cloud Director, создаём маршрутизируемую сеть, подключаем её к роутеру, а затем создаём 3 VM и подключаем их к сети.

Первым блоком идёт код инициализации провайдера:terraform {
        required_providers {
        vcd = {
        source = "vmware/vcd"
        }
    }
    required_version = ">= 0.13"
}

Далее идёт блок кода подключения к Cloud Director:provider "vcd" {
    auth_type = "integrated" #Тип авторизации
    max_retry_timeout = 10 #Максимальное число попыток соединения
    user = var.connect["user"] #Имя пользователя
    password = var.connect["password"] #Пароль пользователя
    org = var.connect["organization"] #Название организации
    url = var.connect["url_connect"] #Адрес, на который будут посылаться API-запросы
}

В этом коде мы использовали ссылки на переменные, которые содержатся в settings.tf. Мы обратились к ним через ключевое слово var.название переменной [«имя ключа»].

Переходим к одному из важнейших блоков — блоку создания маршрутизируемой сети:#=====Создание маршрутизируемой сети======== resource "vcd_network_routed" "net" {
  org = var.connect["organization"] #Название организации
  vdc = var.connect["data_center"] #Название ЦОДа

  name = var.routed_web["name"] #Название сети
  edge_gateway = var.routed_web["edge"] #Название виртуального роутера
  gateway = var.routed_web["gateway"] #Шлюз сети

  netmask = var.routed_web["netmask"] #Маска сети
  dns1 = var.routed_web["dns_1"] #1 DNS-сервер
  dns2 = var.routed_web["dns_2"] #2 DNS-сервер

  dhcp_pool { #Пул dhcp-адресов
    start_address = var.routed_web["dhcp_start"] #первый адрес пула
    end_address = var.routed_web["dhcp_end"] #последний адрес пула
  }

  static_ip_pool { #Пул статик IP-адресов
    start_address = var.routed_web["static_start"] #первый адрес пула
    end_address = var.routed_web["static_end"] #последний адрес пула
  }
}

Важно обратить внимание на ручное указание DNS-серверов. У нас нет прав менять настройки виртуального роутера, иногда, может сложиться ситуация при которой прописанные DNS на роутере будут работать не корректно, поэтому мы указываем DNS-адреса вручную.

Далее нужно настроить NAT-правила для входящего (DNAT) и исходящего трафика(SNAT). Для всего исходящего трафика правило будет единым, поэтому оно одно на всю сеть:#SNAT-правила для исходящего трафика
resource "vcd_nsxv_snat" "web" {
  org = var.connect["organization"]
  vdc = var.connect["data_center"]

  edge_gateway = var.edge["name"]
  network_type = var.main_web["type"]
  network_name = var.main_web["name"]

  original_address   = var.routed_web["cidr"]
  translated_address = var.edge["ip"]

  depends_on = [vcd_network_routed.net] 
}

Для входящего трафика правила будут индивидуальные для каждой виртуальной машины. Это нужно для того, чтобы Ansible имел доступ по SSH к каждой машине. Дело в том, что у нас 1 внешний IP и, чтобы Ansible смог зайти на каждую VM — нам нужно настроить правильный проброс портов.

Смысл в том, что мы открываем на роутере 22, 23 и 24 порты и пробрасываем их на 22 порт каждой VM:

IP и порт, на который будет стучаться AnsibleФорвардингIP и порт VM в локальной сети
89.22.173.56:2210.10.0.3:22 — Nginx
89.22.173.56:2310.10.0.4:22 — Django
89.22.173.56:2410.10.0.5:22 — PostgreSQL

Код DNAT-правил выглядит так:

  • DNAT-правило для Nginx VMresource "vcd_nsxv_dnat" "Nginx" {
    org = var.connect["organization"]
    vdc = var.connect["data_center"]

    edge_gateway = var.edge["name"]
    network_type = var.main_web["type"]
    network_name = var.main_web["name"]

    original_address = var.edge["ip"]
    original_port = var.Nginx["ssh_port"]
    translated_address = var.Nginx["ip"]
    translated_port = "22"
    protocol = "tcp"
    }
  • DNAT-правило для Django VMresource "vcd_nsxv_dnat" "Django" {
    org = var.connect["organization"]
    vdc = var.connect["data_center"]

    edge_gateway = var.edge["name"]
    network_type = var.main_web["type"]
    network_name = var.main_web["name"]

    original_address = var.edge["ip"]
    original_port = var.Django["ssh_port"]
    translated_address = var.Django["ip"]
    translated_port = "22"
    protocol = "tcp"
    }
  • DNAT-правило для PostgreSQL VMresource "vcd_nsxv_dnat" "PostgreSQL" {
    org = var.connect["organization"]
    vdc = var.connect["data_center"]

    edge_gateway = var.edge["name"]
    network_type = var.main_web["type"]
    network_name = var.main_web["name"]

    original_address = var.edge["ip"]
    original_port = var.PostgreSQL["ssh_port"]
    translated_address = var.PostgreSQL["ip"]
    translated_port = "22"
    protocol = "tcp"
    }

Мы создали маршрутизируемую сеть и установили правила для входящего и исходящего трафиков. Сейчас можно запустить проект и убедиться в том, что всё работает исправно на данном этапе. Первая команда, которая обычно выполняется при запуске проекта — это terraform plan. Команда протестирует код на валидность и ошибки, если всё правильно вернётся отчет о том, что будет добавлено в инфраструктуру.

Вторая команда для развертывания инфраструктуры — это terraform apply. Обычно эта команда выполняется после terraform plan. Однако на этапе выполнения terraform apply могут возникнуть ошибки, которых не было при выполнении terraform plan.

Если при выполнении команды terraform apply ошибок не возникло можно двигаться далее и создавать виртуальные машины. Напомним, что исходный код проекта вы можете скачать здесь — в репозитории GitHub.

Приведем код создания только одной VM и на её примере покажем ключевые моменты этого процесса:resource "vcd_vm" "Nginx" {
  org = var.connect["organization"] #Имя организации
  vdc = var.connect["data_center"] #Имя ЦОДа

  name = var.Nginx["name"] #Название VM
  computer_name = var.Nginx["name"] #Имя хоста
  power_on = true #Машина будет включена после создания

  cpus = var.vm_main_template["cpus"] #Количество процессорных ядер
  memory = var.vm_main_template["memory"] #Количество оперативной памяти

  catalog_name  = var.vm_main_template["catalog_name"] #Каталог, с шаблоном VM
  template_name = var.vm_main_template["template_name"] #Шаблон VM

  network { #Настройки сети, к которой подключается VM
    name = var.routed_web["name"] #Имя сети
    type = var.routed_web["type"] #Тип сети
    ip_allocation_mode = var.vm_main_template["ip_mode"] #Тип выдачи IP -- ручной из Static pool
    ip = var.Nginx["ip"] #IP VM
  }

  customization { #Кастомизация ОС
    force                      = true #Применить параметры кастомизации
    allow_local_admin_password = true #Наличие локального пароля админа
    auto_generate_password     = false #Отмена автогенерации пароля
    admin_password             = var.guest_properties["admin_passwd"] #Пароль администратора
  }

  depends_on = [vcd_nsxv_snat.web] # Ожидания выполнения блока с кодом
}

Обратите внимание на блок customization. Он вносит изменения в ОС. В частности здесь мы указали на наличие локального пароля админа — allow_local_admin_password и задали его — admin_password. По умолчанию логин у всех Linux VM — root, а пароль может генерироваться случайно. Мы установили его фиксированным. Теперь Ansible может подключаться по SSH по паролю.

Следующий важный момент при создании VM — это директива depends_on. Она задает порядок обработки блоков кода. Мы буквально говорим ей следующее: выполнить создание VM только после создания маршрутизируемой сети. Такой порядок действий необходим, чтобы избежать ошибки на этапе подключения VM к сети.

Теперь можно запустить код инфраструктуры и убедиться в его работоспособности. После выполнения кода и появления VM в панели Cloud Director доступ к ним по SSH будет возможен спустя 1-1,5 минуты, не сразу. При первом запуске ОС будет выполняться настройка сетевого адаптера — это занимает время, поэтому между созданием инфраструктуры и запуском Ansible должно пройти время.

Подытожим всё вышесказанное и ещё раз ретроспективно взглянем на наш проект, а затем двинемся дальше — настраивать ПО с помощью Ansible.

Свежие комментарии

Подписка

Лучшие статьи


Fatal error: Uncaught Error: Call to a member function have_posts() on null in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php:391 Stack trace: #0 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/widget.php(257): fox56_blog_grid(NULL, Array) #1 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/register.php(33): include('/home/host18670...') #2 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/class-wp-widget.php(394): Wi_Widget_Latest_Posts->widget(Array, Array) #3 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/widgets.php(837): WP_Widget->display_callback(Array, Array) #4 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(417): dynamic_sidebar('sidebar') #5 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(136): fox56_single_sidebar() #6 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(7): fox56_single_inner() #7 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/single.php(23): fox56_single() #8 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/template-loader.php(106): include('/home/host18670...') #9 /home/host1867038/the-devops.ru/htdocs/www/wp-blog-header.php(19): require_once('/home/host18670...') #10 /home/host1867038/the-devops.ru/htdocs/www/index.php(17): require('/home/host18670...') #11 {main} thrown in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php on line 391