- PVSM.RU - https://www.pvsm.ru -
Новый эпизод нашего сериала о любопытных историях из практики. Использовать эти истории для развлечения или как практические рекомендации — решать вам, но мы сразу предупреждаем, что приводимые в них инструкции зачастую далеки от универсальных. Вместо этого вы можете встретить обходные пути для решения специфичных проблем в специфичных условиях. Зато они всегда расширяют кругозор и помогают посмотреть на некоторые технологии и их применение под новым углом.
В этой серии:
переносим Ceph с одного бэкенда на другой;
подпираем костылём бесконечное падение liveness- и readiness-проб в Kubernetes;
устраняем баг в Kubernetes-операторе для Redis.
Все нижеописанные действия в этой истории НЕ рекомендуются как лучшие практики. Мы выполняли их на свой страх и риск, осторожно и осознанно. Эта история о том, что бывает, когда всё пошло по наклонной, а другие варианты решения не подходили.
При развертывании Ceph-хранилища у одного из клиентов в качестве бэкенда мы изначально выбрали BlueStore. Он производителен, надежен и функционален, для обработки данных используются внутренние механизмы кэширования, а не page cache.
Однако через некоторое время заметили, что Ceph просто «съедает» всю память узла: мы получали Out of memory, узел перезагружался. Можно было бы заняться тюнингом выделения памяти в BlueStore, но данный бэкенд требует больше памяти, которой у нас не было. Имея в распоряжении кластер не самых богатых мощностей и без возможности их нарастить, мы приняли нестандартное решение: мигрировать на FileStore. В Ceph уже были данные, которые нельзя было терять (но имелись актуальные бэкапы).
Расскажем, как мы это делали пошагово.
Важно: все действия должны выполняться на гипервизоре с OSD на борту.
Смотрим, как разбиты диски, на каких разделах работает Ceph. Для этого воспользуемся командой lsblk
.
Получаем список OSD: ceph osd tree
.
Выставляем количество репликаций объектов в 2: ceph osd pool set kube size 2
.
Важно: намеренное снижение избыточности перед потенциально деструктивными операциями в кластере очень опасно. В нашем случае это было сознательное допущение, так как «под рукой» были бэкапы всех необходимых данных.
Помечаем OSD на «выброс» из кластера: ceph osd out <OSD_ID>
. Дожидаемся окончания ребалансировки и убеждаемся, что все pg имеют статус active+clean
. Для этого можно использовать команду: watch ceph -s
.
Останавливаем сервис OSD, чтобы Ceph не пытался запустить его: service ceph-osd@<OSD_ID> stop
.
Параллельно наблюдаем за использованием OSD, чтобы ребалансировка не прервалась: watch ceph osd df
.
Чистим раздел от данных и файловой системы. Для этого смотрим, какие партиции используются Сeph’ом: ceph-volume lvm list
.
Выполняем ceph-volume lvm zap <lvm_partition_for_remove OSD>
.
Удаляем данные OSD для мониторов, включая OSD ID- и CRUSH-позиции: ceph osd purge <OSD_ID> --yes-i-really-mean-it
.
Удаляем неиспользуемый LVM-том: lvremove vgname/lvname
.
Преобразуем таблицу разделов MBR в GPT (ды, мы «сорвали куш» и имели таблицу MBR):
a) если не установлена утилита gdisk
— устанавливаем: apt install gdisk
;
b) создаем новый раздел с расположением на секторах 34-2047
и типом ef02
.
Создаем разделы для новой OSD:
a) fdisk /dev/sdX
;
b) удаляем раздел, который использовался удаленной OSD:
c) Создаем два новых раздела: первый — для журналирования, второй — для данных:
Выполняем grub-install /dev/sdX
.
Финальные шаги — действия, которые необходимо выполнить на узле, что используется для деплоя из-под пользователя ceph-deploy
.
Начинаем с команды: ceph-deploy osd create --filestore --data /dev/sdXY --journal /dev/sdXY <target_hypervisor>
.
Дожидаемся завершения установки и ребалансировки.
Выставляем количество репликаций объектов в 3: ceph osd pool set kube size 3
.
В одном из проектов мы столкнулись с проблемой постоянно падающих liveness- и readiness-проб у приложений в production-окружении. Из-за этого все экземпляры приложения могли быть недоступны, как и сам клиентский сервис. Инженеру приходилось вручную рестартить проблемные приложения — это спасало, но ненадолго.
Клиент знал об этой ситуации, но, к сожалению, приоритеты бизнеса были таковы, что изменений на стороне приложения ждать не приходилось. Поэтому, несмотря на все наши представления об идеальных мирах Dev и Ops, пришлось автоматизировать… костыль.
Чтобы не тратить время на рутинную и неблагодарную работу, мы решили сделать оператор, который выполняет rollout restart необходимых приложений. Рестарт происходит при условии, что нет ни одного работоспособного экземпляра приложения. Механизм был реализован на Bash с помощью shell-operator [1].
Как это работает:
подписываемся на изменение ресурса Deployment;
при каждом изменении сверяем два поля: .status.replicas
и .status.unavailableReplicas
;
если значения равны — выполняется rollout restart. В противном случае ждем дальше.
Всё это было согласовано с клиентом. И заодно мы начали высылать клиенту алерты, когда количество доступных реплик приложения уменьшается.
Внимательный читатель мог задаться вопросом, почему не подошла liveness-проба. Дело в том, что постоянно падающая liveness-проба приводила к CrashLoopBackOff, что в своё время увеличивало счётчик рестартов и, соответственно, время для следующего перезапуска Pod’а.
Оператор работал исправно. Мы всё реже напоминали о проблемах с пробами, и клиенту получившаяся времянка нравилась. Но как-то раз он решил перекатить Deployment с одной репликой — и тут началось интересное… Приложение ушло в бесконечный рестарт:
shell-operator видел новый Pod, статус которого на момент его обнаружения был отличен от Ready
;
Pod перезапускался согласно логике… и так — по кругу.
Пришлось сделать дополнительную проверку с отложенным запуском в 15 секунд: если через 15 секунд после первой проверки количество реплик у приложения по-прежнему равно нулю, выполняется перезапуск.
Итоговая времянка — двойной костыль. Есть ли этому оправдание — вопрос, освещение которое требует самостоятельной статьи. А пока мы продолжаем время от времени напоминать клиенту о необходимости доработки liveness- и readiness-проб…
Для запуска Redis в Kubernetes долгое время мы пользовались redis-operator [2] от spotahome. До поры мы не замечали глобальных проблем (кроме той, что уже обсуждали [3] пару лет назад), но однажды столкнулись с неработающим priorityClassName
.
Priority Class [4] — это функция, которая позволяет учитывать приоритет Pod’а (из его принадлежности к классу) при планировании. Если Pod не может быть запущен на подходящем узле из-за нехватки ресурсов, то планировщик (scheduler) пытается «вытеснить» Pod’ы с более низким приоритетом и переместить их на другие узлы кластера, чтобы освободить ресурсы и запустить ожидающий Pod.
Выставлять Priority Class нужно осторожно, так как в случае его неверного определения могут быть самые неожиданные последствия.
У приложений был установлен priorityClass с value 10 000. А у манифестов, созданных с помощью оператора, приоритет не выставлялся, несмотря на его описание в ресурсе redisfailover
. Поэтому Pod’ы приложения «вытесняли» Redis.
На момент решения нами этой проблемы последняя версия redis-operator’а была годичной давности, так как разработчик давно его не обновлял, из-за чего мы были вынуждены подготовить свой релиз. И нам удалось даже найти PR с фиксом [5] (пользуясь случаем, передаем привет @identw [6]!). Однако совсем недавно, в январе, появился новый релиз на GitHub [7] (пока только RC), где эта задача решается «из коробки». Поэтому свою инструкцию по сборке спрячем в спойлер.
Мы воспользовались werf [8] и субмодулями [9]:
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, которая помогла «умерить аппетит» некоторых побочных приложений. (Кстати, в нашем блоге есть перевод подробной статьи [10] про VPA.)
В реальной жизни инженерам приходится сталкиваться не только с особенностями технологий, но и с приоритетами бизнеса или, как в последнем случае, с полузаброшенными Open Source-проектами… Тем интереснее работа, и тем больше любопытных историй из нашей практики! Скоро вернемся с новой порцией.
Читайте также в нашем блоге:
«Практические истории из наших SRE-будней. Часть 4» [11] (проблема ClickHouse с ZooKeeper, обновление MySQL, битва Cloudflare с Kubernetes);
«Практические истории из наших SRE-будней. Часть 3» [12] (миграция Linux-сервера, Kubernetes-оператор ClickHouse, реплика PostgreSQL, обновление CockroachDB);
«Практические истории из наших SRE-будней. Часть 2» [13] (Kafka и Docker, ClickHouse и MTU, перегретый Kubernetes, pg_repack для PostgreSQL).
Автор: Максим
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/redis/371628
Ссылки в тексте:
[1] shell-operator: https://github.com/flant/shell-operator
[2] redis-operator: https://github.com/spotahome/redis-operator
[3] уже обсуждали: https://habr.com/ru/company/flant/blog/480722/
[4] Priority Class: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
[5] PR с фиксом: https://github.com/spotahome/redis-operator/pull/282
[6] @identw: https://www.pvsm.ru/users/identw
[7] релиз на GitHub: https://github.com/spotahome/redis-operator/releases/tag/v1.1.0-rc.2
[8] werf: https://ru.werf.io/
[9] субмодулями: https://ru.werf.io/documentation/v1.1/configuration/stapel_image/git_directive.html#%D1%87%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-git-mapping
[10] статьи: https://habr.com/ru/company/flant/blog/541642/
[11] «Практические истории из наших SRE-будней. Часть 4»: https://habr.com/ru/company/flant/blog/558346/
[12] «Практические истории из наших SRE-будней. Часть 3»: https://habr.com/ru/company/flant/blog/531686/
[13] «Практические истории из наших SRE-будней. Часть 2»: https://habr.com/ru/company/flant/blog/510486/
[14] Источник: https://habr.com/ru/post/648175/?utm_source=habrahabr&utm_medium=rss&utm_campaign=648175
Нажмите здесь для печати.