Инструментарий для работы с облачной инфраструктурой сегодня огромен: Terraform, Pulumi, Ansible, всевозможные SDK, прямое взаимодействие с REST API. У каждого варианта своя ниша — Terraform хорош, когда инфраструктуру нужно описать декларативно и хранить в репозитории, Ansible берёт на себя конфигурацию уже запущенных машин. Однако бывают ситуации проще: нужно развернуть полсотни одинаковых серверов под нагрузочный тест и удалить их через пару часов. Тащить для этого тяжеловесный фреймворк — лишние усилия.
Между тем существует куда более лёгкий способ, о котором почему-то вспоминают в последнюю очередь: консольный клиент OpenStack плюс стандартные утилиты командной оболочки. Ни state-файлов, ни YAML-манифестов — терминал и одна строчка, которая запускает весь процесс.
Эта статья построена как практическое руководство: читаете абзац — тут же повторяете шаг за шагом. В финале вы будете точно понимать, каким образом одна команда поднимает сотню серверов:
openstack server create \
--image "Ubuntu 22.04 LTS 64-bit" \
--network my_network \
--flavor SL1.1-2048-16 \
--max 100 \
my_server
Формально на этом можно остановиться. Но если хочется разобраться, что означает каждый аргумент, как подготовить рабочее окружение, выстроить сеть и автоматизировать рутину через shell — идём дальше.
Подготовка окружения
Первым делом организуем рабочее место. Консольный клиент OpenStack — обычный Python-пакет, который устанавливается через pip и взаимодействует с облаком по HTTP. Запускать его можно на локальной рабочей станции или на любой машине, у которой есть доступ к API-эндпоинту облака.
Установка клиента. Рекомендуется создать отдельное виртуальное окружение Python, чтобы зависимости клиента не пересекались с системными пакетами:
python3 -m venv ~/openstack-env
source ~/openstack-env/bin/activate
pip install python-openstackclient
Когда установка завершится, в терминале появится команда openstack.
Создание сервисного пользователя. Для обращения к API необходим отдельный сервисный аккаунт с правами администратора проекта. Заводится он через веб-панель облачного провайдера в разделе контроля доступа. Такой аккаунт предназначен строго для программного взаимодействия — входить через него в графический интерфейс не нужно.
Скачивание RC-файла. Это небольшой shell-скрипт, содержащий переменные окружения для авторизации клиента: URL Keystone, название проекта, имя пользователя, регион. Как правило, его можно загрузить из панели управления провайдера. Содержимое файла выглядит приблизительно так:
export OS_AUTH_URL=https://cloud.example.com:5000/v3
export OS_PROJECT_NAME="my_project"
export OS_USERNAME="service_user"
export OS_REGION_NAME="ru-1"
export OS_IDENTITY_API_VERSION=3
echo "Please enter your OpenStack Password for project $OS_PROJECT_NAME as user $OS_USERNAME: "
read -sr OS_PASSWORD_INPUT
export OS_PASSWORD=$OS_PASSWORD_INPUT
При каждом запуске скрипт запрашивает пароль в интерактивном режиме. Если вы часто переподключаетесь и хотите обойтись без ввода — допустимо записать значение OS_PASSWORD напрямую в файл, но на разделяемых серверах это создаёт риск утечки.
Загружаем переменные в текущую сессию:
source rc.sh
Проверка подключения. Убедиться, что клиент сконфигурирован верно, проще всего запросом токена авторизации:
openstack token issue
Ожидаемый результат:
+------------+----------------------------------+
| Field | Value |
+------------+----------------------------------+
| expires | 2025-06-15T12:00:00+0000 |
| id | gAAAAABmTk... |
| project_id | a1b2c3d4e5f6 |
| user_id | f6e5d4c3b2a1 |
+------------+----------------------------------+
Появилась таблица — значит, связь с облаком установлена и можно переходить к следующему этапу. Если терминал выдаёт HTTP 401 Unauthorized — перепроверьте учётные данные в RC-файле.
Знакомство с ключевыми сущностями
Для запуска виртуальной машины в OpenStack требуется четыре объекта: Image, Flavor, Network и Keypair. Рассмотрим назначение каждого.
Image — готовый образ операционной системы, на основе которого формируется диск ВМ. Облачный провайдер, как правило, предлагает набор публичных образов: Ubuntu, CentOS, Debian и ряд других дистрибутивов.
Flavor — шаблон ресурсов виртуальной машины: число vCPU, объём RAM, размер диска. По сути, это аналог «размера» или «типа инстанса» в других облаках. Ключевой нюанс: когда значение поля Disk в Flavor-е равно нулю, подразумевается подключение сетевого тома (Volume), создаваемого отдельной командой. При ненулевом значении Disk применяется локальный диск, и Volume не нужен.
Network — изолированная виртуальная сеть, к которой подключается сервер в момент создания. Без указания сети запустить ВМ невозможно.
Keypair — публичный SSH-ключ, который инжектируется в экземпляр при развёртывании. Благодаря ему на сервер можно зайти по ключу, минуя парольную аутентификацию.
Архитектурно OpenStack разделён на независимые сервисы, каждый из которых закрывает свою зону ответственности. При повседневной работе с CLI-клиентом задумываться об этом не обязательно — клиент сам определяет, к какому сервису направить запрос. Тем не менее общее представление о модулях полезно: Keystone занимается аутентификацией и ведёт каталог эндпоинтов, Nova (Compute) управляет жизненным циклом виртуальных машин, Neutron (Network) отвечает за виртуальные сети, Glance (Images) хранит образы ОС, а Cinder (Volume) управляет сетевыми дисками.
Посмотрим, как выглядят эти объекты через командную строку. Запрашиваем перечень образов:
openstack image list
Фрагмент вывода:
+--------------------------------------+------------------------------------------+--------+
| ID | Name | Status |
+--------------------------------------+------------------------------------------+--------+
| 41383415-4c61-46e5-8cc4-ead5a6f78cf8 | CentOS 7 64-bit | active |
| a1b081d7-9eff-44b0-8d3f-85fabf2c88a6 | CentOS 9 Stream 64-bit | active |
| 7a827898-058b-4faf-bf8a-0d2311bef7c3 | Ubuntu 20.04 LTS 64-bit | active |
| 1fd341e8-82c2-4421-8a37-912cd5ee3ae3 | Ubuntu 22.04 LTS 64-bit | active |
+--------------------------------------+------------------------------------------+--------+
У каждого образа есть UUID и текстовое название. В командах допускается использовать оба варианта идентификации.
Смотрим доступные Flavor-ы:
openstack flavor list
Фрагмент вывода:
+------+--------------------+-------+------+-----------+-------+-----------+
| ID | Name | RAM | Disk | Ephemeral | VCPUs | Is Public |
+------+--------------------+-------+------+-----------+-------+-----------+
| 1312 | SL1.1-2048-16 | 2048 | 16 | 0 | 1 | True |
| 1313 | SL1.2-4096-32 | 4096 | 32 | 0 | 2 | True |
| 1314 | SL1.2-8192-64 | 8192 | 64 | 0 | 2 | True |
| 1012 | SL1.1-2048 | 2048 | 0 | 0 | 1 | True |
| 1013 | SL1.2-4096 | 4096 | 0 | 0 | 2 | True |
+------+--------------------+-------+------+-----------+-------+-----------+
Столбец Disk здесь принципиален: у SL1.1-2048-16 значение 16 ГБ — это Flavor с локальным хранилищем. У SL1.1-2048 стоит 0 — под такой Flavor понадобится отдельно созданный Volume. Выбор между ними определяет порядок действий при развёртывании.
Создание сети и SSH-ключа
До запуска серверов нужно организовать сетевую среду и зарегистрировать SSH-ключ.
Заводим приватную сеть:
openstack network create my_network
Внутри неё определяем подсеть:
openstack subnet create \
--subnet-range 192.168.0.0/24 \
--network my_network \
my_subnet
Маска /24 обеспечивает 254 адреса — с запасом хватает для ста серверов. Если в перспективе экземпляров будет больше, имеет смысл сразу взять более широкий диапазон, например /16.
Обратите внимание: пока мы не создаём ни роутера, ни маршрута наружу. Машины останутся без интернет-доступа — это нормально, сетевую связность с внешним миром мы настроим чуть позже через bastion-хост.
Регистрируем SSH-ключ. Без него войти на сервер получится лишь по паролю, что неудобно и менее безопасно. Передаём публичную часть ключа в OpenStack:
openstack keypair create --public-key ~/.ssh/id_rsa.pub my_keypair
Если ключевой пары ещё нет, сгенерируйте её: ssh-keygen -t ed25519. Путь ~/.ssh/id_rsa.pub — расположение по умолчанию для RSA-ключей. Для ed25519 файл будет лежать по пути ~/.ssh/id_ed25519.pub.
Убедимся, что ключ зарегистрирован:
openstack keypair list +------------+-------------------------------------------------+------+
| Name | Fingerprint | Type |
+------------+-------------------------------------------------+------+
| my_keypair | ab:cd:ef:12:34:56:78:90:ab:cd:ef:12:34:56:78:90 | ssh |
+------------+-------------------------------------------------+------+
Подготовительная часть завершена — можно переходить к самому интересному.
Массовое создание серверов с локальным диском
Ради этого момента мы и проделали всю подготовку. Запускаем сразу сто серверов одной командой:
openstack server create \
--image "Ubuntu 22.04 LTS 64-bit" \
--network my_network \
--flavor SL1.1-2048-16 \
--key-name my_keypair \
--max 100 \
my_server
Пройдёмся по аргументам. Флаг --image указывает, из какого образа создать диск. Флаг --flavor задаёт конфигурацию ресурсов (1 vCPU, 2 ГБ RAM, 16 ГБ локального диска). Флаг --network определяет сеть для подключения. Флаг --key-name добавляет SSH-ключ. Наконец, --max 100 — ключевой параметр, который указывает Nova сформировать не один экземпляр, а сразу сотню в рамках единственного обращения к API.
Что происходит на уровне API. Nova получает запрос и отдаёт его планировщику (scheduler). Тот размещает экземпляры по вычислительным узлам, учитывая загрузку и доступные ресурсы каждого гипервизора. Серверы получают автоматические суффиксы — my_server-1, my_server-2 и так далее до my_server-100, а также индивидуальные IP-адреса из нашей подсети.
В зависимости от текущей нагрузки на облако весь процесс занимает от десятка секунд до нескольких минут. Смотрим, что получилось:
openstack server list
Фрагмент вывода:
+--------------------------------------+--------------+--------+--------------------------+-------------------------+---------------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+--------------+--------+--------------------------+-------------------------+---------------+
| 031080fa-3aa0-4848-8551-fd1e6f6ec65f | my_server-94 | ACTIVE | my_network=192.168.0.58 | Ubuntu 22.04 LTS 64-bit | SL1.1-2048-16 |
| 03864c9b-6b9b-4bea-8679-93367ca52250 | my_server-87 | ACTIVE | my_network=192.168.0.24 | Ubuntu 22.04 LTS 64-bit | SL1.1-2048-16 |
| 0efb6875-c481-4ec0-bc80-3392d84c89d6 | my_server-82 | ACTIVE | my_network=192.168.0.110 | Ubuntu 22.04 LTS 64-bit | SL1.1-2048-16 |
| 1619eaf7-c7ab-4db0-a875-38c39acabdac | my_server-76 | ACTIVE | my_network=192.168.0.52 | Ubuntu 22.04 LTS 64-bit | SL1.1-2048-16 |
+--------------------------------------+--------------+--------+--------------------------+-------------------------+---------------+
Считаем серверы в рабочем состоянии:
openstack server list -f value | grep -c ACTIVE
100
Все сто экземпляров перешли в статус ACTIVE. Единственная строка в терминале — и готово, без единого скрипта или конфигурационного файла.
Настройка сети и доступ через bastion
Серверы запущены, но попасть на них пока нельзя: вся сотня живёт в изолированной приватной сети. Стандартное решение — выделить один экземпляр под роль bastion-хоста, назначить ему внешний IP и заходить на остальные машины уже через него.
Формируем виртуальный роутер и подключаем к нему нашу подсеть:
openstack router create my_router
openstack router add subnet my_router my_subnet
Для того чтобы машины из приватной сети могли обращаться в интернет (обновлять пакеты, скачивать зависимости), роутеру требуется шлюз во внешнюю сеть. Сначала выясним её название:
openstack network list --external -f value -c Name
external-network
Назначаем внешний шлюз:
openstack router set --external-gateway external-network my_router
С этого момента все экземпляры в подсети имеют исходящий доступ в интернет через NAT. Однако инициировать подключение снаружи к ним по-прежнему нельзя — для этого понадобится плавающий (floating) IP-адрес.
Выделяем floating IP:
openstack floating ip create external-network
Команда вернёт присвоенный адрес. Увидеть все выделенные адреса можно так:
openstack floating ip list
Предположим, нам достался адрес 31.129.42.122. Привязываем его к первому серверу — он станет нашим bastion-хостом:
openstack server add floating ip my_server-1 31.129.42.122
Теперь my_server-1 доступен извне. Чтобы попасть на произвольную машину, достаточно узнать её приватный адрес. Допустим, нас интересует my_server-99:
openstack server show my_server-99 -f value -c addresses
{'my_network': ['192.168.0.111']}
Подключаемся транзитом через bastion — для этого в SSH предусмотрен механизм ProxyJump (флаг -J):
ssh -J root@31.129.42.122 root@192.168.0.111 root@my-server-99:~#
Мы на месте. Схема рабочая: единственный публичный адрес, один bastion — и с него открыт путь ко всей приватной инфраструктуре.
Массовое создание серверов с сетевым диском
Ранее мы использовали Flavor с ненулевым полем Disk — локальное хранилище создаётся вместе с ВМ автоматически. В реальных проектах нередко применяют сетевые диски (Volumes): они существуют независимо от гипервизора, поддерживают моментальные снимки и сохраняются при пересоздании экземпляра.
Процесс выглядит иначе: сперва формируется Volume на основе образа ОС, а затем он передаётся команде создания ВМ. Принципиальный момент — Cinder API не предоставляет аналога параметра --max для пакетного создания томов. Именно тут вступает в дело автоматизация средствами shell.
Сначала уберём ранее созданные серверы. Предупреждение: приведённая ниже команда удалит все экземпляры в текущем проекте. Убедитесь, что работаете в верном окружении.
openstack server list -f value -c ID | xargs -I {} -P 10 bash -c 'openstack server delete {}'
Как это устроено: openstack server list -f value -c ID возвращает только идентификаторы серверов — по одному на строку, без оформления таблицы. Утилита xargs -I {} вставляет каждый ID на место плейсхолдера {}. Параметр -P 10 запускает до десяти параллельных процессов, что кратно ускоряет операцию по сравнению с последовательным выполнением.
Переходим к созданию ста сетевых дисков. Генерируем числовую последовательность утилитой seq и передаём её в xargs:
seq 1 100 | xargs -I {} -P 10 bash -c \
'openstack volume create --size 10 --image "Ubuntu 22.04 LTS 64-bit" my_volume_{}'
Каждый том получает ёмкость 10 ГБ и создаётся на базе образа Ubuntu. Результатом станут объекты my_volume_1, my_volume_2, ..., my_volume_100. Десять параллельных потоков (-P 10) делают процесс существенно быстрее последовательного цикла.
Определяем собственный Flavor с нулевым диском — под сетевое хранилище:
openstack flavor create --private --disk 0 --vcpus 1 --ram 1024 my_flavor_net_hdd
Параметр --private ограничивает видимость этого Flavor-а только нашим проектом.
Финальный шаг — запуск ста серверов, каждый с привязанным Volume:
seq 1 100 | xargs -I {} -P 10 bash -c \
'openstack server create \
--network my_network \
--key-name my_keypair \
--flavor my_flavor_net_hdd \
--volume my_volume_{} \
my_server_{}'
Обратите внимание на замену: вместо --image используется --volume. Сервер не создаёт диск из образа самостоятельно, а получает заранее подготовленный том.
Ждём перехода всех экземпляров в рабочее состояние:
openstack server list -f value | grep -c ACTIVE
100
Сотня серверов на сетевых дисках готова к работе.
Полезные приёмы shell-автоматизации
Выше мы уже задействовали xargs и seq, но возможности shell-автоматизации этим не ограничиваются. Консольный клиент OpenStack поддерживает несколько форматов вывода, удобных для машинной обработки. Ниже — подборка приёмов, полезных в ежедневной работе.
Управление форматом вывода. Флаг -f value отключает табличную разметку и выдаёт «голые» значения, разделённые пробелом. Флаг -c COLUMN ограничивает набор колонок. Вместе они позволяют вытянуть ровно ту информацию, которая нужна:
# Только имена серверов
openstack server list -f value -c Name
# Только ID и статус
openstack server list -f value -c ID -c Status
Когда задача сложнее, на помощь приходит вывод в JSON и утилита jq:
# Получить IP-адреса всех серверов
openstack server list -f json | jq -r '.[].Networks'
# Найти серверы в статусе ERROR
openstack server list -f json | jq -r '.[] | select(.Status=="ERROR") | .Name'
Параллельное выполнение операций. Конструкция xargs -I {} -P N работает с любыми командами OpenStack — достаточно подставить нужную:
# Перезагрузить все серверы с именем my_server-*
openstack server list -f value -c ID -c Name | grep my_server | awk '{print $1}' \
| xargs -I {} -P 5 bash -c 'openstack server reboot {}'
# Удалить все floating IP
openstack floating ip list -f value -c ID \
| xargs -I {} -P 5 bash -c 'openstack floating ip delete {}'
Наблюдение за прогрессом. При массовом развёртывании удобно отслеживать, сколько серверов уже поднялось:
watch -n2 'openstack server list -f value | grep -c ACTIVE'
Утилита watch перезапускает указанную команду раз в 2 секунды, показывая актуальный счётчик активных экземпляров.
Полная очистка проекта одной серией команд. Когда эксперимент закончен, инфраструктуру нужно аккуратно снести. Важен порядок: сначала экземпляры, затем тома, после — сетевые объекты:
# Серверы
openstack server list -f value -c ID | xargs -I {} -P 10 bash -c 'openstack server delete --wait {}'
# Volumes
openstack volume list -f value -c ID | xargs -I {} -P 10 bash -c 'openstack volume delete {}'
# Floating IP
openstack floating ip list -f value -c ID | xargs -I {} bash -c 'openstack floating ip delete {}'
# Роутер (сначала отвязать подсеть и шлюз)
openstack router remove subnet my_router my_subnet
openstack router unset --external-gateway my_router
openstack router delete my_router
# Сеть
openstack subnet delete my_subnet
openstack network delete my_network
Параметр --wait при удалении серверов блокирует выполнение до фактического завершения операции. Это существенно, если следующим шагом вы удаляете Volume, ещё привязанный к экземпляру, — без --wait удаление тома может завершиться ошибкой.
Итоги и шпаргалка
Связка консольного клиента OpenStack со стандартными утилитами shell закрывает большую часть разовых задач: пакетное развёртывание и удаление серверов, конфигурирование сети, работа с дисками. Для экспериментов и кратковременных инфраструктур это наиболее прямой путь — без деклараций, плейбуков и файлов состояния.
В какой момент разумно перейти на полноценные IaC-инструменты? Когда инфраструктура превращается из временной в постоянную, когда нужна история изменений и код-ревью, когда настройка серверов выходит за пределы «создать и подключиться по SSH». В таких случаях стоит обратиться к Terraform или Ansible. А до тех пор — терминал и shell справятся.
Справочник по командам, использованным в статье:
# Авторизация и проверка
source rc.sh
openstack token issue
# Образы и Flavor-ы
openstack image list
openstack flavor list
openstack flavor create
# SSH-ключи
openstack keypair create
openstack keypair delete
openstack keypair list
openstack keypair show
# Серверы
openstack server create
openstack server delete
openstack server list
openstack server show
openstack server reboot
openstack server add floating ip
openstack server remove floating ip
# Сетевые диски
openstack volume create
openstack volume delete
openstack volume list
openstack volume show
# Сеть
openstack network create
openstack network delete
openstack network list
openstack network show
openstack network list --external
# Подсети
openstack subnet create
openstack subnet delete
openstack subnet list
openstack subnet show
# Роутеры
openstack router create
openstack router delete
openstack router list
openstack router show
openstack router add subnet
openstack router remove subnet
openstack router set --external-gateway
openstack router unset --external-gateway
# Floating IP
openstack floating ip create
openstack floating ip list
openstack floating ip delete
# Полезные флаги
-f value # вывод без таблицы
-f json # JSON-формат
-c COLUMN # выбор колонки
--max N # массовое создание серверов
--wait # ждать завершения операции
# Shell-утилиты
seq 1 N # последовательность чисел
xargs -I {} -P N bash -c '...' # параллельное выполнение
watch -n2 'команда' # мониторинг в реальном времени
jq # обработка JSON