До сих пор мы поднимали виртуалки и контейнеры руками на одном-двух хостах. Когда машин становятся десятки, а команд, которым нужны ресурсы, несколько, ручное управление превращается в источник ошибок и дрейфа. Этот урок про два слоя облака. Первый - OpenStack, открытая платформа, которая из стойки серверов делает частное IaaS-облако с самообслуживанием. Второй - Terraform (и его форк OpenTofu), декларативный инструмент инфраструктуры как кода, который описывает желаемое состояние и приводит реальную инфраструктуру к нему. Задача администратора - понимать, из чего собран OpenStack, и уметь управлять им и любым другим облаком кодом, а не кликами.

Как это работает
OpenStack - не один демон, а набор слабо связанных сервисов, общающихся через REST API и общую шину сообщений (RabbitMQ). Каждый сервис отвечает за свой ресурс, и понимать надо именно границы ответственности.
Keystone - служба идентификации. Выдает токены, хранит проекты (бывшие tenant), пользователей, роли и каталог эндпоинтов. Любой запрос к любому сервису сначала проходит через Keystone: без валидного токена Nova даже не станет с вами разговаривать. Glance - реестр образов: хранит готовые шаблоны дисков (qcow2, raw), из которых создаются ВМ. Nova - вычисления: планировщик выбирает гипервизор (обычно KVM через libvirt), создает и управляет жизненным циклом инстансов. Neutron - сети как сервис: виртуальные сети, подсети, маршрутизаторы, security groups, floating IP; под капотом - Open vSwitch или OVN. Cinder - блочные тома, которые подключаются к инстансам как диски и переживают их пересоздание. Поверх этого - Horizon (веб-панель) и Swift (объектное хранилище, аналог S3).
Логика создания ВМ: вы просите Nova запустить инстанс из образа Glance, заданного flavor (CPU/RAM/диск), в сети Neutron, опционально с томом Cinder. Nova-scheduler фильтрует хосты по фильтрам (хватает ли ресурсов, совпадают ли метки) и взвешивает оставшиеся, выбирая лучший. Все это - вызовы API, а значит, автоматизируемо.
Тут на сцену выходит инфраструктура как код. Terraform описывает желаемое состояние в файлах .tf на языке HCL. Ключевая идея - декларативность: вы пишете не как создать, а что должно существовать. Terraform строит граф зависимостей ресурсов и сам вычисляет порядок операций.
Механика держится на трех понятиях. Провайдер - плагин, который умеет говорить с конкретным API (openstack, aws, libvirt, docker). Ресурс - единица инфраструктуры (инстанс, сеть, том). State - файл terraform.tfstate, в котором Terraform запоминает, что он уже создал и с какими реальными ID. State - это карта между вашим кодом и реальным миром. Без него Terraform не знал бы, какой именно инстанс соответствует вашему блоку в коде.
Отсюда вытекает идемпотентность: команда plan сравнивает три вещи - ваш код (желаемое), state (что Terraform думает о реальности) и при refresh реальное API (что есть на самом деле) - и показывает только разницу. Запустите apply дважды без изменений в коде - второй раз не сделает ничего. Это и есть защита от дрейфа конфигурации: если кто-то руками поменял ресурс в обход кода, plan покажет расхождение, а apply вернет все к описанному состоянию.
Команды и примеры
Установка клиента OpenStack и Terraform/OpenTofu различается по семействам.
Код: Выделить всё
# Debian 13 / Ubuntu 24.04
apt install python3-openstackclient
# RHEL 10 / Fedora 41+
dnf install python3-openstackclient
# Terraform ставится из репозитория HashiCorp, либо берем OpenTofu (открытый форк):
# Fedora/RHEL
dnf install opentofu # бинарь называется tofu, флаги те же
# Debian/Ubuntu
apt install opentofuКод: Выделить всё
source ~/admin-openrc.sh # OS_AUTH_URL, OS_PROJECT_NAME, OS_USERNAME...
openstack token issue # проверяем, что Keystone нас пускает
openstack image list # образы из Glance
openstack flavor list # типоразмеры
openstack network list # сети Neutron
openstack server create \
--image debian-13 --flavor m1.small \
--network internal --key-name mykey web01 # Nova поднимает инстанс
openstack volume create --size 10 data01 # том Cinder
openstack server add volume web01 data01Код: Выделить всё
# main.tf
terraform {
required_providers {
openstack = { source = "terraform-provider-openstack/openstack" }
}
}
provider "openstack" {} # читает переменные OS_* из окружения
resource "openstack_compute_instance_v2" "web" {
name = "web01"
image_name = "debian-13"
flavor_name = "m1.small"
key_pair = "mykey"
network { name = "internal" }
}
resource "openstack_blockstorage_volume_v3" "data" {
name = "data01"
size = 10
}
resource "openstack_compute_volume_attach_v2" "att" {
instance_id = openstack_compute_instance_v2.web.id # неявная зависимость
volume_id = openstack_blockstorage_volume_v3.data.id
}Код: Выделить всё
terraform init # скачивает плагин провайдера в .terraform/
terraform fmt # причесывает форматирование
terraform validate # проверяет синтаксис и типы
terraform plan # показывает diff: + создать, ~ изменить, - удалить
terraform apply # применяет; спросит подтверждение
terraform destroy # сносит все, что описано в кодеЧастые грабли
- State хранится локально и попадает в git вместе с секретами. На проде выносите его в remote backend (Swift, S3, Postgres) с блокировкой, иначе два инженера затрут изменения друг друга.
- Правка ресурса руками в Horizon в обход кода. Дрейф всплывет на следующем plan как неожиданное изменение, а apply молча откатит вашу ручную правку.
- Путаница floating IP и fixed IP в Neutron. Инстанс с приватным адресом недоступен снаружи, пока не назначен и не связан floating IP, а security group не открыла порт.
- Смена immutable-поля (например image_name) ведет не к правке, а к пересозданию инстанса. Всегда читайте план: -/+ означает destroy и create, данные на эфемерном диске пропадут.
- Удаление ресурса из .tf не равно удалению ресурса. Если убрать блок, apply его уничтожит. Чтобы перестать управлять, не разрушая, используйте terraform state rm.
- Версии провайдера не закреплены. Без блока required_providers с version у соседа init подтянет другую версию и план разойдется. Коммитьте .terraform.lock.hcl.
- Если нет доступа к OpenStack, разверните учебный стенд DevStack в отдельной ВМ или возьмите провайдер libvirt - цикл Terraform идентичен.
- Загрузите openrc, выполните openstack token issue и openstack catalog list - разберите, какие эндпоинты Keystone отдает.
- Создайте проект Terraform из примера выше, выполните init и plan. Прочитайте план вслух: сколько ресурсов и в каком порядке.
- Сделайте apply, затем зайдите в Horizon и вручную поменяйте имя или размер security group инстанса.
- Запустите plan еще раз и найдите дрейф. Решите: вернуть кодом (apply) или зафиксировать в коде.
- Добавьте второй том и подключение, снова plan/apply - убедитесь, что существующий инстанс не пересоздается.
- Выполните destroy и проверьте в CLI, что все ресурсы исчезли, а tfstate опустел.
- Какой сервис OpenStack выдает токены, и почему запрос к Nova без него обречен?
- Чем Cinder отличается от эфемерного диска инстанса, и что переживет пересоздание ВМ?
- Что хранится в файле terraform.tfstate и почему его потеря - серьезная проблема?
- Объясните разницу plan и apply и как из них следует идемпотентность.
- Как Terraform определяет порядок создания ресурсов без явного указания очередности?
- Что покажет plan, если ресурс изменили вручную в обход кода, и как это связано с дрейфом конфигурации?