CEPH

Для тех, кому не хватило денег на интерпрайз, придумали костыли в виде ПОКХД, но и здесь всё не так просто, поскольку костыли эти поставляются не в готовом виде, а вполне себе в стиле ИКЕА - вот тебе ведро болтов с левой резьбой и хитрым шлицем, пачка гаек с 3 и 5 гранями и инструкция в 8 томах. Но умные люди нашли способ собирать костыли быстрее - они собрали костыли для костылей и имя им - ceph-deploy, но обо всё по порядку.

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

Сам же CEPH можно использовать как:

Структура

Сам по себе CEPH состоит из нескольких, вполне себе автономных, кусков, как то

  • Mon - управляющая часть CEPH-а, входная точка для клиентов и прочий менеджмент для кворума
  • Osd - storage daemon, по простому - хранилка, обычно это либо физический диск в сервере, либо логическое место (тот же LUN, поданный с СХД, но использовать СХД, чтобы подавать его в CEPH мне кажется странным решением)
  • Mds - управлялка метаданными (используется для CephFS)
  • Mgr - управлялка, которая может выдавать метрики для систем мониторинга (в limunous и дальше без неё получить HEALTH_OK чуть болле, чем невозможно)

да и, кажется, всё. Остальное уже идёт поверх данного набора. Соответственно как размазать эти роли по серверам - задача того, кто разворачивает систему. Можно даже сделать всё на одном сервере, но об отказоустойчивости можно будет забыть. За сим обычно разворачивают так: в сете из, минимум, 4 серверов - 3 mon, у каждого сервера свои osd, mds по вкусу. Дополнительно, например, можно ещё и журналы osd вынести на отдельные диски, но это уже как приятные излишества.1)

Разворачивание

Интересный факт - в репозиториях CentOS, который якобы бесплатный RHEL, есть даже отдельная ветка с ceph, однако ceph-deploy, который в ней представлен, не умеет в systemd, хотя поделку Лёни, вроде бы, затащили давно. Ceph-deploy из апстрима же вполне себе всё умеет, поэтому имеет смысл склонить более свежие пакеты.

Ответом же на этот интересный факт являются два других факта. Первый - в RHEL до сих пор hammer, ceph-deploy выпускается по принципу rooling release.

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

rsync://download.ceph.com/ceph/rpm-jewel/el7/noarch/
rsync://download.ceph.com/ceph/rpm-jewel/el7/x86_64/

Подготовка системы

Как и в большинстве случаев при работе с CentOS производим обычную подготовку:

# echo 'SELINUX=disabled' > /etc/sysconfig/selinux
# systemctl disable firewalld NetworkManager
# yum remove firewalld NetworkManager

После чего настриваем сети, подаём vlan'ы и проверяем, что всё хорошо с сетью. Можно поиграться в bonding/teaming и прочие попытки засунуть слона в холодильник, но об этом будет в другой статье. Сейчас нас интересует полностью рабочий L3. Лично я подглядел как сделано у коллег, поэтому использую два изолированных VLAN'a - один для связи с OpenStack, второй для технологических целей самого ceph'a. (Ну и третий, вестимо, менеджмент, но его можно и нативно подать) Данная конфигурация служит для того, чтобы в случае чего можно было пошейпить тот или иной канал.

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

Добавляем пользователя, под которым будем работать с кластером

# mkdir /home/ceph
# useradd ceph -s /bin/bash -d /home/ceph || usermod ceph -s /bin/bash -d /home/ceph
# chown ceph. /home/ceph
# passwd ceph

Проделываем эту операцию на всех хостах, которые будут входить в кластер, после чего выбираем хост, с которого будем деплоиться и генерируем на нём ssh ключ для ceph'a:

$ ssh-keygen

Раскатываем файл /var/lib/ceph/.ssh/id_rsa.pub по всем серверам (включая тот, с которого разворачиваемся) в файл /var/lib/ceph/.ssh/authorized_keys (если мы не пользуем SCM можно использовать ssh-copy-id), после чего проверяем, что всё получилось и выдаём пользователю ceph на всех нодах следующие права на sudo:

/etc/sudoers.d/ceph
Defaults:ceph !requiretty
ceph  ALL=(ALL) NOPASSWD:ALL

После создания файла требуется выставить на него права 0400.

Так же официальная документация советует использовать файл /etc/hosts в обход использования глобальной системы доменных имён, поскольку в случае смерти последней можно не хило так выстрелить себе в ногу с переходом кластера в RO. Так же данное решение позволяет сократить задержки на ресолвинг адресов. Поэтому на все ноды заносим на всякий случай всех соседей:

/etc/hosts
192.168.1.10 mon1 mon1.sds2.owlhost.in
192.168.1.11 osd1 osd1.sds2.owlhost.in
192.168.1.12 osd2 osd2.sds2.owlhost.in

Важный момент - в /etc/hosts мы вносим адреса из публичной сети.

Если всё получилось и все наши соседи доступны, то переходим непосредственно к деплою.

Создание кластера

А вот и те саме костыли для костылей, о которых я говорил в начале. Устанавливаются просто и незамысловато:

# yum install ceph-deploy

Не забываем, что стоковые репозитории содержат не совместимую версию ceph-deploy с CentOS 7, поэтому заранее правильно настройте свои репозитории.2)

В нашем случае мы будем разворачивать кластер из 3 нод с фактором репликации 2 3)4) Соответственно произведя нехитрые математические рассчёты из трёх osd, общим объёмом 300 Гб мы получим 150 5) Гб полезного пространства. Стоит отметить, что CEPH при заполнении OSD на 85% останавливает репликацию новых данных и переводит кластер в HEALTH_WARN, а когда заполнение достигает 95% вся запись на OSD останавливается и кластер переходит в HEALTH_ERR, поэтому максимальное безопасное количество данных, которые можно хранить в кластере CEPH не должно по возможности первышать 75%. При достижении данной отметки желательно начать расширять кластер. Для пущей уверенности в отказоустойчивости системы сделаем ещё и три монитора, чтобы было кому достигать кворума, но вполне можно обойтись и одним.

Итак, action! Начинаем с того, что создаём себе новую директорию для разворачивания кластера. Она может понадобиться, например, если захочется ввести ещё несколько нод хранения или сделать иные не менее интересные, но и не менее затратные операции:

$ mkdir cluster
$ cd cluster

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

$ ceph-deploy purge {node} [{node-1}[, {node-2}[, ...]]]
$ ceph-deploy purgedata {node} [{node-1}[, {node-2}[, ...]]]
$ ceph-deploy forgetkeys
$ rm ceph.*

И уже теперь создаём новый кластер с чистого листа

$ ceph-deploy new mon1 osd1 osd2

На выходе мы получим первичный ceph.conf, куда я сразу же советую добавить нужные настроечки, как то

ceph.conf
...
public_network = 192.168.0.0/23
private_network = 192.168.10.0/24
osd_pool_default_size = 2 # по желанию, но вообще 3 лучше
...

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

Так же требуется проверить правильность адресов mon нод (public сеть), а так же mon_initial_members (адреса тоже должны быть из public сети)

И, наконец, можно переходить к установке пакетов на сервера. Так как у нас уже все репозитории подключены и нам никчему мусор в системе - мы едем с аргументом –no-adjust-repos. В противном случае ceph-deploy затянет пакеты centos-epel-repo, centos-storage-ceph-repo и прочие, а так же может испортить уже существующие настройки репозиториев:

# ceph-deploy install --no-adjust-repos mon1 osd1 osd2

После того, как всё ПО приехало, инициализируем ранее объявленные мониторы:

# ceph-deploy mon create-initial

Для удобства управления кластером раскатываем админские ключи и прочие конфиги по мониторным нодам:

# ceph-deploy admin mon1 osd1 osd2  

После деплоя требуется обязательно на всех нодах проверить, что /etc/ceph/ceph.client.admin.keyring и /etc/ceph/ceph.conf имеет права доступа 644.

Если у нас релиз luminous+, то потребуется ещё настроить управляющий демон

# ceph-deploy mgr create mon1

Добавление OSD

Сами по себе osd могут представлять из себя как raw device (/dev/sda, /dev/sdb, /dev/md0, /dev/dm-3), так и уже смонтированные ФС (например люди любят брать xfs). В нашей инсталляции мы будем использовать и xfs, и raw device под OSD. Поэтому размечаем на всех серверах поданные под данные диски, форматируем и добавляем их в fstab.

mon1# fdisk /dev/sdb
mon1# mkfs.xfs /dev/sdb1
mon1# mkdir /var/lib/ceph/osd/0
mon1# chown ceph. /var/lib/ceph/osd/0
mon1# echo '/dev/sdb1 /var/lib/ceph/osd/0 xfs auto,noatime 0 0' >> /etc/fstab

osd1# fdisk /dev/sdb
osd1# mkfs.xfs /dev/sdb1
osd1# mkdir /var/lib/ceph/osd/1
osd1# chown ceph. /var/lib/ceph/osd/1
osd1# echo '/dev/sdb1 /var/lib/ceph/osd/1 xfs auto,noatime 0 0' >> /etc/fstab

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

mon1$ ceph-deploy disk zap osd2:/dev/sdb

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

После того, как всё подготовлено - вводим osd в кластер:

# ceph-deploy osd create mon1:/var/lib/ceph/osd/0 osd1:/var/lib/ceph/osd/1 osd2:/dev/sdb

и активируем их

# ceph-deploy osd activate mon1:/var/lib/ceph/osd/0 osd1:/var/lib/ceph/osd/1 osd2:/dev/sdb1

После всех манипуляций проверяем

# ceph health

и если на выходе HEALTH_OK - значит у нас получилось и кластер готов. Если HEALTH_WARN, то можно выполнить

# ceph -s

и посмотреть что не так.

Посмотреть список и статус OSD можно командой

# ceph osd tree

CRUSHMAP

Как я понял это возможность распределить OSD внутри кластера по группам. Идея проста - мы складываем нужные OSD в одну группу, задаём и веса, а потом определяем правила для этих групп.

Изначально все osd попадают в следующую иерархию

# ceph osd crush tree
[
    {
        "id": -1,
        "name": "default",
        "type": "root",
        "type_id": 10,
        "items": [
            {
                "id": -2,
                "name": "mon1",
                "type": "host",
                "type_id": 1,
                "items": [
                    {
                        "id": 0,
                        "name": "osd.0",
                        "type": "osd",
                        "type_id": 0,
                        "crush_weight": 0.097595,
                        "depth": 2
                    }
                ]
            },
            {
                "id": -3,
                "name": "osd1",
                "type": "host",
                "type_id": 1,
                "items": [
                    {
                        "id": 1,
                        "name": "osd.1",
                        "type": "osd",
                        "type_id": 0,
                        "crush_weight": 0.097595,
                        "depth": 2
                    }
                ]
            },
            {
                "id": -4,
                "name": "osd2",
                "type": "host",
                "type_id": 1,
                "items": [
                    {
                        "id": 2,
                        "name": "osd.2",
                        "type": "osd",
                        "type_id": 0,
                        "crush_weight": 0.097595,
                        "depth": 2
                    }
                ]
            }
        ]
    }
]

И на всю группу root=default применяется replucated_ruleset

# ceph osd crush rule list
[
  "replicated_ruleset"
]

Мы же в свою очередь можем создать свою группу со своими правилами. Например если у нас есть 20 OSD и мы хотим сделать две независимых зоны. (4 сервера по 5 OSD на каждом) Для этого назначаем нашим OSD сначала нужную иерархию:

# ceph osd crush create-or-move osd.0 1.0 root=zone1 rack=r1 host=mon1
# ceph osd crush create-or-move osd.1 1.0 root=zone1 rack=r1 host=mon1
# ceph osd crush create-or-move osd.2 1.0 root=zone2 rack=r1 host=mon1
# ceph osd crush create-or-move osd.3 1.0 root=zone2 rack=r1 host=mon1
# ceph osd crush create-or-move osd.4 1.0 root=zone2 rack=r1 host=mon1
# ceph osd crush create-or-move osd.5 1.0 root=zone1 rack=r1 host=osd1
# ceph osd crush create-or-move osd.6 1.0 root=zone1 rack=r1 host=osd1
# ceph osd crush create-or-move osd.7 1.0 root=zone1 rack=r1 host=osd1
# ceph osd crush create-or-move osd.8 1.0 root=zone2 rack=r1 host=osd1
# ceph osd crush create-or-move osd.9 1.0 root=zone2 rack=r1 host=osd1
# ceph osd crush create-or-move osd.10 1.0 root=zone1 rack=r1 host=osd2
# ceph osd crush create-or-move osd.11 1.0 root=zone1 rack=r1 host=osd2
# ceph osd crush create-or-move osd.12 1.0 root=zone2 rack=r1 host=osd2
# ceph osd crush create-or-move osd.13 1.0 root=zone2 rack=r1 host=osd2
# ceph osd crush create-or-move osd.14 1.0 root=zone2 rack=r1 host=osd2
# ceph osd crush create-or-move osd.15 1.0 root=zone1 rack=r1 host=osd3
# ceph osd crush create-or-move osd.16 1.0 root=zone1 rack=r1 host=osd3
# ceph osd crush create-or-move osd.17 1.0 root=zone1 rack=r1 host=osd3
# ceph osd crush create-or-move osd.18 1.0 root=zone2 rack=r1 host=osd3
# ceph osd crush create-or-move osd.19 1.0 root=zone2 rack=r1 host=osd3

Создаём правило

# ceph osd crush rule create-simple zone1 zone1 host
# ceph osd crush rule create-simple zone2 zone2 host

Где первое zone1 - название правила, второе zone1 - значение root= в иерархии, а rack узел, который является единицей отказа. Так у нас в иерархии zone1-r1-<server>-osdX точкой отказа являются <server>, бишь сервера. Если указать, например, точкой отказа стойку - то в случае с одной стойкой репликация не пойдёт и кластер умрёт с HEALTH_WARN. Очень важно, чтобы в иерархии osd лежал на том же сервере, на котором находится физически, иначе при перезапуске osd-сервера все osd на нём находящиеся попадут в root=default,

OSD уникальны в пределах crushmap.

Пулы

Итак, у нас имеется кластер, однако пока мы ничего с ним сделать не можем, поэтому сперва нам требуется создать пул, в который уже и будут записываться наши данные. Итак, в процессе создания пула нужно указать количество Placement groups. С помощью PG объекты в CEPH группируются и именно PG осуществляется резервирование данных. Если OSD умирает - его PG реплицируется на ещё живые OSD. Кто-то скажет - чем больше атомарность - тем больше отказоустойчивость и я, возможно даже соглашусь, но для рассчёта правильных количеств PG на пул существует несколько различных калькуляторов на просторах интернетов. Так же есть ограничения - минимум 30 PG на один OSD, максимум - 300. Я, лично, возьму рандомное число с потолка, потому что всё одно это стенд:

# ceph osd pool create volumes 128 zone1

По аргументам здесь следующее: volumes - название пула, 128 - количество PG, zone1 - правило в crushmap.

1)
мы ж нищие, откуда деньги?
2)
Проще всего использовать SCM для работы с серверами количеством больше одного
3)
по умолчанию фактор репликации 3, однако в нашем случае кластер можно сопоставить с ничтожно малой величиной, которой можно пренебречь
4)
Никогда не повторяйте это на продуктиве!
5)
чуть меньше