Хабр, привет!
Сегодня рассказываем об еще одном нашем кейсе с NSX-T. В прошлый раз мы написали о проблеме долгой реализации портов. Технические вводные остались те же: NSX используется для построения микросегментированной сети в кластерах Kubernetes + Kyverno. Взаимодействие K8s и NSX реализовано при помощи плагина VMware NCP. Что случилось в этот раз? Команда этого заказчика обновила продукт до версии 4.1., после чего обратилась к нам с проблемой перехода IP pool в состояние Failed. Иллюстрируем:
Диагностика
На начальном этапе анализа мы увидели, что проблема возникала в момент редактирования настроек любого из пулов, созданных до момента обновления. Так как данная инсталляция NSX использовалась для создания автоматизированных микросегментированных сетей кластера Kubernetes с помощью NCP, то проблема носила глобальный характер (сам масштаб мы увидим позже). Более того, она препятствовала нормальному созданию и изменению настроек каждого из тысяч подов.
Пошли смотреть NCP. К сожалению, с его стороны при попытке создания пода увидели только ошибки типа Unhandled Exception, что мало говорило об ошибках в NSX:
NSX 8 - [nsx@6876 comp="nsx-container-ncp" subcomp="ncp" level="WARNING"] nsx_ujo.common.controller NamespaceController worker 2 failed to sync b273350c-0d80-4c04-af31-f824fcc032a3 due to unexpected exception Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/nsx_ujo/common/controller.py", line 469, in worker
self.sync_handler(key)
File "/usr/local/lib/python3.8/dist-packages/nsx_ujo/ncp/k8s/namespace_controller.py", line 116, in sync_handler
self._sync_namespace_update(namespace)
File "/usr/local/lib/python3.8/dist-packages/nsx_ujo/common/metrics_utils.py", line 118, in wrapper
return func(*args, **kwargs)
File "<decorator-gen-2>", line 2, in _sync_namespace_update
В то же время со стороны NSX мы наблюдали следующую картину: сервис пытался пересоздать пул, но процесс завершался ошибкой из-за уже аллоцированных адресов:
1. 1970-01-01T09:32:48.390Z WARN providerTaskExecutor-1-63 RealizationFetchUtility 5795 POLICY [nsx@6876 comp="nsx-manager" level="WARNING" subcomp="manager"] Provider is not ready, realized object for /infra/realized-state/enforcement-points/default/ip-blocks/##########/ip-pools/ipp_#########################_0/block-subnets/ibs_#######################_0 is missing or incomplete
2. ....
3. 1970-01-01T09:32:48.391Z INFO providerTaskExecutor-1-63 RealizationFetchUtility 5795 POLICY [nsx@6876 comp="nsx-manager" level="INFO" subcomp="manager"] Added dependent paths:[/infra/ip-pools/ipp__#########################_0] on:/infra/ip-blocks//##########/
4. 1970-01-01T09:32:48.391Z INFO providerTaskExecutor-1-63 PolicyIpamBaseProvider 5795 POLICY [nsx@6876 comp="nsx-manager" level="INFO" subcomp="manager"] IPAM - Realized resource not found with path:/infra/realized-state/enforcement-points/default/ip-blocks/##########/ip-pools/ipp_#########################_0/block-subnets/ibs_#######################_0 and type RZ_IP_ADDRESS_POOL_BLOCK_SUBNET_NEW
5.
Далее мы пытались оживить проблемные пулы через api. Однако все оказалось безуспешным: наши попытки (использовали PATCH и PUT) завершаются одной и той же ошибкой — Provider is not ready.
В результате мы выяснили, что пул штатными средствами может быть вылечен только полным выключением всей нагрузки. После высвобождения всех адресов пул «зеленел», и при включении нагрузки происходила аллокация из новой подсети. Этот вариант, что ожидаемо, никого не устраивал, потому что требовал выключения тысяч контейнеров — “back to the drawing board”.
И вот мы подошли к масштабу. В начале мы думали, что проблема затрагивала 30–40 пулов. По факту она негативно отражалась на работе более 1200 пулов:
А если посмотреть на проблемной площадке, мы видим, что 1213 пулов (ipp_) имеют всего 19 block_subnet:
root@host# grep -A2 'REALIZED_ENTITY_TYPE_RZ_IP_ADDRESS_POOL' GenericPolicyRealizedResource.export | grep ipp_ | wc -l
2426
root@host# grep -A2 'REALIZED_ENTITY_TYPE_RZ_IP_ADDRESS_POOL' GenericPolicyRealizedResource.export | grep ibs_ | wc -l
1213
root@host# grep -c 'RZ_IP_ADDRESS_POOL_BLOCK_SUBNET' GenericPolicyRealizedResource.export
19
У этих пулов в процессе обновления потерялся статус реализации (готовности) объекта и аллоцированный сабнет. Соответственно, внутренняя логика NSX пыталась их восстановить, пересоздавая новый сабнет, но получала ошибку из-за наличия уже аллоцированных адресов. Это и препятствовало любым попыткам взаимодействия с пулами штатными средствами.
Соответственно, устраивающим всех (включая сам NSX) решением было бы засунуть эти записи в одну из нужных баз данных. Тем самым мы могли вернуть штатную реализацию пулов обратно.
На пути к решению — наконец-то!
В итоге изучения тестовых NSX Manager и базы заказчика мы собрали немного информации и выяснили:
Данные о реализации объектов хранятся в базе Corfu в таблице GenericPolicyRealizedResource;
Данные о каждой реализации объекта типа 'RZ_IP_ADDRESS_POOL_BLOCK_SUBNET' завязаны на каждом из шести индексов (таблиц) уже из другой БД — Elastic;
Каждый объект состоит из двух половин policy и manager. Несмотря на то, что VMware с релиза NSX 3.х заявляет о выводе в EoL-функционала Manager UI/API, в каждом, в том числе свежесозданном продукте версии 4.x, все еще присутствует этот функционал;
Несмотря на то что скрипт для взаимодействия с Corfu (corfu_tool_runner.py) не имеет функционала для добавления данных в инстанс базы, мы нашли его при анализе библиотек jvm. Оказалось, что org.corfudb.browser.CorfuStoreBrowserEditorMain имеет описанную функцию addRecord, которая принимает на ввод ключи: keyToAdd, valueToAdd, metadataToAdd.
Реализация
Исходя из пунктов выше, подготовили нужные json-объекты. С ними мы можем вернуть нормальную реализацию проблемных пулов. Дальше нужно понять структуру данных для объекта и зависимости, которые нам потребуются. Пример готового объекта:
Скрытый текст
Key:
{ "stringId": "/infra/realized-state/enforcement-points/default/ip-blocks/$ip_block/ip-pools/$ipp/block-subnets/$ibs"}
Value:
{
"abstractPolicyRealizedResource": {
"abstractPolicyResource": {
"managedResource": {
"displayName": "$ibs"
},
"markedForDelete": false,
"deleteWithParent": false,
"locked": false,
"isOnboarded": false,
"internalKey": {
"left": "$l_key",
"right": "$r_key"
},
"gmKey": {
"left": "$l_key",
"right": "$r_key"
},
"parentPath": "infra/realized-state/enforcement-points/default/ip-blocks/$ip_block/ip-pools/$ipp/"
},
"realizationObjectId": "$intent_uuid",
"realizationState": "REALIZATION_STATE_REALIZED",
"runtimeStatus": "RUNTIME_STATUS_UNINITIALIZED",
"intentVersion": "$intent_version",
"realizedVersionOnEnforcement": "0",
"realizationApi": "/api/v1/pools/ip-subnets/$intent_uuid"
},
"entityType": "REALIZED_ENTITY_TYPE_RZ_IP_ADDRESS_POOL_BLOCK_SUBNET",
"extendedAttributes": {
"start_ip": {
"dataType": "DATA_TYPE_TYPE_STRING",
"value": [
"$start_ip"
]
},
"gateway_ip": {
"dataType": "DATA_TYPE_TYPE_STRING",
"value": [
"$gw"
]
},
"cidr": {
"dataType": "DATA_TYPE_TYPE_STRING",
"value": [
"$CIDR"
]
},
"end_ip": {
"dataType": "DATA_TYPE_TYPE_STRING",
"value": [
"$end_ip"
]
}
},
"intentPath": [
"/infra/ip-pools/$ipp/ip-subnets/$ibs"
]
}
Metadata:
{
"revision": "11",
"createTime": "1660212755641",
"createUser": "system",
"lastModifiedTime": "1708445082785",
"lastModifiedUser": "system"
}
Где переменные:
ipp – policy индекс IP pool
ibs – policy индекс IP block subnet (дочерняя сущность пула, описывающая выданный сабнет)
ip_block – policy индекс IP block (родительская сущность глобального блока адресации)
ip_block_mgr – manager индекс IP block
ipp_mgr_id – manager индекс IP pool
CIDR – CIDR адрес выделенной подсети
l_key – уникальные ключи зависимости таблиц, в данном случае должны быть уникальными
r_key – и не должны пересекаться с другими записями в пределах БД Corfu
meta – метадата, версия ревизии должна соответствовать с объектом типа REALIZED_ENTITY_TYPE_RZ_IP_ADDRESS_POOL_BLOCK_SUBNET
start_ip – стартовый IP сабнета
end_ip – конечный IP сабнета
Итого, с понимаем, что нам нужно и выгрузки баз с проблемной инсталляцией, мы сводим итоговый csv-файл с необходимыми нам переменными.
Трудности
Тем не менее, в процессе реализации все оказалось несколько сложнее. Так, мало того, что данные были размазаны по двум разным базам elastic/corfu и десятку разных таблиц, между ними не существовало четкой связанности — вся работа по «склейке» готового объекта возлагалась на сам приклад.
Один и тот же объект, например IP pool, имеет несколько уникальных идентификаторов в elastic, причем иногда без указания явного пересечения. Так, если для Policy есть явные родительские отношения между IP pool и IP block subnet (_id последнего содержит явное указание на родителя):
"_index": "nsx_policy_ipaddresspool",
"_type": "_doc",
"_id": "/infra/ip-pools/ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"_score": 1.0,
"_source": {
"check_overlap_with_existing_pools": false,
"ip_address_type": "IPV4",
"sync_realization": false,
"resource_type": "IpAddressPool",
"id": "ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"display_name": "ipp-test4-1",
"path": "/infra/ip-pools/ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"relative_path": "ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"parent_path": "/infra",
"remote_path": "",
"unique_id": "fd1392d5-f188-4e16-a023-15d315bb1ad3",
"realization_id": "fd1392d5-f188-4e16-a023-15d315bb1ad3",
"owner_id": "40cc9f04-12af-408c-aecc-b4dd44c44b4b",
"_index": "nsx_policy_ipaddresspoolblocksubnet",
"_type": "_doc",
"_id": "/infra/ip-pools/ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1/ip-subnets/ibs_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"_score": 1.0,
"_source": {
"size": 128,
"subnet_size": "128",
"ip_block_path": "/infra/ip-blocks/0f252ba2-86ff-4584-ae0f-0477bc3cb6ae",
"auto_assign_gateway": true,
"resource_type": "IpAddressPoolBlockSubnet",
"id": "ibs_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"display_name": "ibs-test4-1",
"path": "/infra/ip-pools/ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1/ip-subnets/ibs_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"relative_path": "ibs_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
"parent_path": "/infra/ip-pools/ipp_f9fa99f4-6269-4124-a8e6-df245a9600c4_1",
То для manager-части объекта все выглядит менее радостно. Для наглядности показываем manager-половину того же IP pool:
"_index": "nsx_manager_ippool",
"_type": "_doc",
"_id": "fd1392d5-f188-4e16-a023-15d315bb1ad3",
"_score": 1.0,
"_source": {
"subnets": [
{
"cidr": "169.254.0.128/25",
"gateway_ip": "169.254.0.129",
"dns_nameservers": [],
"allocation_ranges": [
{
"start": "169.254.0.130",
"end": "169.254.0.254"
}
]
}
],
"check_overlap_with_existing_pools": false,
"resource_type": "IpPool",
"id": "fd1392d5-f188-4e16-a023-15d315bb1ad3",
"display_name": "01-test4-1",
"_index": "nsx_manager_ipblocksubnet",
"_type": "_doc",
"_id": "b54b6c13-5300-49df-881b-f6577fab694f",
"_score": 1.0,
"_source": {
"size": 128,
"cidr": "169.254.0.128/25",
"allocation_ranges": [
{
"start": "169.254.0.128",
"end": "169.254.0.255"
}
],
"block_id": "67f06508-61a8-4e75-b9a2-0b93a1337e8e",
"start_ip": "",
"resource_type": "IpBlockSubnet",
"id": "b54b6c13-5300-49df-881b-f6577fab694f",
"display_name": "b54b6c13-5300-49df-881b-f6577fab694f",
Как мы видим, связь между индексами nsx_manager_ippool и nsx_manager_ipblocksubnet существует только через CIDR, а общая связь этих частей объекта с policy осуществляется через manager._id=policy.unique_id.
Продолжение следует
После пары дней кропотливой работы нам все же удалось свести данные из всех таблиц и получить готовый csv-файл с нужными переменными.
Далее оставалось только сделать простенький скрипт для залива данных обратно:
while IFS=';' read ipp ibs ip_block ip_block_mgr ipp_mgr_id CIDR intent_uuid l_key r_key intent_version meta start_ip gw end_ip one;
do
echo "'{ \"stringId\": \"/infra/realized-state/enforcement-points/default/ip-blocks/$ip_block/ip-pools/$ipp/block-subnets/$ibs\"}'" > /tmp/newKey
echo "'{ \"abstractPolicyRealizedResource\": { \"abstractPolicyResource\": { \"managedResource\": { \"displayName\": \"$ibs\" }, \"markedForDelete\": false, \"deleteWithParent\": false, \"locked\": false, \"isOnboarded\": false, \"internalKey\": { \"left\": \"$l_key\", \"right\": \"$r_key\" }, \"gmKey\": { \"left\": \"$l_key\", \"right\": \"$r_key\" }, \"parentPath\": \"infra/realized-state/enforcement-points/default/ip-blocks/$ip_block/ip-pools/$ipp/\" }, \"realizationObjectId\": \"$intent_uuid\", \"realizationState\": \"REALIZATION_STATE_REALIZED\", \"runtimeStatus\": \"RUNTIME_STATUS_UNINITIALIZED\", \"intentVersion\": \"$intent_version\", \"realizedVersionOnEnforcement\": \"0\", \"realizationApi\": \"/api/v1/pools/ip-subnets/$intent_uuid\" }, \"entityType\": \"REALIZED_ENTITY_TYPE_RZ_IP_ADDRESS_POOL_BLOCK_SUBNET\", \"extendedAttributes\": { \"start_ip\": { \"dataType\": \"DATA_TYPE_TYPE_STRING\", \"value\": [ \"$start_ip\" ] }, \"gateway_ip\": { \"dataType\": \"DATA_TYPE_TYPE_STRING\", \"value\": [ \"$gw\" ] }, \"cidr\": { \"dataType\": \"DATA_TYPE_TYPE_STRING\", \"value\": [ \"$CIDR\" ] }, \"end_ip\": { \"dataType\": \"DATA_TYPE_TYPE_STRING\", \"value\": [ \"$end_ip\" ] } }, \"intentPath\": [ \"/infra/ip-pools/$ipp/ip-subnets/$ibs\" ] }'" > /tmp/newVal
echo "'$meta'" > /tmp/newMeta
echo "Adding key for $ipp" | tee -a /tmp/restore_log
cmd="java -cp /usr/share/corfu/lib/corfudb-tools-4.1.20230926154243.2130.1-shaded.jar -Djava.io.tmpdir=/image/corfu-tools/temp org.corfudb.browser.CorfuStoreBrowserEditorMain --host=10.11.12.13 --port=9000 --tlsEnabled=true --keystore=/config/cluster-manager/cluster-manager/private/keystore.jks --ks_password=/config/cluster-manager/cluster-manager/private/keystore.password --truststore=/config/cluster-manager/cluster-manager/public/truststore.jks --truststore_password=/config/cluster-manager/cluster-manager/public/truststore.password --namespace=nsx --tablename=GenericPolicyRealizedResource --operation=addRecord --keyToAdd=`cat /tmp/newKey` --valueToAdd=`cat /tmp/newVal` --metadataToAdd=`cat /tmp/newMeta`"
eval $cmd | tail -8 | tee -a /tmp/restore_log
done <./payload.csv
После пары часов отработки скрипта (да-да, corfudb-tools умеет заливать записи только по одной и тратит на это достаточно большое количество времени) все пулы перешли в ожидаемо «зеленое» состояние и проблема была решена.
green_bag94
А, Вы, уважаемый автор, не подумали о том, что картинка на заставке некорректная и может вызывать противоречивые чувства?