Pull to refresh
ЮMoney
Всё о разработке сервисов онлайн-платежей

Тонкая настройка OpenStack под высокой нагрузкой

Reading time6 min
Views9.3K

Привет, меня зовут Максим, я системный администратор. Три года назад мы с коллегами начали переводить продукты на микросервисы, а в качестве платформы решили использовать Openstack, и столкнулись с некоторым количеством неочевидных граблей при автоматизации тестовых схем. Этот пост про нюансы настройки OpenStack, которые с трудом находятся на пятой странице выдачи поисковика (а лучше, чтобы легко находились на первой).



Нагрузка на ядра: было — стало


NAT


В некоторых инстансах мы используем dualstack. Это когда виртуальная машина получает сразу два адреса — IPv4 и IPv6. Сначала мы сделали так, что «плавающий» v4-адрес назначался во внутренней сети через NAT, а v6 машина получала через BGP, но с этим есть пара проблем.


NAT — дополнительный узел в сети, где и без него нужно следить за нормальным распределением нагрузки. Появление NAT в сети почти всегда ведёт к сложностям с отладкой — на хосте один IP, в базе другой, и отследить запрос становится сложно. Начинаются массовые поиски, а разгадка всё равно будет внутри OpenStack.


Ещё NAT не позволяет сделать нормальную сегментацию доступов между проектами. У всех проектов свои подсети, плавающие IP постоянно мигрируют, и с NAT управлять этим становится решительно невозможно. В некоторых инсталляциях говорят об использовании NAT 1 в 1 (внутренний адрес не отличается от внешнего), но это всё равно оставляет лишние звенья в цепочке взаимодействия с внешними сервисами. Мы пришли к мнению, что для нас лучший вариант — это BGP сеть.


Чем проще, тем лучше


Мы пробовали разные средства автоматизации, но остановились на Ansible. Это хороший инструмент, но его стандартной функциональности (даже с учетом дополнительных модулей) может не хватать в некоторых сложных ситуациях.


Например, через Ansible-модуль нельзя указать, из какой подсети нужно выделять адреса. То есть можно указать сеть, но вот конкретный пул адресов задать не получится. Здесь поможет shell-команда, которая создаёт плавающий IP:


openstack floating ip create -c floating_ip_address -f value -project \ {{ project name }} —subnet private-v4 CLOUD_NET

Другой пример недостающей функциональности: из-за dualstack мы не можем штатно создать роутер с двумя портами для v4 и v6. Здесь пригодится такой bash-скрипт:


#! /bin/bash

# $1 - router_name
# $2 - router_project
# $3 - router_network
# echo "${@:4}" - private subnet lists

FIXED4_SUBNET="subnet-bgp-nexthop-v4"
FIXED6_SUBNET="subnet-bgp-nexthop-v6"

openstack --os-project-name $2 router show $1

if [ $? -eq 0 ]
then
   echo "router is exist"
   exit 0
fi

openstack --os-project-name $2 router create --project $2 $1

for subnet in "${@:4}"; do
openstack --os-project-name $2 router add subnet $1 $subnet
done 

openstack --os-project-name $2 router set $1 --external-gateway $3 --fixed-ip subnet=$FIXED4_SUBNET --fixed-ip subnet=$FIXED6_SUBNET

Скрипт создает роутер, добавляет в него подсети v4 и v6 и назначает внешний шлюз.


Retry


В любой непонятной ситуации — перезапускай. Попробовать заново, создать instance, роутер или DNS-запись, потому что не всегда понимаешь быстро, в чем у тебя проблема. Retry может отсрочить деградацию сервиса, а в это время можно спокойно и без нервов решить проблему.


Все советы выше, на самом деле, прекрасно работают с Terraform, Puppet и чем угодно ещё.


Всему своё место


Любой большой сервис (OpenStack — не исключение) объединяет много сервисов поменьше, которые могут мешать работе друг друга. Вот пример.


Сетевой агент Neutron-L2-agent отвечает за сетевую связность в OpenStack. Если все остальные агенты находятся частично на контроллерах, то L2, в силу специфики, присутствует вообще везде.



Так выглядела наша инфраструктура в самом начале, пока число схем не перевалило за 50


В этот момент мы поняли, что из-за такого расположения агентов контроллеры не справляются с нагрузкой, и перенесли агентов на compute-ноды. Они мощнее контроллеров, и к тому же контроллеру не обязательно заниматься обработкой всего — он должен выдать задание исполняющему узлу, а узел его выполнит.



Перенесли агентов на compute-ноды


Однако и этого оказалось недостаточно, потому что такое расположение плохо сказывалось на производительности виртуальных машин. При плотности в 14 виртуальных ядер на одно физическое, если один сетевой агент начинал загружать поток, это могло задеть сразу несколько виртуальных машин.



Третья итерация. Появились выделенные ноды


Мы подумали и вынесли агенты на отдельные сетевые узлы. Теперь на compute-нодах остались лишь сервисы для виртуальных машин, все агенты работают на сетевых узлах, а на контроллерах остаются только bgp-агенты, которые занимаются v6-сетью (так как один bgp-агент может обслуживать только один тип сети). L2 остался везде, потому что без него, как мы писали выше, в сети не будет связности.



График нагрузки compute-ноды до того, как всё перемешали. Было около 60%, но нагрузка упала несущественно



Нагрузка на softirq, до того как с compute-нод убрали сетевые агенты. Остались нагружены 3 ядра. На тот момент мы посчитали, что это нормально


Code as Documentation


Иногда бывает так, что код и есть документация, особенно в таких больших сервисах, как OpenStack. С релизным циклом в полгода разработчики забывают либо просто не успевают документировать какие-то вещи, и получается так, как в примере ниже.


Про таймауты


Однажды мы увидели, что обращения Neutron к Open vSwitch не укладываются в пять секунд и падают по таймауту.


127.0.0.1:29696: no response to inactivity probe after 10 seconds,
disconnecting

neutron.agent.ovsdb.native.commands TimeoutException:
Commands [DbSetCommand(table=Port, col_values=((’tag’, 11),),
record=qtoq69a81c6-e2)] exceeded timeout 5 seconds

Конечно, мы предположили, что где-то в настройках это исправляется. Посмотрели в конфиги, документацию и deb-пакет, но сначала ничего не нашли. В итоге описание нужной настройки нашлось на пятой странице поисковой выдачи — посмотрели в код ещё раз и нашли нужное место. Настройка такая:


ovs_vsctl_timeout = 30

Мы выставили её на 30 секунд (было 5), и всё стало работать чуть лучше.


Вот еще неочевидное — при перезагрузке сетевых компонент некоторые настройки Open vSwitch могут сбрасываться. Так, например, происходит с ovs-vsctl inactivity_probe. Это тоже timeout, но он влияет на обращения самого ovs-vsctl к своей базе данных. Мы добавили её в systemd init, что позволило при старте запускать все свичи с нужными нам параметрами.


ovs-vsctl set Controller "br-int" inactivity_probe=30000

Про настройки сетевого стека


Также пришлось немного отойти от общепринятых настроек в сетевом стеке, которые мы используем на других наших серверах.


Вот настройка того, сколько времени нужно хранить ARP-записи в таблице:


net.ipv4.neigh.default.base_reachable_time = 60
net.ipv4.neigh.default.gc_stale_time=60

Значение по умолчанию — 1 день. Вообще, одна схема может жить пару недель, но за день схемы могут пересоздаваться 4-6 раз, при этом постоянно меняется соответствие MAC-адреса и IP-адреса. Чтобы мусор не копился, мы выставили время в одну минуту.


net.ipv4.conf.default.arp_notify = 1
net.nf_conntrack_max = 1000000 (default 262144)
net.netfilter.nf_conntrack_max = 1000000 (default 262144)

Вдобавок мы сделали принудительную отправку ARP-нотификаций при поднятии сетевого интерфейса. Также увеличили conntrack-таблицу, потому что при использовании NAT и плавающих ip нам не хватало значения по умолчанию. Увеличили до миллиона (при дефолтном в 262 144), всё стало ещё лучше.


Правим размер MAC-таблицы самого Open vSwitch:


ovs-vsctl set bridge bt-int other-config:mac-table-size=50000 (default 2048)


После всех настроек 40% нагрузки превратились практически в ноль


rx-flow-hash


Чтобы распределить обработку udp-трафика по всем очередям и потокам процессора, мы включили rx-flow-hash. На сетевых картах Intel, а именно в драйвере i40e, данная опция выключена по-умолчанию. У нас в инфраструктуре есть гипервизоры с 72 ядрами, и если занято только одно, то это не очень оптимально.


Делается это так:


ethtool -N eno50 rx-flow-hash udp4 sdfn


Важный вывод: настроить можно вообще всё. Конфигурация по умолчанию сгодится до какого-то момента (как было и у нас), но проблема с таймаутами привела к необходимости искать. И это нормально.


Правила безопасности


По требованиям службы безопасности, у всех проектов внутри компании есть персональные и глобальные правила — их довольно много. Когда мы перевалили за рубеж в 300 виртуальных машин на один гипервизор, всё это вытекло в 80 тысяч правил для iptables. Для самого iptables это не проблема, но Neutron подгружает эти правила из RabbitMQ в один поток (потому что написан на Python, а там всё грустно с многопоточностью). Происходят зависания Neutron-агента, потеря связи с RabbitMQ и цепная реакция из таймаутов, а после восстановления Neutron заново запрашивает все правила, начинает синхронизацию, и всё начинается сначала.


Вместе с этим увеличилось время создания стендов — с 20-40 минут до, в лучшем случае, часа.


Сначала мы просто обернули всё ретраями (уже на данном этапе мы поняли, что так быстро проблему не решить), а потом начали использовать FWaaS. С его помощью мы вынесли правила безопасности с compute-нод на отдельные сетевые ноды, где расположен сам роутер.



Источник — docs.openstack.org


Таким образом, внутри проекта есть полный доступ ко всему, что нужно, а для внешних соединений применяются правила безопасности. Так мы снизили нагрузку на Neutron и вернулись обратно к 20-30 минутам создания тестовой среды.


Итог


OpenStack — классная штука, в которой можно утилизировать железо, создать внутреннее облако и на его базе создать что-то другое. Вдобавок к этому есть большое комьюнити и активная группа в Telegram, где нам и подсказали по поводу таймаутов.


На этом всё. Задавайте вопросы, мы с коллегами готовы ответить и поделиться опытом.

Tags:
Hubs:
+28
Comments6

Articles

Information

Website
jobs.yoomoney.ru
Registered
Founded
Employees
1,001–5,000 employees
Location
Россия
Representative
yooteam