Создание стека на основе модуля оркестрации Heat
Обратите внимание! Для построения инфраструктуры рекомендуется использовать terraform.
Основная документация к провайдеру Дополнительные примеры работы
Рассмотрим построение готовой инфраструктуры из виртуальных устройств при помощи модуля оркестрации Heat.
Для работы с Heat потребуется пакет python-heat, который присутствует в репозиториях большинства современных дистрибутивов.
Установить его можно из репозитория PyPI c помощью утилиты PIP.
Для того, чтобы скачать дистрибутив, выполните следующие действия:
- Перейдите на вкладку Доступ в подразделе Проекты раздела Облачная платформа Selectel панели управления.
- Установите переключатель на кнопку Внешний доступ.
- Укажите имя пользователя.
- Нажмите кнопку Скачать.
Основные понятия
Стек (англ. stack) — это набор облачных ресурсов (машин, логических томов, сетей и т.д.), объединённых в цельную структуру.
Шаблон — это описание стека. Обычно оно представлено в виде текстового файла в особом формате. Шаблон содержит описание ресурсов и их связи. При этом ресурсы могут быть описаны в любом порядке: сборка стека осуществляется в автоматическом режиме. Созданные ранее стеки можно использовать в качестве ресурса для описания в других шаблонах, что позволяет создавать так называемые вложенные стеки (nested stacks).
Структуру шаблонов и правила их написания рассмотрим на практическом примере. Создадим стек, состоящий из двух серверов, локальной сети и роутера, через который будет осуществляться выход в открытую сеть.
Форматы шаблонов
Шаблоны могут быть представлены в нескольких форматах. К использованию рекомендуется формат HOT, который был создан специально для проекта Heat и отличается достаточно простым и понятым синтаксисом. Формат основан на YAML, поэтому при редактировании текста важно следить за использованием пробелов в отступах и их иерархии.
Для обеспечения совместимости с шаблонами, используемыми в Amazon EC2, поддерживается также формат CFN (AWS CloudFormation).
Структура шаблона
Создается стек при помощи следующего шаблона:
heat_template_version: 2013-05-23
description: Basic template of two servers, one network and one router
parameters:
key_name:
type: string
description: Name of keypair to assign to servers for ssh authentication
public_net_id:
type: string
description: UUID of public network to outer world
server_flavor:
type: string
description: UUID of virtual hardware configurations that are called flavors in openstack
private_net_name:
type: string
description: Name of private network (L2 level)
private_subnet_name:
type: string
description: Name of private network subnet (L3 level)
router_name:
type: string
description: Name of router that connects private and public networks
server1_name:
type: string
description: Custom name of server1 virtual machine
server2_name:
type: string
description: Custom name of server2 virtual machine
image_centos7:
type: string
description: UUID of glance image with centos 7 distro
image_debian7:
type: string
description: UUID of glance image with debian 7 distro
resources:
private_net:
type: OS::Neutron::Net
properties:
name: { get_param: private_net_name }
private_subnet:
type: OS::Neutron::Subnet
properties:
name: { get_param: private_subnet_name }
network_id: { get_resource: private_net }
allocation_pools:
- start: "192.168.0.10"
end: "192.168.0.254"
cidr: "192.168.0.0/24"
enable_dhcp: True
gateway_ip: "192.168.0.1"
router:
type: OS::Neutron::Router
properties:
name: { get_param: router_name }
external_gateway_info: { "enable_snat": True, "network": { get_param: public_net_id }}
router_interface:
type: OS::Neutron::RouterInterface
properties:
router_id: { get_resource: router }
subnet_id: { get_resource: private_subnet }
server1:
type: OS::Nova::Server
properties:
name: { get_param: server1_name }
block_device_mapping:
- volume_size: 5
volume_id: { get_resource: "server1_disk" }
device_name: "/dev/vda"
config_drive: "False"
flavor: { get_param: server_flavor }
image: { get_param: image_centos7 }
key_name: { get_param: key_name }
networks:
- port: { get_resource: server1_port }
server1_disk:
type: OS::Cinder::Volume
properties:
name: server1_disk
image: { get_param: image_centos7 }
size: 5
server1_port:
type: OS::Neutron::Port
properties:
network_id: { get_resource: private_net }
fixed_ips:
- subnet_id: { get_resource: private_subnet }
server1_floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network_id: { get_param: public_net_id }
port_id: { get_resource: server1_port }
depends_on: router_interface
server2:
type: OS::Nova::Server
properties:
name: { get_param: server2_name }
block_device_mapping:
- volume_size: 5
volume_id: { get_resource: "server2_disk" }
device_name: "/dev/vda"
config_drive: "False"
flavor: { get_param: server_flavor }
image: { get_param: image_debian7 }
key_name: { get_param: key_name }
networks:
- port: { get_resource: server2_port }
server2_disk:
type: OS::Cinder::Volume
properties:
name: server2_disk
image: { get_param: image_debian7 }
size: 5
server2_port:
type: OS::Neutron::Port
properties:
network_id: { get_resource: private_net }
fixed_ips:
- subnet_id: { get_resource: private_subnet }
outputs:
server1_private_ip:
description: private ip within local subnet of server1 with installed Centos 7 distro
value: { get_attr: [ server1_port, fixed_ips, 0, ip_address ] }
server1_public_ip:
description: floating_ip that is assigned to server1 server
value: { get_attr: [ server1_floating_ip, floating_ip_address ] }
server2_private_ip:
description: private ip within local subnet of server2 with installed Debian 7 distro
value: { get_attr: [ server2, first_address ] }
Рассмотрим его структуру более подробно.
Шаблон состоит из нескольких блоков. В первом указывается версия шаблона и используемый формат описания. В каждом новом выпуске платформы openstack поддерживается свой набор свойств и атрибутов, который постепенно изменяется. В приводимых примерах используется версия 2013-05-23, которая поддерживает все свойства, реализованные при выпуске релиза Icehouse.
heat_template_version: 2013-05-23
description: >
Basic template of two servers, one network and one router
Во втором блоке приводится общее описание шаблона и его назначения:
parameters:
key_name:
type: string
description: Name of keypair to assign to servers for ssh authentication
public_net_id:
type: string
description: UUID of public network to outer world
default: 98863f6c-638e-4b48-a377-01f0e86f34ae
server_flavor:
type: string
description: UUID of virtual hardware configurations that are called flavors in openstack
private_net_name:
type: string
description: The Name of private network (L2 level)
private_subnet_name:
type: string
description: the Name of private subnet (L3 level)
router_name:
type: string
description: The Name of router that connects private and public networks
server1_name:
type: string
description: Custom name of server1 virtual machine
server2_name:
type: string
description: Custom name of server2 virtual machine
image_centos7:
type: string
description: UUID of glance image with centos 7 distro
image_debian7:
type: string
description: UUID of glance image with debian 7 distro
Далее перечисляются некоторые дополнительные параметры, которые будут переданы Heat при создании стека. В параметре key_name указывается пара ключей для подключения к созданному серверу по ssh. А в параметрах server_flavor и public_net_id — идентификаторы (UUID) «аппаратной» конфигурации виртуальной машины и публичной сети.
Здесь же указываем произвольные имена для новых устройств и машин:
resources:
private_net:
type: OS::Neutron::Net
properties:
name: { get_param: private_net_name }
private_subnet:
type: OS::Neutron::Subnet
properties:
name: { get_param: private_subnet_name }
network_id: { get_resource: private_net }
allocation_pools:
- start: "192.168.0.10"
end: "192.168.0.254"
cidr: "192.168.0.0/24"
enable_dhcp: True
gateway_ip: "192.168.0.1"
router:
type: OS::Neutron::Router
properties:
name: { get_param: router_name }
external_gateway_info: { "enable_snat": True, "network": { get_param: public_net_id}}
router_interface:
type: OS::Neutron::RouterInterface
properties:
router_id: { get_resource: router }
subnet_id: { get_resource: private_subnet }
server1:
type: OS::Nova::Server
properties:
name: { get_param: server1_name }
block_device_mapping:
- volume_size: 5
volume_id: { get_resource: "server1_disk" }
device_name: "/dev/vda"
config_drive: "False"
flavor: { get_param: server_flavor }
image: { get_param: image_server1 }
key_name: { get_param: key_name }
networks:
- port: { get_resource: server1_port }
server1_disk:
type: OS::Cinder::Volume
properties:
name: server1_disk
image: { get_param: image_server1 }
size: 5
server1_port:
type: OS::Neutron::Port
properties:
network_id: { get_resource: private_net }
fixed_ips:
- subnet_id: { get_resource: private_subnet }
server1_floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network_id: { get_param: public_net_id }
port_id: { get_resource: server1_port }
depends_on: router_interface
server2:
type: OS::Nova::Server
properties:
name: { get_param: server2_name }
block_device_mapping:
- volume_size: 5
volume_id: { get_resource: "server2_disk" }
device_name: "/dev/vda"
config_drive: "False"
flavor: { get_param: server_flavor }
image: { get_param: image_server2 }
key_name: { get_param: key_name }
networks:
- port: { get_resource: server2_port }
server2_disk:
type: OS::Cinder::Volume
properties:
name: server2_disk
image: { get_param: image_server2 }
size: 5
server2_port:
type: OS::Neutron::Port
properties:
network_id: { get_resource: private_net }
fixed_ips:
- subnet_id: { get_resource: private_subnet }
В следующем блоке описываются создаваемые ресурсы: сети, роутер, серверы и другие. В этой части шаблона описывается общая локальная сеть (private_net) и её подсеть, для которой указывается диапазон используемых адресов и включается поддержка DHCP.
Следующий этап — создание роутера и интерфейса на нём. Через этот интерфейс роутер подключается к созданной локальной сети. Затем перечисляются серверы. У каждого сервера должно быть по порту и диску. Для первого сервера, в отличие от второго, указан также плавающий IP-адрес (floating_ip), с помощью которого внешний адрес из публичной сети можно ассоциировать с «серым» адресом из локальной.
server1_floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network_id: { get_param: public_net_id }
port_id: { get_resource: server1_port }
depends_on: router_interface
Обратите внимание на то, как используются параметры и ресурсы при описании новых устройств. Выше приведен фрагмент описания ресурса плавающего IP-адреса для первого сервера. В его свойствах необходимо указать UUID публичной сети, откуда будет взят плавающий IP-адрес (floating_network_id) и UUID порта сервера (port_id), с которым этот адрес будет связан. В функции get_param указывается, что значение следует брать из параметра public_net_id (ниже будет описано, как использовать параметры). Идентификатора порта первого сервера ещё нет. Он появится только после того, как сервер будет создан.
Функция get_resource указывает, что сразу после создания ресурса server1_port его значение должно использоваться в качестве UUID для port_id.
Resource DELETE failed: Conflict: Router interface for subnet 8958ffad-7622-4d98-9fd9-6f4423937b59 on router 7ee9754b-beba-4301-9bdd-166117c5e5a6 cannot be deleted, as it is required by one or more floating IPs.
Согласно этому сообщению, роутер не может быть удалён, потому что к сети, ассоциированной с этим роутером, привязаны плавающие IP-адреса. Вполне ожидаемо, что при удалении стека необходимо в первую очередь удалить плавающие IP-адреса, а уже затем роутер и связанную с ним сеть. Проблема заключается в том, что все ресурсы компонентов neutron, cinder, nova, glance являются независимыми друг от друга сущностями, между которыми устанавливаются связи и отношения.
В большинстве случаев Heat определяет нужный порядок создания ресурсов и построения между ними связей при создании стека. При удалении стека эти связи также будут учитываться: по ним будет определён порядок удаления ресурсов. Но иногда, как в приведённом выше примере, возникают ошибки. С помощью директивы depends_on явно указано, что плавающий IP-адрес связан с роутером и интерфейсом на нём. Благодаря этой связи, теперь IP-адрес будет создаваться после того, как будет создан роутер и интерфейс на нём. При удалении всё будет происходить в обратном порядке: сначала будет удалён плавающий IP-адрес, а затем — роутер и его интерфейс.
В последней секции шаблона описываются нужные параметры виртуальных устройств, чтобы получить их значения после создания стека.
outputs:
server1_private_ip:
description: private ip address within local subnet of server 1 with installed Centos7 distro
value: { get_attr: [ server1_port, fixed_ips, 0, ip_address]}
server1_public_ip:
description: floating ip that is assigned to server1 server
value: { get_attr: [ server1_floating_ip, floating_ip_address ]}
server2_private_ip:
description: private ip address within local subnet of server2 with installed Debian7 distro
value: { get_attr: [ server2, first_address ]}
В приведённом фрагменте указывается, что желательно получить следующие значения для создаваемых в процессе сборки стека ресурсов: адрес первого сервера в локальной сети, публичный адрес первого сервера (плавающий IP-адрес) и адрес второго сервера в локальной сети. Для каждого параметра указано его краткое описание и запрашиваемое значение (value). Для этого используется функция get_attr, которой необходимо два значения: первый — имя ресурса, второй — его атрибуты.
Обратите внимание на разные способы получения адреса в локальной сети у первого и второго серверов. Оба варианта допустимы и равнозначны. Разница в том, что в первом случае происходит обращение к компоненту Neutron (у server1_port тип «OS::Neutron::Port») и берётся первый IP-адрес из атрибута fixed_ips. Во втором случае часто упоминаемый в примерах шаблонов в сети, происходит обращение к компоненту nova (ресурс server2 с типом «OS::Nova::Server») и атрибуту first_address.
Такие компоненты платформы Openstack, как Neutron и Cinder, появились позже, чем Nova. Поэтому Nova раньше использовался для гораздо большего количества функций, в том числе и для управления дисками и сетями. С полноценным развитием Neutron и Cinder такая необходимость отпала, но оставлена в целях совместимости. Политика в отношении Nova постепенно пересматривается, и некоторые функции со временем объявляются устаревшими. Возможно, что и атрибут first_address скоро не будет поддерживаться.
value: { get_attr: [ server1_port, fixed_ips, 0, ip_address]}
value: { get_attr: [ server2, first_address ]}
Более подробно о шаблонах и правилах их составления можно прочитать в официальном руководстве.
Создание стека
Подготовив шаблон, проверим его на наличие синтаксических ошибок и на соответствие стандарту:
$ heat template-validate -f publication.yml
Если шаблон составлен правильно, то в качестве ответа будет представлен вывод в формате json:
{
"Description": "Basic template of two servers, one network and one router\n",
"Parameters": {
"server2_name": {
"NoEcho": "false",
"Type": "String",
"Description": "",
"Label": "server2_name"
},
"private_subnet_name": {
"NoEcho": "false",
"Type": "String",
"Description": "the Name of private subnet",
"Label": "private_subnet_name"
},
"key_name": {
"NoEcho": "false",
...
Для создания стека необходимо следующее:
$ heat stack-create TESTA -f testa.yml -P key_name="testa" \
-P public_net_id="ab2264dd-bde8-4a97-b0da-5fea63191019" \
-P server_flavor="1406718579611-8007733592" \
-P private_net_name=localnet -P private_subnet_name="192.168.0.0/24" \
-P router_name=router -P server1_name=Centos7 -P server2_name=Debian7 \
-P image_server1="CentOS 7 64-bit" \
-P image_server2="ba78ce9b-f800-4fb2-ad85-a68ca0f19cb8"
Каждый раз передавать параметры клиенту Heat вручную неудобно: легко можно сделать ошибку. Чтобы избежать этого недостатка, создайте дополнительный файл, повторяющий формат основного шаблона, но содержащий только самые основные параметры:
parameters:
key_name: testa
public_net_id: ab2264dd-bde8-4a97-b0da-5fea63191019
server_flavor: myflavor
private_net_name: localnet
private_subnet_name: 192.168.0.0/24
router_name: router
server1_name: Centos7
server2_name: Debian7
image_server1: CentOS 7 64-bit
image_server2: ba78ce9b-f800-4fb2-ad85-a68ca0f19cb8
В этом случае создание стека с помощью консольной утилиты Heat будет упрощено:
$ heat stack-create TESTA -f testa.yml -e testa_env.yml
+--------------------------------------+------------+--------------------+----------------------+
| id | stack_name | stack_status | creation_time |
+--------------------------------------+------------+--------------------+----------------------+
| 96d37fd2-52e8-4b59-bf42-2ce72566e03e | TESTA | CREATE_IN_PROGRESS | 2014-12-17T15:17:17Z |
+--------------------------------------+------------+--------------------+----------------------+
Чтобы узнать необходимые значения передаваемых Heat параметров, можно использовать стандартный набор утилит для работы с Openstack. Например, узнать идентификатор публичной сети public_net_id можно с использованием Neutron:
$ neutron net-list
+--------------------------------------+------------------+-----------------------------------------------------+
| id | name | subnets |
+--------------------------------------+------------------+-----------------------------------------------------+
| 168bb122-a00a-4e34-bcc9-3bd0b417ee2b | localnet | 256647b7-7b73-4534-8a79-1901c9b25527 192.168.0.0/24 |
| ab2264dd-bde8-4a97-b0da-5fea63191019 | external-network | 102a9263-2d84-4335-acfb-6583ac8e70aa |
| | | aa9e4fc4-63b0-432e-bcbd-82a613310acb |
+--------------------------------------+------------------+-----------------------------------------------------+
Чтобы узнать имя или идентификатор server_flavor и image_server1, image_server2 можно аналогичным образом воспользоваться соотвествующими утилитами.
Операции со стеком
После создания стека нужно убедиться в том, что всё ли прошло без ошибок, а также узнать, какие IP-адреса были присвоены серверам (прежде всего — публичный IP первого сервера).
Список всех созданных стеков можно получить с помощью команды heat-list. В её вывод будет включена информация о состоянии каждого стека:
$ heat stack-list
+--------------------------------------+------------+-----------------+----------------------+
| id | stack_name | stack_status | creation_time |
+--------------------------------------+------------+-----------------+----------------------+
| e7ad8ef1-921d-4e70-a203-20dbc32d4a02 | TESTA | CREATE_COMPLETE | 2014-12-17T18:30:54Z |
| ab5159d2-08ad-47a2-a964-a2c3425eca8f | TESTNODE | CREATE_FAILED | 2014-12-17T18:39:38Z |
+--------------------------------------+------------+-----------------+----------------------+
Как видно из вывода, UUID локальной сети, к которой должен быть подключен порт создаваемого сервера, указан неправильно — из-за этого и возникла ошибка. Также ошибки часто случаются из-за отсутствия свободных ресурсов (для каждого проекта выставляются лимиты количества используемых ядер, RAM и другие).
Если стек создан успешно, то в общем выводе команды stack-show появится также секция outputs, в которой содержатся интересующие значения:
+----------------------+----------------------------------------------------------------------------------------------------------------------------------+
| Property | Value |
+----------------------+----------------------------------------------------------------------------------------------------------------------------------+
| capabilities | [] |
| creation_time | 2014-12-17T15:17:17Z |
| description | Basic template of two servers, one network and one |
| | router |
| disable_rollback | True |
| id | 96d37fd2-52e8-4b59-bf42-2ce72566e03e |
| links | https://api.selvpc.ru/orchestration/v1/58ad5a5408ad4ad5864f260308884539/stacks/TESTA/96d37fd2-52e8-4b59-bf42-2ce72566e03e (self) |
| notification_topics | [] |
| outputs | [ |
| | { |
| | "output_value": "192.168.0.10", |
| | "description": "private ip within local subnet of server2 with installed Debian 7 distro", |
| | "output_key": "server2_private_ip" |
| | }, |
| | { |
| | "output_value": "192.168.0.13", |
| | "description": "private ip within local subnet of server1 with installed Centos 7 distro", |
| | "output_key": "server1_private_ip" |
| | }, |
| | { |
| | "output_value": "95.213.154.134", |
| | "description": "floating_ip that is assigned to server1 server", |
| | "output_key": "server1_public_ip" |
| | } |
| | ] |
| parameters | { |
| | "server2_name": "Debian7", |
| | "image_centos7": "CentOS 7 64-bit", |
| | "OS::stack_id": "96d37fd2-52e8-4b59-bf42-2ce72566e03e", |
| | "OS::stack_name": "TESTA", |
| | "private_subnet_name": "192.168.0.0/24", |
| | "key_name": "testa", |
| | "server1_name": "Centos7", |
| | "public_net_id": "ab2264dd-bde8-4a97-b0da-5fea63191019", |
| | "private_net_name": "localnet", |
| | "router_name": "router", |
| | "server_flavor": "myflavor", |
| | "image_debian7": "d3e1be2a-e0fc-4cfc-ac07-35c9706f02cc" |
| | } |
| stack_name | TESTA |
| stack_status | CREATE_COMPLETE |
| stack_status_reason | Stack CREATE completed successfully |
| template_description | Basic template of two servers, one network and one |
| | router |
| timeout_mins | None |
| updated_time | None |
+----------------------+----------------------------------------------------------------------------------------------------------------------------------+
Для большинства случаев вывод команды heat stack-show cлишком большой и подробный. Найти в этом выводе какую-нибудь небольшую, но важную деталь (например, IP-адрес первого сервера) крайне затруднительно. Если интересует только значение плавающего адреса первого сервера, то получить его можно следующей командой, где после имени стека укажите также описанный вывод о публичном IP-адресе:
$ heat output-show TESTA server1_public_ip
"95.213.154.192"
Удаление стека осуществляется при помощи команды heat stack-delete:
$ heat stack-delete TESTA
+--------------------------------------+------------+--------------------+----------------------+
| id | stack_name | stack_status | creation_time |
+--------------------------------------+------------+--------------------+----------------------+
| e7ad8ef1-921d-4e70-a203-20dbc32d4a02 | TESTA | DELETE_IN_PROGRESS | 2014-12-17T18:30:54Z |
+--------------------------------------+------------+--------------------+----------------------+
В ситуации, когда необходимо временно высвободить системные ресурсы, не удаляя при этом сам стек, можно приостановить его работу командой heat action-suspend и вернуть в рабочее состояние позже с помощью команды heat action-resume.
Более подробную информацию можно получить в официальной документации или с помощью команды heat help.