Привет, меня зовут Максим Качинский, я ведущий DevOps‑инженер в компании RocketData. Более 6 лет мы развиваем одноимённую платформу для управления репутацией — за это время она выросла с монолитного MVP на единственном сервере до 15+ микросервисов с пятью разными видами баз данных. И всё это в гибридной инфраструктуре.
Чем больше рос проект, тем сложнее нам было его поддерживать. Чтобы облегчить жизнь DevOps‑инженерам, мы начали постепенно переходить на managed‑решения. Но этот путь не был идеально прямым, и нам пришлось пересмотреть многие привычные рабочие инструменты.
В этом посте поделюсь нашим опытом: покажу, как мы используем Terraform, и расскажу, что стоит учесть при масштабировании гибридной инфраструктуры и переезде в облако.
Техническая изнанка платформы к моменту переезда
Платформа RocketData помогает компаниям управлять информацией в 80+ онлайн‑источниках: каталогах, справочниках, картах, ресурсах с отзывами и т. д. Например, клиент нашей платформы может размечать на картах точки присутствия компании, централизованно редактировать описание для посетителей, отслеживать отзывы в геосервисах и реагировать на них.
За последние пару лет количество продуктов для разных пользовательских сценариев перевалило за 20, практически каждый из них представлял собой отдельный микросервис. Машин, выделенных под разные задачи и типы данных, становилось больше, и поддерживать их было всё труднее.
Из чего конкретно состояла инфраструктура на старте истории переезда:
-
Self‑hosted кластер PostgreSQL, где хранились основные данные, от пользователей до отзывов.
Размер кластера:-
96 vCPU, 384 GB RAM, 960 Gb SATA NVMe, 10 GB/s MC‑LAG lan master
-
96 vCPU, 384 GB RAM, 960 Gb SATA NVMe, 1 GB/s lan slave.
-
-
Инстанс ClickHouse в качестве хранилища аналитических данных: статистики по отзывам, скорости ответов и т. д.
Размер: 12 vCPU, 48 GB RAM, 500 Gb SSD. -
Инстанс MongoDB для редко используемых больших данных.
Размер: 4 vCPU, 16 GB RAM, 100 Gb SSD. -
Шесть разных инстансов Redis для короткоживущих ключей: токенов авторизаций, метрик, результатов Celery‑задач.
-
10 серверов размером 400 vCPU 1500 Gb RAM 15Tb SSD RAID1.
При этом часть «железа» мы арендовали у сервис-провайдера.
Все сервисы запускали через Docker Compose, собирали и разворачивали с помощью GitLab CI/CD. Для упрощения деплоя очевидным решением было перейти на Managed Kubernetes в облаке, таким образом отдать на аутсорсинг часть инфраструктурных задач и облегчить поддержание инфраструктуры. Перенести в облако сразу всё с нашими объёмами выходило очень дорого, ибо воркеры потребляли очень много ресурсов. Так что нам пришлось изобретать варианты гибридной инфраструктуры: API и микросервисы пусть работают в облаке, а тяжеловесные базы и воркеры остаются на bare metal.
Мы не преследовали идею сразу и полностью распилить наш монолит — решили действовать постепенно. В наследство от первых монолитных версий платформы у нас оставался основной репозиторий, в котором в том числе лежали API для кабинета и некоторые внешние API‑функции: воркеры для фоновых задач, синхронизация данных. Но если хотя бы микросервисы переедут в облако, возможно, станет полегче? Оказалось, что всё было не так просто.
Первое облако и первые грабли
Первым поставщиком Managed Kubernetes стал провайдер, который предоставлял нам в аренду серверы. Это казалось отличным решением, так как ресурсы располагались бы в одном дата‑центре (да, на тот момент мы не разносили данные по разным ДЦ), и потери в сети были бы минимальными.
Для запуска сервисов в выбранном облаке мы создали 2 кластера Kubernetes: production и staging. При этом «на земле» у нас оставались 5 серверов (2 с БД, остальные под воркеры), где никакого Kubernetes, разумеется, не было.
Всем деплоем управлял пайплайн, который принимал на вход тег, подготавливал образы и обновлял окружение. Как вкратце выглядел процесс деплоя в гибридной конфигурации:
-
Бастион, с которого происходит деплой, обращается к bare‑metal‑серверам через Docker Remote и останавливает воркеры.
-
Мы выкатываем наш Helm Chart в облако, там запускаются миграции в виде pre‑upgrade job, мы приводим базы данных к необходимому состоянию и обновляем деплойменты для нашего API.
-
После того как у нас выкатывается API, мы через Docker Remote опять выкатываем обновлённые воркеры.
На этом пути мы встретили сразу несколько трудностей.
Грабли гибридного деплоя. Сама схема деплоя неизбежно вызывала вопросы. Во‑первых, наши воркеры не ожидают, что база может поменяться, поэтому их приходилось тушить до миграций, а после запускать заново. Возникал простой в работе — от начала деплоя до его полного завершения у нас проходило где‑то около получаса:
-
7–10 минут уходило на то, чтобы полностью остановить задачи.
-
Дальше применялась миграция, и код выкатывался в Kubernetes, на это требовалось 5–7 минут.
-
Потом на каждый сервер нужно было зайти через Docker Remote, спулить образы и запустить необходимые контейнеры. Это тоже занимало 7–10 минут, так как каждый сервер обновлялся по очереди.
Во‑вторых, из‑за невозможности использовать Kubernetes «на земле» мы не могли применить все возможности autodiscovery & failover. Контейнеры, которые деплоились с помощью Docker, были жёстко привязаны к серверу и сетевому порту. Если бы везде был Kubernetes или хотя бы Docker Swarm, можно было бы обращаться не к какому‑то серверу напрямую, а в настроенный endpoint c возможностями autodiscovery, что значительно облегчило бы задачи по балансировке нагрузки.
Но самой большой проблемой гибридного деплоя было даже не это. Поскольку мы обновляли воркеры через бастион‑хост с подключением Docker Remote, самую большую боль вызывало то, что Docker Remote достаточно часто попросту терял соединение с другой машиной. Когда контейнеры шли обновляться, соединение по какой‑то причине рвалось посреди процесса и деплой падал. Выяснить причину сразу не удалось ни самостоятельно, ни с подключением техподдержки (уже позже мы выяснили, что в этих падениях всему виной был баг Docker Compose, но открытый нами тикет до сих пор не решён).
Грабли взаимодействия с первым провайдером. Помимо трудностей с техподдержкой, со временем мы стали замечать и другие ограничения выбранного провайдера.
-
Он плохо поддерживал работу с Terraform для описания Infrastructure as a Сode. Если с описанием Kubernetes‑кластера много вопросов не возникало, то описать сетевую связность L3 между облаком и bare‑metal не предоставлялось возможным. Для решения этой задачи половину инфраструктуры (L3 связанность, сервисные аккаунты) приходилось настраивать через личный кабинет, половину через OpenStack CLI (роуты между облаком и железом).
-
Не хватало возможностей Keda и теперь запускаем поды только тогда, когда у нас действительно есть задачи.
--- {{- range $key, $value := .Values.deployments }} apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: {{ .name }} namespace: {{ $.Release.Namespace }} annotations: autoscaling.keda.sh/paused-replicas: "0" spec: minReplicaCount: {{ .minReplicas }} maxReplicaCount: {{ .maxReplicas }} scaleTargetRef: kind: Deployment name: {{ $.Release.Name }}-{{ .name }} advanced: {{- toYaml $.Values.hpaConfig | nindent 4 }} {{- if .cooldownPeriod }} cooldownPeriod: {{ .cooldownPeriod }} {{- end }} triggers: {{- range $value.queues }} - type: rabbitmq metadata: protocol: http queueName: {{ .name }} mode: QueueLength value: "{{ .length }}" authenticationRef: name: keda-trigger-auth-rabbitmq-conncd {{- end }} {{- end }}Также для фоновых задач SLA у нас ниже, чем для того же самого API, поэтому мы можем ещё удешевить инфраструктуру путём предпочтения прерываемых нод для асинхронных воркеров.
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: yandex.cloud/preemptible operator: In values: - "true"Для оптимизации производительности баз данных мы начали мониторить эффективность запросов к БД и решать проблемы с теми, где слишком много операций на чтение или запись. Помог инструмент PGWatch2, благодаря которому мы можем просмотреть наши запросы, оценить их стоимость и подумать над улучшением. Также в этом инструменте есть рекомендации по работе с индексами, благодаря которым мы избавились от нескольких неработоспособных индексов, заменив их на другие.

Подобрать оптимальную конфигурацию также помог более широкий взгляд на возможности облака: часть задач мы вынести из managed‑сервисов в другие хранилища. Например, для медиафайлов по соотношению цена — качество больше подошло объектное хранилище в связке с CDN: туда переехали размещённые в сервисах фотографии от клиентов платформы, а также виджеты, которые размещаются на клиентских сайтах.
Осваиваем больше возможностей Terraform. Самое главное — в облаке поддерживались нужные Тerraform‑модули, что позволило нам наконец избавиться от многих рутинных операций. Мы начали с готового набора для Terraform и освоили модули yandex‑cloud/yandex, cloudflare/cloudflare, kubernetes, helm. Но этого нам показалось мало, и мы написали свои. Вот какие задачи решаются уже сейчас:
-
Описание кодом статичных приложений: NGINX Ingress, RabbitMQ, Cert Manager, Vault, Prometheus Operator и т. д.
-
Создание новой базы данных для нового микросервиса с генерацией пароля по нашим требованиям.
-
Создание поддоменов.
-
Создание неймспейса в K8s, токенов Vault, GitLab Container Registry и TLS‑секретов в нём.
-
Создание токенов и сервисных аккаунтов, а также самих бакетов S3.
Письмо себе в прошлое: какие уроки мы извлекли из переездов
Второй переезд снял ограничения, которые не давали нам масштабироваться, и позволил платформе вырасти в 2 раза. Также удалось улучшить показатели CI/CD: скорость доставки обновления для клиентов выросла с 15 до 5 минут.
Говорить об экономическом эффекте пока рано, но, по крайней мере, сейчас мы детально видим все расходы и через некоторое время сможем всё подсчитать. Как оказалось, когда большая часть инфраструктуры была на нашей стороне, многие издержки были невидимыми: например, сложно было оценить ad‑hoc затраты команды DevOps на решение незапланированных проблем с гибридным деплоем.
Какие выводы мы сделали из всей этой истории:
-
Не экономить на спичках. Временные решения, которые призваны решить проблемы малой кровью, могут привести к неожиданным издержкам. Из‑за ограниченных ресурсов мы растягивали распил монолита во времени, но возникший потолок в масштабировании вынудил нас искать ресурсы для быстрого решения проблемы.
-
Не планировать облачные ресурсы впритык. В облаке придётся учитывать поправку на гипервизор и необходимость использовать те же самые ресурсы не так расточительно, а это может потребовать рефакторинга приложений.
-
Учитывать нюансы работы провайдера. Мало кто из инженеров в процессе выбора облака фокусируется на таких вещах, как актуальность документации и условия работы техподдержки — но больше всего неприятных сюрпризов при нашем первом переезде мы получили именно здесь.
-
Ценить управление доступом на основе ролей — чем больше объёмы, тем очевиднее необходимость создавать сервисные аккаунты и выдавать гранулярные права доступа разным группам.
Отдельный респект хочу выразить команде саппорта Yandex Cloud, которая поддерживала нас на всех этапах переезда и продолжает отвечать на наши вопросы во время эксплуатации облака.
Буду рад ответить на вопросы, связанные с Terraform и не только.
-
ссылка на оригинал статьи https://habr.com/ru/articles/771308/

Добавить комментарий