
Привет! Меня зовут Никита Усатов, я занимаюсь разработкой сервисов облачной сети MWS. Сегодняшний рассказ затронет устройство наложенной (overlay) сети облака MWS и то, как виртуальные машины пользователей получают свои сетевые настройки.
Облачная сеть
Для начала напомним об устройстве оверлейной сети (более подробно и комплексно о ней рассказывал Яков в своей статье).
На каждом гипервизоре есть сущность под названием виртуальный маршрутизатор (VRF — Virtual Routing and Forwarding). Если нужно отправить пакет на другой хост, он инкапсулирует пакеты, которые получает от виртуальных машин, а затем передаёт их по underlay-сети. На конечном хосте пакет декапсулируется и доставляется до той виртуальной машины, которой он предназначен. Несколько виртуальных машин могут находиться как внутри одного VRF, так и в разных, изолированно друг от друга. Также виртуальные машины даже из одного VRF могут располагаться на разных гипервизорах. VRF позволяет логически изолировать сетевые пространства клиентов. Это означает, что у разных виртуальных машин во всей облачной сети могут совпадать IP-адреса, если они относятся к разным виртуальным маршрутизаторам, т. к., несмотря на совпадение IP-адресов, изоляция между VRF'ами исключает пересечения трафика — каждый VRF действует как независимая таблица маршрутизации.

Пользователь создал всё необходимое для сети своих виртуальных машин (сеть, подсеть, адаптеры), создал виртуальные машины. Возникает вопрос: как нам раздать виртуальной машине сетевую конфигурацию, то есть IP-адрес, доменное имя, список DNS-серверов и так далее?
Для этой задачи решено использовать DHCP. Однако нам нужен такой DHCP-сервер, который сможет не просто выделять адреса из подсети, а учитывать клиентский виртуальный маршрутизатор по его идентификатору (VRF ID) и настройки подсети клиента в заданном VRF. Соответственно, возникла потребность во внедрении отдельного компонента — DHCP-сервера с поддержкой VRF.
Прохождение DHCP-запроса
В классических сетевых устройствах принято разделять функции на две плоскости:
Control Plane — управляющий слой, отвечает за конфигурацию устройства, логику, мониторинг, логирование и настройку Data Plane.
Data Plane — передающий слой, обрабатывает трафик в соответствии с настройками от Control Plane.
Основным Data Plane компонентом облачной сети MWS является VPP (Vector Packet Processor).
VPP — «молотилка трафика», весь трафик клиентских виртуальных машин проходит через него. VPP развёрнут на всех гипервизорах, где и разворачиваются виртуальные машины. Вследствие этого, когда ВМ пользователя посылает любой DHCP-запрос, он сначала принимается на стороне VPP, после чего отправляется по адресу DHCP-сервера. Получается VPP должен реализовывать функционал DHCP-ретранслятора (DHCP Relay, описанный в RFC 3046). В терминах VPP такой функционал называется DHCP Proxy. С помощью узла DHCP Proxy Node происходит обработка DHCP-запроса. При попадании запроса в этот узел проставляются опции информации о ретрансляторе, а именно опция 82 (Relay Agent Information). Данная опция используется для передачи дополнительной информации от ретранслятора до DHCP-сервера — например, в каком сегменте сети находится клиент:
/* Add option 82 */
o->option = 82; /* option 82 */
o->length = 12; /* 12 octets to follow */
Мы используем опцию 82, чтобы сервер понял, из какого VRF пришёл запрос, но для этого нужно ещё указать, какая именно информация содержится в ней, то есть указать подопцию. В данном случае нужна подопция с информацией о VSS (Virtual Subnet Selection, описанная в RFC 6607. Узел узнаёт о выборе виртуальной подсети пользователя и записывает эту информацию под соответствующим номером опции. В нашем случае в поле выбора виртуальной подсети записан идентификатор VRF клиентской виртуальной машины. Выставление подопции 151 о VSS в узле DHCP Proxy в VPP:
vss = dhcp_get_vss_info (dpm, fib_index, FIB_PROTOCOL_IP4);
...
id_len = vec_len (vss->vpn_ascii_id);
memcpy (&o->data[15], vss->vpn_ascii_id, id_len);
...
o->data[12] = 151; /* vss suboption */
Таким образом исходящие DHCP Broadcast-запросы от пользовательских виртуальных машин, как и любые другие запросы, попадают в VPP, после чего преобразуются в Unicast-запросы, отправляясь напрямую в DHCP-сервер, который находится в системном VRF на гипервизоре, где разворачиваются виртуальные машины.

DHCP-сервер
Основное требование к DHCP-серверу — поддержка пулов с пересекающимися адресными пространствами. Пока в облаке поддерживаются клиентские сети IPv4, нам нужна поддержка DHCPv4 с соответствующими опциями. В качестве целевого решения выбрали CoreDHCP. Это реализация полноценного DHCP-сервера на Go с поддержкой модулей расширения (плагинов) для реализации любого функционала, вдохновлённая Caddy и CoreDNS.
На стороне DHCP-сервера требуется описать несколько пулов с пересекающимся адресным пространством для пользовательских VM. DHCP-сервер должен распознать информацию о принадлежности к VRF, содержащуюся в Option 82 suboption 151, и на основании идентификатора VRF выбрать необходимый пул с настройками для VM.
Реализация
CoreDHCP предоставляет возможность модификации пользовательскими плагинами. Для этого достаточно разработать обработчик DHCP-запросов со следующей сигнатурой:
func (s *PluginState) Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool)
А также функцию инициализации плагина:
func setup4(args ...string) (handler.Handler4, error)
У CoreDHCP из коробки поставляется некоторое количество плагинов. Изначально нам приглянулся уже существующий плагин file, который позволяет выдавать сетевые конфигурации из файла конфигураций, что нам могло бы подойти. Однако этот плагин позволяет выдавать только IP-адрес в зависимости от MAC-адреса, чего нам недостаточно. Во-первых, мы хотим получать в ответе от DHCP-сервера не только IP, но и другие сетевые параметры. Во-вторых, в нём недостаточно зависимости от физического адреса, так как резервация осуществляется через пару значений: MAC-адрес и идентификатор VRF.
Вдохновившись плагином file, мы разработали свой плагин, предоставляющий возможность конфигурировать выдаваемые клиенту DHCP-опции на базе файлов конфигураций с поддержкой любых (почти) DHCP-опций согласно RFC2132. Пример файла конфигураций:
subnets:
- id: "subnet_id_1"
options:
- code: 1 # subnet-mask
value: "255.255.255.0"
- code: 3 # router-id
value: "192.168.0.1"
- code: 6 # dns-server
value: 1.1.1.1,8.8.8.8
reservations:
- hw-addr: "52:69:e1:af:58:78"
vss: VRF1000
ip: 192.168.0.1
options:
- code: 1 # subnet-mask
value: "255.255.255.0"
- code: 3 # router-id
value: "192.168.0.1"
- code: 6 # dns-server
value: 1.1.1.1,8.8.8.8
- hw-addr: "d2:cf:88:b4:c1:10"
subnet: "subnet_id_1"
vss: VRF1001
ip: 192.168.0.1
- hw-addr: "d2:cf:88:b4:c1:11"
subnet: "subnet_id_1"
vss: VRF1001
ip: 192.168.0.2
options:
- code: 1 # subnet-mask
value: "255.255.255.240"
- code: 3 # router-id
value: "192.168.0.1"
- code: 6 # dns-server
value: 192.168.0.1
Файл конфигурации содержит в себе информацию о бронированиях, в каждой из которых описаны физический адрес, опция VSS, IP-адрес и прочие DHCP-опции. Для некоторых конфигураций виртуальных машин прописана ссылка на подсеть, чтобы переиспользовать общие для подсети DHCP-опции. В момент создания виртуальной машины Control Plane выбирает адрес для неё и создаёт объект сетевого адаптера, который будет присвоен виртуальной машине, после чего DHCP-сервер узнаёт о спецификации сетевого адаптера и может выдавать соответствующие виртуальным машинам сетевые конфигурации. Поэтому, в отличие от классических DHCP-серверов, наш сервер не подбирает свободный адрес из пула, а выдаёт конкретно заранее прописанный — для жёсткой привязки адресов к адаптерам. Мы предлагаем клиенту только тот адрес и те настройки, которые уже прописаны в файле конфигурации. Выбор настроек из файла конфигураций происходит с помощью уникального для сетевого адаптера ключа, который является парой значений: физический адрес клиента и идентификатор VRF. Соответственно, DHCP-сервер читает option 82, извлекает оттуда VRF ID и смотрит в конфиг, где пул привязан к этому VRF.
Конфигурационные файлы, содержащие информацию о подсетях и параметрах DHCP-опций для различных VRF и интерфейсов, могут обновляться и дополняться динамически. Для отслеживания изменений используется механизм fsnotify, позволяющий DHCP-серверу автоматически подхватывать обновления без перезапуска и без необходимости дополнительного API. Из всех возможных способов доставки конфигураций — через сокеты, базу данных, брокеры сообщений или обычные файлы — был выбран именно файловый подход. Он оказался самым надёжным и простым в эксплуатации: не требуется следить за состоянием ещё одного рантайма, как это могло бы быть в случае с базой данных или брокером сообщений, держать активное соединение или обрабатывать сетевые ошибки. Файлы можно атомарно перезаписать, легко версионировать, а сам механизм отслеживания изменений остаётся предсказуемым и отказоустойчивым.
DHCP-сервер разворачивается локально на каждом гипервизоре, где работают VPP и виртуальные машины. Каждый DHCP-сервер обслуживает только свою локальную зону ответственности — ему предоставляются конфигурационные файлы, содержащие только те подсети и настройки сетевых адаптеров, которые относятся к данному гипервизору.
Всё хорошо, но…
Однажды на стенде пропала сетевая связность между виртуальными машинами. Виртуалки не получали IP-адреса, DHCP-запросы уходили, но ответов не поступало. Сначала мы начали искать проблему на стороне самого DHCP-сервера, но по его метрикам и логам было видно, что он вообще не получает никаких запросов. Тогда в дело пошли трейсы в VPP:
02:01:49:366672: vhost-user-input
VirtualEthernet0/0/0 queue 0
virtio flags:
SINGLE_DESC Single descriptor packet
virtio_net_hdr first_desc_len 353
flags 0x00 gso_type 0
num_buff 0
02:01:49:366730: ethernet-input
frame: flags 0x1, hw-if-index 9, sw-if-index 9
IP4: d2:cf:88:b4:c1:10 -> ff:ff:ff:ff:ff:ff
02:01:49:366773: ip4-input
UDP: 0.0.0.0 -> 255.255.255.255
tos 0xc0, ttl 64, length 327, checksum 0x78e7 dscp CS6 ecn NON_ECN
fragment id 0x0000
UDP: 68 -> 67
length 307, checksum 0xc611
...
02:01:49:366958: dhcp-proxy-to-server
DHCP proxy: sent to server 169.254.169.103
original_sw_if_index: 9, sw_if_index: 8
opcode:request hw[type:1 addr-len:6 addr:d2cf88b4c110] hops0 transaction-ID:0x9818be83 seconds:33280 flags:0x0 client:0.0.0.0 your:0.0.0.0 server:0.0.0.0 gateway:169.252.252.2 cookie:99.130.83.99, option-53: type:discover option-61: skipped option-55: skipped option-57: skipped option-80: skipped, option-12: hostname:6e6574776f726b2d766d2d31 option-82: skipped
...
02:01:49:366985: tap988-output
tap988 flags 0xc0180005
IP4: 02:fe:a7:ea:09:e0 -> 02:fe:cd:f5:7f:cc
UDP: 169.252.252.2 -> 169.254.169.103
tos 0xc0, ttl 63, length 353, checksum 0x8067 dscp CS6 ecn NON_ECN
fragment id 0x0000
UDP: 68 -> 67
length 333, checksum 0x0000
02:01:49:366987: tap988-tx
buffer 0x1271f6: current data 0, length 367, buffer-pool 0, ref-count 1, trace handle 0x1000010
l4-cksum-computed l4-cksum-correct l2-hdr-offset 0 l3-hdr-offset 14
hdr-sz 0 l2-hdr-offset 0 l3-hdr-offset 14 l4-hdr-offset 0 l4-hdr-sz 0
IP4: 02:fe:a7:ea:09:e0 -> 02:fe:cd:f5:7f:cc
UDP: 169.252.252.2 -> 169.254.169.103
tos 0xc0, ttl 63, length 353, checksum 0x8067 dscp CS6 ecn NON_ECN
fragment id 0x0000
UDP: 68 -> 67
length 333, checksum 0x0000
Запросы действительно доходят до VPP и даже успешно ретранслируются на адрес DHCP-сервера через DHCP Proxy. Но дальше — тишина.
Получается, запросы не могут достучаться до самого сервера, значит, дело в настройках сетевых интерфейсов на подах с DHCP-сервером. Ну а про покорение CNI в наших кластерах и прохождение запросов от клиентских виртуальных машин до сервисов из наложенной сети можно почитать в статье Глеба.
Здесь мы плавно переходим к мониторингам. Помимо метрик и логов по работе и обрабатываемым запросам на DHCP-сервере, конкретно здесь выручили метрики самого VPP, особенно счётчики DHCP packets relayed to the server и DHCP packets relayed to clients. Они показывают, сколько пакетов ушло на DHCP-сервер и сколько было отправлено клиентами через DHCP Proxy соответственно. Сопоставив эти значения, делаем алерт на недоступность DHCP-сервера — если запросы отправляются, но не доходят до сервера, разница будет ненулевой.

Полный процесс работы приватной сети
Теперь разберём, как будет выглядеть полный цикл работы виртуальной сети от её создания до получения адреса. Первым шагом необходимо создать сеть и подсеть в глобальном инстансе SDN-контроллера (Control Plane облака). Это нужно для распространения данных ресурсов на разные зоны доступности. Далее при создании виртуальной машины создаётся объект сетевого адаптера. Сетевой адаптер создаётся уже в зональном инстансе SDN-контроллера, потому что виртуальная машина, как и её адаптер, будет находиться в конкретной зоне доступности.
Для создания адаптера зональному SDN-контроллеру нужно узнать о том, какие сети и подсети были созданы в глобальном контроллере. В этот момент в процесс вступает реконсиляция на уровне Control Plane.
Реконсиляция — процесс приведения состояния из исходного в желаемое. В облаке мы реконсилируем ресурсы. Например, пользователь создал или изменил ресурс, значит, у этого ресурса есть спецификация, которую нужно прокинуть в другие места. Реконсиляция помогает гарантировать, что все элементы инфраструктуры соответствуют заданным параметрам и любые изменения, внесённые пользователем, автоматически применяются к связанным системам. Этот процесс включает в себя периодическую проверку фактического состояния ресурсов и сравнение его с желаемым состоянием, указанным в спецификации.
После того как ресурсы сети и подсети реконсилированы на уровне Control Plane, их нужно реконсилировать на уровень Data Plane. Здесь на помощь приходит компонент под названием SDN-агент (сетевой агент). А более подробно про реконсиляцию ресурсов в Data Plane (на примере блочных устройств) также рассказывал Сергей в статье.

Сетевой агент настраивает сетевые компоненты, в том числе VPP и DHCP-сервер. Для VPP настраиваются VRF и выставляются настройки VSS-опций для узла DHCP Proxy, а для DHCP-сервера агент создаёт файлы конфигураций, о которых сказано выше, с описаниями подсетей и адаптеров клиентских виртуальных машин. В то же время DHCP-сервер следит за обновлениями файлов конфигураций и обновляет своё локальное хранилище.

Как только виртуальная машина создалась и запустилась, она отправляет запрос DHCP Discover, который, проходя через VPP, попадает на DHCP-сервер. DHCP-сервер сопоставляет MAC-адрес и идентификатор VRF клиента и предлагает ему адрес из того, что отправил серверу сетевой агент. Пример ответа DHCP Offer от сервера:
$ nmap --script broadcast-dhcp-discover --script-args mac=f0:86:32:24:4c:cc,timeout=2pcd -o domain_name_servers -T
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-04 09:15 UTC
Pre-scan script results:
| broadcast-dhcp-discover:
| Response 1 of 1:
| Interface: ens3
| IP Offered: 192.168.1.4
| Subnet Mask: 255.255.255.0
| Router: 192.168.1.1
| Domain Name Server: 169.254.169.254
| Domain Name: test-project.c.mws
| IP Address Lease Time: 3d00h00m00s
| DHCP Message Type: DHCPOFFER
|_ Server Identifier: 169.254.169.103
Заключение
Такая архитектура особенно актуальна для мультиарендных (multi-tenant) облаков, где десятки или сотни клиентов разворачивают свои изолированные окружения. Каждый такой «тенант» может использовать собственную адресацию, не заботясь о конфликте с другими, — даже если все они, например, используют один и тот же диапазон адресов. Это становится возможным благодаря VRF, которые логически изолируют сетевые пространства друг от друга: как будто каждый клиент работает в своём собственном, полностью независимом сегменте сети.
Подход с VRF и DHCP с Option 82 позволяет масштабировать инфраструктуру, не усложняя конфигурацию на стороне клиентов. Им не нужно вручную прописывать адреса, DHCP-сервер сам выдаст правильные настройки. Это не только упрощает управление, но и снижает риск ошибок и конфликтов. При этом CoreDHCP с кастомными плагинами позволяет гибко и надёжно управлять адресным пространством.
Результат — облачная платформа, в которой легко запускать изолированные окружения с пересекающимися адресными пространствами, обеспечивая автоматизацию сетевых настроек виртуальных машин.
P.S. Если хотите узнать больше, как работает наша overlay-сеть, приходите 4 июня смотреть 6-й эпизод реалити для разработчиков Building the Cloud. Мои коллеги Ренат Ахмеров и Александр Костриков расскажут про архитектуру control & data plane нашей сети.
Читайте и смотрите другие материалы про создание нового публичного облака MWS:
Зачем мы строим собственное публичное облако? Рассказывает CTO MWS Данила Дюгуров.
Реалити-проект для инженеров про разработку облака. Рассказываем про архитектуру сервисов в серии видео ещё до релиза.
Подкаст "Расскажите про MWS". Рассказываем про команду новой облачной платформы MWS.