Привет, Хабр! На связи Кирилл Савин, я — архитектор 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 можно найти по ссылке.  

Комментарии (0)