Хорошее хранилище давно перестало быть просто дисками, прицепленными к одному серверу. Данные растут быстрее, чем растёт один корпус, а железо, каким бы дорогим оно ни было, умеет ломаться в самый неподходящий момент. Ceph предлагает другой путь: несколько серверов с обычными дисками объединяются в кластер, каждая запись раскладывается по разным машинам, и падение одного узла не отрывает пользователей от файлов. В этой статье пройдём путь от идеи до работающего кластера: разберём, как устроен RADOS, развернём три узла с помощью cephadm, настроим репликацию с фактором три и посмотрим, что происходит в момент отказа целого сервера.

Одиночный сервер с RAID упирается в потолок и не переживает собственный отказ

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

Второй потолок - масштабирование. Когда свободное место на массиве заканчивается, администратор либо добавляет полки дисков в тот же корпус, либо строит рядом ещё один островок с собственной копией настроек. Через пару лет в компании живёт зоопарк из RAID-томов, и каждый новый терабайт обходится дороже предыдущего.

Распределённая файловая система снимает оба ограничения. Узлов много, их объединяет сеть, а согласованность поддерживает сама система: она знает, на каких машинах лежат копии данных, и следит за их состоянием. Добавили четвёртый сервер - пул расширился без миграций и остановки сервисов. Вышел из строя узел - оставшиеся машины продолжают отвечать на запросы, потому что каждая запись есть минимум на двух других. Расплата за такую автономность одна: сеть становится полноправным ресурсом, и экономить на ней так же вредно, как на дисках.

RADOS и его службы, которые держат кластер вместе

Под маркой Ceph скрывается надстройка над ядром с длинным именем RADOS, Reliable Autonomic Distributed Object Store. Именно RADOS хранит данные в виде объектов, следит за их копиями и лечит кластер, а файловые и блочные интерфейсы - лишь удобные фасады над ним.

Внутри работают три ключевые службы. ceph-mon, монитор, хранит карту кластера: список узлов, OSD и их состояние. Мониторы работают кворумом, поэтому их ставят нечётным числом: три переживают отказ одного узла, пять - двух. ceph-osd занимает диск и занимается самой трудоёмкой работой: хранит объекты, отвечает соседям за репликацию, докладывает мониторам о своём здоровье. ceph-mgr собирает статистику, рисует веб-интерфейс и запускает модули автоматизации.

Данные на дисках лежат в формате BlueStore: объекты раскладываются по большому хранилищу, а метаданные и журнал WAL выносятся отдельно, при желании - на быстрый NVMe. Такой разбор заметно снижает латентность мелких записей на нагруженных кластерах.

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

Развёртывание кластера из трёх узлов с помощью cephadm

Возьмём типовой сценарий: три сервера ceph01, ceph02 и ceph03 с адресами 10.0.0.11, 10.0.0.12 и 10.0.0.13. На каждом системный SSD и три HDD по 4 ТБ, между серверами гигабитная, а лучше десятигигабитная сеть. Для брандмауэра пригодятся порты: мониторы слушают 3300 и 6789, панель управления - 8443. Вся установка укладывается в четыре действия:

  1. установить cephadm на первом узле и выполнить bootstrap;
  2. раздать кластерный SSH-ключ остальным серверам и добавить их командой ceph orch host add;
  3. объявить три монитора через ceph orch apply mon;
  4. отдать кластеру свободные диски командой ceph orch apply osd.

Первым делом на ceph01, под root, ставим сам cephadm. Инструменту нужен Python 3.6 или новее и контейнерный рантайм, который он подхватит или поставит сам:

CEPH_RELEASE=18.2.0
curl -sSL -o cephadm https://download.ceph.com/rpm-${CEPH_RELEASE}/el9/noarch/cephadm
chmod +x cephadm
./cephadm add-repo --release reef
./cephadm install

Теперь стартуем кластер одной командой на первом узле:

cephadm bootstrap --mon-ip 10.0.0.11 --cluster-network 10.0.2.0/24

Bootstrap поднимет первые службы ceph-mon и ceph-mgr, сгенерирует кластерный SSH-ключ, запишет конфигурацию в /etc/ceph/ceph.conf и выведет в конце адрес панели управления на порту 8443 вместе с учётными данными для входа. Подсеть 10.0.2.0/24 выделена под служебный трафик репликации, и разделить сети лучше сразу, а не после наполнения данными.

Для удобной работы ставим консольный клиент:

cephadm install ceph-common

Дальше ключи и хосты. Кластерный ключ надо скопировать на каждый новый узел, иначе cephadm не сможет им управлять:

ssh-copy-id -f -i /etc/ceph/ceph.pub root@ceph02
ssh-copy-id -f -i /etc/ceph/ceph.pub root@ceph03
ceph orch host add ceph02 10.0.0.12
ceph orch host add ceph03 10.0.0.13

Кворум мониторов:

ceph orch apply mon --placement="ceph01,ceph02,ceph03"

И наконец диски. Список того, что кластер видит на узлах, выдаёт ceph orch device ls, а следующая команда отдаёт под данные все незанятые устройства без таблиц разделов:

ceph orch apply osd --all-available-devices

Через несколько минут ceph status показывает HEALTH_OK: девять OSD на трёх хостах, три монитора и по менеджеру рядом с каждым из них. На этом кластер готов к работе.

Репликация, группы размещения и цена трёх копий

Пул в Ceph - это логическое разделение данных с собственными настройками надёжности. Создаётся одной командой:

ceph osd pool create data01
ceph osd pool set data01 size 3
ceph osd pool set data01 min_size 2

Параметр size задаёт число копий каждого объекта, по умолчанию их три. min_size запрещает кластеру отвечать на запись, если живых копий меньше двух: лучше показать недоступность, чем молча потерять данные. Эта страховка выручает в опасной ситуации, когда узлы потеряли связь друг с другом, но продолжают жить своей жизнью.

Список пулов с подробностями показывает всю математику:

pool 3 'data01' replicated size 3 min_size 2 crush_rule 0 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 21 flags hashpspool stripe_width 0

Объекты не раскладываются по дискам по одному: они группируются в placement groups, сокращённо PG, и копируются уже группами. Автоматический масштабатор балансирует их число из расчёта примерно 150 PG на OSD, поэтому вмешиваться руками приходится редко.

Тройная репликация стоит дорого: из 36 ТБ сырья на трёх серверах пользователю достанется 12 ТБ. Есть альтернатива - erasure coding. Профиль по умолчанию k=4 m=2 раскладывает объект на шесть частей, из которых четыре несут данные и две - чётность, а издержки падают до полутора раз вместо трёх. Профиль и пул под архив создаются так:

ceph erasure-code-profile set ec42 k=4 m=2
ceph osd pool create archive 64 64 erasure ec42

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

Отказ узла в реальном времени и самоисцеление кластера

Картинка, ради которой всё затевалось, выглядит так. Выдёргиваем питание у ceph03. Через несколько секунд кластер замечает пропажу, группы размещения переходят в состояние degraded, а записи продолжают уходить на оставшиеся два узла, ведь min_size равно двум. Приблизительно через пятнадцать минут монитор пометит OSD узла как out, и начнётся восстановление: недостающие копии перельются на свободное место оставшихся серверов.

Восстановление съедает сеть и место, поэтому у него есть внутренние тормоза. Порог nearfull на уровне 0.85 просит замедлиться, 0.90 останавливает перебалансировку backfill, а 0.95 объявляет пул полным и запрещает запись. Держать кластер заполненным больше чем на восемьдесят процентов - плохая привычка: восстановлению нужно свободное место, и чем его меньше, тем тоньше лёд.

Плановые работы проходят спокойнее, если предупредить кластер:

ceph osd set noout

После перезагрузки узла флаг снимается командой ceph osd unset noout, и данные никуда не переезжают зря.

Тихую порчу данных кластер вычищает сам. Каждые сутки OSD прогоняют лёгкую сверку метаданных, а раз в неделю, каждые 604800 секунд, идёт глубокая проверка с чтением всего содержимого. Нашлись расхождения - потерянная копия перезаписывается с живых соседей, и пользователь этого даже не заметит. Запуск вручную тоже возможен: ceph osd scrub osd.5 прогоняет быструю сверку, ceph osd deep-scrub osd.5 - полную.

Загрузку кластера глазами администратора показывает ceph df: колонка RAW USED считает данные со всеми копиями, а в разрезе пулов видно, сколько сырых терабайт съедает каждый из них. Прогноз свободного места стоит делать заранее: восстановление после отказа узла потребует места на оставшихся серверах, и кластер, заполненный под завязку, просто откажется балансировать.

Три интерфейса, через которые приложения получают данные

Блочный доступ RBD - главный путь для виртуальных машин и баз данных. Команды короткие:

ceph osd pool create vmdisks
rbd create --size 10240 vmdisks/vm-web-01
rbd map vmdisks/vm-web-01
rbd showmapped

Устройство /dev/rbd0, которое выдаст команда map, форматируется и монтируется как обычный диск, а в QEMU и libvirt такой том подключается нативно. Снимки и клоны RBD дают дешёвые копии виртуальных машин на момент среза.

Файловый доступ даёт CephFS. Том создаётся одной строкой на узле с кластерным доступом:

ceph fs volume create fs01

Монтирование на клиенте выглядит привычно:

mount -t ceph 10.0.0.11:/ /mnt/cephfs -o name=admin

Клиенту понадобится ключ из /etc/ceph/ceph.client.admin.keyring, скопированный с узла кластера. Дальше это обычный POSIX-каталог: rsync, шлюзы NFS, архивы - всё работает без переделок.

Объектное хранилище с S3-совместимым API разворачивается службой rgw:

ceph orch apply rgw gw01 --placement=ceph01

Слушатель встанет на порт 8080, и любой клиент, умеющий S3, от rclone до самописных загрузчиков, сможет складывать данные в бакеты.

Практические настройки, которые потом сэкономят нервы

Первое правило - не экономить на сети. Репликация и восстановление множат трафик втрое, и десятигигабитный интерфейс на узлах окупается с первого же крупного сбоя. Служебный трафик репликации стоит развести с клиентским по отдельным подсетям, как в примере с --cluster-network выше.

Второе - время. Разница часов между узлами больше 50 миллисекунд приводит к предупреждениям мониторов, поэтому chrony на всех серверах обязателен ещё до bootstrap.

Третье - память. По умолчанию cephadm включает автоподбор памяти для OSD с лимитом в семьдесят процентов от объёма узла, а каждая служба ceph-osd стремится занять до 4 ГиБ под кэш BlueStore. На узле с тремя дисками и 16 ГБ ОЗУ закладывайте запас под операционную систему и не верьте минимальным требованиям из рекламных буклетов.

Четвёртое - скорость метаданных. Если бюджет позволяет, журнал WAL и базу BlueStore выносят на SSD, оставив HDD под данные: латентность мелких записей падает в разы, и это самая дешёвая модернизация кластера.

Пятое правило старше Ceph: резервные копии никто не отменял. Репликация честно копирует каждый объект трижды, включая случайно удалённые. Хранилище с тройной защитой и архивная копия вне кластера решают разные задачи, и менять одно на другое не стоит.

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