Русский
Русский
English
Статистика
Реклама

Флант

Как мы собираем общие сведения о парке из Kubernetes-кластеров

16.06.2021 10:13:29 | Автор: admin

Имея в обслуживании большой (более 150) парк Kubernetes-кластеров, всегда хотелось иметь удобное представление их общего состояния, в том числе и для того, чтобы поддерживать их гомогенными. В первую очередь нас интересовали следующие данные:

  • версия Kubernetes чтобы все кластеры были on the edge;

  • версия Deckhouse (наша Kubernetes-платформа) для лучшего планирования релизных циклов;

  • количество узлов с разбивкой по типам (управляющие, виртуальные и статические) для отдела продаж;

  • количество ресурсов (CPU, memory) на управляющих узлах;

  • на какой инфраструктуре запущен кластер (виртуальные облачные ресурсы, bare metal или гибридная конфигурация);

  • какой облачный провайдер используется.

И вот каким был наш путь к тому, чтобы превратить эту потребность в наглядную реальность

Истоки и проверка концепции

В какой-то момент времени мы стали использовать Terraform для раскатки инфраструктуры в облака и вопрос отслеживания соответствия желаемых конфигураций реальности встал еще острее. Мы храним Terraform state в самих кластерах и проверку соответствия их с реальностью проверяет отдельно написанный Prometheus exporter. Хотя ранее у нас уже была информация для реагирования на изменения (через соответствующие алерты в системе управления инцидентами), хотелось ещё иметь полное представление о ситуации в отдельной аналитической системе.

Итак, изначально в качестве PoC был несложный Bash-скрипт, которым мы вручную время от времени собирали интересующие данные с K8s-кластеров по SSH. Он выглядел примерно так:

((kubectl -n d8-system get deploy/deckhouse -o json | jq .spec.template.spec.containers[0].image -r | cut -d: -f2 | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o name | wc -l | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o json | jq "if .items | length > 0 then .items[].status.capacity.cpu else 0 end" -r | sort -n | head -n 1 | tr "\n" ";") &&(kubectl get nodes -l node-role.kubernetes.io/master="" -o json | jq "if .items | length > 0 then .items[].status.capacity.memory else \"0Ki\" end | rtrimstr(\"Ki\") | tonumber/1000000 | floor" | sort -n | head -n 1 | tr "\n" ";") &&(kubectl version -o json | jq .serverVersion.gitVersion -r | tr "\n" ";") &&(kubectl get nodes -o wide | grep -v VERSION | awk "{print \$5}" | sort -n | head -n 1 | tr "\n" ";") &&echo "") | tee res.csvsed -i '1ideckhouse_version;mastersCount;masterMinCPU;masterMinRAM;controlPlaneVersion;minimalKubeletVersion' res.csv

(Здесь приведен лишь фрагмент для демонстрации общей идеи.)

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

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

  • собирал желаемую информацию,

  • агрегировал ее,

  • отправлял в какое-то централизованное хранилище.

а заодно соответствовал каноном высокой доступности и cloud native.

Этот путь дал начало истории модуля в Kubernetes-платформе Deckhouse, развёрнутой на всех наших кластерах, и сопутствующего ему хранилища.

Реализация

Хуки на shell-operator

В первой итерации источником данных в клиентских кластерах служили Kubernetes-ресурсы, параметры из ConfigMap/Deckhouse, версия образа Deckhouse и версия control-plane из вывода kubectl version. Для соответствующей реализации лучше всего подходил shell-operator.

Были написаны хуки (да, снова на Bash) с подписками на ресурсы и организована передача внутренних values. По результатам работы этих хуков мы получали список желаемых Prometheus-метрик (их экспорт поддерживается в shell-operator из коробки).

Вот пример хука, генерирующего метрики из переменных окружения, он прост и понятен:

#!/bin/bash -efor f in $(find /frameworks/shell/ -type f -iname "*.sh"); do  source $fdonefunction __config__() {  cat << EOF    configVersion: v1    onStartup: 20EOF}function __main__() {  echo '  {    "name": "metrics_prefix_cluster_info",    "set": '$(date +%s)',    "labels": {      "project": "'$PROJECT'",      "cluster": "'$CLUSTER'",      "release_channel": "'$RELEASE_CHANNEL'",      "cloud_provider": "'$CLOUD_PROVIDER'",      "control_plane_version": "'$CONTROL_PLANE_VERSION'",      "deckhouse_version": "'$DECKHOUSE_VERSION'"    }  }' | jq -rc >> $METRICS_PATH}hook::run "$@"

Отдельно хочу обратить ваше внимание на значение метрики (параметр set). Изначально мы писали туда просто 1, но возник резонный вопрос: Как потом получить через PromQL именно последние, свежие labels, включая те series, которые уже две недели не отправлялась? Например, в том же MetricsQL от VictoriaMetrics для этого есть специальная функция last_over_time. Оказалось, достаточно в значение метрики отправлять текущий timestamp число, которое постоянно инкрементируется во времени. Вуаля! Теперь стандартная функция агрегации max_over_time из Prometheus выдаст нам самые последние значения labels по всем series, которые приходили хоть раз в запрошенном периоде.

Чуть позже к источникам данных добавились метрики из Prometheus в кластерах. Для их получения был написан еще один хук, который через curl ходил в кластерный Prometheus, подготавливал полученные данные и экспортировал их в виде метрик.

Чтобы вписаться в парадигму cloud-native и обеспечить HA агента, мы запустили его в несколько реплик на управляющих узлах кластера.

Grafana Agent

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

Выбор пал на разработку Grafana Labs, а именно Grafana Agent. Он умеет делать scrape метрик с endpointов, отправлять их по протоколу Prometheus remote write, а также (что немаловажно!) ведет свой WAL на случай недоступности принимающей стороны.

Задумано сделано: и вот приложение из shell-operator и sidecarом с grafana-agent уже способно собирать необходимые данные и гарантировать их поступление в центральное хранилище.

Конфигурация агента делается довольно просто благо, все параметры подробно описаны в документации. Вот пример нашего итогового конфига:

server:  log_level: info  http_listen_port: 8080prometheus:  wal_directory: /data/agent/wal  global:    scrape_interval: 5m  configs:  - name: agent    host_filter: false    max_wal_time: 360h    scrape_configs:    - job_name: 'agent'      params:        module: [http_2xx]      static_configs:      - targets:        - 127.0.0.1:9115      metric_relabel_configs:      - source_labels: [__name__]        regex: 'metrics_prefix_.+'      - source_labels: [job]        action: keep        target_label: cluster_uuid        replacement: {{ .Values.clusterUUID }}      - regex: hook|instance        action: labeldrop    remote_write:    - url: {{ .Values.promscale.url }}      basic_auth:        username: {{ .Values.promscale.basic_auth.username }}        password: {{ .Values.promscale.basic_auth.password }}

Пояснения:

  • Директория /data это volumeMount для хранения WAL-файлов;

  • Values.clusterUUID уникальный идентификатор кластера, по которому мы его идентифицируем при формировании отчетов;

  • Values.promscale содержит информацию об endpoint и параметрах авторизации для remote_write.

Хранилище

Разобравшись с отправкой метрик, необходимо было решить что-то с централизованным хранилищем.

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

NB. Справедливости ради, хочется отметить, что на данный момент Cortex выглядит уже вполне жизнеспособным, оформленным как конечный продукт. Очень вероятно, что через какое-то время вернемся к нему и будем использовать. Уж очень сладко при мысли о generic S3 как хранилище для БД: никаких плясок с репликами, бэкапами и растущим количеством данных

К тому времени у нас была достаточная экспертиза по PostgreSQL и мы выбрали Promscale как бэкенд. Он поддерживает получение данных по протоколу remote-write, а нам казалось, что получать данные используя pure SQL это просто, быстро и незатратно: сделал VIEWхи и обновляй их, да выгружай в CSV.

Разработчики Promscale предоставляют готовый Docker-образ, включающий в себя PostgreSQL со всеми необходимыми extensions. Promscale использует расширение TimescaleDB, которое, судя по отзывам, хорошо справляется как с большим количеством данных, так и позволяет скейлиться горизонтально. Воспользовались этим образом, задеплоили connector данные полетели!

Далее был написан скрипт, создающий необходимые views, обновляющий их время от времени и выдающий на выход желаемый CSV-файл. На тестовом парке dev-кластеров всё работало отлично: мы обрадовались и выкатили отправку данных со всех кластеров.

Но с хранилищем всё не так просто

Первую неделю всё было отлично: данные идут, отчет генерируется. Сначала время работы скрипта составляло около 10 минут, однако с ростом количества данных это время увеличилось до получаса, а однажды и вовсе достигло 1 часа. Смекнув, что что-то тут не так, мы пошли разбираться.

Как оказалось, ходить в таблицы базы данных мимо магических оберток, предоставляемых Promscale (в виде своих функций и views, опирающихся в свою очередь на функции TimescaleDB), невероятно неэффективно.

Было решено перестать ковыряться в потрохах данных и положиться на мощь и наработки разработчиков Promscale. Ведь их connector может не только складывать данные в базу через remote-write, но и позволяет получать их привычным для Prometheus способом через PromQL.

Одним Bashем уже было не обойтись мы окунулись в мир аналитики данных с Python. К нашему счастью, в сообществе уже были готовы необходимые инструменты и для походов с PromQL! Речь про замечательный модуль prometheus-api-client, который поддерживает представление полученных данных в формате Pandas DataFrame.

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

Изначально мы выбрали период скрейпинга данных grafana-agentом равным одной минуте, что отразилось на огромных аппетитах конечной БД в диске: ~800 мегабайт данных в день. Это, конечно, не так много в масштабах одного кластера (~5 мегабайт), но когда кластеров много суммарный объём начинает пугать. Решение оказалось простым: увеличили период scrapeа в конфигах grafana-agentов до одного раза в 5 минут. Прогнозируемый суммарный объем хранимых данных с retentionом в 5 лет уменьшился с 1,5 Тб до 300 Гб, что, согласитесь, уже выглядит не так ужасающе.

Некоторый профит от выбора PostgreSQL как конечного хранилища мы уже получили: для успешного переезда хранилища в финальный production-кластер достаточно было отреплицировать базу. Единственное текущий недостаток пока не получилось самостоятельно собрать свой кастомный PostgreSQL с необходимыми расширениями. После пары неудачных попыток мы остались на готовом образе от разработчиков Promscale.

Получившаяся архитектура выглядит так:

Итоги и перспективы

Мы смотрим в будущее и планируем отказаться от отчётов в формате CSV в пользу красивого интерфейса собственной разработки. А по мере разработки собственной биллинговой системы начнём отгружать данные и туда для нужд отдела продаж и развития бизнеса. Но даже те CSV, что мы получили сейчас, уже сильно упрощают рабочие процессы всего Фланта.

А пока не дошли руки до фронтенда, мы сделали dashboard для Grafana (почему бы и нет, раз всё в стандартах Prometheus?..). Вот как это выглядит:

Общая сводная таблица по кластерам с Terraform-состояниямиОбщая сводная таблица по кластерам с Terraform-состояниямиРаспределение кластеров по облачным провайдерамРаспределение кластеров по облачным провайдерамРазбивка по используемым Inlet в Nginx Ingress-контроллерахРазбивка по используемым Inlet в Nginx Ingress-контроллерахКоличество podов Nginx Ingress-контроллеров с разбивкой по версиямКоличество podов Nginx Ingress-контроллеров с разбивкой по версиям

Впереди нас ждет продолжение пути автоматизации всего и вся с уменьшением необходимости ручных действий. В числе первых горячо ожидаемых плюшек переход к автоматическому применению изменений конфигураций Terraform, если таковые не подразумевают удаление каких-либо ресурсов (не являются деструктивными для кластера).

P.S.

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

Подробнее..

Простое создание Kubernetes-операторов с shell-operator прогресс проекта за год

10.07.2020 14:07:02 | Автор: admin


Kubernetes-операторы удобный механизм для расширения возможностей этой контейнерной платформы, по праву снискавший широкое признание в среде инженеров эксплуатации и им сочувствующих. О том, как они устроены и работают, мы рассказывали в уже далёком 2017-м. А в апреле прошлого года мы представили Open Source-проект shell-operator, который значительно упростил процесс создания Kubernetes-операторов.

Для этого был разработан фреймворк, позволяющий запускать произвольные скрипты (на Bash, Python и т.п.) в случае наступления определённых событий в K8s-кластере.

За минувшее время shell-operator обрёл свою пользовательскую базу (см. подробности в конце статьи) и, конечно, новые возможности. По случаю недавнего релиза v1.0.0-beta.11 (о бета-статусе см. дальше) мы решили рассказать о том, к чему проект пришёл за время своего существования, с момента анонса первой публичной версии.

Об устройстве и назначении


Но начнем с краткого пояснения, как работает shell-operator и для чего в принципе он может понадобиться.

Shell-operator запускается в podе Kubernetes-кластера. Он представлен там как:

  • Go-бинарник, который подписывается на события в K8s API и запускает хуки (передавая им подробности о случившемся);
  • набор хуков, каждый из которых скрипт на Bash, Python или любой другой исполняемый файл.

Хуки:

  • сами определяют, какие события и для каких объектов им нужны;
  • выполняют необходимые действия в случае наступления этих событий в K8s.

Таким образом, shell-operator это прослойка между событиями в Kubernetes API и скриптами для их обработки.

image

Зачем вообще был создан shell-operator? Операторы это стандарт для правильной автоматизации в рамках Kubernetes, однако их полноценная разработка (на Go с привлечением соответствующего SDK) не так проста. Наличие такого простого фреймворка, как shell-operator, значительно снижает порог вхождения в эту область, позволяя быстро и эффективно решать небольшие эксплуатационные задачи* внутри кластера. И, что не менее важно, делать это правильным путём.

О каких задачах идёт речь? Готовые примеры использования shell-operator можно найти в репозитории проекта. У нас же, в компании Флант, мы его применяем как библиотеку (да, так тоже можно было!). Он стал основой для addon-operator, управляющего дополнительными компонентами в Kubernetes.

NB: Анонс этого Open Source-проекта (addon-operator) можно найти здесь. А в докладе Расширяем и дополняем Kubernetes мы подробно рассказывали о причинах его появления, взаимосвязи с shell-operator и принципах функционирования.

Теперь о главных изменениях, представленных в shell-operator за последний год.

Основные новшества


В первых версиях shell-operator для хука был доступен только один объект тот, который связан с событием от кластера. Развитие хуков, используемых в рамках addon-operator, привело к тому, что хук подписывался на изменение объекта, но вызывал kubectl чтобы получить актуальный список других объектов. Чтобы убрать лишние вызовы kubectl и тем самым ускорить работу хуков, реализовано несколько возможностей получить доступ к актуальным спискам объектов:

  • Режим Synchronization + Event, когда хук при старте получает список актуальных объектов, а затем работает только с одним объектом. Этот режим включен по умолчанию можно сказать, что получился аналог reconcile loop из operator-sdk.
  • Режим с получением snapshot'ов, когда хук получает полные актуальные списки объектов при каждом запуске. (Snapshotы используются для кэширования объектов Kubernetes, используемых в хуках.)
  • Режим получения группы snapshot'ов. Может использоваться в случаях, когда хук подписан на разные типы ресурсов, а реагировать на изменения нужно на основе актуальной информации о всех ресурсах независимо от того, что изменилось.
  • Также появилась возможность следить за ресурсом, но не реагировать на его изменения, т.е. накапливать snapshot. Например, хук может реагировать на изменения в CustomResource и при этом получать актуальный объект ConfigMap без дополнительного вызова kubectl. (Для подробностей см. флаги executeHookOnSynchronization и executeHookOnEvent.)

Другие значительные нововведения:

  • Благодаря переходу на использование динамического клиента Kubernetes в shell-operator стало возможным подписываться на любой существующий kind (тип ресурса в Kubernetes API), в том числе и Custom Resources.
  • Хуки можно запускать в разных очередях (см. параметр queue). В дальнейшем для диагностики состояния очередей хуков была добавлена команда и соответствующие endpoints.
  • Добавлена возможность подписываться на несколько имён ресурсов.
  • Появилась подписка с динамическими пространствами имен, которая позволяет следить за ресурсами в namespaceах с указанными лейблами.
  • Хуки получили возможность экспортировать произвольные метрики для их дальнейшего scraping'а Prometheus'ом. Можно указать отдельный порт для этих метрик.
  • Добавлен специальный фреймворк, который упрощает написание хуков на shell.

Менее значимые изменения


  • Хук может возвращать конфигурацию в YAML-формате (в дополнение к JSON).
  • Добавлено логирование в формате JSON на базе logrus (см. LOG_TYPE в переменных окружения).
  • Добавлена настройка listen-address для возможности запуска в режиме hostNetwork: true.
  • Появились настройки rate limit (qps, burst) для клиента Kubernetes API.
  • Представлен флаг kube-server для указания адреса сервера Kubernetes API.
  • Убраны различные блокировки для ускорения параллельной работы хуков.
  • Выражения jqFilter теперь выполняются с помощью библиотеки libjq-go, не требуя запуска отдельного процесса jq.
  • Удалён zombie reaper, обрабатывающий сигнал SIGCHLD и устраняющий процессы-сироты, которые могут порождаться Bash-скриптами. Вместо внутренней реализации теперь используется внешняя tini.
  • Реализованы разные упрощения для подключения shell-operator как библиотеки.
  • Обновлена версия kubectl (с 1.13 до 1.17.4) и сделана сборка на основе alpine-3.11.

Статус и планы


Проект shell-operator всё ещё официально имеет статус бета-версии. Несмотря на это, как уже отмечалось выше, мы весьма интенсивно используем его как основу для addon-operator инструмента, который постоянно эксплуатируется во множестве (100+) K8s-кластеров.

Для стабильного релиза shell-operator как публичного проекта мы планируем (как минимум):

  • добавить e2e-тестирование (#63),
  • реализовать мульти-архитектурную сборку (#184),
  • обновить client-go до 0.18.0, внедрить context и окончательно разобраться с кэшированием объектов в client-go (#188).

Признание сообществом


За прошедшее время мы увидели явный интерес сообщества к shell-operator:

  • Проект упоминался не только в различных списках полезных утилит для Kubernetes (awesome-kubernetes, Cloud Zone), но и на специализированных вебинарах (Weaveworks), митапах (K8s Meetup Tokyo) и даже в книге.
  • Он нашёл свое практическое применение в реальных проектах (самый интересный из известных нам это инсталлятор для китайской K8s-платформы KubeSphere). На просторах GitHub можно найти множество простых операторов, реализованных на основе shell-operator (небольшую подборку из них мы приводили здесь).
  • У проекта появились сторонние контрибьюторы: их вклад пока не очень велик, но мы приветствуем всех желающих поучаствовать.
  • На GitHub собрано 600+ звёзд конечно, будем рады и новым! ;-)

Спасибо за интерес к shell-operator! Задавайте вопросы, если они у вас появились.

P.S.


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

Подробнее..

Краткий обзор операторов PostgreSQL для Kubernetes, наш выбор и опыт

25.09.2020 10:10:53 | Автор: admin


Всё чаще от клиентов поступают такие запросы: Хотим как Amazon RDS, но дешевле; Хотим как RDS, но везде, в любой инфраструктуре. Чтобы реализовать подобное managed-решение на Kubernetes, мы посмотрели на текущее состояние наиболее популярных операторов для PostgreSQL (Stolon, операторы от Crunchy Data и Zalando) и сделали свой выбор.

Эта статья полученный нами опыт и с теоретической точки зрения (обзор решений), и с практической стороны (что было выбрано и что из этого получилось). Но для начала давайте определимся, какие вообще требования предъявляются к потенциальной замене RDS

Что же такое RDS


Когда люди говорят про RDS, по нашему опыту, они подразумевают управляемый (managed) сервис СУБД, которая:

  1. легко настраивается;
  2. имеет возможность работы со снапшотами и восстанавливаться из них (желательно с поддержкой PITR);
  3. позволяет создавать топологии master-slave;
  4. имеет богатый список расширений;
  5. предоставляет аудит и управление пользователями/доступами.

Если говорить вообще, то подходы к реализации поставленной задачи могут быть весьма разными, однако путь с условным Ansible нам не близок. (К схожему выводу пришли и коллеги из 2GIS в результате своей попытки создать инструмент для быстрого развертывания отказоустойчивого кластера на основе Postgres.)

Именно операторы общепринятый подход для решения подобных задач в экосистеме Kubernetes. Подробнее о них применительно к базам данных, запускаемым внутри Kubernetes, уже рассказывал техдир Фланта, distol, в одном из своих докладов.

NB: Для быстрого создания несложных операторов рекомендуем обратить внимание на нашу Open Source-утилиту shell-operator. Используя её, можно это делать без знаний Go, а более привычными для сисадминов способами: на Bash, Python и т.п.

Для PostgreSQL существует несколько популярных K8s-операторов:

  • Stolon;
  • Crunchy Data PostgreSQL Operator;
  • Zalando Postgres Operator.

Посмотрим на них более внимательно.

Выбор оператора


Помимо тех важных возможностей, что уже были упомянуты выше, мы как инженеры по эксплуатации инфраструктуры в Kubernetes также ожидали от операторов следующего:

  • деплой из Git и с Custom Resources;
  • поддержку pod anti-affinity;
  • установку node affinity или node selector;
  • установку tolerations;
  • наличие возможностей тюнинга;
  • понятные технологии и команды.

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

Теперь к самим операторам PostgreSQL.

1. Stolon


Stolon от итальянской компании Sorint.lab в уже упомянутом докладе рассматривался как некий эталон среди операторов для СУБД. Это довольно старый проект: первый его публичный релиз состоялся еще в ноябре 2015 года, а GitHub-репозиторий может похвастать почти 3000 звёздами и 40+ контрибьюторами.

И действительно, Stolon отличный пример продуманной архитектуры:



С устройством этого оператора в подробностях можно ознакомиться в докладе или документации проекта. В целом же, достаточно сказать, что он умеет всё описанное: failover, прокси для прозрачного доступа клиентов, бэкапы. Причем прокси предоставляют доступ через один сервис endpoint в отличие от двух других решений, рассмотренных дальше (у них по два сервиса для доступа к базе).

Однако у Stolon нет Custom Resources, из-за чего его нельзя так деплоить, чтобы просто и быстро как горячие пирожки создавать экземпляры СУБД в Kubernetes. Управление осуществляется через утилиту stolonctl, деплой через Helm-чарт, а пользовательские пароли задаются через ConfigMap.

С одной стороны, получается, что оператор не очень-то является оператором (ведь он не использует CRD). Но с другой это очень гибкая система, которая позволяет настраивать ресурсы в K8s так, как вам удобно.

Но лично для нас не показался оптимальным путь заводить отдельный чарт под каждую БД. Поэтому мы стали искать альтернативы.

2. Crunchy Data PostgreSQL Operator


Оператор от Crunchy Data, молодого американского стартапа, выглядел логичной альтернативой. Его публичная история начинается с первого релиза в марте 2017 года, с тех пор GitHub-репозиторий получил чуть менее 1300 звёзд и 50+ контрибьюторов. Последний релиз от сентября был протестирован на работу с Kubernetes 1.151.18, OpenShift 3.11+ и 4.4+, GKE и VMware Enterprise PKS 1.3+.

Архитектура Crunchy Data PostgreSQL Operator также соответствует нашим требованиям:



Управление происходит через утилиту pgo, однако она в свою очередь генерирует Custom Resources для Kubernetes. Поэтому с точки зрения потенциальных пользователей оператор нас порадовал:

  • есть управление через CRD;
  • удобное управление пользователями (тоже через CRD);
  • интеграция с другими компонентами Crunchy Data Container Suite специализированной коллекции образов контейнеров для PostgreSQL и утилит для работы с ней (включая pgBackRest, pgAudit, расширения из contrib и т.д.).

Однако попытки начать использовать оператор выявили несколько проблем:

  • Не оказалось возможности tolerations предусмотрен только nodeSelector.
  • Создаваемые podы были частью Deploymentа, несмотря на то, что мы деплоили stateful-приложение. В отличие от StatefulSet, Deploymentы не умеют создавать диски.

Последний недостаток приводит к забавным моментам: на тестовой среде удалось запустить 3 реплики с одним диском localstorage, в результате чего оператор сообщал, что 3 реплики работают (хотя это было не так).

Еще одной особенностью этого оператора является его готовая интеграция с различными вспомогательными системами. Например, легко установить поставить pgAdmin и pgBounce, а в документации рассматриваются преднастроенные Grafana и Prometheus. В недавнем релизе 4.5.0-beta1 отдельно отмечается улучшенная интеграция с проектом pgMonitor, с которой оператор предлагает визуализацию метрик по PgSQL из коробки.

Тем не менее, странный выбор генерируемых Kubernetes-ресурсов привел нас к необходимости найти иное решение.

3. Zalando Postgres Operator


Продукты Zalando нам известны давно: есть опыт использования Zalenium и, конечно, мы пробовали Patroni их популярное HA-решение для PostgreSQL. О подходе компании к созданию Postgres Operator рассказывал один из его авторов Алексей Клюкин в эфире Postgres-вторника 5, и нам он приглянулся.

Это самое молодое решение из рассматриваемых в статье: первый релиз состоялся в августе 2018 года. Однако, даже несмотря на небольшое количество формальных релизов, проект прошёл большой путь, уже опередив по популярности решение от Crunchy Data (1300+ звёзд на GitHub) и максимальное число контрибьюторов (70+). Под капотом этого оператора используются решения, проверенные временем: Patroni и Spilo для управления, WAL-E для бэкапов, PgBouncer в качестве пула подключений.

Вот как представлена архитектура оператора от Zalando:



Оператор полностью управляется через Custom Resources, автоматически создает StatefulSet из контейнеров, которые затем можно кастомизировать, добавляя в pod различные sidecarы. Всё это значительный плюс в сравнении с оператором от Crunchy Data.

Поскольку именно решение от Zalando мы выбрали среди 3 рассматриваемых вариантов, дальнейшее описание его возможностей будет представлено ниже, сразу вместе с практикой применения.

Практика с Postgres Operator от Zalando


Деплой оператора происходит очень просто: достаточно скачать актуальный релиз с GitHub и применить YAML-файлы из директории manifests. Как вариант, можно также воспользоваться OperatorHub.

После установки стоит озаботиться настройкой хранилищ для логов и бэкапов. Она производится через ConfigMap postgres-operator в пространстве имен, куда вы установили оператор. Когда хранилища настроены, можно развернуть первый кластер PostgreSQL.

Например, стандартный деплой у нас выглядит следующим образом:

apiVersion: acid.zalan.do/v1kind: postgresqlmetadata: name: staging-dbspec: numberOfInstances: 3 patroni:   synchronous_mode: true postgresql:   version: "12" resources:   limits:     cpu: 100m     memory: 1Gi   requests:     cpu: 100m     memory: 1Gi sidecars: - env:   - name: DATA_SOURCE_URI     value: 127.0.0.1:5432   - name: DATA_SOURCE_PASS     valueFrom:       secretKeyRef:         key: password         name: postgres.staging-db.credentials   - name: DATA_SOURCE_USER     value: postgres   image: wrouesnel/postgres_exporter   name: prometheus-exporter   resources:     limits:       cpu: 500m       memory: 100Mi     requests:       cpu: 100m       memory: 100Mi teamId: staging volume:   size: 2Gi

Данный манифест деплоит кластер из 3 экземпляров с sidecar в виде postgres_exporter, с которого мы снимаем метрики приложения. Как видите, всё очень просто, и при желании можно сделать буквально неограниченное количество кластеров.

Стоит обратить внимание и на веб-панель для администрирования postgres-operator-ui. Она поставляется вместе с оператором и позволяет создавать и удалять кластеры, а также позволяет работать с бэкапами, которые делает оператор.


Список кластеров PostgreSQL


Управление бэкапами

Другой интересной особенностью является поддержка Teams API. Данный механизм автоматически создаёт роли в PostgreSQL, исходя из полученного списка имён пользователей. После этого API позволяет вернуть список пользователей, для которых автоматически создаются роли.

Проблемы и их решение


Однако использование оператора вскоре выявило несколько весомых недостатков:

  1. отсутствие поддержки nodeSelector;
  2. невозможность отключить бэкапы;
  3. при использовании функции создания баз не создаются привилегии по умолчанию;
  4. периодически не хватает документации или же она находится в неактуальном состоянии.

К счастью, многие из них могут быть решены. Начнём с конца проблем с документацией.

Скорее всего вы столкнетесь с тем, что не всегда ясно, как прописать бэкап и как подключить бэкапный бакет к Operator UI. Об этом в документации говорится вскользь, а реальное описание есть в PR:

  1. нужно сделать секрет;
  2. передать его оператору в параметр pod_environment_secret_name в CRD с настройками оператора или в ConfigMap (зависит от того, как вы решили устанавливать оператор).

Однако, как оказалось, на текущий момент это невозможно. Именно поэтому мы собрали свою версию оператора с некоторыми дополнительными сторонними наработками. Подробнее о ней см. ниже.

Если передавать оператору параметры для бэкапа, а именно wal_s3_bucket и ключи доступа в AWS S3, то он будет бэкапить всё: не только базы в production, но и staging. Нас это не устроило.

В описании параметров к Spilo, что является базовой Docker-обёрткой для PgSQL при использовании оператора, выяснилось: можно передать параметр WAL_S3_BUCKET пустым, тем самым отключив бэкапы. Более того, к большой радости нашёлся и готовый PR, который мы тут же приняли в свой форк. Теперь достаточно просто добавить enableWALArchiving: false в ресурс кластера PostgreSQL.

Да, была возможность сделать иначе, запустив 2 оператора: один для staging (без бэкапов), а второй для production. Но так мы смогли обойтись одним.

Ок, мы научились передавать в базы доступ для S3 и бэкапы начали попадать в хранилище. Как заставить работать страницы бэкапов в Operator UI?



В Operator UI потребуется добавить 3 переменные:

  • SPILO_S3_BACKUP_BUCKET
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

После этого управление бэкапами станет доступно, что в нашем случае упростит работу со staging, позволив доставлять туда срезы с production без дополнительных скриптов.

В качестве еще одного плюса называлась работа с Teams API и широкие возможности для создания баз и ролей средствами оператора. Однако создаваемые роли не имели прав по умолчанию. Соответственно, пользователь с правами на чтение не мог читать новые таблицы.

Почему так? Несмотря на то, что в коде есть необходимые GRANT, они применяются далеко не всегда. Есть 2 метода: syncPreparedDatabases и syncDatabases. В syncPreparedDatabases несмотря на то, что в секции preparedDatabases есть есть условие defaultRoles и defaultUsers для создания ролей, права по умолчанию не применяются. Мы в процессе подготовки патча, чтобы данные права автоматически применялись.

И последний момент в актуальных для нас доработках патч, добавляющий Node Affinity в создаваемый StatefulSet. Наши клиенты зачастую предпочитают сокращать расходы, используя spot-инстансы, а на них явно не стоит размещать сервисы БД. Этот вопрос можно было бы решить и через tolerations, но наличие Node Affinity даёт большую уверенность.

Что получилось?


По итогам решения перечисленных проблем мы форкнули Postgres Operator от Zalando в свой репозиторий, где он собирается со столь полезными патчами. А для пущего удобства собрали и Docker-образ.

Список PR, принятых в форк:


Будет здорово, если сообщество поддержит эти PR, чтобы они попали в следующую версию оператора (1.6).

Бонус! История успеха с миграцией production


Если вы используете Patroni, на оператор можно мигрировать живой production с минимальным простоем.

Spilo позволяет делать standby-кластеры через S3-хранилища с Wal-E, когда бинарный лог PgSQL сначала сохраняется в S3, а затем выкачивается репликой. Но что делать, если у вас не используется Wal-E в старой инфраструктуре? Решение этой проблемы уже было предложено на хабре.

На помощь приходит логическая репликация PostgreSQL. Однако не будем вдаваться в детали, как создавать публикации и подписки, потому что наш план потерпел фиаско.

Дело в том, что в БД было несколько нагруженных таблиц с миллионами строк, которые, к тому же, постоянно пополнялись и удалялись. Простая подписка с copy_data, когда новая реплика копирует всё содержимое с мастера, просто не успевала за мастером. Копирование контента работало неделю, но так и не догнало мастер. В итоге, разобраться с проблемой помогла статья коллег из Avito: можно перенести данные, используя pg_dump. Опишу наш (немного доработанный) вариант этого алгоритма.

Идея заключается в том, что можно сделать выключенную подписку, привязанную к конкретному слоту репликации, а затем исправить номер транзакции. В наличии были реплики для работы productionа. Это важно, потому что реплика поможет создать консистентный dump и продолжить получать изменения из мастера.

В последующих командах, описывающих процесс миграции, будут использоваться следующие обозначения для хостов:

  1. master исходный сервер;
  2. replica1 потоковая реплика на старом production;
  3. replica2 новая логическая реплика.

План миграции


1. Создадим на мастере подписку на все таблицы в схеме public базы dbname:

psql -h master -d dbname -c "CREATE PUBLICATION dbname FOR ALL TABLES;"

2. Cоздадим слот репликации на мастере:

psql -h master -c "select pg_create_logical_replication_slot('repl', 'pgoutput');"

3. Остановим репликацию на старой реплике:

psql -h replica1 -c "select pg_wal_replay_pause();"

4. Получим номер транзакции с мастера:

psql -h master -c "select replay_lsn from pg_stat_replication where client_addr = 'replica1';"

5. Снимем dump со старой реплики. Будем делать это в несколько потоков, что поможет ускорить процесс:

pg_dump -h replica1 --no-publications --no-subscriptions -O -C -F d -j 8 -f dump/ dbname

6. Загрузим dump на новый сервер:

pg_restore -h replica2 -F d -j 8 -d dbname dump/

7. После загрузки дампа можно запустить репликацию на потоковой реплике:

psql -h replica1 -c "select pg_wal_replay_resume();"

7. Создадим подписку на новой логической реплике:

psql -h replica2 -c "create subscription oldprod connection 'host=replica1 port=5432 user=postgres password=secret dbname=dbname' publication dbname with (enabled = false, create_slot = false, copy_data = false, slot_name='repl');"

8. Получим oid подписки:

psql -h replica2 -d dbname -c "select oid, * from pg_subscription;"

9. Допустим, был получен oid=1000. Применим номер транзакции к подписке:

psql -h replica2 -d dbname -c "select pg_replication_origin_advance('pg_1000', 'AA/AAAAAAAA');"

10. Запустим репликацию:

psql -h replica2 -d dbname -c "alter subscription oldprod enable;"

11. Проверим статус подписки, репликация должна работать:

psql -h replica2 -d dbname -c "select * from pg_replication_origin_status;"psql -h master -d dbname -c "select slot_name, restart_lsn, confirmed_flush_lsn from pg_replication_slots;"

12. После того, как репликация запущена и базы синхронизированы, можно совершать переключение.

13. После отключения репликации надо исправить последовательности. Это хорошо описано в статье на wiki.postgresql.org.

Благодаря такому плану переключение прошло с минимальными задержками.

Заключение


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

Рассмотрев три самых популярных Kubernetes-оператора для PostgreSQL, мы остановили свой выбор на проекте от Zalando. И с ним пришлось преодолеть определенные трудности, но результат действительно порадовал, так что мы планируем расширить этот опыт и на некоторые другие инсталляции PgSQL. Если у вас есть опыт использования схожих решений будем рады увидеть подробности в комментариях!

P.S.


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

Подробнее..

Проблема умной очистки образов контейнеров и её решение в werf

06.10.2020 10:05:04 | Автор: admin


В статье рассмотрена проблематика очистки образов, которые накапливаются в реестрах контейнеров (Docker Registry и его аналогах) в реалиях современных CI/CD-пайплайнов для cloud native-приложений, доставляемых в Kubernetes. Приведены основные критерии актуальности образов и вытекающие из них сложности при автоматизации очистки, сохранения места и удовлетворения потребностям команд. Наконец, на примере конкретного Open Source-проекта мы расскажем, как эти сложности можно преодолеть.

Введение


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

  1. использовать фиксированное количество тегов для образов;
  2. каким-либо образом очищать образы.

Первое ограничение иногда допустимо для небольших команд. Если разработчикам хватает постоянных тегов (latest, main, test, boris и т.п.), реестр не будет раздуваться в размерах и долгое время можно вообще не думать об очистке. Ведь все неактуальные образы перетираются, а для очистки просто не остаётся работы (всё делается штатным сборщиком мусора).

Тем не менее, такой подход сильно ограничивает разработку и редко применим к CI/CD современных проектов. Неотъемлемой частью разработки стала автоматизация, которая позволяет гораздо быстрее тестировать, развертывать и доставлять новый функционал пользователям. Например, у нас во всех проектах при каждом коммите автоматически создается CI-пайплайн. В нём собирается образ, тестируется, выкатывается в различные Kubernetes-контуры для отладки и оставшихся проверок, а если всё хорошо изменения доходят до конечного пользователя. И это давно не rocket science, а обыденность для многих скорее всего и для вас, раз вы читаете данную статью.

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

Но как вообще определить, актуален ли образ?

Критерии актуальности образа


В подавляющем большинстве случаев основные критерии будут таковы:

1. Первый (самый очевидный и самый критичный из всех) это образы, которые в настоящий момент используются в Kubernetes. Удаление этих образов может привести к серьезным издержкам в связи с простоем production (например, образы могут потребоваться при репликации) или свести на нет усилия команды, которая занимается отладкой на каком-либо из контуров. (По этой причине мы даже сделали специальный Prometheus exporter, отслеживающий отсутствие таких образов в любом Kubernetes-кластере.)

2. Второй (менее очевиден, но тоже очень важен и снова относится к эксплуатации) образы, которые требуются для отката в случае выявления серьёзных проблем в текущей версии. Например, в случае с Helm это образы, которые используются в сохраненных версиях релиза. (К слову, по умолчанию в Helm лимит в 256 ревизий, но вряд ли у кого-то реально есть потребность в сохранении такого большого количества версий?..) Ведь мы, в частности, для того и храним версии, чтобы можно было их потом использовать, т.е. откатываться на них в случае необходимости.

3. Третий потребности разработчиков: все образы, которые связаны с их текущими работами. К примеру, если мы рассматриваем PR, то имеет смысл оставлять образ, соответствующий последнему коммиту и, скажем, предыдущему коммиту: так разработчик сможет оперативно возвращаться к любой задаче и работать с последними изменениями.

4. Четвертый образы, которые соответствуют версиям нашего приложения, т.е. являются конечным продуктом: v1.0.0, 20.04.01, sierra и т.д.

NB: Определенные здесь критерии были сформулированы на основе опыта взаимодействия с десятками команд разработчиков из разных компаний. Однако, конечно, в зависимости от особенностей в процессах разработки и используемой инфраструктуры (например, не используется Kubernetes), эти критерии могут отличаться.

Соответствие критериям и существующие решения


Популярные сервисы с container registry, как правило, предлагают свои политики очистки образов: в них вы можете определять условия, при которых тег удаляется из registry. Однако возможности этих условий ограничиваются такими параметрами, как имена, время создания и количество тегов*.

* Зависит от конкретных реализаций container registry. Мы рассматривали возможности следующих решений: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io по состоянию на сентябрь'2020.

Такого набора параметров вполне достаточно, чтобы удовлетворить четвёртому критерию то есть отбирать образы, соответствующие версиям. Однако для всех остальных критериев приходится выбирать какое-то компромиссное решение (более жесткую или, наоборот, щадящую политику) в зависимости от ожиданий и финансовых возможностей.

Например, третий критерий связанный с потребностями разработчиков может решаться за счет организации процессов внутри команд: специфичного именования образов, ведения специальных allow lists и внутренних договоренностей. Но в конечном счете его всё равно необходимо автоматизировать. А если возможностей готовых решений не хватает, приходится делать что-то своё.

Аналогична и ситуация с двумя первыми критериями: их невозможно удовлетворить без получения данных от внешней системы той самой, где происходит развёртывание приложений (в нашем случае это Kubernetes).

Иллюстрация workflow в Git


Предположим, вы работаете примерно по такой схеме в Git:



Иконкой с головой на схеме отмечены образы контейнеров, которые в настоящий момент развёрнуты в Kubernetes для каких-либо пользователей (конечных пользователей, тестировщиков, менеджеров и т.д.) или используются разработчиками для отладки и подобных целей.

Что произойдёт, если политики очистки позволяют оставлять (не удалять) образы только по заданным названиям тегов?



Очевидно, такой сценарий никого не обрадует.

Что изменится, если политики позволяют не удалять образы по заданному временному интервалу / числу последних коммитов?



Результат стал значительно лучше, однако всё ещё далёк от идеала. Ведь у нас по-прежнему есть разработчики, которым нужны образы в реестре (или даже развёрнутые в K8s) для отладки багов

Резюмируя сложившуюся на рынке ситуацию: доступные в реестрах контейнеров функции не предлагают достаточной гибкости при очистке, а главная тому причина нет возможности взаимодействовать с внешним миром. Получается, что команды, которым требуется такая гибкость, вынуждены самостоятельно реализовывать удаление образов снаружи, используя Docker Registry API (или нативный API соответствующей реализации).

Однако мы искали универсальное решение, которое автоматизировало бы очистку образов для разных команд, использующих разные реестры

Наш путь к универсальной очистке образов


Откуда такая потребность? Дело в том, что мы не отдельно взятая группа разработчиков, а команда, которая обслуживает сразу множество таковых, помогая комплексно решать вопросы CI/CD. И главный технический инструмент для этого Open Source-утилита werf. Её особенность в том, что она не выполняет единственную функцию, а сопровождает процессы непрерывной доставки на всех этапах: от сборки до деплоя.

Публикация в реестре* образов (сразу после их сборки) очевидная функция такой утилиты. А раз образы туда помещаются на хранение, то если ваше хранилище не безгранично нужно отвечать и за их последующую очистку. О том, как мы добились успеха в этом, удовлетворяя всем заданным критериям, и будет рассказано далее.

* Хоть сами реестры могут быть различными (Docker Registry, GitLab Container Registry, Harbor и т.д.), их пользователи сталкиваются с одними и теми же проблемами. Универсальное решение в нашем случае не зависит от реализации реестра, т.к. выполняется вне самих реестров и предлагает одинаковое поведение для всех.

Несмотря на то, что мы используем werf как пример реализации, надеемся, что использованные подходы будут полезны и другим командам, столкнувшимся с аналогичными сложностями.

Итак, мы занялись внешней реализацией механизма для очистки образов вместо тех возможностей, что уже встроены в реестры для контейнеров. Первым шагом стало использование Docker Registry API для создания всё тех же примитивных политик по количеству тегов и времени их создания (упомянутых выше). К ним был добавлен allow list на основе образов, используемых в развёрнутой инфраструктуре, т.е. Kubernetes. Для последнего было достаточно через Kubernetes API перебирать все задеплоенные ресурсы и получать список из значений image.

Такое тривиальное решение закрыло самую критичную проблему (критерий 1), но стало только началом нашего пути по улучшению механизма очистки. Следующим и куда более интересным шагом стало решение связать публикуемые образы с историей Git.

Схемы тегирования


Для начала мы выбрали подход, при котором конечный образ должен хранить необходимую информацию для очистки, и выстроили процесс на схемах тегирования. При публикации образа пользователь выбирал определённую опцию тегирования (git-branch, git-commit или git-tag) и использовал соответствующее значение. В CI-системах установка этих значений выполнялась автоматически на основании переменных окружения. По сути конечный образ связывался с определённым Git-примитивом, храня необходимые данные для очистки в лейблах.

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

  • При удалении ветки/тега в Git автоматически удалялись и связанные образы в registry.
  • Количество образов, связанное с Git-тегами и коммитами, можно было регулировать количеством тегов, использованных в выбранной схеме, и временем создания связанного коммита.

В целом, получившаяся реализация удовлетворяла нашим потребностям, но вскоре нас ожидал новый вызов. Дело в том, что за время использования схем тегирования по Git-примитивам мы столкнулись с рядом недостатков. (Поскольку их описание выходит за рамки темы этой статьи, все желающие могут ознакомиться с подробностями здесь.) Поэтому, приняв решение о переходе на более эффективный подход к тегированию (content-based tagging), нам пришлось пересмотреть и реализацию очистки образов.

Новый алгоритм


Почему? При тегировании в рамках content-based каждый тег может удовлетворять множеству коммитов в Git. При очистке образов больше нельзя исходить только из коммита, на котором новый тег был добавлен в реестр.

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

  • коммита, на котором выполнялась публикация (при этом не имеет значения, добавился, изменился или остался прежним образ в реестре контейнеров);
  • и нашего внутреннего идентификатора, соответствующего собранному образу.

Другими словами, была обеспечена связь публикуемых тегов с коммитами в Git.

Итоговая конфигурация и общий алгоритм


Пользователям при конфигурации очистки стали доступны политики, по которым осуществляется выборка актуальных образов. Каждая такая политика определяется:

  • множеством references, т.е. Git-тегами или Git-ветками, которые используются при сканировании;
  • илимитом искомых образов для каждого reference из множества.

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

cleanup:  keepPolicies:  - references:      tag: /.*/      limit:        last: 10  - references:      branch: /.*/      limit:        last: 10        in: 168h        operator: And    imagesPerReference:      last: 2      in: 168h      operator: And  - references:        branch: /^(main|staging|production)$/    imagesPerReference:      last: 10

Такая конфигурация содержит три политики, которые соответствуют следующим правилам:

  1. Сохранять образ для 10 последних Git-тегов (по дате создания тега).
  2. Сохранять по не более 2 образов, опубликованных за последнюю неделю, для не более 10 веток с активностью за последнюю неделю.
  3. Сохранять по 10 образов для веток main, staging и production.

Итоговый же алгоритм сводится к следующим шагам:

  • Получение манифестов из container registry.
  • Исключение образов, используемых в Kubernetes, т.к. их мы уже предварительно отобрали, опросив K8s API.
  • Сканирование Git-истории и исключение образов по заданным политикам.
  • Удаление оставшихся образов.

Возвращаясь к нашей иллюстрации, вот что получается с werf:



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

Заключение


  • Рано или поздно с проблемой переполнения registry сталкивается большинство команд.
  • При поиске решений в первую очередь необходимо определить критерии актуальности образа.
  • Инструменты, предлагаемые популярными сервисами container registry, позволяют организовать очень простую очистку, которая не учитывает внешний мир: образы, используемые в Kubernetes, и особенности рабочих процессов в команде.
  • Гибкий и эффективный алгоритм должен иметь представление о CI/CD-процессах, оперировать не только данными Docker-образов.

P.S.


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

Подробнее..

Обзор операторов PostgreSQL для Kubernetes. Часть 2 дополнения и итоговое сравнение

13.11.2020 10:21:32 | Автор: admin


На прошлую статью, где мы рассмотрели три оператора PostgreSQL для Kubernetes (Stolon, Crunchy Data и Zalando), поделились своим выбором и опытом эксплуатации, поступила отличная обратная связь от сообщества*.

Продолжая эту тему, мы добавили в обзор два других решения, на которые нам указали в комментариях: StackGres и KubeDB, и сделали сводную таблицу сравнения. Также за время эксплуатации оператора от Zalando у нас появились новые интересные кейсы спешим поделиться и ими.

* Первую часть статьи заметили даже разработчики Zalando, благодаря чему(?) они решили активизироваться в приёме некоторых PR. Это не может не радовать!



Итак, сначала дополним обзор ещё двумя решениями

KubeDB




Оператор KubeDB разрабатывается командой AppsCode c 2017 года и хорошо известен в Kubernetes-сообществе. Он претендует на звание платформы для запуска различных stateful-сервисов в Kubernetes и поддерживает:

  • PostgreSQL;
  • Elasticsearch;
  • MySQL;
  • MongoDB;
  • Redis;
  • Memcache.

Однако в контексте статьи мы рассмотрим только PostgreSQL.

В KubeDB реализован интересный подход с использованием нескольких CRD, которые содержат в себе различные настройки, такие как:

  • версия с образом базы и вспомогательными утилитами за это отвечает ресурс postgresversions.catalog.kubedb.com;
  • параметры восстановления ресурс бэкапа <codesnapshots.kubedb.com

собственно, корневой ресурс postgreses.kubedb.com, который собирает в себе все эти ресурсы и запускает кластер PostgreSQL.

Особой кастомизации KubeDB не предоставляет (в отличие от оператора Zalando), но вы всегда можете управлять конфигом PostgreSQL через ConfigMap.

Интересной особенностью является ресурс dormantdatabases.kubedb.com, который предоставляет защиту от дурака: все удалённые базы сначала переводятся в такое архивное состояние и могут быть легко восстановлены в случае необходимости. Жизненный цикл БД в KubeDB описывается следующей схемой:



Что же касается самого технологического стека, то тут используются свои наработки для управления кластерами, а не знакомые всем Patroni или Repmgr. Хотя для полинга соединений используется pgBouncer, который также создается отдельным CRD (pgbouncers.kubedb.com). Кроме того, разработчики предоставляют плагин для kubectl, который позволяет удобно управлять базами через всем привычную утилиту, и это огромный плюс на фоне Stolon или Crunchy Data.

KubeDB интегрируется с другими решениями AppsCode, чем напоминает Crunchy Data. Если вы везде используете решения этого вендора, то KubeDB, безусловно, отличный выбор.

Наконец, хочется отметить отличную документацию этого оператора, которая находится в отдельном репозитории GitHub. В ней есть развернутые примеры использования, включая полные примеры конфигураций CRD.

Есть минусы у KubeDB. Многие возможности: бэкапы, полинг соединений, снапшоты, dormant-базы доступны только в enterprise-версии, а для её использования требуется купить подписку у AppsCode. Кроме того, самой старшей поддерживаемой версией PostgreSQL из коробки является 11.x. Перечеркивают ли эти моменты изящную архитектуру KubeDB решать вам.

StackGres




В твиттере и в комментариях к предыдущей статье нам резонно указали на оператор StackGres. Разработка данного оператора началась совсем недавно, в мае 2019 году. В нем используются известные и проверенные технологии: Patroni, PgBouncer, WAL-G и Envoy.

Общая схема оператора выглядит так:



Кроме того, в комплекте с оператором можно установить:

  • веб-панель, как в Zalando;
  • систему сбора логов;
  • систему мониторинга, аналогичную Crunchy Data, о которой мы говорили в первой части;
  • систему сбора бэкапов на основе MinIO, хотя можно подключить и внешнее хранилище.

В целом же, данный оператор использует подход, очень схожий с KubeDB: он предоставляет сразу несколько ресурсов для описания компонентов кластера, создания конфигов и задания ресурсов.

Вкратце про все CRD:

  • sgbackupconfigs.stackgres.io, sgpgconfigs.stackgres.io, sgpoolconfigs.stackgres.io описание кастомных конфигов;
  • sginstanceprofiles.stackgres.io размер инстанса Postgres, который будет использоваться как limit/request для контейнера с PostgreSQL/Patroni. Для остальных контейнеров лимитов нет;
  • sgclusters.stackgres.io когда есть конфигурации для базы, пула коннектов и бэкапа, можно создать кластер PostgreSQL, который описывается этим CRD;
  • sgbackups.stackgres.io ресурс, схожий со snapshot у KubeDB и позволяющий обратиться к конкретному бэкапу из кластера K8s.

Однако оператор не позволяет использовать свои кастомные сборки образов или же несколько вспомогательных sidecar для сервера баз данных. Pod c Postgres содержит 5 контейнеров:



Из них мы можем отключить экспортер метрик, пулер коннектов и контейнер со вспомогательными утилитами администратора (psql, pg_dump и так далее). Да, оператор позволяет при инициализации кластера баз данных подключить скрипты с SQL-кодом для инициализации БД или создания пользователей, но не более того. Это сильно ограничивает нас в кастомизации, например, в сравнении с тем же оператором Zalando, где можно добавлять нужные sidecar с Envoy, PgBouncer и любыми другими вспомогательными контейнерами (хороший пример подобной связки будет ниже).

Подводя итог, можно сказать, что этот оператор понравится тем, кто хочет просто управлять PostgreSQL в кластере Kubernetes. Отличный выбор для тех, кому нужен инструмент с простой документацией, покрывающей все аспекты работы оператора, и кому не нужно делать сложные связки контейнеров и баз.

Итоговое сравнение


Мы знаем, что наши коллеги нежно любят сводные таблицы, поэтому приводим подобную для Postgres-операторов в Kubernetes.

Забегая вперед: после рассмотрения двух дополнительных кандидатов мы остаёмся при мнении, что решение от Zalando оптимальный для нашего случая продукт, так как позволяет реализовывать очень гибкие решения, имеет большой простор для кастомизации.

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

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

Stolon Crunchy Data Zalando KubeDB StackGres
Текущая версия 0.16.0 4.5.0 1.5.0 0.13 0.9.2
Версии PostgreSQL 9.49.6, 10, 11, 12 9.5, 9.6, 10, 11, 12 9.6, 10, 11, 12 9.6, 10, 11 11, 12
Общие возможности
Кластеры PgSQL
Теплый и горячий резерв
Синхронная репликация
Потоковая репликация
Автоматический failover
Непрерывное архивирование
Инициализация: из WAL-архива
Бэкапы: мгновенные, по расписанию
Бэкапы: управляемость из кластера
Инициализация: из снапшота, со скриптами
Специализированные возможности
Встроенная поддержка Prometheus
Кастомная конфигурация
Кастомный Docker-образ
Внешние CLI-утилиты
(kubectl-плагин)
Конфигурация через CRD
Кастомизация Pod'ов
NodeSelector и NodeAffinity
(через патчи)
Tolerations
Pod anti-affinity

Бонусы про оператор от Zalando


Ежедневно пользуясь решением от Zalando, мы столкнулись с некоторыми сложностями, и этим опытом хотелось бы поделиться с сообществом.

1. Приватные репозитории


Недавно нам потребовалось запустить через оператор наряду с PostgreSQL контейнер с SFTP, который позволил бы получать выгрузки из базы в формате CSV. Сам SFTP-сервер со всеми нужными параметрами был собран в закрытом registry.

И тут стало ясно, что оператор не умеет работать с registry secrets. К счастью, с такой проблемой мы были не одни с такой проблемой: все легко решилось коллегами на GitHub. Оказалось, что достаточно добавить в описание ServiceAccount имя с доступами в registry:

pod_service_account_definition: '{ "apiVersion": "v1", "kind": "ServiceAccount", "metadata": { "name": "zalando-postgres-operator" }, "imagePullSecrets": [ { "name": "my-fine-secret" } ] }'

2. Дополнительные хранилища и init-контейнеры


Для работы SFTP нам также требовалось корректно выставлять права на директории, чтобы заработал chroot. Возможно, не все знают, но OpenSSH-сервер требует особых привилегий на директории. Например, чтобы пользователь видел только свой /home/user, необходимо, чтобы /home принадлежал root с правами 755, а уже /home/user был доступен пользователю. Соответственно, мы решили использовать init-контейнер, который исправлял бы права на директории.

Но оператор не умеет пробрасывать дополнительные диски в init-контейнеры! Благо, есть подходящий PR, которым мы и дополнили свою сборку оператора.

3. Перезапуск PgSQL при проблемах с control plane


В процессе эксплуатации кластеров на основе Patroni в Kubernetes мы получили от клиента странную проблему: ровно в 4 часа ночи по Москве обрывались все подключения PostgeSQL. Разбираясь в ситуации, мы обнаружили следующее в логах Spilo:

2020-10-21 01:01:10,538 INFO: Lock owner: production-db-0; I am production-db-02020-10-21 01:01:14,759 ERROR: failed to update leader lock2020-10-21 01:01:15,236 INFO: demoted self because failed to update leader lock in DCS2020-10-21 01:01:15,238 INFO: closed patroni connection to the postgresql cluster2020-10-21 01:01:15 UTC [578292]: [1-1] 5f8f885b.8d2f4 0     LOG:  Auto detecting pg_stat_kcache.linux_hz parameter...

Согласно issue на GitHub, это означает, что Patoni не смог обработать ошибку Kubernetes API и упал с ошибкой. А проблемы с API были связаны с тем, что в 4 часа стартовали сразу 50 CronJob, что приводило к проблемам в etcd:

2020-10-21 01:01:14.589198 W | etcdserver: read-only range request "key:\"/registry/deployments/staging/db-worker-db1\" " with result "range_response_count:1 size:2598" took too long (3.688609392s) to execute

Ситуация исправлена в версии Patroni 2.0. По этой причине мы собрали версию Spilo из мастера. При сборке стоит учитывать, что требуется взять PR с исправлениями сборки, который на данный момент уже принят в мастер.

4. Пересоздание контейнеров


Напоследок, еще одна интересная проблема. В последнем релизе в оператор была добавлена возможность делать sidecar-контейнеры с их полным описанием в CRD оператора. Разработчики заметили, что базы периодически перезапускаются. В логах оператора было следующее:

time="2020-10-28T20:58:25Z" level=debug msg="spec diff between old and new statefulsets: \nTemplate.Spec.Volumes[2].VolumeSource.ConfigMap.DefaultMode: &int32(420) != nil\nTemplate.Spec.Volumes[3].VolumeSource.ConfigMap.DefaultMode: &int32(420) != nil\nTemplate.Spec.Containers[0].TerminationMessagePath: \"/dev/termination-log\" != \"\"\nTemplate.Spec.Containers[0].TerminationMessagePolicy: \"File\" != \"\"\nTemplate.Spec.Containers[1].Ports[0].Protocol: \"TCP\" != \"\"\nTemplate.Spec.Containers[1].TerminationMessagePath: \"/dev/termination-log\" != \"\"\nTemplate.Spec.Containers[1].TerminationMessagePolicy: \"File\" != \"\"\nTemplate.Spec.RestartPolicy: \"Always\" != \"\"\nTemplate.Spec.DNSPolicy: \"ClusterFirst\" != \"\"\nTemplate.Spec.DeprecatedServiceAccount: \"postgres-pod\" != \"\"\nTemplate.Spec.SchedulerName: \"default-scheduler\" != \"\"\nVolumeClaimTemplates[0].Status.Phase: \"Pending\" != \"\"\nRevisionHistoryLimit: &int32(10) != nil\n" cluster-name=test/test-psql pkg=cluster worker=0

Ни для кого не секрет, что при создании контейнера и podа добавляются директивы, которые не обязательно описывать. К ним относятся:

  • DNSPolicy;
  • SchedulerName;
  • RestartPolicy;
  • TerminationMessagePolicy;

Логично предположить, чтобы такое поведение учитывалось в операторе, однако, как оказалось, он плохо воспринимает секцию портов:

    ports:    - name: sftp      containerPort: 22

При создании podа автоматически добавляется протокол TCP, что не учитывается оператором. Итог: чтобы решить проблему, надо или удалить порты, или добавить протокол.

Заключение


Управление PostgreSQL в рамках Kubernetes нетривиальная задача. Пожалуй, на текущий момент на рынке нет оператора, который покрывал бы все потребности DevOps-инженеров. Однако среди уже существующих инструментов есть продвинутые и довольно зрелые варианты, из которых можно выбрать то, что лучше всего соответствует имеющимся потребностям.

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

Говоря же об операторах, рассмотренных в этой части статьи (KubeDB и StackGres), стоит отметить, что они оснащены уникальными функциями для управления бэкапами из кластера, что может стать одним из факторов роста их популярности в ближайшем будущем.

P.S.


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

Подробнее..

Прогресс shell-operator и addon-operator хуки как admission webhooks, Helm 3, OpenAPI, хуки на Go и многое другое

18.01.2021 10:10:41 | Автор: admin

Shell-operator и addon-operator Open Source-проекты компании Флант для администраторов Kubernetes, представленные в апреле 2019 года. Первый призван упростить создание K8s-операторов: для этого с ним достаточно писать простые скрипты (на Bash, Python и т.п.) или любые бинарники, которые будут вызываться в случае наступления определённых событий в Kubernetes API. Второй (addon-operator) его старший брат, цель которого упростить установку Helm-чартов в кластер, используя для их настройки хуки shell-operatorа.

В последний раз мы рассказывали о возможностях shell-operator по состоянию на релиз v1.0.0-beta.11 (летом прошлого года), если не считать последовавшего доклада на KubeCon EU2020, который знакомил с проектом тех, кто о нём ещё не знает. (К слову, этот доклад мы по-прежнему рекомендуем всем желающим разобраться, как shell-operator облегчает жизнь при создании операторов, и увидеть наглядные примеры его применения.)

За минувшее время и shell-operator, и addon-operator получили множество интересных новшеств, которым и посвящена эта статья.

Изменения в shell-operator v1.0.0-rc1

Хуки для shell-operator теперь можно использовать как обработчики для ValidatingWebhookConfiguration одного из вариантов admission webhook. Т.е. хук может проверить ресурс Kubernetes во время создания или редактирования и отклонить операцию, если ресурс не соответствует каким-то правилам. Таким правилом может быть такая политика: можно создавать только ресурсы с образом из repo.example.com. Пример реализации подобной политики можно посмотреть в директории 204-validating-webhook. Shell-operator поддерживает такие хуки для кластеров Kubernetes с версией не ниже 1.16.

Иллюстрация того, как происходит конфигурация такого хука (фрагмент shell-хука из примера выше):

function __config__(){    cat <<EOFconfigVersion: v1kubernetesValidating:- name: private-repo-policy.example.com  namespace:    labelSelector:      matchLabels:        # helm adds a 'name' label to a namespace it creates        name: example-204  rules:  - apiGroups:   ["stable.example.com"]    apiVersions: ["v1"]    operations:  ["CREATE", "UPDATE"]    resources:   ["crontabs"]    scope:       "Namespaced"EOF}

Другое новшество группу произвольных метрик теперь можно удалить, вернув ключ action:

{"group":"group_name_1", "action":"expire"}

Это удобно, когда пропадают объекты, сведения о которых были в метриках. Подробный пример разобран в документации.

Остальные значимые нововведения в shell-operator разбиты на категории:

1. Улучшения в потреблении ресурсов и производительности

  • Период ресинхронизации информеров теперь случайно распределён во времени. Без распределения все информеры одновременно обращались к API-серверу, что периодически создавало лишнюю нагрузку.

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

  • В операциях на чтение в очередях сделаны read-only-локи вместо общего лока и на запись, и на чтение.

  • Добавлены метрики с процессорным временем и с потреблением памяти для каждого хука (см. shell_operator_hook_run_sys_cpu_seconds в METRICS).

2. Изменения в сборке образа

  • Теперь flant/shell-operator это образ с поддержкой архитектур AMD64, ARM и ARM64 (привет любителям Raspberry Pi!).

  • Бинарный файл shell-operator собирается статически и должен работать в любом Linux-дистрибутиве.

  • Образ flant/shell-operator с Bash, kubectl и jq теперь только на основе Alpine. Если требуется другой дистрибутив, то бинарный файл можно взять из основного образа, а Dockerfile есть в примерах.

  • Убрана директория .git, попавшая в образ по ошибке.

  • Обновлены версии инструментов: Alpine 3.12, kubectl 1.19.4, Go 1.15.

  • Бинарный файл jq собран из того же коммита, что и libjq*, чтобы устранить проблемы производительности jq-1.6 (#206).

* Кстати, libjq-go это наш небольшой Open Source-проект, предлагающий CGO bindings для jq. Он был создан для нужд shell-operator, но недавно мы встретили и другой пример его использования в проекте Xbus. Это платформа французской компании для интеграции enterprise-систем, построенная поверх NATS. Здорово видеть, когда Open Source сам делает своё полезное дело даже в небольших проектах, от которых ничего особого не ожидаешь.

3. Менее значимые изменения

  • В лог при старте записываются предупреждения про файлы хуков без флага исполнения (+x).

  • Проект можно собирать без включения CGO. Теперь удобно использовать shell-operator в других проектах, если быстрый обработчик jqFilter не нужен.

  • Добавлен shell_lib.sh, чтобы подключать shell framework одной строкой. Пример использования этой библиотеки мы демонстрировали в уже упомянутом докладе на KubeCon.

Новости addon-operator

Последний релиз addon-operator состоялся в начале прошлого года, с тех пор в нем было по-настоящему много изменений.

Одно из главных поддержка схем OpenAPI для values. Можно задавать контракты для values, которые нужны для Helm, и для config values, которые хранятся в ConfigMap и используются для конфигурации модулей пользователем.

Например, такая схема определяет два обязательных поля для глобальных values (project и clusterName), а также два опциональных поля (строка clusterHostname и объект discovery без ограничений по ключам):

# /global/openapi/config-values.yamltype: objectadditionalProperties: falserequired:  - project  - clusterNameminProperties: 2properties:  project:    type: string  clusterName:    type: string  clusterHostname:    type: string  discovery:    type: object

Подробнее см. в документации.

Ещё одно знаковое событие экспериментальная поддержка написания хуков на языке Go. Для их работы придётся компилировать свой addon-operator, добавив импорты с путями к хукам. Пример их использования можно найти в каталоге 700-go-hooks.

Иллюстрация глобального хука на Go из примера выше:

package global_hooksimport "github.com/flant/addon-operator/sdk"var _ = sdk.Register(&GoHook{})type GoHook struct {sdk.CommonGoHook}func (h *GoHook) Metadata() sdk.HookMetadata {return h.CommonMetadataFromRuntime()}func (h *GoHook) Config() *sdk.HookConfig {return h.CommonGoHook.Config(&sdk.HookConfig{YamlConfig: `configVersion: v1onStartup: 10`,MainHandler: h.Main,})}func (h *GoHook) Main(input *sdk.BindingInput) (*sdk.BindingOutput, error) {input.LogEntry.Infof("Start Global Go hook")return nil, nil}

Реализация соответствующего SDK пока находится на уровне альфа-версии и не может похвастать достаточной документацией, но если вас заинтересовала такая возможность смело спрашивайте в комментариях, а лучше приходите в Tg-канал @kubeoperator.

Среди других ключевых изменений в addon-operator выделим следующие:

  • Поддержка установки модулей с помощью Helm 3.

  • Введены понятия сходимости и сходимости при старте это название цикла рестарта всех модулей. Добавлен endpoint для readiness-пробы: Pod addon-operatorа переводится в состояние Ready, когда прошёл первый старт всех модулей, т.е. сходимость при старте достигнута.

  • Возможность включать модули из глобальных хуков, благодаря чему теперь проще регулировать состав модулей (ранее отключить модуль можно было только enabled-скриптом самого модуля).

  • Запуск информеров и запуск Synchronization для Kubernetes-хуков теперь производится в отдельных очередях, а также появилась возможность отключить ожидание выполнения таких хуков при старте.

  • Сборка образа изменена аналогично shell-operatorу: Alpine в качестве основы, мультиплатформенный образ, статический бинарный файл и т.д.

  • Доступно больше метрик для мониторинга состояния подробнее в METRICS.

Также в addon-operator перекочевали многие улучшения из shell-operator, была актуализирована документация и сделаны другие мелкие правки. В данный момент заканчиваются работы по поддержке схем OpenAPI, после чего будет опубликован релиз v1.0.0-rc1.

Новые применения shell-operator

За минувшее время shell-operator обрёл не только новые возможности, но и новых пользователей. Среди них отметим следующие известные нам проекты:

  • В Confluent сделали проект Kafka DevOps. В нём реализована модель production-окружения, в котором запущено streaming-приложение, пишущее в Apache Kafka на Confluent Cloud. Это окружение построено на основе Kubernetes, приложения и ресурсы в котором управляются в духе декларативной инфраструктуры. В частности, для этого реализованы операторы (Confluent Cloud Operator и Kafka Connect Operator) на основе на shell-operator. Подробнее об этом проекте можно почитать в блоге авторов, а совсем недавно они даже выпустили подкаст, где рассказывают о своём Kafka DevOps.

  • Образовательный проект edukates подготовил практическое занятие по shell-operator, однако его дальнейшая судьба осталась для нас под вопросом (найти его в опубликованном виде на сайте проекта нам не удалось).

  • Docker Captain из Германии создал специальный контроллер для обновления DNS-записей при рестарте podа с Traefik. Вскоре после этого он узнал про shell-operator и перевел свою разработку на его использование.

  • Solution Architect из Red Hat занялся созданием r53-operator оператора для кастомных доменов, который управляет доменами Ingress в AWS Route 53.

Если у вас тоже есть опыт применения shell-operator, будем рады соответствующим рассказам в GitHub Discussions проекта: собирая такие примеры, мы надеемся помочь широкому сообществу инженеров. Случаи использования addon-operator гораздо более редкое явление, так что им мы будем рады вдвойне.

Заключение

Shell-operator и addon-operator давно используются нами в ежедневной работе. Основные проблемы изучены и устранены, а сейчас в проекты преимущественно добавляются новые возможности. В ближайших планах для shell-operator поддержка conversion webhook и возможность писать хуки без побочных эффектов, т.е. не вызывать kubectl для изменений в кластере, а возвращать в shell-operator набор действий (см. #94, #239).

Фактически оба проекта давно вышли из статуса beta, поэтому мы решили синхронизироваться с реальностью и представляем их версии rc1, а следующий релиз shell-operator в этом году может стать окончательным v1.0.0.

P.S. В ноябре прошлого года shell-operator преодолел рубеж в 1000 звёзд на GitHub, а addon-operator более скромные 250. Спасибо всем, кто заинтересовался проектами!

P.P.S.

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

Подробнее..

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

25.12.2020 10:05:30 | Автор: admin
Рады продолжить цикл статей с подборками из недавних вызовов, случившихся в нашей повседневной практике эксплуатации. Для этого мы описываем свои мысли и действия, которые привели к их успешному преодолению.



Новый выпуск посвящён опыту с неожиданно затянувшейся миграцией одного Linux-сервера, знакомству с Kubernetes-оператором для ClickHouse, способу ускорить восстановление данных в сломавшейся реплике PostgreSQL и последствиями обновления CockroachDB. Если вы тоже думаете, что это может быть полезно или хотя бы просто интересно, добро пожаловать под кат!

История 1. Затянувшийся перенос сервера в виртуальную машину


План миграции


Казалось, что может пойти не так, если требуется перенести legacy-приложение с железного сервера в виртуальную машину? У приложения и его инфраструктуры привычный, хорошо понятный стек: Linux, PHP, Apache, Gearman, MySQL. Причины для миграции тоже обычны: клиент захотел уменьшить плату за хостинг, отказавшись от реального сервера, на котором остался только вспомогательный сервис (парсер соцсетей).

Вообще говоря, конечно, бывают и другие причины для миграции (например, многочисленные удобства в последующем обслуживании инфраструктуры и её масштабирования), но не буду заострять на этом внимание.

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

Общий план выглядит следующим образом:

  1. Произвести очистку сервера, поняв, сколько ресурсов требуется.
  2. Подготовить виртуальный сервер, выделить память, ядра, зарезервировать IP-адреса.
  3. Если требуется минимальный простой организовать внешний балансировщик, который можно переключить на свежесозданный виртуальный сервер, или же запустить копию приложения.
  4. Произвести начальную загрузку с образа выбранной ОС/дистрибутива, содержащего все необходимые драйверы, чтобы скопировать данные в виртуальную машину тем или иным способом.
  5. Создать chroot, чтобы исправить загрузчик системы.
  6. Переключить пользовательские запросы или сервисные задачи на новую систему.

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

Подготовка к миграции


Перед началом непосредственного переезда мы решили почистить сервер. На нем было занято 300 Гб диска, однако среди них удалось найти давно потерявшие актуальность бэкапы, совсем старые логи, а также излишки в базе данных (см. ниже). В результате файловую систему удалось оптимизировать до 60 Гб.

Отдельно хочется рассказать про похудение MySQL. Дело в том, что MySQL изначально была версии 5.5 и настроена без innodb_file_per_table. Из-за этого, как многие могут догадаться, файл ibdata1 разросся до 40 Гб. В таких ситуациях нам всегда помогает pt-online-schema-change (входит в состав Percona Toolkit).

Достаточно проверить таблицы, которые находятся в shared innodb tablespace:

SELECT i.name FROM information_schema.INNODB_SYS_TABLES i WHERE i.space = 0;

после чего запустить упомянутую команду pt-online-schema-change, которая позволяет совершать различные действия над таблицами без простоя и поможет нам совершить OPTIMIZE без простоя для всех найденных таблиц:

pt-online-schema-change --alter "ENGINE=InnoDB" D=mydb,t=test --execute

Если файл ibdata1 не слишком велик, то его можно оставить. Чтобы полностью избавиться от мусора в файле ibdata1, потребуется сделать mysqldump со всех баз, оставив только базы mysql и performance_schema. Теперь можно остановить MySQL и удалить ibdata1.

После перезапуска MySQL создаст недостающие файлы системного namespace InnoDB. Загружаем данные в MySQL и готово.

Подготовка дисков и копирование


Казалось бы, теперь можно произвести перенос данных с помощью dd, однако в данном случае это не представлялось возможным. На сервере был созданный с md RAID 1, который не хотелось бы видеть на виртуальной машине, так как её разделы создаются в Volume Group, которая создана на RAID 10. Кроме того, разделы были очень большие, хотя занято было не более 15% места. Поэтому было принято решение переносить виртуальную машину, используя rsync. Такая операция нас не пугает: мы часто мигрировали серверы подобным образом, хотя это и несколько сложнее, чем перенос всех разделов с использованием dd.

Что потребуется сделать? Тут нет особой тайны, так как некоторые шаги полностью соответствуют действиям при копировании диска с dd:

  1. Создаем виртуальную машину нужного размера и загружаемся с systemrescuecd.
  2. Делаем разбивку диска, аналогичную серверу. Обычно нужен root-раздел и boot с этим поможет parted. Допустим, у нас есть диск /dev/vda:

    parted /dev/vdamklabel gptmkpart P1 ext3 1MiB 4MiB t 1 bios_grubmkpart P2 ext3 4MiB 1024MiBmkpart P3 ext3 1024MiB 100%t 3 lvm 
    
  3. Создадим на разделах файловые системы. Обычно мы используем ext3 для boot и ext4 для root.
  4. Монтируем разделы в /mnt, в который будем chroot'иться:

    mount /dev/vda2 /mntmkdir -p /mnt/bootmount /dev/vda1 /mnt/boot
    
  5. Подключим сеть. Актуальные версии systemrescuecd построены на ArchLinux и предполагают настройку системы через nmcli:

    nmcli con add con-name lan1 ifname em1 type ethernet ip4 192.168.100.100/24 gw4 192.168.100.1 ipv4.dns "8.8.8.8 8.8.4.4"nmcli con up lan1
    
  6. Копируем данные: rsync -avz --delete --progress --exclude "dev/*" --exclude "proc/*" --exclude "sys/*" rsync://old_ip/root/ /mnt/
  7. Затем монтируем dev, proc, sys:

    mount -t proc proc /mnt/procmount -t sysfs sys /mnt/sysmount --bind /dev /mnt/dev
    
  8. Зайдем в полученный chroot: chroot /mnt bash
  9. Поправим fstab, изменив адреса точек монтирование на актуальные.
  10. Теперь надо восстановить загрузчик:
    1. Восстановим загрузочный сектор: grub-install /dev/vda
    2. Обновим конфиг grub: update-grub
  11. Обновим initramfs: update-initramfs -k all -u
  12. Перезагрузим виртуалку и загрузим перенесенную систему.

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

Проблема и её решение


Система упорно помнила различные дисковые подразделы, которые были до переноса на сервере. Проблем разобраться с mdadm не было достаточно просто удалить файл /etc/mdadm/mdadm.conf и запустить update-initramfs.



Однако система все равно пыталась найти еще и /dev/mapped/vg0-swap. Оказалось, что initrd пытается подключить swap из-за конфига, который добавляет Debian installer. Удаляем лишний файл, собираем initramfs, перезагружаемся и снова попадаем в консоль busybox.

Поинтересуемся у системы, видит ли она наши диски. lsblk выдает пустоту, да и поиск файлов устройств в /dev/disk/by-uuid/ не даёт результатов. Выяснилось, что ядро Debian Jessie 3.16 скомпилировано без поддержки virtio-устройств (точнее, сама поддержка, конечно, доступна, но для этого нужно загрузить соответствующие модули).

К счастью, модули добавляются в initrd без проблем: нужные модули можно либо прописать в /etc/initramfs-tools/modules, либо изменить политику добавления модулей в /etc/initramfs-tools/initramfs.conf на MODULES=most.



Однако магии и в этот раз не произошло. Даже несмотря на наличие модулей система не видела диски:



Пришлось в настройках виртуальной машины переключить диски с шины Virtio на SCSI такое действие позволило загрузить виртуальную машину.

В загруженной системе отсутствовала сеть. Попытки подключить сетевые драйверы (модуль virtio_net) ни к чему не привели.



Дабы не усложнять задачу и не затягивать переключение, было решено переключить и сетевые адаптеры на эмуляцию реального железа сетевой карты Intel e1000e. Виртуальная машина была остановлена, драйвер изменён, однако при запуске мы получили ошибку: failed to find romfile "efi-e1000.rom".



Поиск дал интересный результат: ROM-файл был потерян в Debian некоторое время назад и возвращать его в пакет коллеги не собирались. Однако этот же файл фигурирует в пакете ipxe-qemu, откуда и был с успехом взят. Оказалось, достаточно распаковать этот пакет (ipxe-qemu) и скопировать /usr/lib/ipxe/qemu/efi-e1000.rom в /usr/share/qemu/efi-e1000e.rom. После этого виртуальная машина с эмулированным адаптером начала стартовать.

Вы думаете, это всё? Конечно же, нет, когда в деле замешан e1000e Данный драйвер известен тем, что может под нагрузкой начать перезапускать сетевой адаптер. Именно это и произошло, когда мы стали загружать базу данных для приложения. Пришлось прибегнуть к старому способу с отключение аппаратного offload:

ethtool -K eth0 gso off gro off tso off

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

История 2. Безопасность для Kubernetes-оператора ClickHouse


Не так давно мы начали использовать ClickHouse operator от Altinity. Данный оператор позволяет гибко разворачивать кластеры ClickHouse в Kubernetes:

  • с репликацией для повышенной надёжности;
  • с шардами для горизонтального масштабирования.

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

[2020-11-25 15:00:20] Code: 516, e.displayText() = DB::Exception: Received from chi-cluster-cluster-0-0:9000. DB::Exception: default: Authentication failed: password is incorrect or there is no user with such name.

К счастью, ClickHouse позволяет сделать whitelist с использованием rDNS, IP, host regexp Так можнодобавить в конфиг кластера следующее:

      users:        default/networks/host_regexp: (chi-cluster-[^.]+\d+-\d+|clickhouse\-cluster)\.clickhouse\.svc\.cluster\.local$

Тогда кластер сможет нормально функционировать. В репозитории оператора есть issue по этому поводу (мы не забыли добавить туда и свой workaround). Однако не похоже, что там будут какие-то движения в ближайшее время из-за того, что потребуется хранить пароли в конфигурации remote_servers.

История 3. Ускоренная перезаливка реплик PostgreSQL


К сожалению, ничто не вечно и любая техника стареет. А это приводит к различным сбоям. Один из таких сбоев произошел на реплике баз данных PostgreSQL: отказал один из дисков и массив перешёл в режим read only.

После замены диска и восстановления работы сервера встал вопрос: как же быстро ввести его в строй, учитывая, что база у проекта довольно объемна (более 2 терабайт)?

Дело осложнялось тем, что репликация была заведена без слотов репликации, а за время, пока сервер приводили в чувство, все необходимые WAL-сегменты были удалены. Архивацией WAL в проекте никто не озаботился и момент для её включения был упущен. К слову, сами слоты репликации представляют угрозу в версиях PostgreSQL ниже 13, т.к. могут занять всё место на диске (а неопытный инженер о них даже не вспомнит). С 13-й версии PgSQL размер слота уже можно ограничить директивой max_slot_wal_keep_size.

Итак, казалось бы, надо вооружаться pg_basebackup и переливать базу с нуля, но по нашим подсчетам такая операция заняла бы 9 дней, и всё это время основной сервер БД работал бы без резерва. Что же делать? У нас же есть почти актуальные файлы, некоторые из которых база вообще не трогает, так как это старые партиции партицированных таблиц Но pg_basebackup требует чистой директории для начала копирования. Вот бы изобрести метод, который бы позволил докачать базу!..

И тут я вспомнил про исходный метод, которым мы снимали бэкапы еще во времена PostgreSQL 9.1. Он описывается в статье документации про Continuous Archiving and Point-in-Time Recovery. Суть его крайне проста и основана на том, что можно копировать файлы PgSQL, если вызвать команду pg_start_backup, а после процедуры копирования pg_stop_backup. В голове созрел следующий план:

  1. Создадим слот репликации для реплики командой на мастере:

    SELECT pg_create_physical_replication_slot('replica', true);
    

    Важно, чтобы при создании второй аргумент функции был именно true тогда база начнёт немедленно собирать сегменты WAL в этот слот, а не будет ждать первого подключения к нему.
  2. Выполним команду на мастере:

    SELECT pg_start_backup('copy', true);
    

    Снова важно, чтобы при создании второй аргумент функции был именно true тогда база немедленно выполнит checkpoint и можно будет начать копирование.
  3. Скопируем базу на реплику. Мы для этой цели использовали rsync:

    rsynс -avz --delete --progress rsync://leader_ip/root/var/lib/postgresql/10/main/ /var/lib/postgresql/10/main/
    

    С такими параметрами запуска rsync заменит изменившиеся файлы.
  4. По окончании копирования на мастере выполним:

    SELECT pg_stop_backup();
    
  5. На реплике положим такой recovery.conf с указанием нашего слота:

    standby_mode = 'on'primary_conninfo = 'user=rep host=master_ip port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any'recovery_target_timeline = 'latest'primary_slot_name = replica
    
  6. Запустим реплику.
  7. Удалим слот репликации на реплике, так как он так же скопируется с мастера:

    SELECT pg_drop_replication_slot('replica');
    
  8. Проверим, что она появилась в системной таблице pg_stat_replication.

Однако один момент я всё-таки упустил. Мы выполнили копирование всех WAL-файлов, которые были на мастере. А значит даже тех, которые не требовались. Поэтому на следующий день после перелива реплики место на сервере с репликой начало заканчиваться. И пришлось думать над тем, как удалить бесхозные сегменты WAL.

Мы знаем, что checkpoint_timeout равен 1 часу. Следовательно, надо удалить все файлы старше 1 часа, но от какого момента? Для этого на мастере делаем запрос:

SELECT pg_walfile_name(replay_lsn) from pg_stat_replication;     pg_walfile_name      -------------------------- 0000000200022107000000C8(1 row)

Исходя из него сверяем временную метку файла:

stat /var/lib/postgresql/10/main/pg_wal/0000000200022107000000C8...Access: 2020-12-02 13:11:20.409309421 +0300Modify: 2020-12-02 13:11:20.409309421 +0300Change: 2020-12-02 13:11:20.409309421 +0300

у удаляем все файлы старше. С этим помогут find и bash:

# Вычислим смещениеdeleteBefore=`expr $(date --date='2020-12-02 13:11:20' +%s) - 3600`mins2keep=`expr $(expr $(expr $(date +%s) - $deleteBefore) / 60) + 1`# Удалим файлы размером 16 МБ (стандартный размер сегмента WAL),# которые старше, чем mins2keepfind /var/lib/postgresql/10/main/pg_wal/ -size 16M -type f -mmin +$mins2keep -delete

Вот и всё: реплика была перелита за 12 часов (вместо 9 дней), функционирует и очищена от мусора.

История 4. CockroachDB не тормозит?


После обновления CockroachDB до версии 20.2.x мы столкнулись с проблемами производительности. Они выражались в долгом старте приложения и общем снижении производительности некоторых типов запросов. На CockroachDB 20.1.8 подобного поведения не наблюдалось.

Изначально имелось предположение, что дело в сетевых проблемах в кластере Kubernetes. Однако подтвердить его не удалось: cеть чувствовала себя отлично.



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

SET CLUSTER SETTING sql.log.slow_query.latency_threshold = '100ms';SET CLUSTER SETTING sql.log.slow_query.internal_queries.enabled = 'true';

Благодаря этому стало ясно, что используемый в приложении драйвер PostgreSQL JDBC при старте делает запросы к pg_catalog, а наличие базы Keyсloak сильно влияет на скорость работы этих запросов. Мы пробовали загрузить несколько копий базы и с каждый загруженным экземпляром скорость работы pg_catalog падала всё ниже и ниже:

I201130 10:52:27.993894 5920071 sql/exec_log.go:225  [n3,client=10.111.7.3:38470,hostssl,user=db1] 3 112.396ms exec "PostgreSQL JDBC Driver" {} "SELECT typinput = 'array_in'::REGPROC AS is_array, typtype, typname FROM pg_catalog.pg_type LEFT JOIN (SELECT ns.oid AS nspoid, ns.nspname, r.r FROM pg_namespace AS ns JOIN (SELECT s.r, (current_schemas(false))[s.r] AS nspname FROM ROWS FROM (generate_series(1, array_upper(current_schemas(false), 1))) AS s (r)) AS r USING (nspname)) AS sp ON sp.nspoid = typnamespace WHERE typname = $1 ORDER BY sp.r, pg_type.oid DESC" {$1:"'jsonb'"} 1 "" 0 { LATENCY_THRESHOLD }

Вот тот же запрос, но с загруженной проблемной базой:

I201130 10:36:00.786376 5085793 sql/exec_log.go:225  [n2,client=192.168.114.18:21850,hostssl,user=db1] 67 520.064ms exec "PostgreSQL JDBC Driver" {} "SELECT typinput = 'array_in'::REGPROC AS is_array, typtype, typname FROM pg_catalog.pg_type LEFT JOIN (SELECT ns.oid AS nspoid, ns.nspname, r.r FROM pg_namespace AS ns JOIN (SELECT s.r, (current_schemas(false))[s.r] AS nspname FROM ROWS FROM (generate_series(1, array_upper(current_schemas(false), 1))) AS s (r)) AS r USING (nspname)) AS sp ON sp.nspoid = typnamespace WHERE typname = $1 ORDER BY sp.r, pg_type.oid DESC" {$1:"'jsonb'"} 1 "" 0 { LATENCY_THRESHOLD }

Получается, что тормозили системные таблицы CockroachDB.

После того, как клиент подтвердил проблемы с производительностью уже в облачной инсталляции CockroachDB, источник проблемы стал проясняться: было похоже на улучшенную поддержку SQL, что появилась в релизе 20.2. План запросов к схеме pg_catalog заметно отличался от 20.1.8, и мы стали свидетелями регрессии.

Собрав все факты, сделали issue на GitHub, где разработчики после нескольких попыток воспроизведения проблемы смогли подтвердить её и пообещали решить в скором времени. Исходя из этого клиент принял решение переходить на новую версию, так как сейчас баг мешает нам только при старте, увеличивая время старта инстанса приложения.

ОБНОВЛЕНО (уже после написания статьи): Проблемы были исправлены в релизе CockroachDB 20.2.3 в Pull Request 57574.

Заключение


Как видно, иногда даже очевидные и простые операции могут повлечь за собой головную боль. Но выход всё равно можно найти, не так ли?.. Надеюсь, эти истории помогут и другим инженерам в повседневной работе. Stay tuned!

P.S.


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

Подробнее..

Представляем ovpn-admin веб-интерфейс для управления пользователями OpenVPN

17.03.2021 16:04:48 | Автор: admin

Обслуживая большое количество проектов, мы пришли к необходимости простого управления OpenVPN (сертификатами и маршрутами для пользователей), подразумевая под этим веб-интерфейс вместо того, чтобы ходить по серверам/контейнерам и выполнять в них вручную команды. Поскольку существующие для этого решения (вроде Pritunl и OpenVPN AS) коммерческие, несколько лет назад мы создали (и используем по сей день) свой интерфейс.

Недавно мы его переписали с Python на Go и обновили внешний вид*, что и навело на мысль поделиться разработкой с более широким сообществом. Итак, встречайте ovpn-admin!

* За первоначальный вариант на Python благодарю коллегу @vitaliy-sn, а за нескучные обои обновлённый интерфейс erste.

Возможности и интерфейс

Ovpn-admin это Open Source-проект, реализующий веб-интерфейс для управления OpenVPN. В настоящий момент утилита поддерживает только Linux и умеет:

  • добавлять пользователей (генерировать сертификаты для них);

  • отзывать/восстанавливать сертификаты пользователей;

  • выдавать готовый файл конфига;

  • отдавать метрики для Prometheus: срок действия сертификатов, количество пользователей (всего/подключенных), информация о подключенных пользователях;

  • (опционально) прописывать CCD (client-config-dir) для каждого пользователя;

  • (опционально) работать в режиме master/slave (синхронизация сертификатов и CCD с другим сервером);

  • (опционально) задавать/менять пароль для дополнительной авторизации в OpenVPN.

Вот как выглядит интерфейс ovpn-admin:

Список пользователей и возможные действия с нимиСписок пользователей и возможные действия с нимиОкно добавления кастомных маршрутов для пользователяОкно добавления кастомных маршрутов для пользователяПример панели на основе метрик, полученных от ovpn-adminПример панели на основе метрик, полученных от ovpn-admin

Как попробовать

Ovpn-admin можно установить в систему или запускать в Docker-контейнере. Инструкции описаны в README проекта.

Исходный код проекта распространяется на условиях свободной лицензии (Apache License 2.0). Будем рады новым фичам, а также, конечно, ожидаем ваши issues и просто обсуждения на GitHub или в комментариях к этому посту.

Планы по развитию

Что бы хотелось улучшить в проекте? На данный момент у нас такой список доделок:

  • добавить возможность дополнительной авторизации через OTP;

  • добавить Helm-чарт как вариант установки;

  • добавить группы пользователей;

  • уйти от вызова утилиты easyrsa для генерации сертификатов;

  • уйти от вызова bash.

P.S.

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

Подробнее..

Аварии как опыт 3. Как мы спасали свой мониторинг во время аварии в OVH

09.06.2021 12:21:23 | Автор: admin

В данной статье я поделюсь опытом, как недавняя авария в OVH отразилась на нашей инфраструктуре мониторинга, как мы решали проблему и какие уроки из этого извлекли.

О мониторинге у нас

Начать стоит с того, как во Фланте вообще устроен мониторинг клиентских приложений и инфраструктуры в целом. Для этих целей задействованы три разных вида мониторинга:

1. Blackbox-мониторинг для проверки состояния сайтов. Цель проста собирать статистику с определенных endpointов и проверять их состояние по тем условиям, которые требуется установить. Например, это может быть health page в виде JSON-страницы, где отражено состояние всех важных элементов инфраструктуры, или же мониторинг отдельных страниц сайта. У нас принято считать, что данный вид мониторинга самый критичный, так как следит за работоспособностью сайта/сервисов клиента для внешних пользователей.

Вкратце о технической стороне этого мониторинга: он выполняет запросы разного уровня сложности по HTTP/HTTPS. Есть возможность определять размер страницы, ее содержимое, работать с JSON (они обычно используются для построения status page-страниц приложений клиента). Он географически распределен для повышения отказоустойчивости и во избежание всевозможных блокировок (например, от РКН).

Работа данного мониторинга не была затронута аварией благодаря геораспределенности (к тому же, у него вообще нет инсталляций в OVH).

2. Мониторинг Kubernetes-инфраструктуры и приложений клиента, запущенных в ней. С технической точки зрения этот мониторинг основан на связке Prometheus + Grafana + Alertmanager, которые устанавливаются локально на кластеры клиента. Часть данных, собираемых на данных системах мониторинга (например, метрики, напрямую связанные с Kubernetes и Deckhouse), по умолчанию отправляется в нашу общую систему, а остальные могут отправляться опционально (например, для мониторинга приложений и реакции от наших дежурных инженеров).

3. Мониторинг ресурсов, которые находятся вне кластеров Kubernetes. Например, это железные (bare metal) серверы, виртуальные машины и СУБД, запущенные на них. Данную зону мониторинга мы покрываем с помощью стороннего сервиса Okmeter (а с недавних пор уже не очень-то для нас и стороннего). Именно его работа была фатально нарушена в момент аварии OVH.

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

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

Как мы справлялись с этой аварией? Какие шаги предпринимали во время инцидента? И какие после?

Первые признаки аварии

Проверка доступности внешних средств мониторинга осуществляется с применением метода Dead mans switch (DMS). По своей сути это мониторинг, который работает наоборот:

  • Создается поддельный алерт OK, означающий, что всё хорошо, и он постоянно отправляется с локальных мониторингов (Prometheus, Okmeter и т.п.) в нашу систему инцидент-менеджмента.

  • Пока с мониторингом действительно всё хорошо, алерт OK активен, но в системе не показывается.

  • В обратной ситуации, когда возникают проблемы с мониторингом, алерт OK перестает быть активным, из-за чего автоматически создаётся алерт ERROR о неработающем мониторинге. И вот уже на него надо реагировать.

Данная проверка очень эффективна в ситуациях, которая произошла в роковой день (10 марта): первый алерт о проблеме (ERROR) мы получили как раз от DMS.

События разворачивались следующим образом:

  • Первое сообщение о проблеме алерт от DMS, полученный приблизительно в 3:20 ночи по Москве.

  • Связываемся с коллегами из Okmeter и узнаем, что они испытывают проблемы с ЦОД, в котором расположены. Детальной информации на данный момент не получаем, из-за чего масштабы аварии нам неизвестны. Кроме того, дежурный инженер видит, что у нас корректно работают другие системы мониторинга (blackbox и Kubernetes). Поэтому принимается решение отложить инцидент до утра.

  • Утром (в 8:14) становится известно, что ЦОД сгорел, а Okmeter не будет доступен до тех пор, пока не восстановит свою инфраструктуру в другом ЦОД.

Здесь также стоит остановиться подробнее на том, как была устроена инфраструктура у Okmeter. Основные её компоненты находились в дата-центрах OVH:

  • SBG-2 который был полностью уничтожен пожаром;

  • SBG-1 который был частично поврежден и обесточен.

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

Руководствуясь этой информацией утром 10 марта, мы сделали вывод, что нужно срочно замещать важные для нас функции Okmeter хоть какими-то решениями на временной основе.

Первые действия

В компании была собрана специальная команда, основными целями которой являлись:

  1. Определение масштабов аварии;

  2. Поэтапное планирование, как устранять последствия инцидента;

  3. Дальнейшее внедрение полученных решений.

В неё вошли тимлиды наших DevOps-команд, инженеры нашей платформенной команды и CTO компании. Позже к ней также добавились отдельные инженеры, помогавшие со срочной разработкой и тестированием новых инструментов, заменивших критичные проверки от Okmeter.

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

Матрица критичности алертовМатрица критичности алертов

Эта матрица распространяется на события из всех 3 используемых у нас систем мониторинга. Каждому инциденту, зафиксированному в любой из систем, назначается критичность от S1 (полная потеря работоспособности компонента системы) до S9 (диагностическая информация). Большая часть алертов с критичностью S1 это blackbox-мониторинг, который проверяет состояние сайтов клиента. Однако также к этой категории относится и незначительная часть алертов, отправляемых со стороны Okmeter (подробнее о них см. ниже). Другая незначительная часть приходится на S2, а все остальные имеют более низкую критичность (S3 и т.п.).

Проведя учет количества и видов алертов с S1 и S2 от Okmeter, мы приняли очевидное решение заняться ими в первую очередь, полностью компенсировав другим временным решением. А уже после этого можно было переходить на алерты с меньшей критичностью.

Оповестив всех клиентов, инфраструктуру которых задела авария у Okmeter, мы приступили к составлению плана восстановления.

Порядок и план восстановления алертов от Okmeter

Первая очередь алертов: S1-S2

Итак, какие именно критичные алерты мы потеряли с недоступностью Okmeter? Чтобы оперативно собрать эту информацию, каждая команда инженеров тех, что сопровождают проекты с мониторингом от Okmeter, подготовила статистику алертов, приходивших с 1 июля 2020 года.

Получился следующий список:

  1. Различные алерты для СУБД.

    1. Проверка работоспособности СУБД в целом (запущен ли процесс).

    2. Состояние репликации.

    3. Состояние запросов к БД (например, сколько запросов находятся в ожидании).

  2. Дисковое потребление на виртуальных машинах.

  3. Доступность виртуальных машин в целом.

Вторая очередь алертов: S3 и далее

Ситуация с алертами категории S3 была проще: их меньше, они менее критичные, да и вообще эта категория оказалась не у всех команд.

По итогам этого аудита получилась вторая очередь. Она в основном состояла из проверок доступности менее критичных серверных приложений, таких как ZooKeeper.

Немного магического Bash

Чтобы реализовать замену проверкам и алертам Okmeter, требовалось уметь деплоить на все серверы актуальные скрипты мониторинга, чтобы поддерживать их в актуальном состоянии. Реализация: с помощью Ansible-плейбуков мы задеплоили все необходимые скрипты, среди которых был скрипт автообновления. Последний раз в 10 минут загружал новые версии других скриптов. Из-за очень сжатых сроков основную часть скриптов мы писали на Bash.

Общий подход получился следующим:

  1. Сделали shell-скрипты, которые запускались на виртуальных машинах и bare metal-серверах. Помимо своей основной функции (проверка работоспособности некоторых компонентов) они должны были доставлять сообщения в наш мониторинг с такими же лейблами и другими элементами, как и у Okmeter: имя триггера, его severity, другие лейблы и т.д. Это требовалось для сохранения той логики работы мониторинга, которая была и до падения. В общем, чтобы процесс работы дежурных инженеров оставался неизменным и чтобы работали прежние правила управления инцидентами.

    Быстрой реализации этого этапа способствовал тот факт, что у нас уже были готовые инструменты для работы с API мониторинга внутренний инструмент под названием flint (flant integration).

  2. С помощью Ansible-плейбуков в каждый проект и на каждый сервер, где это было необходимо, задеплоили новые скрипты мониторинга. Ansible-плейбуки пришлось писать с нуля, но благодаря нашей внутренней системе, которая хранит список всех обслуживаемых хостов, деплоить изменения на серверы оказалось просто.

  3. В течение некоторого времени мы дополнительно проверяли, что алерты корректные, а сами скрипты выполняются правильно.

Результаты

На полную реализацию этого временного решения нам понадобился один рабочий день для алертов типа S1-S2 и еще один для S3. Все остальные инциденты (с более низким приоритетом) команды добавляли в индивидуальном порядке по мере необходимости.

В общей сложности новые скрипты были задеплоены примерно на 3000 хостов.

На каждом этапе решения проблемы мы подробно информировали клиентов о ходе восстановительных работ:

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

В тот момент, когда Okmeter стал доступен и функционировал полностью корректно, мы отключили свой кастомный мониторинг, после чего удалили все его следы, оставшиеся на серверах.

Выводы

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

Более практичные выводы таковы:

  1. Если сервис требует повышенной отказоустойчивости, всегда важно разделять инфраструктуру не только на уровне разных ЦОД, но и географически. Казалось бы, разные ЦОДы у OVH? А в реальной жизни они горели вместе

  2. В процессе подобного инцидента могут возникнуть проблемы и недопонимания в коммуникациях как между Флантом и Okmeter, так и между инженерами внутри нашей компании: недостаточное информирование о масштабах инцидента, медленная реакция и т.п. Хотя в этот раз нам повезло (их не возникло), мы пересмотрели свой регламент эскалации и еще немного ужесточили его, чтобы исключить вероятность появления таких проблем в будущем.

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

P.S.

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

Подробнее..

Кому-то Okmeter даже сможет заменить людей. Как будет развиваться сервис мониторинга после его покупки Флантом

31.05.2021 10:13:24 | Автор: admin

Флант и Okmeter сотрудничают с 2017 года. Для Фланта Okmeter один из основных инструментов мониторинга инфраструктуры клиентов; на протяжении этих лет компании сообща улучшают его возможности.

В середине мая Флант объявил о покупке Okmeter. Несмотря на то, что для некоторых участников DevOps-рынка сделка стала новостью, во многом это естественный результат плодотворного сотрудничества двух компаний.

Чтобы ответить на накопившиеся в сообществе вопросы, мы представляем интервью с Николаем Сивко, сооснователем и теперь уже бывшим владельцем Okmeter, и Андреем Колаштовым, совладельцем и управляющим партнёром Фланта, который будет заниматься развитием проекта Okmeter. Николай и Андрей рассказали, почему компании решились на эту сделку, как она повлияет на существующих и будущих клиентов Okmeter, а также о том, в каком направлении теперь будет развиваться сервис и какую функциональность получит в ближайшее время.

Николай Сивко выступает на одной из конференций HighLoad++Николай Сивко выступает на одной из конференций HighLoad++

О сделке и немного предыстории

Как компании к этому пришли?

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

Андрей: В то время у нас был фокус на платформу. То есть на то, чтобы делать свою работу хорошо, именно ее DevOps-часть. Нам нужно было эффективно обслуживать клиентов, и закрыть направление мониторинга готовым решением. Для обеих компаний это было стратегическое сотрудничество.

Андрей: Мы тогда очень устали от того, чтобы настраивать разные небезызвестные системы мониторинга, приделывать к ним какие-то экспортеры и ждать, что у нас что-то будет действительно замониторено. И у нас не было команды, которая бы занималась сутью мониторинга: какую метрику замониторить, как это реализовать, как ее вывести на графики и так далее. Конечно, собственный мониторинг у нас как-то развивался и работал, но сказать, чтобы это было сделано круто, нет. А у ребят это было именно так.

А как появилась сама идея сделки?

Николай: Флант был у нас самым большим клиентом. В какой-то момент им стало интереснее больше влиять на продукт, быстрее закрывать конкретно свои потребности. Они захотели контролировать Okmeter. Нам эта идея понравилась.

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

Плюс, с точки зрения бизнеса, это дает Фланту возможность получать продуктовую выручку. То есть продавать не только услуги, но и продукт. И этот продукт теперь будет содержать в себе еще больше нашей экспертизы.

Андрей Колаштов (в центре) на HighLoad++ Весна 2021Андрей Колаштов (в центре) на HighLoad++ Весна 2021

Николай как-то будет участвовать в дальнейшем развитии Okmeter?

Андрей: Мы бы хотели использовать огромный опыт Николая для помощи в реализации некоторых функций мониторинга. На начальном этапе точно будем прибегать к его консультациям, и достаточно часто.

Николай: Сейчас мы с командой передаем Фланту все дела так, чтобы сервис для всех клиентов оставался непрерывным. Также мы передаем бэклог нашего видения развития Okmeter. Но дальше Флант будет развивать продукт самостоятельно.

Дело в том, что Флант уже многое умеет и многое знает про Okmeter. Это для них не новые ворота, не blackbox какой-то. У Фланта сформировалось собственное видение того, как развивать сервис, и они уже начали его воплощать. Скажем так, у них уже руки чешутся сделать всё как надо.

Как сделка повлияет на рынок и на клиентов

Этот кейс может как-то повлиять на местный рынок мониторинга?

Андрей: Сейчас на рынке мало игроков, которые делают мониторинг и при этом имеют огромный опыт в эксплуатации, игроков, которые понимают, как делать мониторинг правильно. Сделка даст рынку возможность получить реальный опыт людей, эксплуатирующих серверы.

Николай: Я думаю, сделка повлияет не столько на рынок мониторинга, сколько на рынок эксплуатации и всего, что связано с DevOps. Потому что часть того, что делает сейчас Флант как сервисная компания эту экспертизу, можно заложить в продукт. И экспертиза будет, естественно, дешевле, чем люди. Кому-то Okmeter даже сможет заменить людей.

Андрей: Да, потому что Okmeter это мониторинг, который многое делает сам. Допустим, вы поставили 5 нод, и Okmeter вам сразу всё сам замониторил. Он автоматически нашел какой-нибудь Postgres, нарисовал по нему графики, тут же зажег алерты, что у вас есть какие-то проблемы. То есть это не вы создаете и добавляете эти алерты. За вас уже подумали, в какие точки стукнуться, чтобы проверить, что у вас всё хорошо или наоборот, и почему.

Николай: Можно привести пример известного в России сервиса мониторинга, который многие используют. И все знают, что эта штука, которую вы ставите, по своей сути пустая. Пока вы сами там всё не настроите в ней, она бесполезна. То есть это лопата, которая задачу никак не решает: она просто позволяет вам самому копать.

А Okmeter делает решение, которое самостоятельно работает, причем из коробки. Это то же самое, как если бы опытный человек взглянул на графики, на алерты, и сказал: болит вот здесь.

А что касается существующих клиентов Okmeter, как продажа сервиса скажется на них?

Николай: Положительно, конечно. У Фланта есть экспертиза, которой у Okmeter не было, и клиенты теперь смогут ее получать. Флант сидит на таком потоке знаний, которого в России реально ни у кого нет. Если всю эту экспертизу привнести в мониторинг, он станет квадратично более классным.

Андрей: С точки зрения тарифов пока мы ничего кардинально менять не планируем. С технической же стороны сделка повлияет только позитивно. Мы сейчас будем развивать продукт, добавлять туда новые функции, и планируем двигаться очень активно у нас набирается большая команда под задачи развития.

Связь с инцидентом OVH

Были слухи, что продажа Okmeter связана с недавним пожаром в дата-центрах французского провайдера OVH, когда мониторинг на некоторое время вышел из строя. Насколько это близко к реальности?

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

Пожар в дата-центрах OVH в марте этого года; автор фото Xavier GarreauПожар в дата-центрах OVH в марте этого года; автор фото Xavier Garreau

В чем была главная проблема с OVH?

Николай: В том, что OVH особо не рассказывал про свои дата-центры. Мы считали, что три дата-центра, в которых была размещена инфраструктура Okmeter в Страсбурге, это настоящая availability-зона, что дата-центры независимые. Мы на это полагались, и это было главной нашей ошибкой.

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

Прим. ред.: кстати, о событиях этого пожара и вынужденных действиях инженеров компании Флант мы ещё планируем опубликовать статью в скором времени.

Ближайшие планы

Как будет развиваться сервис?

Андрей: Мы видим Okmeter как несколько взаимодополняющих продуктов: хранилище, платформа и insights (идеи).

Хранилище будет обеспечивать высокопроизводительное и экономически эффективное хранение метрик, алертов, логов и трейсов. Планируется, что в него можно будет загружать не только метрики из Okmeter, но и метрики сторонних сервисов мониторинга; пользователи смогут подключить, например, Grafana и смотреть свои метрики, пользуясь нашим хранилищем.

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

Insights это история про готовые метрики, дашборды и алерты для популярных технологий и частых кейсов. Мы создадим удобную базу из коробки. Можно сказать, что команда insights будет заниматься смыслом. Она будет разбираться с тем, что именно нужно замониторить так, чтобы действительно понимать, что происходит в сервисах, корректно ли они работают и не собираются ли упасть. Плюс эта команда будет заниматься тем, чтобы правильно строить дашборды так, чтобы была нормальная наблюдаемость (observability). Чтобы можно было посмотреть на проблему и сразу понять, как это починить, не залезая в логи. То есть, чтобы максимально ускорить процесс решения проблем.

Расскажите чуть подробнее о платформе и, в частности, об интерфейсе Okmeter: что планируете улучшить в первую очередь?

Андрей: Мы планируем сделать более современные способы визуализации тех или иных компонентов, в зависимости от того, что потребуется нам и нашим клиентам. Также расширим интеграцию с Open Source-сервисами мониторинга, добавим возможность подключать к Okmeter сторонние плагины, добавлять в интерфейс графики и дашборды из других инструментов. По сути, в Okmeter уже есть почти все графики и всё, что нужно для мониторинга, но нам самим нужно больше, и мы это сделаем.

В командах будут только разработчики?

Андрей: В командах хранилища и платформы да. В insights и разработчики, и опытные SRE-инженеры; они будут разбираться, что у клиентов упало, почему мы не смогли вовремя это спрогнозировать и что надо замониторить, чтобы подобные проблемы предотвращать. Это будет большая команда, которая займется доработками как интерфейса, так и агента (пользовательская программа-клиент Okmeter прим. ред.), и которая будет помогать быстрее находить проблемы.

То есть это будут действующие инженеры?

Андрей: Не совсем. Они будут приходить на аварию, но не для того, чтобы починить ее, а чтобы проанализировать, почему Okmeter не предсказал эту аварию. Допустим, лежала база данных. У нее постепенно переполнялся некоторый буфер. Но графика и алертов на это переполнение не было, хотя мы могли бы за неделю до этого узнать, что буфер начал переполняться. И вот команда insights разбирается с этим, думает, как этот буфер замониторить и в итоге помочь администраторам базы сделать так, чтобы база потом не упала.

Планируете ли расширять количество интеграций, с теми же базами данных, например?

Андрей: Конечно. Будет очень хорошее расширение. Мы будем добавлять всё самое популярное, чего сейчас нет: MongoDB, ClickHouse, ProxySQL, HAProxy, Ceph и расширять функционал существующих. И не только базы данных. Также будем сильно расширять историю с мониторингом Kubernetes.

У Фланта, кажется, уже достаточно много наработок по мониторингу Kubernetes? Как это теперь будет совмещаться с Okmeter?

Андрей: Да, Kubernetes у нас уже замониторен своими силами. Это сделано на базе Prometheus и кучи кастомных и собственных экспортеров, чем занималась специальная команда внутри Фланта, отвечающая за платформу Kubernetes. Много сил было вложено в правильный мониторинг Kubernetes. В то же время своя интеграция с Kubernetes есть и у Okmeter. Okmeter уже работает с Kubernetes аналогично тому, как с другими сервисами и софтом на обычных узлах. То есть его можно поставить внутрь Kubernetes, он сам определит весь софт и попытается к нему подключиться, чтобы снимать метрики. Мы будем объединять эту интеграцию и наши наработки в более мощное и универсальное решение на базе Okmeter, добавляя в него наши дашборды, экспортеры и опыт в целом.

Наша детальная статистика потребления трафика по конкретному пространству имен Kubernetes-кластераНаша детальная статистика потребления трафика по конкретному пространству имен Kubernetes-кластера

А что касается инсталляций on-premises какие здесь планы?

Андрей: Это направление мы тоже будем активно развивать. Okmeter раньше был почти для всех облачным, сейчас же появилась возможность устанавливать его on-premises силами Фланта. У нас уже есть опыт в таких инсталляциях Okmeter.

То есть будет два варианта установки?

Андрей: Да, можно будет выбирать облачную или on-premises-версию. Если вам надо, например, замониторить пару серверов и у вас нет жестких требований по безопасности, оптимальный вариант облачная версия. Если требования по ИБ высокие, можно установить Okmeter on-premises в свой закрытый контур. Хотя принцип работы тот же: ставится агент, который отправляет данные не в облако, а в локальное хранилище.

Планируете ли создание Open Source-компонентов Okmeter?

Андрей: Да. Хранилище Okmeter с большой вероятностью будет основано на Open Source-компонентах. Соответственно, все эти компоненты мы будем выкладывать на GitHub, будем в них контрибьютить и добавлять что-то, что нам помогает улучшать хранилище. Планов по открытию кода платформы и Insights в настоящий момент нет, но всё может измениться. И, конечно, это не касается тех компонентов, которые уже являются Open Source-проектами и в upstream которых мы будем приносить свои улучшения.

Николай упоминал о планах по повышению отказоустойчивости инфраструктуры Okmeter. Как именно это будет реализовано?

Андрей: Это одна из первоочередных наших задач, мы очень сфокусированы на том, чтобы сделать отказоустойчивый storage.

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

Второй шаг разместить инфраструктуру в нескольких ЦОДах, в разных странах. Клиенты смогут сами выбирать уровень отказоустойчивости, то есть сколько копий данных хранить, в каких ЦОДах.

Глобальные планы

Какова стратегия Фланта в плане повышения конкурентоспособности Okmeter на международном рынке?

Андрей: Во-первых, расширение функциональности. Обязательно будем улучшать UХ, чтобы сделать платформу реально удобной и функциональной, дополнить всем, что у нас самих болит, всем, что мы хотим замониторить.

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

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

P.S.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru