Ужасы нашего городка-2
2012-10-07 22:48![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
В продолжение темы.
Абонентская база неуклонно растёт, место в стойках и мощности упсов не резиновые, а процессор E5507 - младшенький в линейке. Кроме того, top(1) врёт про потребление процессора dummynet'ом, показывая незначительные проценты. В то же время график, построенный по выдаче команды (выдаёт потребление CPU ядерным тредом dummynet в сотых долях секунды, монотоно возрастающий счетчик):
ps -Hxo time,lwp | awk -vT=$(procstat -t 0 | awk '/dummynet/ {print $2}')\
' $2 == T { split($1, a, /[:.]/); print a[1]*6000+a[2]*100+a[3]; }'
показывает, что dummynet грузит CPU до 25% в моих условиях, а вовсе не на единицы процентов. Поэтому стал заменять 4-ядерный E5507 на 6-ядерный X5675 - максимум, что можно воткнуть в тот же сервер, уложившись в лимит тепловыделения в 100 ватт.
Попытка выяснить предел мощности проапгрейженной таким образом системы, посчитанный в количестве одновременно обслуживаемых пользователей, на уровне порядка 2000 штук выявила очередную проблему в ядре, приводящую к парадоксальной ситуации, когда локальная консоль и пользовательские приложения живы, но вся сетевая подсистема умирает (включая разваливание lagg - циска на втором конце линка говорит об исчезновении протокола LACP в линии).
Дело в том, что ядерная часть ipfw использует rwlocks для корректной мультитредовой работы, или, по-русски, модель "читатели-писатели": если какой-то "писатель" (writer) захватил блокировку, то другие "писатели", а также "читатели" (readers) при попытке захватить эту же блокировку, будут ждать; если же блокировку захватил "читатель", то другие "читатели" тоже могут получить блокировку, а "писатели" будут ждать разблокировки всех "читателей".
Небольшое отступление: исполняющийся на собранной в виде NanoBSD системе процесс syslogd пишет логи на локальный "диск" в оперативной памяти плюс шлет их по сети на центральный коллектор логов, который принимает их со всех таких "бездисковых" машин и пишет себе в архив. Посылает логи syslogd стандартным API для отправки UDP-дейтаграмм, функцией sendto(), которая является системным вызовом, приводящим к цепочке вложенных вызовов ядерных функций:sendto > sendit > kern_sendit > sosend_dgram > udp_send > ip_output > pfil_run_hooks > ipfw_check_hook > ipfw_chk > ipfw_lookup_table > rn_match > ...
Как видно из этой цепочки, в какой-то момент отправка UDP-пакета приводит к проверке его правилами ipfw, которые выполняются с захваченным ipfw reader lock.
Очень редко происходит следующее: планировщик прекращает выполнение syslogd и работающего в его контексте ядерного кода на функции rn_match() из-за исчерпания кванта времени и даёт поработать другим процессам/ядерным тредам. Те из них, которые работают с сетью и нуждаются в ipfw writer-lock, оказываются заблокированными до того момента, пока syslogd/его ядерный тред не получат свой квант процессорного времени, чтобы завершить свою работу и отпустить блокировку.
И тут на сцену, весь в белом, выходит драйвер ipmi(4), который выставил себе приоритет реального времени. Это значит, что пока он исполняется на процессорном ядре и добровольно не отдаёт управление другим частям системы (считает, что у него всё ещё есть работа), планировщик не будет отбирать у него процессорное ядро, несмотря ни на какие кванты. Если в системе есть процессы/ядерные треды, жестко привязанные к тому же процессорному ядру, на котором изволит работать ipmi(4), они будут ждать, пока он не отдаст ядро. Выполняющиеся на этом же ядре, но жестко к нему не привязанные процесссы/треды при некоторых условиях могут быть перемещены планировщиком на другие процессорные ядра и продолжить работу там.
В моём случае уже дважды случалось следующее: syslogd прерван в указанном выше месте и что-то случилось в драйвере ipmi(4), который начинает крутить внутри себя вечный цикл. По какой-то причине syslogd, который жестко не привязан ни к какому ядру, тем не менее не получает своего кванта процессора в течение более чем получаса реального времени. В течение этого получаса практически все сетевые процессы замирают, ожидая разблокировки ipfw. Процессы, которые не обращаются к сети, продолжают работать.
Так как в FreeBSD 8.3-STABLE драйвер сетевых карт igb(4) по умолчанию привязывает свои ядерные треды к процессорным ядрам и делает это очень криво, в моих роутерах эти треды перепривязаны по CPU одним из стартовых скриптов: к CPU4 привязана обработка трафика, приходящего через интерфейс igb0, к CPU5 - igb1, к CPU2 - em0, к CPU3 - em1. dummynet "по старой памяти" привязан к CPU0. Все остальные процессы/ядерные треды ни к чему не привязаны и могут перемещаться планировщиком по свободным ядрам, но, по факту, уже и этого оказалось достаточно, чтобы всё стало колом и требовалась аварийная перезагрузка системы для продолжения работы.
Сам драйвер ipmi(4) используется для общения с платой IPMI внутри железки - например, снимать данные с датчиков. Пришлось в качестве временного выхода выгрузить драйвер из роутеров, а скрипты, снимающие данные с датчиков при помощи ipmitool переделать на работу с платой через IP, а не через локальный драйвер.
Больше технических подробностей в PR: kern/172166.