Практические истории из наших SRE-будней. Часть 5

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

В этой серии:

  • переносим Ceph с одного бэкенда на другой; 

  • подпираем костылём бесконечное падение liveness- и readiness-проб в Kubernetes;

  • устраняем баг в Kubernetes-операторе для Redis.

История 1. Как мы сбежали от прожорливого BlueStore

Внимание!

Все нижеописанные действия в этой истории НЕ рекомендуются как лучшие практики. Мы выполняли их на свой страх и риск, осторожно и осознанно. Эта история о том, что бывает, когда всё пошло по наклонной, а другие варианты решения не подходили.

При развертывании Ceph-хранилища у одного из клиентов в качестве бэкенда мы изначально выбрали BlueStore. Он производителен, надежен и функционален, для обработки данных используются внутренние механизмы кэширования, а не page cache.

Однако через некоторое время заметили, что Ceph просто «съедает» всю память узла: мы получали Out of memory, узел перезагружался. Можно было бы заняться тюнингом выделения памяти в BlueStore, но данный бэкенд требует больше памяти, которой у нас не было. Имея в распоряжении кластер не самых богатых мощностей и без возможности их нарастить, мы приняли нестандартное решение: мигрировать на FileStore. В Ceph уже были данные, которые нельзя было терять (но имелись актуальные бэкапы).

Расскажем, как мы это делали пошагово.

Важно: все действия должны выполняться на гипервизоре с OSD на борту.

  1. Смотрим, как разбиты диски, на каких разделах работает Ceph. Для этого воспользуемся командой lsblk.

  1. Получаем список OSD: ceph osd tree.

  1. Выставляем количество репликаций объектов в 2: ceph osd pool set kube size 2.

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

  1. Помечаем OSD на «выброс» из кластера: ceph osd out <OSD_ID>. Дожидаемся окончания ребалансировки и убеждаемся, что все pg имеют статус active+clean. Для этого можно использовать команду: watch ceph -s.

  1. Останавливаем сервис OSD, чтобы Ceph не пытался запустить его: service ceph-osd@<OSD_ID> stop.

  2. Параллельно наблюдаем за использованием OSD, чтобы ребалансировка не прервалась: watch ceph osd df.

  1. Чистим раздел от данных и файловой системы. Для этого смотрим, какие партиции используются Сeph’ом: ceph-volume lvm list.

Выполняем ceph-volume lvm zap <lvm_partition_for_remove OSD>.

  1. Удаляем данные OSD для мониторов, включая OSD ID- и CRUSH-позиции: ceph osd purge <OSD_ID> --yes-i-really-mean-it.

  1. Удаляем неиспользуемый LVM-том: lvremove vgname/lvname.

  1. Преобразуем таблицу разделов MBR в GPT (ды, мы «сорвали куш» и имели таблицу MBR):

    a) если не установлена утилита gdisk — устанавливаем: apt install gdisk;

    b) создаем новый раздел с расположением на секторах 34-2047 и типом ef02.

  1. Создаем разделы для новой OSD:

    a) fdisk /dev/sdX;

    b) удаляем раздел, который использовался удаленной OSD:

    c) Создаем два новых раздела: первый — для журналирования, второй — для данных:

  1. Выполняем grub-install /dev/sdX.

Финальные шаги — действия, которые необходимо выполнить на узле, что используется для деплоя из-под пользователя ceph-deploy.

  1. Начинаем с команды: ceph-deploy osd create --filestore --data /dev/sdXY --journal /dev/sdXY <target_hypervisor>.

  1. Дожидаемся завершения установки и ребалансировки.

  2. Выставляем количество репликаций объектов в 3: ceph osd pool set kube size 3.

История 2. Бесконечно падающие пробы

В одном из проектов мы столкнулись с проблемой постоянно падающих liveness- и readiness-проб у приложений в production-окружении. Из-за этого все экземпляры приложения могли быть недоступны, как и сам клиентский сервис. Инженеру приходилось вручную рестартить проблемные приложения — это спасало, но ненадолго.

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

Решение: shell-operator

Чтобы не тратить время на рутинную и неблагодарную работу, мы решили сделать оператор, который делает rollout restart необходимых приложений. Рестарт выполняется при условии, что нет ни одного работоспособного экземпляра приложения. Механизм был реализован на Bash с помощью shell-operator.

Как это работает:

  • подписываемся на изменение ресурса Deployment;

  • при каждом изменении сверяем два поля: .status.replicas и .status.unavailableReplicas;

  • если значения равны — выполняется rollout restart. В противном случае ждем дальше.

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

Внимательный читатель мог задаться вопросом, почему не подошла liveness-проба. Дело в том, что постоянно падающая liveness-проба приводила к CrashLoopBackOff, что в своё время увеличивало счётчик рестартов и, соответственно, время для следующего перезапуска Pod’а.

Подводный камень

Оператор работал исправно. Мы всё реже напоминали о проблемах с пробами, и клиенту получившаяся времянка нравилась. Но как-то раз он решил перекатить Deployment с одной репликой — и тут началось интересное… Приложение ушло в бесконечный рестарт:

  • shell-operator видел новый Pod, статус которого на момент его обнаружения был отличен от Ready;

  • Pod перезапускался согласно логике… и так — по кругу.

Пришлось сделать дополнительную проверку с отложенным запуском в 15 секунд: если через 15 секунд после первой проверки количество реплик у приложения по-прежнему равно нулю, выполняется перезапуск.

Итоговая времянка — двойной костыль. Есть ли этому оправдание — вопрос, освещение которое требует самостоятельной статьи. А поак мы продолжаем время от времени напоминать клиенту о необходимости доработки liveness- и readiness-проб…

История 3. redis-operator и потерявшийся приоритет

Для запуска Redis в Kubernetes долгое время мы пользовались redis-operator от spotahome. До поры мы не замечали глобальных проблем (кроме той, что уже обсуждали пару лет назад), но однажды столкнулись с неработающим priorityClassName.

Priority Class — это функция, которая позволяет учитывать приоритет Pod’а (из его принадлежности к классу) при планировании. Если Pod не может быть запущен на подходящем узле из-за нехватки ресурсов, то планировщик (scheduler) пытается «вытеснить» Pod’ы с более низким приоритетом и переместить их на другие узлы кластера, чтобы освободить ресурсы и запустить ожидающий Pod.

Выставлять Priority Class нужно осторожно, так как в случае его неверного определения могут быть самые неожиданные последствия.

У приложений был установлен priorityClass с value 10 000. А у манифестов, созданных с помощью оператора, приоритет не выставлялся, несмотря на его описание в ресурсе redisfailover. Поэтому Pod’ы приложения «вытесняли» Redis.

На момент решения нами этой проблемы последняя версия redis-operator’а была годичной давности, так как разработчик давно его не обновлял, из-за чего мы были вынуждены подготовить свой релиз. И нам удалось даже найти PR с фиксом (пользуясь случаем, передаем привет @identw!). Однако совсем недавно, в январе, появился новый релиз на GitHub (пока только RC), где эта задача решается «из коробки». Поэтому свою инструкцию по сборке спрячем в спойлер.

Инструкция по сборке

Мы воспользовались werf и субмодулями:

1. Добавляем субмодули и локаемся на одном из коммитов (чтобы не нарваться на внезапное обновление оператора):

git submodule add https://github.com/spotahome/redis-operator.git
cd redis-operator
git checkout $commit

2. Создаем werf.yaml, который использует Dockerfile внутри субмодуля:

project: redis-operator
configVersion: 1
---
image: redisOperator
dockerfile: docker/app/Dockerfile
context: redis-operator

3. В CI добавляем конструкцию werf build.

В итоге получаем собранный образ оператора на нужной версии коммита. Далее можем использовать его в Helm-чарте с помощью {{.Values.werf.image.redisOperator }}.

В итоговом образе есть нужные изменения с priorityClassName— всё работает, как задумано!

Кто-то может задаться вопросами: «А как планируется уместить приложение и Redis при условии, что у них одинаковые классы? Кто-то окажется в статусе Pending?».

Данный вопрос больше относится к теме capacity planning, нежели к проблемам стабильности приложения… Но в целом ответ таков: в данном случае клиент вкладывается в «железо», так как работоспособность Redis’а напрямую влияет на клиентское приложение, и его (Redis’а) «вытеснение» приводило к неприятным последствиям. Также мы провели ревизию потребляемых ресурсов на основе рекомендаций VPA, которая помогла «умерить аппетит» некоторых побочных приложений. (Кстати, в нашем блоге есть перевод подробной статьи про VPA.)

Заключение

В реальной жизни инженерам приходится сталкиваться не только с особенностями технологий, но и с приоритетами бизнеса или, как в последнем случае, с полузаброшенными Open Source-проектами… Тем интереснее работа, и тем больше любопытных историй из нашей практики! Скоро вернемся с новой порцией.

P.S.

Читайте также в нашем блоге:

  • «Практические истории из наших SRE-будней. Часть 4» (проблема ClickHouse с ZooKeeper, обновление MySQL, битва Cloudflare с Kubernetes);

  • «Практические истории из наших SRE-будней. Часть 3» (миграция Linux-сервера, Kubernetes-оператор ClickHouse, реплика PostgreSQL, обновление CockroachDB);

  • «Практические истории из наших SRE-будней. Часть 2» (Kafka и Docker, ClickHouse и MTU, перегретый Kubernetes, pg_repack для PostgreSQL).

Источник: https://habr.com/ru/company/flant/blog/648175/


Интересные статьи

Интересные статьи

В предыдущей части мы рассмотрели построение элемента «Основание “Паука”». Теперь нужно построить бобышку, от которой затем по четырем сторонам основания будут построены ребра. Впрочем, обо всем по по...
Публикуем вторую часть материала о трансформерах. В первой части речь шла о теоретических основах трансформеров, были показаны примеры их реализации с использованием PyTorch. Здесь поговорим о том, ка...
Предлагаю ознакомиться с ранее размещенными материалами по проекту Starlink (SL): ‣ Часть 25. EPFD или «административно-физическая гиря» на ногах SpaceX ‣ Часть 26. Первые итоги. Часть ...
Это продолжение серии статей о нестабильных тестах.В первой статье(оригинал/перевод на хабре) говорилось о 4 компонентах, в которых могут возникать нестабильные тесты.В этой статье дадим ...
Начиная с 2018 года, пятьсот самых высокопроизводительных систем в мире работают на Linux. Обсуждаем причины сложившейся ситуации и приводим мнения экспертов.