Год назад я понял, что хочу создать собственный кластер Kubernetes. Я — разработчик программного обеспечения. Обычно я либо использую локальный кластер, состоящий из одного узла, либо применяю удалённый многоузловой кластер для тестирования своих проектов. В случае работы с кластером, состоящим из одного узла, я обычно полагаюсь на Minikube, хотя тут есть и другие решения, вроде проекта Kind, который может эмулировать наличие в кластере нескольких узлов. Поддержка нескольких узлов может появиться и в Minikube.

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



Уже написано множество замечательных руководств, посвящённых созданию многоузловых кластеров Kubernetes с использованием одноплатных компьютеров. Во многих из таких руководств в роли SBC применяется Raspberry Pi. Я, видя это, решил пойти по пути наименьшего сопротивления и тоже остановил свой выбор на этом компьютере. Платформа Raspberry Pi зарекомендовала себя как недорогое и доступное решение.

Надо отметить, что выбор этой платформы предусматривает некоторые компромиссы. Например, Broadcom и Raspberry Pi Foundation не лицензировали криптографические расширения ARMv8 (это нужно для аппаратного ускорения поддержки AES). Ещё одним неоднозначным решением является использование microSD-карт в виде стандартного носителя информации, с которого загружается операционная система.

Анализируя существующие руководства по созданию кластеров на одноплатных компьютерах, я не нашёл ни одного, в котором описывалось бы решение, удовлетворяющее моим требованиям. Рассказ о созданном мной кластере я начну именно с них.

Требования


  • Кластер должен быть заключён в отдельный корпус, который можно легко перемещать, открывать и обслуживать, работая с отдельными модулями системы;
  • Корпус кластера не должен содержать высоковольтных компонентов для снижения риска поражения электрическим током, которое может произойти при случайном контакте с источником тока. Базовое правило безопасности при обслуживании кластера заключается в том, что во время обслуживания кластер должен быть выключен, основной кабель питания должен быть отсоединён от розетки. Дело в том, что опасность представляют любые источники тока. Хотя надо отметить, что использование низковольтного постоянного тока снижает риск электротравмы.
  • С кластером можно работать как с автономной системой, подключая к нему экран и клавиатуру.
  • Питание каждого рабочего узла можно включать программно, что, в итоге, сделает возможным реализацию автоматического масштабирования кластера.

За этими требованиями стоит одна неочевидная цель, которая заключается в том, что мне нужно было, чтобы с кластером могла бы работать моя четырёхлетняя дочь. Я надеюсь на то, что он станет для неё чем-то вроде учебного пособия, которое поможет ей познакомиться с компьютерами, командными оболочками и терминалами.

Комплектующие


Деталь Источник
Корпус Pico 5 Raspberry PI 5S Starter Kit picocluster.com
Одноплатные компьютеры:2 Raspberry Pi 4B (4GB)3 Raspberry Pi 3B+
5 microSD-карт 32GB class 10/A1  raspberrypi.org
Ethernet-кабели:2 0.25m cat.83 0.15m cat.7, S/FTP 1attack.de
USB-кабели PortaPow 20AWG2 кабеля USB-C3 кабеля micro-USB portablepowersupplies.co.uk
Официальный сенсорный экран Raspberry Pi 7? raspberrypi.org
Блок питания Dehner Elektronik STD-12090 12V/DC 9A 108W dehner.net
Понижающий преобразователь постоянного тока 12V в 5V 15A droking.com
Выключатель Heschen 12V 25A SPST 2-Pin ON/OFF heschen.com
Вентилятор Noctua NF-A4x20 5V PWM noctua.at
Модуль коммутационной платы, (15A, 30V) с 4 выходами, построенный на основе MOSFET PSMN011-30YLC ebay.com
2 адаптера M.2 NVMe в USB 3.0, основанных на JMS58 amazon.com
2 накопителя Samsung SSD 970 EVO Plus M.2 PCIe NVMe 500 Go
2 удлинительных кабеля USB 3.0 (с изогнутым штекером, левый угол) длиной 6?/152mm usbfirewire.com
2 вертикальных USB 3.0-адаптера Delock, Male-Female (угол 270°) delock.com
Гибкий кабель Adafruit для Raspberry Pi 24?/610mm adafruit.com
Расширитель кабеля Adafruit (DSI или CSI) для Raspberry Pi  adafruit.com
Соединительные клеммы Wago серии 221 wago.com
Спиральный кабель Lapp Unitronic 300mm/1200mm 2x0.14mm? lappgroup.com
Соединители Lemo FGG.0B.302.CLAD42 и EGG.0B.302.CLL lemo.com
Соединительный кабель для макетной платы DuPont Female-Female

Корпус производства PicoCluster отличается маленькими размерами, с ним очень легко работать. Из заказанного мной начального набора я использовал лишь корпус, включая фиксаторы и стойки, а также 8-портовый коммутатор Gigabit Ethernet.

Сейчас я понимаю, что лучше было бы, если бы компания PicoCluster продавала бы версию своего набора без каких-либо электрических компонентов. Ещё одной альтернативой подобному набору была разработка собственного корпуса. Это, однако, потребовало бы больше времени. Пришлось бы создать векторные чертежи, нужно было бы воспользоваться услугами по лазерной резке в компании, предлагающей акриловые листы, в идеале — листы с покрытием, рассеивающим электростатический разряд.

В ходе работы я столкнулся с предупреждениями о недостаточном напряжении и выяснил, что проблема была вызвана micro-USB-кабелями, поставлявшимися вместе с корпусом. Кроме того, вместе с корпусом шёл понижающий преобразователь постоянного тока 12V в 5V, 30W, который я заменил на более мощный. Сделал я это по причинам, о которых скажу ниже, в разделе, посвящённом питанию системы.

В то время, когда я начал работу над этим проектом, я не планировал пользоваться платами Raspberry Pi 4, выпущенными в 2019 году. Это объясняет то, что у меня всё ещё имеются три платы Raspberry Pi 3, играющие роль рабочих узлов. Две других таких платы я поменял на Raspberry Pi 4. Они применяются для узлов, нуждающихся в большем количестве ресурсов. Это — главный узел и рабочий узел, который, кроме прочего, используется для создания резервных копий данных.

Вскоре после выхода Raspberry Pi 4 компания PicoCluster выпустила новый корпус, предназначенный специально для таких плат. В его состав входит мощный блок питания, два вентилятора и выключатель электропитания. Правда, этот корпус больше, чем мой, да и вентиляторы, наверняка, окажутся более громкими, чем Noctua NF-A4x20. Скоростью вращения этого вентилятора можно управлять с использованием широтно-импульсной модуляции (ШИМ), учитывая при этом результаты замеров температуры, выполняемых на платах.

Надо отметить, что и SoC Broadcom BCM2837 (Raspberry Pi 3), и SoC BCM2711 (Raspberry Pi 4) имеют аппаратные таймеры, способные генерировать ШИМ-сигналы. В результате очень просто выдавать управляющие входные сигналы, ожидаемые Noctua NF-A4x20 PWM, не перегружая процессор. Кроме того, вентилятор едва слышно при его работе на половине поддерживаемой им скорости (около 2500 оборотов в минуту). Этого, как оказалось, достаточно для поддержания температуры системы ниже 45°C/113°F под нормальной нагрузкой.

Новый корпус включает в себя встроенный блок питания. Он, кроме того, дороже того, который выбрал я. Все эти особенности данного корпуса являются причиной того, что я выбрал бы набор Pico 5S и в том случае, если бы покупал корпус сейчас. В его доведение до ума нужно вложить больше сил, но то, что получилось в итоге, кажется мне лучше, чем то, что получилось бы, выбери я другой корпус. В общем, на него стоит потратить силы и время.

Сборка


?Передняя панель



Передняя панель

В передней панели есть отверстие, дающее доступ к microSD-картам. Это удобно, даже учитывая то, что я планирую организовать загрузку системы с использованием USB mass storage boot. Сделать это я планирую сразу после того, как Raspberry Pi 4 будет полностью поддерживать такой режим загрузки. Отверстие, кроме того, помогает организовать циркуляцию воздуха в корпусе. При этом два SSD-диска расположены прямо перед вентилятором, что помогает поддерживать их оптимальную рабочую температуру.

Индикаторы активности и питания плат чётко видны, что позволяет с одного взгляда оценить состояние кластера. Видны и индикаторы активности Ethernet-коммутатора.


Верхняя панель корпуса без экрана

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

?Левая панель



Левая панель

На левой панели корпуса находятся GPIO-порты плат. Один из кабелей соединяет главный узел с вентилятором — для организации ШИМ-управления скоростью его вращения. Ещё один кабель соединяет главный узел с модулем коммутационной платы, который используется для включения и выключения рабочих узлов. Четыре кабеля соединяют каждый из рабочих узлов с главным узлом. Тут используются GPIO-выводы с высоким активным уровнем при выключении. Это позволяет главному узлу безопасно отключить питание рабочего узла после того, как будет завершена последовательность действий, выполняемая при завершении его работы.


Модуль, используемый для подключения вентилятора к главном узлу

В верхнем левом углу находится маленький модуль, предназначенный для подключения 4-контактного разъёма вентилятора Noctua к главному узлу Raspberry Pi. Соответствующий GPIO-разъём, благодаря аппаратной поддержке генерирования ШИМ-сигналов, настроен на управление скоростью вращения вентилятора. Скорость выбирается на основе анализа температуры плат, сведения о которой регулярно собирают с использованием SSH. Главный узел, кроме того, для целей мониторинга, считывает показатели скорости вращения вентилятора.

?Задняя панель



Задняя панель

Задняя панель корпуса скрывает кабели. То, что всё выглядит именно так, можно считать последствием кустарного подхода к модульной организации моего проекта. Сложно оказалось найти качественные кабели. Особенно — короткие USB 3.0-кабели с изогнутыми штекерами. Кабели — это самая многочисленная группа подвижных элементов в моей системе, они должны соответствовать определённым механическим и электрическим характеристикам. Они оказались основным источником проблем.

У меня была мысль использовать единственный спиральный кабель для того, чтобы заменить им ленточный DSI-кабель, с помощью которого к системе подключается экран. Тогда спиральный кабель и подключал бы экран к кластеру, и подавал бы на него питание. Но мне не удалось найти подходящего DSI-коннектора. От этой затеи я воздержался и от того, что меня не прельщала перспектива пайки 17 проводов 0.14mm?. Я воспользовался расширителем DSI-кабеля и применил 2-проводной спиральный питающий кабель micro-USB. Это дало мне возможность легко перемещать и отключать экран, не открывая корпус.


Неудачная попытка размещения кабелей

Вот неудачная попытка использования системы для организации ввода кабелей в корпус, предназначенной для расширительных кабелей USB 3.0. Несмотря на бесчисленные попытки правильно расположить кабели, я систематически сталкивался с ошибками ввода-вывода при передачах данных. Вероятно, они были вызваны тем, что при укладке кабелей они слишком сильно натягивались, или тем, что это ухудшало надёжность соединения кабелей с разъёмами.

?Правая панель



Правая панель

В правой части корпуса можно видеть 8-портовый Ethernet-коммутатор.

Проектирование системы


?Питание



Корпус с открытой передней панелью

Мне нужно было принять решение о том, какой блок питания использовать. В частности — о том, какую мощность в ваттах он должен поддерживать для того, чтобы обеспечить нужды кластера. Это — что-то вроде вопроса о том, что появилось раньше — курица или яйцо, так как измерения провести нельзя до подвода питания к кластеру.

Для того чтобы получить примерную оценку мощности, я обратился к официальной документации, которая содержит некоторые сведения о питании Raspberry Pi, а также — к спецификациям других компонентов. Это дало мне следующие приблизительные цифры, основанные на средних значениях потребления энергии.


А вот что получилось после учёта максимальных значений потребления энергии из документации.


Это было гораздо выше, чем мощность, которую обеспечивали те блоки питания, которые я мог найти, выдающие 5V, так как «маломощным» компонентам понадобился ток в 10A. Это исключило вариант использования единственного блока питания на 5V. В то же время, весьма распространены блоки питания на 12V, которые могут обеспечить необходимый уровень мощности. В результате я выбрал блок питания Dehner Elektronik STD-12090 12V/DC 9A 108W и соединил его с понижающим преобразователем постоянного тока 12V в 5V 75W.

Тут у меня был ещё один вариант, который заключался в использовании пяти менее мощных преобразователей — по одному на каждую плату Raspberry Pi, но это сильно усложнило бы конструкцию кластера.


Коммутационная плата

Коммутационная плата с 4 выходами, основанная на MOSFET PSMN011-30YLC, установлена в нижней части корпуса. Она используется для включения и отключения рабочих узлов. Она маркирована как 15A, 30V, поэтому она отлично справляется даже с нагрузкой, создаваемой четырьмя Raspberry Pi.

Я измерил среднюю и максимальную мощность, потребляемую кластером. Результаты измерений примерно соответствуют тем приблизительным расчётам, которые я провёл ранее. Разницу между ожидаемыми и реальными значениями можно объяснить особенностями конфигурации системы и особенностями проведения теста. В частности, я отключил на моих Raspberry Pi Wi-Fi и Bluetooth, они, кроме того, работают без вывода изображения на монитор. Это может объяснить то, что в реальности значения получились меньше.

Как оказалось, блок питания на 108W гораздо мощнее, чем нужно для кластера. Однако то, что у меня имеется именно такой блок питания, означает, что я смогу расширить возможности системы. Например — заменить Raspberry Pi 3 на Raspberry Pi 4.

?Хранение данных


Одна из новых приятных возможностей Raspberry Pi 4 заключается в наличии на плате двух USB 3.0-портов, которые подключены к SoC BCM2711 с использованием чрезвычайно быстрого PCIe-соединения. Благодаря этому можно надеяться на достижение очень высоких скоростей передачи данных. Я решил, что использую эти USB 3.0-порты для подключения SSD-дисков с помощью адаптеров M.2 NVMe в USB 3.0. Правда, как оказалось, найти такие адаптеры было очень непросто. Я, по наивности, предположил, что мне подойдут любые адаптеры. В результате я купил первый такой адаптер, не проверив его совместимость с Raspberry Pi 4.

Я, к счастью, наткнулся на это руководство по загрузке Raspberry Pi 4 и затем купил рекомендованный адаптер Shinestar M.2 NVMe в USB 3.0. Он имел практически такой же размер, как и M.2 NVMe 2280 (22 мм в ширину и 80 мм в длину), а это отлично подходит для корпуса Pico 5S. Я использовал адаптер для подключения к Raspberry Pi накопителя Samsung SSD 970 EVO Plus M.2 PCIe NVMe 500 Go.


Адаптер M.2 NVMe в USB 3.0

После того, как я всё установил и настроил, я решил по-быстрому протестировать накопитель и выяснить скорость передачи данных. Для этого я скопировал большой файл с SSD-накопителя на мой ноутбук с использованием scp:

$ scp pi@master:<source> <destination>
100% 1181MB 39.0MB/s 00:30


Результат в 39 Мб/с меня разочаровал. Такие показатели далеки от тех, которые нужны для того, чтобы в полной мере нагрузить коммутатор Gigabit Ethernet. Я начал поиски возможного узкого места системы, и понял, что во время передачи файла одно из процессорных ядер всегда нагружено на 100%. После того, как я узнал о том, что узким местом при передаче данных является процессор, я быстро выяснил, что Raspberry Pi 4 не имеет аппаратной поддержки AES, так как Broadcom и Raspberry Pi Foundation не лицензировали криптографические расширения ARMv8. Интересно то, что процессор является узким местом системы на Raspberry Pi 4, в то время как узкими местами Raspberry Pi 3 были USB 2.0 и сетевые интерфейсы.

Новое испытание с использованием netcat дало, в тех же условиях, гораздо лучшие результаты в 104 Мб/с:

$ nc -l 6000 |dd bs=1m of=<destination> & ssh pi@master "dd bs=1M if=<source> | nc -q 0 $(hostname -I | awk '{print $1}') 6000"
[1] 71300 71301
1181+1 records in
1181+1 records out
1238558304 bytes (1.2 GB, 1.2 GiB) copied, 11.8632 s, 104 MB/s
0+740624 records in
0+740624 records out
1238558304 bytes transferred in 14.212518 secs (87145593 bytes/sec)
[1]  + 71300 done       nc -l 6000 |
       71301 done       dd bs=1m of=<destination>

Разобравшись с первым SSD-накопителем, я подключил второй такой же накопитель к другой плате Raspberry Pi 4. Я собираюсь применять этот накопитель для создания резервной копии первого, используя нечто вроде Restic.

Кроме того, я планирую использовать SSD-накопители для организации загрузки с использованием USB mass storage boot сразу же после того, как Raspberry Pi 4 будет полностью поддерживать этот способ загрузки. Он, в сравнении с загрузкой с microSD, обещает более высокую скорость и более стабильный уровень производительности.

Программное обеспечение


Я программист, поэтому я думал, что разобраться с программной частью кластера мне будет легче, чем с прочими вопросами. Мне тут, определённо, помогли результаты замечательной работы над k3s, выполненной командой Rancher. Я не буду тут входить в особенные подробности настройки кластера Kubernetes на Raspberry Pi с использованием k3s. Тем, кому это интересно, я могу порекомендовать обратиться к данному руководству. Вот основные моменты настройки, на которых мне хотелось бы остановиться.

Для начала на каждом узле включим cgroups:

$ sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory/' /boot/cmdline.txt
$ sudo reboot

Затем установим k3s на главном узле:

$ curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
# Проверка статуса сервиса
$ sudo systemctl status k3s

Теперь получим токен, авторизующий подключение рабочих узлов к главному узлу:

$ sudo cat /var/lib/rancher/k3s/server/node-token

Далее — установим k3s на каждом рабочем узле:

$ curl -sfL https://get.k3s.io | K3S_URL="https://<MASTER_IP>:6443" K3S_TOKEN="<NODE_TOKEN>" sh -
# Проверка статуса сервиса
$ sudo systemctl status k3s-agent

Если вы планируете использовать внутренний реестр образов контейнеров, который устанавливается по умолчанию, вам может понадобиться особым образом его настроить, сделав это для того чтобы позволить containerd загружать из него образы:

$ sudo sh -c 'REGISTRY=$(kubectl get svc -n kube-system registry -o jsonpath={.spec.clusterIP}); cat <<EOT >> /etc/rancher/k3s/registries.yaml
mirrors:
  "$REGISTRY":
    endpoint:
      - "http://$REGISTRY"
EOT'
$ sudo service k3s restart

Планы на будущее


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


Возможно, я ещё об этом напишу.

Кроме того, я планирую продолжить работу над кластером, сделав следующее:

  • Загрузка с SSD-накопителей с использованием USB mass storage boot.
  • Настройка резервного копирования с использованием инструмента наподобие Restic.

Уверен, что я, рассказывая о своём опыте создания домашнего кластера Kubernetes, о многом забыл. Я программист, и привык к достаточно неоднородному разбиению больших задач на более мелкие дела. Аппаратное обеспечение, как показывает опыт, гораздо менее терпимо, чем ПО, относится к работе в стиле проб и ошибок.

В целом, чем больше я узнаю, тем большее восхищение во мне вызывают современные аппаратные и программные технологии. Меня поражает то, как изобретательность человека смогла объединить электрические явления и языки программирования, сделав из них нечто такое, что можно счесть примером того, как сознание управляет материей.

Уважаемые читатели! Пытались ли вы сделать что-то подобное тому, о чём рассказал автор этой статьи?