[ English | English (United Kingdom) | Deutsch | русский ]
Пример конфигурации среды с несколькими зонами AZ¶
На этой странице мы предоставим пример конфигурации, которую можно использовать в рабочих окружениях с несколькими зонами доступности.
Это будет расширенная и более конкретная версия статьи про Пример маршрутизируемого окружения, поэтому ожидается, что вы знакомы с определенными там концепциями и подходами.
Чтобы лучше понять, почему некоторые параметры конфигурации были применены в примерах, рекомендуется также просмотреть Configuring the inventory
Общая схема¶
В приведенном ниже примере были приняты следующие проектные решения:
Три зоны доступности (AZ)
Три инфраструктурных (управляющих) хоста, каждый хост размещен в отдельной зоне доступности
Восемь вычислительных хостов, 2 вычислительных хоста в каждой зоне доступности. Первая зона доступности имеет два дополнительных вычислительных хоста для закрепленного агрегата CPU.
Три кластера хранения Ceph, подготовленные с помощью Ceph Ansible.
Вычислительные хосты действуют как шлюзы OVN
Туннелированные сети, доступные между зонами доступности
Публичный API, внешние сети OpenStack и сети управления представлены в виде растянутых сетей L2 между зонами доступности.

Балансировка нагрузки¶
Балансировщик нагрузки (HAProxy) обычно развертывается на хостах инфраструктуры. Поскольку хосты инфраструктуры распределены по зонам доступности, нам необходимо разработать более сложную конструкцию, направленную на решение следующих проблем:
Выдерживание отказа одной зоны доступности
Уменьшение объема трафика между зонами AZ
Распределение нагрузки по зонам доступности
Для решения этих проблем были внесены следующие изменения в базовую конструкцию:
Используйте DNS Round Robin (запись A/AAAA на каждую зону доступности) для публичного API
Определите FQDN внутреннего API с помощью переопределений /etc/hosts, которые являются уникальными для каждой зоны доступности
Определите 6 инстансов keepalived: 3 для публичных и 3 для внутренних VIP-адресов
Убедитесь, что HAProxy отдает приоритет бекэнду из собственной зоны доступности над «удалёнными»
В примере также развертывается HAProxy с Keepalived в их собственных контейнерах LXC в отличие от более традиционного развертывания на bare metal. Вы можете проверить HAProxy и Keepalived внутри LXC контейнеров для получения более подробной информации о том, как это сделать.

Сложности проектирования систем хранения¶
Существует множество сложностей, связанных с организацией хранения, когда хранилище не распределено между зонами доступности.
Во-первых, в любой зоне доступности есть только один контроллер, в то время как для высокой доступности необходимо запустить несколько копий cinder_volume
для каждого провайдера хранилища. Поскольку cinder_volume
нужен доступ к сети хранения, одним из лучших мест для него являются хосты ceph-mon
.
Еще одна проблема заключается в организации общего хранилища для образов Glance, поскольку rbd
больше не может использоваться согласованно. Хотя интерфейс Glance Interoperable Import можно использовать для синхронизации образов между rbd
бекэндами, на самом деле не все клиенты и службы могут работать с import API Glance. Одним из наиболее очевидных решений здесь может быть использование Swift API при настройке политики Ceph RadosGW для репликации контейнера между независимыми инстансами, расположенными в их зонах доступности.
Последняя, но не самая маленькая сложность — планирование Nova, когда cross_az_attach
отключен. Так как Nova не будет добавлять зону доступности к инстансам request_specs
, когда инстанс создается из диска напрямую, в отличие от создания диска вручную заранее и предоставления UUID диска в вызове API создания инстанса. Проблема с таким поведением заключается в том, что Nova попытается выполнить живую миграцию или перепланировать инстансы без зоны доступности в request_specs
в другие AZ, что приведет к сбою, так как cross_az_attach
отключен. Подробнее об этом можно прочитать в отчете об ошибке Nova <https://bugs.launchpad.net/nova/+bug/2047182>`_ Чтобы обойти эту ошибку, вам нужно установить default_schedule_zone
для Nova и Cinder, что гарантирует, что AZ всегда будет определена в request_specs
. Вы также можете пойти дальше и определить фактическую зону доступности как default_schedule_zone
, сделав каждый контроллер своим собственным значением по умолчанию. Поскольку балансировщик нагрузки сначала попытается отправлять запросы только на «локальные» бекэнды, этот подход работает для распределения новых виртуальных машин по всем AZ, когда пользователь явно не указывает AZ. В противном случае AZ «по умолчанию» будет принимать значительно больше новых регистраций.
Примеры конфигурации¶
Настройка сети¶
Распределение сетевых CIDR/VLAN¶
Следующие CIDR назначения используются для данного окружения.
Сеть |
CIDR |
VLAN |
---|---|---|
Управляющая сеть |
172.29.236.0/22 |
10 |
Сеть хранения данных AZ1 |
172.29.244.0/24 |
20 |
Туннелированная сеть AZ1 (Geneve) |
172.29.240.0/24 |
30 |
Сеть хранения данных AZ2 |
172.29.245.0/24 |
21 |
Туннелированная сеть AZ2 (Geneve) |
172.29.241.0/24 |
31 |
Сеть хранения данных AZ3 |
172.29.246.0/24 |
22 |
Туннелированная сеть AZ3 (Geneve) |
172.29.242.0/24 |
32 |
VIP публичного API |
203.0.113.0/28 |
400 |
Назначения IP¶
Следующие имена хостов и IP адреса используются для данного окружения.
Имя сервера |
Управляющий IP |
IP туннеля (Geneve) |
IP хранилища данных |
---|---|---|---|
infra1 |
172.29.236.11 |
||
infra2 |
172.29.236.12 |
||
infra3 |
172.29.236.13 |
||
az1_ceph1 |
172.29.237.201 |
172.29.244.201 |
|
az1_ceph2 |
172.29.237.202 |
172.29.244.202 |
|
az1_ceph3 |
172.29.237.203 |
172.29.244.203 |
|
az2_ceph1 |
172.29.238.201 |
172.29.245.201 |
|
az2_ceph2 |
172.29.238.202 |
172.29.245.202 |
|
az2_ceph3 |
172.29.238.203 |
172.29.245.203 |
|
az3_ceph1 |
172.29.239.201 |
172.29.246.201 |
|
az3_ceph2 |
172.29.239.202 |
172.29.246.202 |
|
az3_ceph3 |
172.29.239.203 |
172.29.246.203 |
|
az1_compute1 |
172.29.237.11 |
172.29.240.11 |
172.29.244.11 |
az1_compute2 |
172.29.237.12 |
172.29.240.12 |
172.29.244.12 |
az1_pin_compute1 |
172.29.237.13 |
172.29.240.13 |
172.29.244.13 |
az1_pin_compute2 |
172.29.237.14 |
172.29.240.14 |
172.29.244.14 |
az2_compute1 |
172.29.238.11 |
172.29.241.11 |
172.29.245.11 |
az2_compute2 |
172.29.238.12 |
172.29.241.12 |
172.29.245.12 |
az3_compute1 |
172.29.239.11 |
172.29.242.11 |
172.29.246.11 |
az3_compute3 |
172.29.239.12 |
172.29.242.12 |
172.29.246.12 |
Настройка сети сервера¶
Для каждого хоста требуется реализация правильных сетевых мостов. В этом примере мы используем роль systemd_networkd
, которая выполняет настройку для нас во время выполнения openstack_hosts
. Она создает все необходимые vlan и мосты. Единственное предварительное требование — иметь подключение к хосту через SSH, доступное для Ansible для управления хостом.
Примечание
В примере предполагается, что шлюз по умолчанию установлен через интерфейс bond0
, который объединяет ссылки eth0
и eth1
. Если в вашей среде нет eth0
, но вместо этого есть p1p1
или какое-либо другое имя интерфейса, убедитесь, что ссылки на eth0
заменены соответствующим именем. То же самое относится к дополнительным сетевым интерфейсам
---
# VLAN Mappings
_az_vlan_mappings:
az1:
management: 10
storage: 20
tunnel: 30
public-api: 400
az2:
management: 10
storage: 21
tunnel: 31
public-api: 400
az3:
management: 10
storage: 22
tunnel: 32
public-api: 400
# Bonding interfaces
_bond0_interfaces:
- eth0
- eth1
# NETDEV defenition
_systemd_networkd_default_devices:
- NetDev:
Name: vlan-mgmt
Kind: vlan
VLAN:
Id: "{{ _az_vlan_mappings[az_name]['management'] }}"
filename: 10-openstack-vlan-mgmt
- NetDev:
Name: bond0
Kind: bond
Bond:
Mode: 802.3ad
TransmitHashPolicy: layer3+4
LACPTransmitRate: fast
MIIMonitorSec: 100
filename: 05-general-bond0
- NetDev:
Name: "{{ management_bridge }}"
Kind: bridge
Bridge:
ForwardDelaySec: 0
HelloTimeSec: 2
MaxAgeSec: 12
STP: off
filename: "11-openstack-{{ management_bridge }}"
_systemd_networkd_storage_devices:
- NetDev:
Name: vlan-stor
Kind: vlan
VLAN:
Id: "{{ _az_vlan_mappings[az_name]['storage'] }}"
filename: 12-openstack-vlan-stor
- NetDev:
Name: br-storage
Kind: bridge
Bridge:
ForwardDelaySec: 0
HelloTimeSec: 2
MaxAgeSec: 12
STP: off
filename: 13-openstack-br-storage
_systemd_networkd_tunnel_devices:
- NetDev:
Name: vlan-tunnel
Kind: vlan
VLAN:
Id: "{{ _az_vlan_mappings[az_name]['tunnel'] }}"
filename: 16-openstack-vlan-tunnel
_systemd_networkd_pub_api_devices:
- NetDev:
Name: vlan-public-api
Kind: vlan
VLAN:
Id: "{{ _az_vlan_mappings[az_name]['public-api'] }}"
filename: 17-openstack-vlan-public-api
- NetDev:
Name: br-public-api
Kind: bridge
Bridge:
ForwardDelaySec: 0
HelloTimeSec: 2
MaxAgeSec: 12
STP: off
filename: 18-openstack-br-public-api
openstack_hosts_systemd_networkd_devices: |-
{% set devices = [] %}
{% if is_metal %}
{% set _ = devices.extend(_systemd_networkd_default_devices) %}
{% if inventory_hostname in (groups['compute_hosts'] + groups['storage_hosts']) %}
{% set _ = devices.extend(_systemd_networkd_storage_devices) %}
{% endif %}
{% if inventory_hostname in (groups[az_name ~ '_ceph_mon_hosts'] + groups[az_name ~ '_ceph_osd_hosts']) %}
{% set _ = devices.extend(_systemd_networkd_cluster_devices) %}
{% endif %}
{% if inventory_hostname in groups['compute_hosts'] %}
{% set _ = devices.extend(_systemd_networkd_tunnel_devices) %}
{% endif %}
{% if inventory_hostname in groups['haproxy_hosts'] %}
{% set _ = devices.extend(_systemd_networkd_pub_api_devices) %}
{% endif %}
{% endif %}
{{ devices }}
# NETWORK definition
# NOTE: this can work only in case management network has the same netmask as all other networks
# while in example manaement is /22 while rest are /24
# _management_rank: "{{ management_address | ansible.utils.ipsubnet(hostvars[inventory_hostname]['cidr_networks']['management']) }}"
_management_rank: "{{ (management_address | split('.'))[-1] }}"
# NOTE: `05` is prefixed to filename to have precedence over netplan
_systemd_networkd_bonded_networks: |-
{% set struct = [] %}
{% for interface in _bond0_interfaces %}
{% set interface_data = ansible_facts[interface | replace('-', '_')] %}
{% set _ = struct.append({
'interface': interface_data['device'],
'filename' : '05-general-' ~ interface_data['device'],
'bond': 'bond0',
'link_config_overrides': {
'Match': {
'MACAddress': interface_data['macaddress']
}
}
})
%}
{% endfor %}
{% set bond_vlans = ['vlan-mgmt'] %}
{% if inventory_hostname in (groups['compute_hosts'] + groups['storage_hosts']) %}
{% set _ = bond_vlans.append('vlan-stor') %}
{% endif %}
{% if inventory_hostname in groups['haproxy_hosts'] %}
{% set _ = bond_vlans.append('vlan-public-api') %}
{% endif %}
{% if inventory_hostname in groups['compute_hosts'] %}
{% set _ = bond_vlans.append('vlan-tunnel') %}
{% endif %}
{% set _ = struct.append({
'interface': 'bond0',
'filename': '05-general-bond0',
'vlan': bond_vlans
})
%}
{{ struct }}
_systemd_networkd_mgmt_networks:
- interface: "vlan-mgmt"
bridge: "{{ management_bridge }}"
filename: 10-openstack-vlan-mgmt
- interface: "{{ management_bridge }}"
address: "{{ management_address }}"
netmask: "{{ cidr_networks['management'] | ansible.utils.ipaddr('netmask') }}"
filename: "11-openstack-{{ management_bridge }}"
_systemd_networkd_storage_networks:
- interface: "vlan-stor"
bridge: "br-storage"
filename: 12-openstack-vlan-stor
- interface: "br-storage"
address: "{{ cidr_networks['storage_' ~ az_name] | ansible.utils.ipmath(_management_rank) }}"
netmask: "{{ cidr_networks['storage_' ~ az_name] | ansible.utils.ipaddr('netmask') }}"
filename: "13-openstack-br-storage"
_systemd_networkd_tunnel_networks:
- interface: "vlan-tunnel"
filename: 16-openstack-vlan-tunnel
address: "{{ cidr_networks['tunnel_' ~ az_name] | ansible.utils.ipmath(_management_rank) }}"
netmask: "{{ cidr_networks['tunnel_' ~ az_name] | ansible.utils.ipaddr('netmask') }}"
static_routes: |-
{% set routes = [] %}
{% set tunnel_cidrs = cidr_networks | dict2items | selectattr('key', 'match', 'tunnel_az[0-9]') | map(attribute='value') %}
{% set gateway = cidr_networks['tunnel_' ~ az_name] | ansible.utils.ipaddr('1') | ansible.utils.ipaddr('address') %}
{% for cidr in tunnel_cidrs | reject('eq', cidr_networks['tunnel_' ~ az_name]) %}
{% set _ = routes.append({'cidr': cidr, 'gateway': gateway}) %}
{% endfor %}
{{ routes }}
_systemd_networkd_pub_api_networks:
- interface: "vlan-public-api"
bridge: "br-public-api"
filename: 17-openstack-vlan-public-api
- interface: "br-public-api"
filename: "18-openstack-br-public-api"
openstack_hosts_systemd_networkd_networks: |-
{% set networks = [] %}
{% if is_metal %}
{% set _ = networks.extend(_systemd_networkd_mgmt_networks + _systemd_networkd_bonded_networks) %}
{% if inventory_hostname in (groups['compute_hosts'] + groups['storage_hosts']) %}
{% set _ = networks.extend(_systemd_networkd_storage_networks) %}
{% endif %}
{% if inventory_hostname in groups['compute_hosts'] %}
{% set _ = networks.extend(_systemd_networkd_tunnel_networks) %}
{% endif %}
{% if inventory_hostname in groups['haproxy_hosts'] %}
{% set _ = networks.extend(_systemd_networkd_pub_api_networks) %}
{% endif %}
{% endif %}
{{ networks }}
Настройка развертывания¶
Индивидуальная настройка окружения¶
Развернутые файлы в /etc/openstack_deploy/env.d
позволяют настраивать группы Ansible.
Для развертывания HAProxy в контейнере нам необходимо создать файл /etc/openstack_deploy/env.d/haproxy.yml
со следующим содержимым:
---
# This file contains an example to show how to set
# the cinder-volume service to run in a container.
#
# Important note:
# In most cases you need to ensure that default route inside of the
# container doesn't go through eth0, which is part of lxcbr0 and
# SRC nat-ed. You need to pass "public" VIP interface inside of the
# container and ensure "default" route presence on it.
container_skel:
haproxy_container:
properties:
is_metal: false
Поскольку мы используем Ceph для этой среды, cinder-volume
запускается в контейнере на хостах Ceph Monitor. Чтобы добиться этого, реализуйте /etc/openstack_deploy/env.d/cinder.yml
со следующим содержимым:
---
# This file contains an example to show how to set
# the cinder-volume service to run in a container.
#
# Important note:
# When using LVM or any iSCSI-based cinder backends, such as NetApp with
# iSCSI protocol, the cinder-volume service *must* run on metal.
# Reference: https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1226855
container_skel:
cinder_volumes_container:
properties:
is_metal: false
Чтобы иметь возможность выполнять плейбук только для хостов в одной зоне доступности, а также иметь возможность устанавливать переменные, специфичные для AZ, нам нужно определить определения групп. Для этого создайте файл /etc/openstack_deploy/env.d/az.yml
со следующим содержимым:
---
component_skel:
az1_containers:
belongs_to:
- az1_all
az1_hosts:
belongs_to:
- az1_all
az2_containers:
belongs_to:
- az2_all
az2_hosts:
belongs_to:
- az2_all
az3_containers:
belongs_to:
- az3_all
az3_hosts:
belongs_to:
- az3_all
container_skel:
az1_containers:
properties:
is_nest: true
az2_containers:
properties:
is_nest: true
az3_containers:
properties:
is_nest: true
Пример выше создаст следующие группы:
azN_hosts
, которые будет содержать только физические узлыazN_containers
, которые будут содержать все контейнеры, которые создаются нафизические серверы, которые являются частью pod-а.
azN_all
, который содержит членов групп azN_hosts и azN_containers
Нам также необходимо определить совершенно новый набор групп для Ceph, чтобы развернуть несколько его независимых инстансов.
Для этого создайте файл /etc/openstack_deploy/env.d/ceph.yml
с следующим содержанием:
---
component_skel:
# Ceph MON
ceph_mon_az1:
belongs_to:
- ceph-mon
- ceph_all
- az1_all
ceph_mon_az2:
belongs_to:
- ceph-mon
- ceph_all
- az2_all
ceph_mon_az3:
belongs_to:
- ceph-mon
- ceph_all
- az3_all
# Ceph OSD
ceph_osd_az1:
belongs_to:
- ceph-osd
- ceph_all
- az1_all
ceph_osd_az2:
belongs_to:
- ceph-osd
- ceph_all
- az2_all
ceph_osd_az3:
belongs_to:
- ceph-osd
- ceph_all
- az3_all
# Ceph RGW
ceph_rgw_az1:
belongs_to:
- ceph-rgw
- ceph_all
- az1_all
ceph_rgw_az2:
belongs_to:
- ceph-rgw
- ceph_all
- az2_all
ceph_rgw_az3:
belongs_to:
- ceph-rgw
- ceph_all
- az3_all
container_skel:
# Ceph MON
ceph_mon_container_az1:
belongs_to:
- az1_ceph_mon_containers
contains:
- ceph_mon_az1
ceph_mon_container_az2:
belongs_to:
- az2_ceph_mon_containers
contains:
- ceph_mon_az2
ceph_mon_container_az3:
belongs_to:
- az3_ceph_mon_containers
contains:
- ceph_mon_az3
# Ceph RGW
ceph_rgw_container_az1:
belongs_to:
- az1_ceph_rgw_containers
contains:
- ceph_rgw_az1
ceph_rgw_container_az2:
belongs_to:
- az2_ceph_rgw_containers
contains:
- ceph_rgw_az2
ceph_rgw_container_az3:
belongs_to:
- az3_ceph_rgw_containers
contains:
- ceph_rgw_az3
# Ceph OSD
ceph_osd_container_az1:
belongs_to:
- az1_ceph_osd_containers
contains:
- ceph_osd_az1
properties:
is_metal: true
ceph_osd_container_az2:
belongs_to:
- az2_ceph_osd_containers
contains:
- ceph_osd_az2
properties:
is_metal: true
ceph_osd_container_az3:
belongs_to:
- az3_ceph_osd_containers
contains:
- ceph_osd_az3
properties:
is_metal: true
physical_skel:
# Ceph MON
az1_ceph_mon_containers:
belongs_to:
- all_containers
az1_ceph_mon_hosts:
belongs_to:
- hosts
az2_ceph_mon_containers:
belongs_to:
- all_containers
az2_ceph_mon_hosts:
belongs_to:
- hosts
az3_ceph_mon_containers:
belongs_to:
- all_containers
az3_ceph_mon_hosts:
belongs_to:
- hosts
# Ceph OSD
az1_ceph_osd_containers:
belongs_to:
- all_containers
az1_ceph_osd_hosts:
belongs_to:
- hosts
az2_ceph_osd_containers:
belongs_to:
- all_containers
az2_ceph_osd_hosts:
belongs_to:
- hosts
az3_ceph_osd_containers:
belongs_to:
- all_containers
az3_ceph_osd_hosts:
belongs_to:
- hosts
# Ceph RGW
az1_ceph_rgw_containers:
belongs_to:
- all_containers
az1_ceph_rgw_hosts:
belongs_to:
- hosts
az2_ceph_rgw_containers:
belongs_to:
- all_containers
az2_ceph_rgw_hosts:
belongs_to:
- hosts
az3_ceph_rgw_containers:
belongs_to:
- all_containers
az3_ceph_rgw_hosts:
belongs_to:
- hosts
Настройка окружения¶
Файл /etc/openstack_deploy/openstack_user_config.yml
определяет, как будет выглядеть окружение.
Для каждой AZ необходимо будет определить группу, содержащую все хосты в этой AZ.
Внутри определенных сетей провайдера `` address_prefix`` используется для переопределения префикса ключа, добавленного к каждому хосту, который содержит информацию IP -адреса. Мы используем AZ-специфические префиксы для `` necuter``, `` tunnel`` или `` storage``. `` reference_group`` содержит имя определенной группы AZ и используется для ограничения объема каждой сети провайдера этой группой.
Якоря и алиасы YAML широко используются в примере ниже для заполнения всех групп, которые могут стать удобными, одновременно не повторяя определения хостов каждый раз. Вы можете прочитать больше по теме в Ansible Documentation
Следующая настройка описывает устройство данного окружения.
---
cidr_networks: &os_cidrs
management: 172.29.236.0/22
tunnel_az1: 172.29.240.0/24
tunnel_az2: 172.29.241.0/24
tunnel_az3: 172.29.242.0/24
storage_az1: 172.29.244.0/24
storage_az2: 172.29.245.0/24
storage_az3: 172.29.246.0/24
public_api_vip: 203.0.113.0/28
used_ips:
# management network - openstack VIPs
- "172.29.236.1,172.29.236.30"
# management network - other hosts not managed by OSA dynamic inventory
- "172.29.238.0,172.29.239.255"
# storage network - reserved for ceph hosts
- "172.29.244.200,172.29.244.250"
- "172.29.245.200,172.29.245.250"
- "172.29.246.200,172.29.246.250"
# public_api
- "203.0.113.1,203.0.113.10"
global_overrides:
internal_lb_vip_address: internal.example.cloud
external_lb_vip_address: example.cloud
management_bridge: "br-mgmt"
cidr_networks: *os_cidrs
provider_networks:
- network:
group_binds:
- all_containers
- hosts
type: "raw"
container_bridge: "br-mgmt"
container_interface: "eth1"
container_type: "veth"
ip_from_q: "management"
is_management_address: true
- network:
container_bridge: "br-storage"
container_type: "veth"
container_interface: "eth2"
ip_from_q: "storage_az1"
address_prefix: "storage_az1"
type: "raw"
group_binds:
- cinder_volume
- nova_compute
- ceph_all
reference_group: "az1_all"
- network:
container_bridge: "br-storage"
container_type: "veth"
container_interface: "eth2"
ip_from_q: "storage_az2"
address_prefix: "storage_az2"
type: "raw"
group_binds:
- cinder_volume
- nova_compute
- ceph_all
reference_group: "az2_all"
- network:
container_bridge: "br-storage"
container_type: "veth"
container_interface: "eth2"
ip_from_q: "storage_az3"
address_prefix: "storage_az3"
type: "raw"
group_binds:
- cinder_volume
- nova_compute
- ceph_all
reference_group: "az3_all"
- network:
container_bridge: "vlan-tunnel"
container_type: "veth"
container_interface: "eth4"
ip_from_q: "tunnel_az1"
address_prefix: "tunnel"
type: "raw"
group_binds:
- neutron_ovn_controller
reference_group: "az1_all"
- network:
container_bridge: "vlan-tunnel"
container_type: "veth"
container_interface: "eth4"
ip_from_q: "tunnel_az2"
address_prefix: "tunnel"
type: "raw"
group_binds:
- neutron_ovn_controller
reference_group: "az2_all"
- network:
container_bridge: "vlan-tunnel"
container_type: "veth"
container_interface: "eth4"
ip_from_q: "tunnel_az3"
address_prefix: "tunnel"
type: "raw"
group_binds:
- neutron_ovn_controller
reference_group: "az3_all"
- network:
group_binds:
- haproxy
type: "raw"
container_bridge: "br-public-api"
container_interface: "eth20"
container_type: "veth"
ip_from_q: public_api_vip
static_routes:
- cidr: 0.0.0.0/0
gateway: 203.0.113.1
### conf.d configuration ###
# Control plane
az1_controller_hosts: &controller_az1
infra1:
ip: 172.29.236.11
az2_controller_hosts: &controller_az2
infra2:
ip: 172.29.236.12
az3_controller_hosts: &controller_az3
infra3:
ip: 172.29.236.13
# Computes
## AZ1
az1_shared_compute_hosts: &shared_computes_az1
az1_compute1:
ip: 172.29.237.11
az1_compute2:
ip: 172.29.237.12
az1_pinned_compute_hosts: &pinned_computes_az1
az1_pin_compute1:
ip: 172.29.237.13
az1_pin_compute2:
ip: 172.29.237.14
## AZ2
az2_shared_compute_hosts: &shared_computes_az2
az2_compute1:
ip: 172.29.238.11
az2_compute2:
ip: 172.29.238.12
## AZ3
az3_shared_compute_hosts: &shared_computes_az3
az3_compute1:
ip: 172.29.239.11
az3_compute2:
ip: 172.29.239.12
# Storage
## AZ1
az1_storage_hosts: &storage_az1
az1_ceph1:
ip: 172.29.237.201
az1_ceph2:
ip: 172.29.237.202
az1_ceph3:
ip: 172.29.237.203
## AZ2
az2_storage_hosts: &storage_az2
az2_ceph1:
ip: 172.29.238.201
az2_ceph2:
ip: 172.29.238.202
az2_ceph3:
ip: 172.29.238.203
## AZ3
az3_storage_hosts: &storage_az3
az3_ceph1:
ip: 172.29.239.201
az3_ceph2:
ip: 172.29.239.202
az3_ceph3:
ip: 172.29.239.203
# AZ association
az1_compute_hosts: &compute_hosts_az1
<<: *shared_computes_az1
<<: *pinned_computes_az1
az2_compute_hosts: &compute_hosts_az2
<<: *shared_computes_az2
az3_compute_hosts: &compute_hosts_az3
<<: *shared_computes_az3
az1_hosts:
<<: *compute_hosts_az1
<<: *controller_az1
<<: *storage_az1
az2_hosts:
<<: *compute_hosts_az2
<<: *controller_az2
<<: *storage_az2
az3_hosts:
<<: *compute_hosts_az3
<<: *controller_az3
<<: *storage_az3
# Final mappings
shared_infra_hosts: &controllers
<<: *controller_az1
<<: *controller_az2
<<: *controller_az3
repo-infra_hosts: *controllers
memcaching_hosts: *controllers
database_hosts: *controllers
mq_hosts: *controllers
operator_hosts: *controllers
identity_hosts: *controllers
image_hosts: *controllers
dashboard_hosts: *controllers
compute-infra_hosts: *controllers
placement-infra_hosts: *controllers
storage-infra_hosts: *controllers
network-infra_hosts: *controllers
network-northd_hosts: *controllers
coordination_hosts: *controllers
compute_hosts: &computes
<<: *compute_hosts_az1
<<: *compute_hosts_az2
<<: *compute_hosts_az3
pinned_compute_hosts:
<<: *pinned_computes_az1
shared_compute_hosts:
<<: *shared_computes_az1
<<: *shared_computes_az2
<<: *shared_computes_az3
network-gateway_hosts: *computes
storage_hosts: &storage
<<: *storage_az1
<<: *storage_az2
<<: *storage_az3
az1_ceph_osd_hosts:
<<: *storage_az1
az2_ceph_osd_hosts:
<<: *storage_az2
az3_ceph_osd_hosts:
<<: *storage_az3
az1_ceph_mon_hosts:
<<: *storage_az1
az2_ceph_mon_hosts:
<<: *storage_az2
az3_ceph_mon_hosts:
<<: *storage_az3
az1_ceph_rgw_hosts:
<<: *storage_az1
az2_ceph_rgw_hosts:
<<: *storage_az2
az3_ceph_rgw_hosts:
<<: *storage_az3
Пользовательские переменные¶
Чтобы правильно настроить зоны доступности, нам нужно использовать `` group_vars`` и определить имя зоны доступности, используемое там для каждой AZ. Для этого создайте файлы:
/etc/openstack_deploy/group_vars/az1_all.yml
/etc/openstack_deploy/group_vars/az2_all.yml
/etc/openstack_deploy/group_vars/az3_all.yml
С контентом как указано ниже, где N должен быть номером AZ в зависимости от файла:
az_name: azN
Что касается этой среды, то балансировщик нагрузки создается в контейнерах LXC на хостах инфраструктуры, где нам нужно обеспечить отсутствие маршрута по умолчанию на интерфейсе eth0
. Чтобы этого не произошло, мы переопределяем lxc_container_networks
в файле /etc/openstack_deploy/group_vars/haproxy/lxc_network.yml
:
---
lxc_container_networks:
lxcbr0_address:
bridge: "{{ lxc_net_bridge | default('lxcbr0') }}"
bridge_type: "{{ lxc_net_bridge_type | default('linuxbridge') }}"
interface: eth0
type: veth
dhcp_use_routes: False
Далее мы хотим обеспечить, чтобы HAProxy всегда указывал на бэкенд, который считается «локальным» для HAProxy. Для этого мы переключаем алгоритм балансировки на первый
и упорядочиваем ре-бэкенды так, чтобы первым в списке оказался тот, который находится в текущей зоне доступности. Это можно сделать, создав файл /etc/openstack_deploy/group_vars/haproxy/backend_overrides.yml
с содержимым:
---
haproxy_drain: True
haproxy_ssl_all_vips: True
haproxy_bind_external_lb_vip_interface: eth20
haproxy_bind_internal_lb_vip_interface: eth1
haproxy_bind_external_lb_vip_address: "*"
haproxy_bind_internal_lb_vip_address: "*"
haproxy_vip_binds:
- address: "{{ haproxy_bind_external_lb_vip_address }}"
interface: "{{ haproxy_bind_external_lb_vip_interface }}"
type: external
- address: "{{ haproxy_bind_internal_lb_vip_address }}"
interface: "{{ haproxy_bind_internal_lb_vip_interface }}"
type: internal
haproxy_cinder_api_service_overrides:
haproxy_backend_nodes: "{{ groups['cinder_api'] | select('in', groups[az_name ~ '_containers']) | union(groups['cinder_api']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_horizon_service_overrides:
haproxy_backend_nodes: "{{ groups['horizon_all'] | select('in', groups[az_name ~ '_containers']) | union(groups['horizon_all']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_keystone_service_overrides:
haproxy_backend_nodes: "{{ groups['keystone_all'] | select('in', groups[az_name ~ '_containers']) | union(groups['keystone_all']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_neutron_server_service_overrides:
haproxy_backend_nodes: "{{ groups['neutron_server'] | select('in', groups[az_name ~ '_containers']) | union(groups['neutron_server']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_nova_api_compute_service_overrides:
haproxy_backend_nodes: "{{ groups['nova_api_os_compute'] | select('in', groups[az_name ~ '_containers']) | union(groups['nova_api_os_compute']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_nova_api_metadata_service_overrides:
haproxy_backend_nodes: "{{ groups['nova_api_metadata'] | select('in', groups[az_name ~ '_containers']) | union(groups['nova_api_metadata']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_placement_service_overrides:
haproxy_backend_nodes: "{{ groups['placement_all'] | select('in', groups[az_name ~ '_containers']) | union(groups['placement_all']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
haproxy_repo_service_overrides:
haproxy_backend_nodes: "{{ groups['repo_all'] | select('in', groups[az_name ~ '_containers']) | union(groups['repo_all']) | unique | default([]) }}"
haproxy_balance_alg: first
haproxy_limit_hosts: "{{ groups['haproxy_all'] | intersect(groups[az_name ~ '_all']) }}"
Нам также нужно определить несколько дополнительных инстансов Keepalived для обеспечения подхода DNS RR, а также настроить Keepalived в одноадресном режиме. Для этого создайте файл /etc/openstack_deploy/group_vars/haproxy/keepalived.yml
со следующим содержимым:
---
haproxy_keepalived_external_vip_cidr_az1: 203.0.113.5/32
haproxy_keepalived_external_vip_cidr_az2: 203.0.113.6/32
haproxy_keepalived_external_vip_cidr_az3: 203.0.113.7/32
haproxy_keepalived_internal_vip_cidr_az1: 172.29.236.21/32
haproxy_keepalived_internal_vip_cidr_az2: 172.29.236.22/32
haproxy_keepalived_internal_vip_cidr_az3: 172.29.236.23/32
haproxy_keepalived_external_interface: "{{ haproxy_bind_external_lb_vip_interface }}"
haproxy_keepalived_internal_interface: "{{ haproxy_bind_internal_lb_vip_interface }}"
keepalived_unicast_peers:
internal: |-
{% set peers = [] %}
{% for addr in groups['haproxy'] | map('extract', hostvars, ['container_networks', 'management_address']) %}
{% set _ = peers.append((addr['address'] ~ '/' ~ addr['netmask']) | ansible.utils.ipaddr('host/prefix')) %}
{% endfor %}
{{ peers }}
external: |-
{% set peers = [] %}
{% for addr in groups['haproxy'] | map('extract', hostvars, ['container_networks', 'public_api_vip_address']) %}
{% set _ = peers.append((addr['address'] ~ '/' ~ addr['netmask']) | ansible.utils.ipaddr('host/prefix')) %}
{% endfor %}
{{ peers }}
keepalived_internal_unicast_src_ip: >-
{{ (management_address ~ '/' ~ container_networks['management_address']['netmask']) | ansible.utils.ipaddr('host/prefix') }}
keepalived_external_unicast_src_ip: >-
{{ (container_networks['public_api_vip_address']['address'] ~ '/' ~ container_networks['public_api_vip_address']['netmask']) | ansible.utils.ipaddr('host/prefix') }}
keepalived_instances:
az1-external:
interface: "{{ haproxy_keepalived_external_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az1_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 40
priority: "{{ (inventory_hostname in groups['az1_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_external_vip_cidr_az1 | default('169.254.1.1/24') }} dev {{ haproxy_keepalived_external_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'internal') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_external_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['external'] | difference([keepalived_external_unicast_src_ip]) }}"
az1-internal:
interface: "{{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az1_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 41
priority: "{{ (inventory_hostname in groups['az1_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_internal_vip_cidr_az1 | default('169.254.2.1/24') }} dev {{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'external') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_internal_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['internal'] | difference([keepalived_internal_unicast_src_ip]) }}"
az2-external:
interface: "{{ haproxy_keepalived_external_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az2_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 42
priority: "{{ (inventory_hostname in groups['az2_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_external_vip_cidr_az2 | default('169.254.1.1/24') }} dev {{ haproxy_keepalived_external_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'internal') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_external_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['external'] | difference([keepalived_external_unicast_src_ip]) }}"
az2-internal:
interface: "{{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az2_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 43
priority: "{{ (inventory_hostname in groups['az2_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_internal_vip_cidr_az2 | default('169.254.2.1/24') }} dev {{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'external') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_internal_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['internal'] | difference([keepalived_internal_unicast_src_ip]) }}"
az3-external:
interface: "{{ haproxy_keepalived_external_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az3_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 44
priority: "{{ (inventory_hostname in groups['az3_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_external_vip_cidr_az3 | default('169.254.1.1/24') }} dev {{ haproxy_keepalived_external_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'internal') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_external_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['external'] | difference([keepalived_external_unicast_src_ip]) }}"
az3-internal:
interface: "{{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
state: "{{ (inventory_hostname in groups['az3_all']) | ternary('MASTER', 'BACKUP') }}"
virtual_router_id: 45
priority: "{{ (inventory_hostname in groups['az3_all']) | ternary(200, (groups['haproxy']|length-groups['haproxy'].index(inventory_hostname))*50) }}"
vips:
- "{{ haproxy_keepalived_internal_vip_cidr_az3 | default('169.254.2.1/24') }} dev {{ haproxy_keepalived_internal_interface | default(management_bridge) }}"
track_scripts: "{{ keepalived_scripts | dict2items | json_query('[*].{name: key, instance: value.instance}') | rejectattr('instance', 'equalto', 'external') | map(attribute='name') | list }}"
unicast_src_ip: "{{ keepalived_internal_unicast_src_ip }}"
unicast_peers: "{{ keepalived_unicast_peers['internal'] | difference([keepalived_internal_unicast_src_ip]) }}"
Чтобы добавить поддержку нескольких вычислительных уровней (с CPU overcommit и привязанным CPU), необходимо создать файл /etc/openstack_deploy/group_vars/pinned_compute_hosts
со следующим содержимым:
nova_cpu_allocation_ratio: 1.0
nova_ram_allocation_ratio: 1.0
Остальные переменные можно определить в /etc/openstack_deploy/user_variables.yml
, но многие из них будут ссылаться на переменную az_name
, поэтому ее наличие (вместе с соответствующими группами) жизненно важно для этого сценария.
---
# Set a different scheduling AZ name on each controller
# You can change that to a specific AZ name which will be used as default one
default_availability_zone: "{{ az_name }}"
# Defining unique internal VIP in hosts per AZ
_openstack_internal_az_vip: "{{ hostvars[groups['haproxy'][0]]['haproxy_keepalived_internal_vip_cidr_' ~ az_name] | ansible.utils.ipaddr('address') }}"
openstack_host_custom_hosts_records: "{{ _openstack_services_fqdns['internal'] | map('regex_replace', '^(.*)$', _openstack_internal_az_vip ~ ' \\1') }}"
# Use local to AZ memcached inside of AZ
memcached_servers: >-
{{
groups['memcached'] | intersect(groups[az_name ~ '_containers'])
| map('extract', hostvars, 'management_address')
| map('regex_replace', '(.+)', '\1:' ~ memcached_port)
| list | join(',')
}}
# Ceph-Ansible variables
ceph_cluster_name: "ceph-{{ az_name }}"
ceph_keyrings_dir: "/etc/openstack_deploy/ceph/{{ ceph_cluster_name }}"
ceph_conf_file: "{{ lookup('file', ceph_keyrings_dir ~ '/ceph.conf') }}"
cluster: "{{ ceph_cluster_name }}"
cluster_network: "{{ public_network }}"
monitor_address: "{{ container_networks['storage_address']['address'] }}"
mon_group_name: "ceph_mon_{{ az_name }}"
mgr_group_name: "{{ mon_group_name }}"
osd_group_name: "ceph_osd_{{ az_name }}"
public_network: "{{ cidr_networks['storage_' ~ az_name] }}"
rgw_group_name: "ceph_rgw_{{ az_name }}"
rgw_zone: "{{ az_name }}"
# Cinder variables
cinder_active_active_cluster_name: "{{ ceph_cluster_name }}"
cinder_default_availability_zone: "{{ default_availability_zone }}"
cinder_storage_availability_zone: "{{ az_name }}"
# Glance to use Swift as a backend
glance_default_store: swift
glance_use_uwsgi: False
# Neutron variables
neutron_availability_zone: "{{ az_name }}"
neutron_default_availability_zones:
- az1
- az2
- az3
neutron_ovn_distributed_fip: True
neutron_plugin_type: ml2.ovn
neutron_plugin_base:
- ovn-router
- qos
- auto_allocate
neutron_ml2_drivers_type: geneve,vlan
neutron_provider_networks:
network_types: "{{ neutron_ml2_drivers_type }}"
network_geneve_ranges: "1:65000"
network_vlan_ranges: >-
vlan:100:200
network_mappings: "vlan:br-vlan"
network_interface_mappings: "br-vlan:bond0"
# Nova variables
nova_cinder_rbd_inuse: True
nova_glance_rbd_inuse: false
nova_libvirt_images_rbd_pool: ""
nova_libvirt_disk_cachemodes: network=writeback,file=directsync
nova_libvirt_hw_disk_discard: unmap
nova_nova_conf_overrides:
DEFAULT:
default_availability_zone: "{{ default_availability_zone }}"
default_schedule_zone: "{{ default_availability_zone }}"
cinder:
cross_az_attach: false
# Create required aggregates and flavors
cpu_pinned_flavors:
specs:
- name: pinned.small
vcpus: 2
ram: 2048
- name: pinned.medium
vcpus: 4
ram: 8192
extra_specs:
hw:cpu_policy: dedicated
hw:vif_multiqueue_enabled: 'true'
trait:CUSTOM_PINNED_CPU: required
cpu_shared_flavors:
specs:
- name: shared.small
vcpus: 1
ram: 1024
- name: shared.medium
vcpus: 2
ram: 4096
openstack_user_compute:
flavors:
- "{{ cpu_shared_flavors }}"
- "{{ cpu_pinned_flavors }}"
aggregates:
- name: az1-shared
hosts: "{{ groups['az1_shared_compute_hosts'] }}"
availability_zone: az1
- name: az1-pinned
hosts: "{{ groups['az1_pinned_compute_hosts'] }}"
availability_zone: az1
metadata:
trait:CUSTOM_PINNED_CPU: required
pinned-cpu: 'true'
- name: az2-shared
hosts: "{{ groups['az2_shared_compute_hosts'] }}"
availability_zone: az2
- name: az3-shared
hosts: "{{ groups['az3_shared_compute_hosts'] }}"
availability_zone: az3