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

02.06.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, рекомендуем прочесть нашу статью с подробным обзором возможностей Cloud Director и сравнением его с классической инфраструктурой и с Open source-решениями.

 

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

Кратко рассмотрим, взаимодействие всех сущностей 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 к Cloud Director мы будем использовать провайдер VCD. Не путайте с vDC! VCD — это сокращение от Virtual Cloud Director, а vDC — расшифровывается как Virtual Data Center. Сам проект Terraform будет состоять буквально из 2 файлов:

Позже в проект добавятся ещё 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-порт:

Последним идёт блок кода, содержащий параметры кастомизации гостевой ОС 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:22 10.10.0.3:22 — Nginx
89.22.173.56:23 10.10.0.4:22 — Django
89.22.173.56:24 10.10.0.5:22 — PostgreSQL

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

Мы создали маршрутизируемую сеть и установили правила для входящего и исходящего трафиков. Сейчас можно запустить проект и убедиться в том, что всё работает исправно на данном этапе. Первая команда, которая обычно выполняется при запуске проекта — это 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.

Что мы узнали о Terraform, Cloud Director и IaC?

Terraform — это Open Source гибкая система управления состоянием инфраструктуры, использующая декларативный стиль. Terraform написан на Go и имеет активное и многочисленное комьюнити, которое постоянно создает и совершенствует модули и провайдеры для Terraform.

Terraform подключается к облачным провайдерам с помощью специальных модулей — провайдеров. Для подключения к VMware Cloud Director используется провайдер VCD, который можно скачать с GitHub.

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

Для удалённого управления облачной инфраструктурой у Cloud Director есть API, к которому подключается Terraform при исполнении инфраструктурного кода.

Инфраструктурный код (IaC) Terraform пишет на специальном языке HCL, разработанном компанией HashiCorp. Синтаксис языка напоминает JSON, но имеет блочную структуру. Для каждого провайдера предусмотрены свои аргументы в блоках кода.

IaC код проекта удобно хранить в GitHub и работать через него же — это даёт единую точку входа для работы с инфраструктурой и повышает прозрачность кода — все правки внесенные в код должны пройти согласование.

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

Если вам хочется углубиться в тему IaC и подробнее в ней разобраться — рекомендуем обратить внимание на следующие статьи:

Зарегистрироваться