Это история про один вечер с Ceph, в котором я сначала героически чинил одиннадцать упавших OSD, потом сам же добил один из них ещё сильнее, а потом ещё пару часов доказывал базе данных, что она умеет читать то, что сама же записала. Спойлер, данные никто не потерял, а я узнал о RocksDB больше, чем собирался узнать в этой жизни.
Кластер большой, несколько сотен OSD, почти тысяча пулов, версия Quincy 17.2.6, всё развёрнуто через cephadm. Обычный рабочий день, ничего не предвещало.
Алерт, который просил откатиться на Luminous
Прилетает алерт, на одном хосте лежат сразу одиннадцать OSD. Хост в этот день перезагружали несколько раз, так что первая мысль была банальной, диски отвалились или железо капризничает. Открываю журнал первого попавшегося OSD и вижу вот такое.
osd.150 2286818 init missing pg_pool_t for deleted pool 1992 for pg 1992.5;please downgrade to luminous and allow pg deletion to complete before upgrading
Ceph вежливо предлагает мне откатиться на Luminous. Это релиз 2017 года, кластер на Quincy, то есть совет примерно того же уровня, что выключить и включить компьютер, а лучше сразу купить компьютер из прошлого века. Над этим сообщением я смеялся минуты две, потом стало не смешно.
Выше по журналу идут тысячи строк про то, что OSD не может загрузить старые карты.
osd.150 2286818 failed to load OSD map for epoch 2281258, got 0 bytes
Картина складывается так. В кластере удалили несколько пулов, а на дисках этих OSD остались их PG. То ли удаление случилось, пока OSD лежали, то ли OSD не успели дочистить хвосты до перезагрузки хоста, точно я так и не выяснил. Чтобы аккуратно удалить такие PG, OSD нужна карта кластера из того времени, когда пулы ещё существовали. Своих копий этих карт у OSD нет, мониторы их тоже уже обрезали, и OSD просто падает при старте, а systemd после пяти попыток сдаётся.
Я проверил все одиннадцать OSD, у всех одно и то же, только номера удалённых пулов разные. Данные кластера при этом целы, OSD уже автоматически выведены, и Ceph спокойно переносит их данные на соседей.
Правильное решение, которое я выполнил неправильно
Лечится это известным способом. Нужно остановленному OSD руками удалить PG пулов, которых больше нет, через ceph-objectstore-tool. Данных живых пулов это не касается, удаляются только хвосты того, что в кластере уже удалено.
Сначала я посмотрел список PG на одном OSD и сверил его со списком пулов. Из 97 PG лишних оказалось всего два, так что масштаб бедствия смешной. Удаляю их как написано во всех инструкциях.
cephadm shell --name osd.150 -- ceph-objectstore-tool \ --data-path /var/lib/ceph/osd/ceph-150 --op remove --pgid 1992.5 --force
Инструмент бодро отвечает Remove successful, я запускаю OSD, предвкушаю зелёный статус и получаю новую ошибку.
rocksdb: Corruption: unknown checksum type 4 from footer of db/226653.sstbluestore(/var/lib/ceph/osd/ceph-150) _open_db erroring opening db
Было одно падение, стало другое, и второе выглядит заметно страшнее первого. Слово Corruption в логе базы данных хорошо бодрит без всякого кофе.
Кто записал в базу то, что она не может прочитать
Тип контрольной суммы 4 в RocksDB означает XXH3. RocksDB, который живёт внутри Quincy, про такой тип ничего не знает. Значит, файл с XXH3 записал кто-то новее, и этого кого-то я быстро нашёл в зеркале.
Дело в том, что cephadm shell без явного образа выбрал не тот образ, на котором работает сам OSD, а самый свежий из лежащих на хосте. А на этом хосте кроме Quincy зачем-то лежал образ Reef 18.2.0, откуда он там взялся, я так и не узнал. Проверяю, и всё сходится.
cephadm shell --name osd.150 -- ceph --versionceph version 18.2.0 (...) reef (stable)
Сам OSD при этом работает на образе Quincy, это видно в его unit.run. Получается, я залез в базу Quincy инструментом от Reef, он при удалении PG записал новые файлы в своём формате, и OSD перестал понимать собственную базу. Технически инструмент ничего не сломал, он просто разговаривал с базой на диалекте, которого она ещё не знает.
Хорошая новость была в том, что этот OSD и так уже был выведен из кластера, а его данные лежали в репликах на других OSD. Потерять что-то было невозможно, но пересоздавать OSD и переносить на него двадцать терабайт обратно не хотелось из принципа.
Попытка номер один, просто переписать
Раз Reef умеет читать XXH3, логично было попросить его же переписать базу со старой контрольной суммой CRC32C. У Ceph есть параметр bluestore_rocksdb_options_annex, через который можно передать в RocksDB дополнительные опции, я передал ему checksum=kCRC32c и запустил полное сжатие.
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-150 compact \ --bluestore_rocksdb_options_annex="block_based_table_factory={checksum=kCRC32c}"
Сжатие прошло, новые файлы снова с XXH3. Добавил format_version=4, результат тот же. Пришлось открыть исходники Ceph и разобраться, почему опция молча игнорируется.
Судя по коду, Ceph после разбора строки опций заново создаёт фабрику таблиц из своих собственных настроек, и всё, что я туда передал, теряется. Отдельной настройки для контрольной суммы среди параметров Ceph просто нет.
Нашлась лазейка в шардинге
Дальше начинается самое интересное. В BlueStore база разбита на несколько семейств столбцов, это так называемый шардинг, и у каждого семейства могут быть свои опции. В Reef эти опции из блока block_cache={...} целиком уходят в разбор параметров таблицы, а значит, туда можно засунуть и контрольную сумму.
Плохая новость в том, что есть ещё семейство default, где лежат суперблок, freelist и статистика. Его параметры всегда берутся из базовых настроек, и никакой шардинг на них не влияет. Если в описание шардинга добавить отдельную запись для default, база перестаёт открываться вообще, потому что проверка шардинга видит лишнее семейство.
После вдумчивого чтения исходников родился план из трёх шагов.
-
Решардингом инструментом Reef разложить все ключи по новым семействам с другими именами и с CRC32C в опциях. При решардинге ключи физически переезжают в новые семейства, а старые семейства вместе со всеми файлами на XXH3 удаляются. Ключи из default тоже выезжают, для каждого префикса заводится своё семейство.
-
Сжать базу инструментом Reef. В семействе default к этому моменту остались только удалённые ключи, при полном сжатии они вычищаются вместе со старыми файлами на XXH3.
-
Открыть базу уже инструментом Quincy и решардингом вернуть исходное разбиение. Ключи возвращаются на свои места, и пишет их уже Quincy в своём родном формате.
Звучит как план ограбления из фильма, и проверять его сразу на продовом OSD было бы слишком смело даже для меня.
Сначала на кошках
На том же хосте я создал маленькую BlueStore размером четыре гигабайта прямо в файле, теми же двумя образами. Создал её Quincy, сжал инструментом Reef и получил ровно ту же ошибку про checksum type 4. Сломанную копию сохранил, чтобы можно было экспериментировать сколько угодно.
Первая версия плана с записью для default ожидаемо уронила открытие базы. Вторая, с переездом всех ключей в новые семейства, отработала целиком, Quincy открыл базу, вернул исходный шардинг, и fsck сказал fsck success.
Вот как выглядело описание шардинга для первого шага, где CK это просто сокращение для опций контрольной суммы, а однобуквенные имена в конце это префиксы ключей BlueStore, которые раньше жили в default.
CK="checksum=kCRC32c;format_version=4"m=block_cache={$CK} p=block_cache={$CK} O=block_cache={type=binned_lru;$CK}L(2)=block_cache={$CK} P(2)=block_cache={$CK}S=block_cache={$CK} T=block_cache={$CK} C=block_cache={$CK} B=block_cache={$CK} b=block_cache={$CK} ...
Имена m, p, O без количества шардов и L(2), P(2) с двумя специально выбраны так, чтобы не совпадать со старыми m-0, L и остальными. Тогда при решардинге старые семейства удаляются целиком, а не остаются жить со своими файлами на XXH3.
На проде
На настоящем OSD база весила около пятнадцати гигабайт, и весь процесс занял минут пятнадцать. Решардинг шёл три минуты, сжатие две с половиной, обратный решардинг почти две, ещё шесть минут ушло на fsck. После этого OSD запустился, минут семь догонял карты кластера и вернулся в строй со всеми своими данными, кроме тех двух PG удалённых пулов.
Остальные OSD я чинил уже правильно, с явным указанием образа той же версии, что у самого OSD. Перед каждым удалением скрипт проверял версию инструмента в контейнере и останавливался, если там не Quincy.
cephadm --image quay.io/ceph/ceph@sha256:<образ quincy> shell --name osd.149 -- \ ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-149 --op list-pgs
Пока я этим занимался, выяснилось, что я не единственный такой умный. По журналу cephadm было видно, что параллельно кто-то из коллег чинил соседний OSD тем же способом, без явного образа, и получил ровно ту же ошибку с XXH3. Его OSD скрипт честно пропустил, потому что не смог открыть базу, и этот OSD пришлось лечить тем же трёхшаговым трюком, после чего он тоже поднялся. Так что если вы сейчас подумали, что уж вы-то так не ошиблись бы, то подумайте ещё раз, мы вот ошиблись вдвоём за один вечер.
Чему меня научил этот вечер
Главное правило теперь висит у меня перед глазами. Любые офлайн-инструменты вроде ceph-objectstore-tool, ceph-kvstore-tool и ceph-bluestore-tool запускаются только той же версией, что и сам демон, и только с явным --image. Посмотреть нужный образ можно в файле unit.run демона или в выводе ceph orch ps.
Второе правило про лишние образы на хостах. Образ другой версии лежит тихо и ничего не просит, пока cephadm shell не решит, что он тут самый главный. Если обновление не идёт прямо сейчас, такие образы лучше с хостов убрать.
Третье правило про удаление пулов. Если после удаления пула на каком-то OSD остались его PG, а карты из нужного времени уже обрезаны, этот OSD после рестарта без ручной работы не поднимется. Автоматике, которая создаёт и удаляет пулы, стоит хотя бы проверять, что все OSD на месте.
И последнее, уже философское. Когда Ceph в логе предлагает откатиться на Luminous, он не издевается, он просто очень старый и очень честный. Откатываться, конечно, не надо, но сообщение стоит прочитать до конца, там обычно написано, что на самом деле случилось.
ссылка на оригинал статьи https://habr.com/ru/articles/1100250/