В предыдущей статье мы поговорили об аппаратной инфраструктуре для селфхостинга. В этой поговорим о программной составляющей. Я помогу сделать выбор между виртуальными машинами, сортами контейнеров и отсутствием виртуальной инфраструктуры, сравню плюсы и минусы, опишу когда они уместны и на чём остановился сам.
Виртуальные машины
Мои фавориты - libvirt + qemu.
Плюсы
- Ограничить потребление ресурсов легко.
Минусы
- Расходы на эмуляцию оборудования.
- Расходы на запуск ядра ОС для каждой машины.
- Потребление памяти. Убедитесь, что virtio-balloon настроен и работает. Ещё одна технология, которая может здорово выручить при использовании однотипных виртуальных машин - KSM (Kernel Same Page Merging): одинаковые фрагменты памяти объединятся, общее потребление сократится.
Теперь вы сами стали хостером, которому нужно накормить толпу хлебами, но каждый просит забронировать за ним всю булку.
Проверьте в BIOS опции аппаратного ускорения виртуализации, без которых производительность сильно хромает:
- Intel (Intel VT-x, EPT, VT-d);
- AMD (AMD-V, RVI, AMD-Vi, SVM).
Когда выбирать виртуальные машины
- Использование чрезмерно устаревшего ПО, зависящего от старой версии ядра ОС.
- Использование системного ПО, которое сильно влияет на операционную систему. Здесь приведу пример: поговаривают, что LXD и ZFS сильно облегчают жизнь. Летом я буду собирать файловый сервер 2.0 для загородного дома, вот тогда и попробую. Пробовать буду в виртуальной машине, не боясь сломать личный ПК, файловый сервер 1.0 или изначально испортить свежеустановленную ОС нового сервера.
- Эксперименты с дистрибутивами, установщиками и десктопными окружениями перед тем, как начать их использовать.
Всё это не совсем про селфхостинг. Кажется, можно отмести практически сразу. В случае аренды VPS вы и так используете виртуальную машину, вложенные виртуальные машины обычно очень непроизводительны, а VPS и без того ограничены в ресурсах.
Docker и Kubernetes (k8s)
Я отнёс оба варианта в один пункт. Перед началом использования советую почитать https://12factor.net/ru/, но не вдохновляться им слепо. В том или ином виде вам всё равно
понадобится:
- Оркестратор - штука, которая запускает нужные сервисы и контролирует
их состояние. Вторым можно пожертвовать. - Ингресс: обратный прокси, который будет отправлять трафик в один из
нужных контейнеров. - Нужно будет собирать и где-то хранить образы. Можно на том же сервере,
где они и запускаются, если он один. Главное не пихать в них секреты и не загружать их в публичные реестры. - Разобраться с хранением секретов.
- Автоматическое создание и изменение DNS-записей, чтобы они вели куда
надо. Может подойдёт Сonsul, может и один раз руками настроить сойдёт. - Разруливать порядок загрузки и зависимости между сервисами (веб-приложению нужна БД, поэтому она должна запуститься вперёд) всё
равно придётся. - Куда-то собирать логи.
Плюсы
- Воспроизводимость. Написав один раз Dockerfile, docker-compose.yml или values.yml и отладив резервное копирование данных сервиса, вы получите очень лёгкое восстановление.
- Экономия места: Общие слои (например, базовый образ Ubuntu) не будут дублироваться между контейнерами. Но если вы используете чужие готовые образы - далеко не факт, что их базовые образы будут одинаковыми. Если упаковываете сами - лучше один раз выбрать предпочтительную основу.
- Отказоустойчивость: в случае с k8s, при правильно организованных health-check оно само будет перезапускаться когда надо, а выход из строя одной железки не скажется на доступности ваших сервисов. При очень сильном желании k8s можно организовать между несколькими VPS у разных провайдеров.
- Масштабируемость: если ваши сервисы доступны
публично и вам нужно справляться с нагрузкой, вы можете настроить
динамическое масштабирование. Но не забывайте, что бесконечного
масштабирования не бывает, всё упрётся в бутылочное горлышко, а самое
частое бутылочное горлышко в селфхостинге - деньги. - Много ПО имеет целевой формат распространения в виде docker-образов. Вы сэкономите кучу времени, вам не потребуется бодаться с конфликтами и подготовкой окружения.
Минусы
- Ручные правки: Если вы привыкли лезть в /etc
и править конфиги на лету — придётся переучиваться. Каждый такая правка
теперь требует пересборки образа. Можно монтировать конфиг-файлы (см.
volumes), но для применения всё равно придётся перезапускать контейнер. - Ресурсы: на правильно приготовленный k8s со
всеми сортами отказоустойчивости и резервирования уйдёт много времени и
денег. У некоторых хостеров есть услуга managed k8s, это может
частично компенсировать время (ценой денег). - Если ПО не адаптировано к докеру, это придётся кому-то сделать. Вам. Вы потратите кучу времени, если у вас нет опыта в использовании docker.
Когда k8s дома нужен?
- Для работы нужна практика и понимание устройства k8s.
- Вы решили масштабироваться за пределы одного сервера, расположив их в
разных местах. Один на даче, другой в квартире, третий у родителей. - В плане на развёртывание более 50 сервисов или вы осознаёте другую причину, по которой k8s необходим.
- Если вы разрабатываете и тестируете распределённое приложение. Но это уже не совсем про селфхостинг, это скорее работа.
В остальных случаях это переусложнение, которое не сделает вас счастливее.
Непосредственно в системе
Плюс - нулевые накладные расходы: Нет изоляции — нет расходов.
Когда имеет смысл
- Когда сервис использует общие данные с другими сервисами, в таком случае - расположите их в одной файловой системе.
- Для демонов вроде SSH, Cron, Samba которые 100% отлажены,
используются миллионами людей, устанавливаются из репозиториев ОС и не
требуют большого числа зависимостей, в отличии от python, nodejs или
ruby приложений. Последние я как раз стараюсь устанавливать в
контейнеры, несмотря на существование вспомогательных средства
виртуализации окружений, вроде nvm, pyenv и так далее.
Мой опыт
Мой файловый сервер (дальше NAS) использует samba для монтирования в проводник и для загрузки резервных копий с Macbook в TimeMachine, sftp + rsync для синхронизации архивов фотографий и музыки, Nginx
для лёгкого доступа на чтение с iOS без установки на него
дополнительного ПО. Эти архивы живут непосредственно в ФС физического
сервера.
Причины две:
- Слой изоляции скорее мешал бы.
- Так исторически сложилось. Samba я установил и настроил ещё до LXC.
Ещё непосредственно в системе живёт NTP-сервер. В LXC завести Chrony не получилось, при попытке запустить привилегированный unconfined контейнер, у которого будут права двигать аппаратные часы часы хост-системы - зависали все LXC. Без этого NTP запускался, но отдавал кривое время, сдвинутое аж на 15 минут вбок. Я установил его в корень NAS, не жалею, всё равно +/- штатное ПО. DHCP-сервер роутера отдаёт адрес NAS в качестве NTP-сервера через DHCP-опцию, теперь единственное устройство в
доме, у которого едет время - это духовой шкаф без Wi-Fi.
Да, неосилятор, мог и разобраться, но перезапускать аппаратный сервер ради отладки NTP на пятый раз сильно надоело. Мой сервер, что хочу то и делаю (маленькая прелесть селфхостинга).
LXC
LXC - это такие легковесные контейнеры: не запускают собственное ядро ОС, не эмулируют аппаратную составляющую, но обладают собственными файловыми системами и могут запускать несколько независимых процессов в одном контексте, штатными средствами ОС. У них есть состояние, которое сохраняется между перезагрузками. По сути это виртуальная машина на минималках, легче qemu, но немного тяжелее docker.
Мой опыт
Я выбрал LXC для размещения сервисов дома. Для контекста - аппаратная составляющая. Это не сверхмощный сервер, а компромисс между размером, энергопотреблением, тишиной и производительностью.
- Платформа: мини-пк ASRock Deskmini 310.
- Процессор: Pentium G5400 (2 x 3.7GHz + гипертрединг).
- RAM: 2x16Gb, 2400MHz.
- Диски: 128gb SSD + RAID1 из 2х2Tb HDD, все с ext4.
Какие цели я преследовал:
- Экономия ресурсов - меньше памяти в сравнении с виртуальными машинами.
- Удобство управления - ведёт себя как виртуальная машина, ничего не
сбрасывается при перезапуске, можно настроить статический IP. У меня был
некоторый опыт использования LXC, так что отчасти это был вопрос
привычки. - Логическая изоляция - поломка окружения ОС одного сервиса не должна
ломать остальные; должно быть легко понятно, какой из контейнеров занял место на диске.
Технические детали
Большая часть моих сервисов - веб-приложения.
- Для удобства адресации я использую API домашнего роутера (Mikrotik
HEX) для регистрации статических DNS-записей. - Я использую TLS, несмотря на то, что они доступны
только дома. У меня есть самопальный "удостоверяющий центр" - ещё один
LXC-контейнер с самоподписными корневым и промежуточным сертификатами. - TLS терминирует Nginx, развёрнутый на самом NAS. Для веб-сервисов я регистрирую два домена. Один для менеджмента (с суффиксом `-ssh`), который резолвится в IP-адрес LXC-контейнера, второй - в адрес самого NAS. Трафик попадает в Nginx и он по SNI разруливает, куда его дальше отправить.
- Конфиг-файлы Nginx, выпуск SSL-сертификатов, создание DNS-записей на роутере - всё это автоматически выполняется скриптом для развёртывания новых контейнеров.
- Тот же скрипт подкладывает мой публичный SSH-ключ внутрь контейнеров, чтобы сразу после установки я мог подключаться к ним по SSH без пароля.
Проблемы и возможные решения
Сейчас число контейнеров достигло 15 и мне не нравятся накладные расходы на дисковое пространство от дублирования одинаковых окружений ОС. Я подумываю о переходе на docker-compose, но пока не разобрался с:
- Сетью: выделением для контейнеров статических IP, маршрутизацией.
- Автозапуском.
- Местом для хранения образов.
- Cronjob внутри контейнера. Как вариант плодить отдельные контейнеры под каждый процесс, пусть и с одним образом.
- Пробросом и хранением SSH-сертификатов.
- Я часто делаю что-то вручную внутри контейнеров, с docker такой подход считается проблемным.
Все эти проблемы решаемы, но пока я не хочу их решать.
Слишком требовательные к дисковому пространству контейнеры переезжают с SSD на HDD. Делается это переносом папки `/var/lib/lxc/container_name/` в директорию, расположенную на HDD. На бывшем месте папки создаём симлинк в новое местоположение. Когда появится сервис, требовательный и к пространству и к скорости - обновлю в своём ПК 2Тб NVMe с PCI-E 3.0 на 4.0, а старый воткну в NAS. Может быть стоит сделать это уже сейчас и тогда предыдущая проблема с дублированием окружений не будет меня беспокоить ещё пять лет.
- Ощущаю, что стоит попробовать k8s. Стандартизирую работу с Nginx, он начнёт называться модным словом ingress.
- Хочу найти трюк, для использования общих слоёв ФС в LXC. Это трюк
называется ZFS, но я уже начал использовать ext4, боюсь сломать
работающую систему, выделяя отдельный размер под ZFS-пул со
snapshot'ами. - Возможно гибридное решение - часть сервисов в LXC, часть в docker-compose будет оптимальным. Я смогу запускать новые сервисы в docker-compose, постепенно переводить старые, которым там будет лучше, ничто не будет подстёгивать и я совершу плавную миграцию. Если сервис не получится запустить в docker-compose, остаётся запасной вариант с LXC.
Выводы и советы
- Не гонитесь за модными инструментами.
- Селфхостинг — это про контроль, а не про сложность.
- Используйте то, что понимаете. Понимание — залог контроля.
- Не хотите — не делайте, SaaS — не порок!