
Привет, Хабр! На связи Кирилл Савин, я — архитектор SDN в Рег.облаке. В прошлой статье «OVN под капотом: как построить сеть в OpenStack» мы уже разбирались, как устроен OVN, чем он отличается от связки Neutron + Open vSwitch и какие инструменты дает для сетевой виртуализации. Если вы с OVN еще только знакомитесь, лучше начать с нее — там собрана базовая архитектура и контекст.
В этом переводе инструкции по трассировке пакетов мы переходим от архитектуры к практике и разбираем, как трассировать пакеты в OVN.
Навигация по тексту:
Трассировка помогает понять, где «теряется» трафик по пути Floating IP — на NAT, роутере или в правилах безопасности. Она позволяет проверить, корректно ли работает DVR: обрабатывается ли трафик на гипервизоре или уходит через туннель. С ее помощью можно выявить неожиданные блокировки со стороны security groups и разобраться, как именно выполняется NAT. По сути, трассировка дает пошаговое понимание того, что происходит с пакетом и на каком этапе возникает проблема.
Перевод инструкции ниже пригодится в первую очередь сетевым инженерам и SRE, которые сопровождают OpenStack-облака и сетевую виртуализацию на базе OVN/OVS, а также инженерам поддержки, которые регулярно разбирают инциденты с FIP, NAT и маршрутизацией.
Оригинал показывает примеры для RHOSP16 с ml2_ovn. Базовый подход — как собирать данные, как использовать ovn-trace и ofproto/trace — остается актуальным и для новых релизов. Но конкретный путь пакета, имена портов и логические сущности в современных развертывании OVN/DVR могут отличаться: их стоит воспринимать как пример, а не как единственно верное решение.
Немного про DVR и L3HA. В статье рассматриваются два режима работы Floating IP. В режиме L3HA трафик к FIP идет через network-ноды, где размещаются виртуальные роутеры. В режиме DVR трафик приходит напрямую на compute-ноду, на которой работает виртуальная машина, и обрабатывается локально. От этого зависит, где именно «слушается» FIP и где искать проблему в трассировке.
Инструменты, которые здесь используются:
ovn-trace — показывает предполагаемый путь пакета через логические коммутаторы и маршрутизаторы OVN, правила security groups и NAT до их компиляции в OpenFlow;
ovs-appctl ofproto/trace — трассировщик на конкретной ноде: показывает фактический путь пакета через реальные таблицы OpenFlow в выбранном OVS-бридже;
tcpdump — сниффер трафика, который показывает реальные пакеты на интерфейсах и помогает сверить «симуляцию» с живым трафиком.
Теперь, когда у нас есть базовые вводные и контекст, можно наконец перейти к практике. Ниже ― перевод гайда Lewis Denny (OpenStack) без наших комментариев. Если что-то покажется неочевидным или захочется обсудить — пишите в комментариях.
Описание окружения
В примерах используется инстанс Cirros и две сети Neutron: одна тенант-сеть и одна внешняя, соединенные маршрутизатором. Для быстрого развертывания можно использовать playbook floating_ip-tenant-network-instance.yaml.
Деплой тестового инстанса
source overcloudrc
curl https://gitlab.com/... -O
ansible-playbook floating_ip-tenant-network-instance.yaml
Сбор исходных данных
Нужно собрать сведения об инстансе и сетях.
Информация о Nova-инстансе
Команда покажет гипервизор, libvirt-имя и IP-адреса:
openstack server show \
-c OS-EXT-SRV-ATTR:host \
-c OS-EXT-SRV-ATTR:instance_name \
-c name \
-c addresses \
-f yaml \
cirros-floating_ip
Пример ответа:
OS-EXT-SRV-ATTR:host: compute-0.redhat.local
OS-EXT-SRV-ATTR:instance_name: instance-00000002
addresses: pri_network=172.20.1.201, 10.0.0.152
name: cirros-floating_ip
Сетевая информация
Внутренняя сеть
Покажем параметры сети, к которой подключен инстанс:
openstack network show \
-c name \
-c id \
-c provider:segmentation_id \
-c router:external \
-c subnets \
-f yaml \
pri_network
Пример:
id: 205ed5bf-e1cd-4622-a2dc-59d5578e5fa1
name: pri_network
provider:segmentation_id: 52953
router:external: false
subnets:
- 51df2626-a3f5-4b27-bcea-397cda60a7cd
Примечание.
router:external: false— пакетам понадобится маршрутизация через другой сетевой сегмент и маршрутизатор.
Внутренний порт
openstack port list \
-f yaml \
--fixed-ip ip-address=172.20.1.201
Пример:
- Fixed IP Addresses:
- ip_address: 172.20.1.201
subnet_id: 51df2626-a3f5-4b27-bcea-397cda60a7cd
ID: 880e395b-d40d-40ad-b97d-52e90ab987c4
MAC Address: fa:16:3e:33:2b:77
Name: ''
Status: ACTIVE
Floating IP
Получим сведения о FIP и порте:
openstack floating ip list \
--long \
-f yaml \
-c "Fixed IP Address" \
-c "Floating IP Address" \
-c "Router" \
-c "Floating Network" \
-c "Port" \
-c "ID" \
--floating-ip-address 10.0.0.152
Пример:
- Fixed IP Address: 172.20.1.201
Floating IP Address: 10.0.0.152
Floating Network: a74c89ff-4f4a-4f92-8feb-d848e9620f6a
ID: 1f9acd22-b165-4d1c-9cfa-c8460d4f291e
Port: 880e395b-d40d-40ad-b97d-52e90ab987c4
Router: 43af05c8-0efc-42aa-a28f-2a89f6e26f40
Порт FIP:
openstack port list \
-f yaml \
--fixed-ip ip-address=10.0.0.152
Пример:
- Fixed IP Addresses:
- ip_address: 10.0.0.152
subnet_id: 53f99136-a2e3-4da5-aee9-f5e136137663
ID: dc4e7dac-ef83-4b57-a0f7-de6b285d6637
MAC Address: fa:16:3e:2c:15:a4
Name: ''
Status: N/A
Внешняя сеть
UUID внешней сети из вывода выше: a74c89ff-4f4a-4f92-8feb-d848e9620f6a.
openstack network show \
-c name \
-c id \
-c provider:segmentation_id \
-c provider:physical_network \
-c provider:network_type \
-c router:external \
-c subnets \
-f yaml \
a74c89ff-4f4a-4f92-8feb-d848e9620f6a
Пример:
id: a74c89ff-4f4a-4f92-8feb-d848e9620f6a
name: ext_network
provider:network_type: flat
provider:physical_network: datacentre
provider:segmentation_id: null
router:external: true
subnets:
- 53f99136-a2e3-4da5-aee9-f5e136137663
Примечание. У сети установлен внешний флаг:
router:external: true.
Маршрутизатор
Из вывода по FIP известен ID роутера: 43af05c8-0efc-42aa-a28f-2a89f6e26f40.
openstack router show \
-c external_gateway_info \
-c id \
-c interfaces_info \
-c name \
-f yaml \
43af05c8-0efc-42aa-a28f-2a89f6e26f40
Пример:
external_gateway_info:
enable_snat: true
external_fixed_ips:
- ip_address: 10.0.0.234
subnet_id: 53f99136-a2e3-4da5-aee9-f5e136137663
network_id: a74c89ff-4f4a-4f92-8feb-d848e9620f6a
id: 43af05c8-0efc-42aa-a28f-2a89f6e26f40
interfaces_info:
- ip_address: 172.20.1.254
port_id: 01932525-1310-4b29-a166-017fb4128bed
subnet_id: 51df2626-a3f5-4b27-bcea-397cda60a7cd
name: router
Информация из libvirt
Переходим на compute-нод с инстансом и смотрим интерфейсы:
ssh heat-admin@overcloud-novacompute-0.ctlplane
sudo su -
podman exec -it nova_libvirt virsh domiflist instance-00000002
Пример:
Interface Type Source Model MAC
----------------------------------------------------------------
tap880e395b-d4 bridge br-int virtio fa:16:3e:33:2b:77
Примечание. У инстанса один интерфейс, но два IP. NAT по FIP выполняется внутри OVN: OpenFlow-правила транслируют FIP во внутренний IP на подключенном интерфейсе.
Резюме собранных данных
# Instance:
host: compute-0.redhat.local
instance_name: instance-00000002
IPs: 172.20.1.201, 10.0.0.152
name: cirros-floating_ip
# Internal Network:
id: 205ed5bf-e1cd-4622-a2dc-59d5578e5fa1
name: pri_network
segmentation_id: 52953
router:external: false
subnet: 51df2626-a3f5-4b27-bcea-397cda60a7cd
# Port (internal):
id: 880e395b-d40d-40ad-b97d-52e90ab987c4
ip: 172.20.1.201
mac: fa:16:3e:33:2b:77
# Floating IP:
FIP: 10.0.0.152
Fixed: 172.20.1.201
FIP Net: a74c89ff-4f4a-4f92-8feb-d848e9620f6a
Router: 43af05c8-0efc-42aa-a28f-2a89f6e26f40
Port: 880e395b-d40d-40ad-b97d-52e90ab987c4
# FIP Port:
id: dc4e7dac-ef83-4b57-a0f7-de6b285d6637
ip: 10.0.0.152
mac: fa:16:3e:2c:15:a4
# Router:
ext gw ip: 10.0.0.234 (enable_snat: true)
int iface: 172.20.1.254
# External Network:
id: a74c89ff-4f4a-4f92-8feb-d848e9620f6a
name: ext_network
type: flat
physnet: datacentre
router:external: true
# Libvirt / OVS:
tap: tap880e395b-d4 (br-int), mac: fa:16:3e:33:2b:77
Генерация трафика
Для примеров используется ping из внешней сети (вне director-ноды, место запуска не критично).
Проверяем DVR
Включен ли DVR — влияет на то, где «слушается» FIP.
На контроллере:
crudini --get /var/lib/config-data/puppet-generated/neutron/etc/neutron/plugins/ml2/ml2_conf.ini ovn enable_distributed_floating_ip
Документация по параметру: https://docs.openstack.org/neutron/latest/configuration/ml2-conf.html#ovn.enable_distributed_floating_ip
Если enable_distributed_floating_ip=true, NAT по FIP выполняется локально на compute-ноде, а не на централизованном шлюзе.
Проверим также external_mac для FIP в OVN NB:
ovn-nbctl lr-nat-list neutron-43af05c8-0efc-42aa-a28f-2a89f6e26f40
Пример:
TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
dnat_and_snat 10.0.0.152 172.20.1.201 fa:16:3e:2c:15:a4 880e395b-d40d-40ad-b97d-52e90ab987c4
snat 10.0.0.234 172.20.1.0/24
В THT включение DVR задается шаблоном:
environments/services/neutron-ovn-dvr-ha.yaml
28: NeutronEnableDVR: true
environments/services/neutron-ovn-ha.yaml
21: NeutronEnableDVR: False
Даже если роутер размещен на контроллере с enable-chassis-as-gw, трафик FIP при DVR обрабатывается локально.
Наблюдение. Пинг до IP роутера виден на внешнем интерфейсе шасси с наивысшим приоритетом. Пинг FIP или выход из инстанса при DVR не проходит через сам роутер.
Трассировка пакета к FIP в среде с DVR
Определяем внешнюю сеть и путь пакета
При DVR пинг FIP обрабатывается локально на compute-ноде. Нужно понять, по какому интерфейсу пакет приходит.
Смотрим bridge-mappings в Neutron:
openstack network agent list -f yaml --host compute-0.redhat.local
openstack network agent show -f yaml -c configuration a2d82774-211d-5c67-adfc-17d56e0af77c
Пример:
configuration:
bridge-mappings: datacentre:br-ex,tenant:br-isolated
Проверим в OVS DB на хосте:
ovs-vsctl get open . external_ids
Пример:
{..., ovn-bridge-mappings="datacentre:br-ex,tenant:br-isolated", ...}
Видно, что datacentre сопоставлен с br-ex.
Список портов br-ex:
ovs-vsctl list-ports br-ex
Пример:
ens5
patch-provnet-189127e8-3244-41e2-b989-9497ae5d044c-to-br-int
Проверим ens5 через tcpdump:
tcpdump -nnei ens5 -c 1 icmp and src host 10.0.0.75
Пример:
52:54:00:be:f5:b1 > fa:16:3e:2c:15:a4 ... 10.0.0.75 > 10.0.0.152: ICMP echo request
Запрос и ответ видны — трафик поступает.
OVN: общий путь
Пакет приходит на ens5 в br-ex, далее попадает в br-int (инстанс сидит на br-int), что можно подтвердить:
ovs-vsctl port-to-br tap880e395b-d4
# br-int
В br-ex используется patch-provnet-...-to-br-int, на стороне br-int — парный patch-br-int-to-provnet-....
Посмотреть пакеты на patch-портах tcpdump нельзя (это не veth-пара), но можно через ovs-tcpdump со стороны br-int:
ovs-tcpdump -nne -i patch-br-int-to-provnet-189127e8-3244-41e2-b989-9497ae5d044c
Пример:
... 10.0.0.75 > 172.20.1.201: ICMP echo request ...
Дальше — NAT и доставка до tap-интерфейса инстанса:
tcpdump -nnei tap880e395b-d4 -c 1 icmp and src host 10.0.0.75
Пример:
... 10.0.0.75 > 172.20.1.201: ICMP echo request ...
Внутри инстанса IP тоже виден:
ip -o a
./tcpdump -nni eth0 -c 1 icmp and src host 10.0.0.75
Логические элементы OVN
Ниже — удобные алиасы для работы с OVN NB/SB и ovn-trace внутри контейнера ovn_controller:
if [ -f "/var/run/docker.pid" ]; then export containerTool=docker; else export containerTool=podman; fi
export SBDB=$(ovs-vsctl get open . external_ids:ovn-remote | sed -e 's/\"//g')
export NBDB=$(ovs-vsctl get open . external_ids:ovn-remote | sed -e 's/\"//g' | sed -e 's/6642/6641/g')
alias ovn-sbctl='$containerTool exec ovn_controller ovn-sbctl --db=$SBDB'
alias ovn-nbctl='$containerTool exec ovn_controller ovn-nbctl --db=$NBDB'
alias ovn-trace='$containerTool exec ovn_controller ovn-trace --db=$SBDB'
alias ovn-appctl='$containerTool exec ovn_controller ovn-appctl'
alias ovn-detrace='cat >/tmp/trace && $containerTool cp /tmp/trace ovn_controller:/tmp/trace && $containerTool exec -it ovn_controller bash -c "ovn-detrace --ovnsb=$SBDB --ovnnb=$NBDB </tmp/trace"'
Подсказка: архитектура OVN — в man ovn-architecture.
Фрагмент структуры NB
Посмотрим, как это выглядит в OVN Northbound:
ovn-nbctl show
Пример (ключевые порты и NAT-правила помечены комментариями):
switch 7c69f1da-2968-4ef1-accc-2bb40859aed1 (neutron-a74c89ff-4f4a-4f92-8feb-d848e9620f6a) (aka ext_network)
port provnet-189127e8-3244-41e2-b989-9497ae5d044c
type: localnet
addresses: ["unknown"]
port 65ff31a4-4106-474c-8c65-709890507931 <--- Порт роутера со стороны внешней сети -------+
type: router |
router-port: lrp-65ff31a4-4106-474c-8c65-709890507931 |
port 33f20266-58b3-4801-bd08-f9f5567ca201 |
type: localport |
addresses: ["fa:16:3e:28:38:30 10.0.0.150"] |
switch 016fd8ab-1bb8-4716-b426-91be1d001560 (neutron-205ed5bf-e1cd-4622-a2dc-59d5578e5fa1) (aka pri_network) |
port 01932525-1310-4b29-a166-017fb4128bed <--- Порт роутера со стороны внутренней сети ---+ |
type: router | |
router-port: lrp-01932525-1310-4b29-a166-017fb4128bed | |
port 5de3fb32-850f-43be-93ae-1c6448cfaff9 | |
type: localport | |
addresses: ["fa:16:3e:8f:bb:1a 172.20.1.1"] | |
port 880e395b-d40d-40ad-b97d-52e90ab987c4 <--- Порт инстанса с внутренним IP | |
addresses: ["fa:16:3e:33:2b:77 172.20.1.201"] | |
router 3aefc84e-a009-4e42-b8f5-c0c49f4302e0 (neutron-43af05c8-0efc-42aa-a28f-2a89f6e26f40) (aka router) | |
port lrp-01932525-1310-4b29-a166-017fb4128bed <--- Порт роутера во внутреннюю сеть -------------+ |
mac: "fa:16:3e:75:01:fc" |
networks: ["172.20.1.254/24"] |
port lrp-65ff31a4-4106-474c-8c65-709890507931 <--- Порт роутера во внешнюю сеть -----------------------+
mac: "fa:16:3e:2a:f7:4a"
networks: ["10.0.0.234/24"]
gateway chassis: [4249e953-391e-4a9e-9851-7a961caca01a e734d771-e0e1-45c1-8b06-4ea7c693778d 10b1f04f-d83d-43ce-9f2d-bd6c32fec0a3]
nat 2bfa771b-b573-4c49-a808-bc8cbf1de2aa <--- NAT-правило для FIP инстанса
external ip: "10.0.0.152"
logical ip: "172.20.1.201"
type: "dnat_and_snat"
nat fcc5acdf-e888-4bb0-a794-de181eced554
external ip: "10.0.0.234"
logical ip: "172.20.1.0/24"
type: "snat"
ovn-trace: симуляция прохождения пакета
С помощью ovn-trace можно смоделировать прохождение ICMP-пакета через логические устройства OVN.
Параметры, которые понадобятся:
switch — логический коммутатор внешней сети;
inport — порт provnet в br-ex;
ip.ttl, icmp4.type;
MAC-адреса и IP из дампа (источник/назначение).
Пример команды:
ovn-trace --ct=new --summary neutron-a74c89ff-4f4a-4f92-8feb-d848e9620f6a \
'inport == "provnet-189127e8-3244-41e2-b989-9497ae5d044c" && \
eth.src == 52:54:00:be:f5:b1 && eth.dst == fa:16:3e:2c:15:a4 && \
ip4.src == 10.0.0.75 && ip4.dst == 172.20.1.20 && ip.ttl == 32 && icmp4.type == 8'
Вывод покажет резюме и детальный проход по таблицам.
OVS: архитектура и путь
Посмотрим конфигурацию OVS:
ovs-vsctl show
Ключевые элементы пути (сокращенно):
br-ex — вход пакета через
ens5;patch-provnet-
...-to-br-int → patch-br-int-to-provnet-...;br-int — доставка до
tap880e395b-d4.Bridge br-ex
Port ens5 <--- Packet enters here
...
Port patch-provnet-... -------------------+
...
Bridge br-int
Port patch-br-int-... <-------------------+
Port tap880e395b-d4 <--- Packet ends here
ovs-appctl ofproto/trace: трассировка по OpenFlow
VN генерирует правила для OVS, поэтому бывает, что все логические потоки правильные, но в OpenFlow недостаёт конкретного правила — и трафик ломается.
Поможет ofproto/trace.
Установим пакет тестовых утилит OVS (для ovs-tcpundump):
dnf install openvswitch2.15-test
# Пакет доступен в репозитории fast-datapath-for-rhel-8-x86_64-rpms
Преобразуем пакет из tcpdump в hex-строку и пробросим в ofproto/trace:
flow=$(tcpdump -nXXi tap880e395b-d4 -c 1 icmp and src host 10.0.0.75 | ovs-tcpundump)
echo $flow
ovs-appctl ofproto/trace br-int \
in_port=$(ovs-vsctl get Interface tapecd8fb74-f4 ofport) \
$flow
Вывод подробно отразит обработку по таблицам, в том числе стадии с
ct(zone=...,nat)и рекуррентные проходы(recirc(...)), изменение MAC/DST/TTL и финальную отправку через нужный порт.
На заметку
Режим L3HA включен всегда; DVR включается поверх него. В режиме L3HA FIP размещается на шасси с максимальным приоритетом: трафик к FIP идет через gw-шасси, затем по туннелю на compute-нод с инстансом.
Приоритеты gw-шасси возрастают с числом (1 < 2 < 3 …). Самое «старшее» шасси хостит gateway-порт, остальные — slave. Документацию по переразмещению L3 HA можно найти по ссылке.