Инфраструктура Yubo на 80M: автоматизация через CRD

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

В Yubo мы сделали радикальный выбор: сделать ставку на массовое шаблонирование на базе Custom Resource Definitions (CRD) в Kubernetes, чтобы автоматизировать провижининг, конфигурирование, наблюдаемость и даже полный жизненный цикл машинного обучения.

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

1. Один кластер, чтобы править всеми

Один из ключевых строительных блоков нашей системы — Argo Workflow.

Мы используем его как движок выполнения для всех наших пайплайнов: от CI до нагрузочного тестирования и провижининга виртуальных машин (кластер Couchbase, реплика-сет MongoDB, кластер Kafka и так далее).

Мы разделили Argo Workflow на несколько инстансов, чтобы команды Yubo (Mobile, Backend, Moderation, Infra и другие) управляли собственными шаблонами workflow. Так проще управлять и RBAC.

Чтобы эффективно управлять этими инстансами, мы шаблонизировали развёртывание Argo Workflow: общая база плюс ресурсы, специфичные для инстанса, — благодаря кастомному плагину ArgoCD на основе Ytt.

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

2. CI/CD как шаблон

Начнём с простого — с типового сценария, который есть в каждой компании.

Вместе с нашими разработчиками мы начали с этого простого CI/CD-процесса на основе наших внутренних инструментов: Argo Workflow для CI и ArgoCD для CD.

Простой CI/CD-процесс Yubo на Argo Workflow и ArgoCD

На самом деле это очень простой CI/CD, но в Yubo сложность не в самом CI/CD, а в том, как масштабировать и поддерживать этот пайплайн такими малыми силами при наших примерно 150 репозиториях с кодом на Node.js, Python, C++, Go или Java и более чем 370 приложениях ArgoCD в продакшене.

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

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

Использование Argo Workflow действительно позволило нам превращать любой частный случай в новый паттерн, который затем включается в наш шаблон, — и так мы расширяем возможности для наших разработчиков!

Благодаря этой модели провижининг CI/CD для нового сервиса сводится к добавлению его имени в YAML-файл конфигурации.

3. Переход от Terraform к CRD

Когда мы взяли инфраструктуру в свои руки в 2021 году, мы столкнулись с довольно типичной ситуацией: сотни манифестов Kubernetes, вручную скопированных и изменённых, и, конечно же, всё это в одном-единственном кластере Kubernetes.

Terraform знаком всем. Это фантастический инструмент Infrastructure as Code, и нашей первой идеей, разумеется, было заново выстроить инфраструктуру GCP, разделённую на проекты с общим VPC, и управлять всем этим через Terraform.

Именно это мы и сделали, встроив Terraform в наши шаблоны Argo Workflow. Но это ещё не всё! В тот момент мы начали определять, что такое «дата-центр» в понимании Yubo, и в результате создали первый слой шаблонирования вокруг Terraform. Так родился первый CRD от Yubo:

---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: datacenters.cloud.yubo.live
spec:
  group: cloud.yubo.live
  names:
    kind: Datacenter
    listKind: DatacenterList
    plural: datacenters
    shortNames:
    - dc
    singular: datacenter
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              region:
                type: string
              zone:
                type: string
              environment:
                type: string
              network:
                type: array
                items:
                  type: object
                  required:
                    - configmap
                    - name
                    - cluster
                    - vars
                  properties:
                    name:
                      type: string
                    configmap:
                      type: string
                    cluster:
                      type: string
                    vars:
                      type: object
                      x-kubernetes-preserve-unknown-fields: true

Мы прогоняли продакшен через этот пайплайн больше года. Со временем нам приходилось менять код и обновлять провайдеры Terraform (ломающие изменения!). С ростом объёма и при размере нашей команды нам становилось всё труднее выдерживать темп и обеспечивать стабильность и функциональность. Более того, каждое изменение требовало запуска пайплайна Terraform:

Пайплайн Terraform в ожидании подтверждения

Ждите подтверждения!

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

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

Какой же инструмент тогда выбрать?

У нас уже есть CRD, описывающий дата-центр, — так почему бы просто не управлять всей инфраструктурой через CRD? Поэтому мы решили просто шаблонизировать всю нашу инфраструктуру на базе «https://cloud.google.com/config-connector/docs/overview[GCP Config Connector]» точно так же, как шаблонизировали развёртывание Argo Workflow.

Сегодня наша инфраструктура целиком управляется в модели GitOps через ArgoCD и наш плагин «Ytt»:

Инфраструктура Yubo под управлением GitOps через ArgoCD и плагин Ytt

Ниже приведён фрагмент конфигурации одного из наших дата-центров:

---
env: prod
region: europe-west9

subnets:
  - type: back
    ipCidrRange: 10.0.0.0/24
    secondaryIpRange:
      - rangeName: back-pods
        ipCidrRange: 10.0.1.0/24
      - rangeName: back-services
        ipCidrRange: 10.0.2.0/24
  - type: front
    ipCidrRange: 10.1.0.0/24
    secondaryIpRange:
      - rangeName: front-pods
        ipCidrRange: 10.1.1.0/24
      - rangeName: front-services
        ipCidrRange: 10.1.2.0/24

internalIps:
  - type: back
    name: saltstack
    address: 10.1.0.250

saltstack:
  - name: saltstack-prod
    machineType: n2-highcpu-16
    ipAddress: 10.1.0.250
    image: debian

topics:
  - name: lives-scaleup
    retention: 86400s
  - name: lives-scaledown
    retention: 86400s

gke:
  - type: front
    workloadIdentity: false
    defaultMaxPodsPerNode: 32
    dnsCacheConfig: false
    nodepools:
      - name: api
        machineType: n2-standard-4
        spot: true
        podRange: main-pods
        enablePrivateNodes: true
        initialNodeCount: 6
        minNodeCount: 6
        maxNodeCount: 300
        diskSizeGb: 100
        diskType: pd-standard
        maxPodsPerNode: 24
        labels:
          nodepool: api
      - name: api-pub
        machineType: n2-standard-2
        spot: true
        podRange: main-pods
        enablePrivateNodes: false
        initialNodeCount: 10
        minNodeCount: 10
        maxNodeCount: 30
        diskSizeGb: 100
        diskType: pd-standard
        maxPodsPerNode: 24
        labels:
          nodepool: api-public

gcsBuckets:
  - name: back-prod
    type: back
    storageClass: STANDARD
    lifecycleRules:
      - action:
          type: Delete
        condition:
          age: 7
          withState: ANY

cloudSQL:
  - name: back-prod
    version: POSTGRES_15
    diskSize: 20
    tier: db-custom-1-3840

Добавление нового дата-центра или нового ресурса во все наши дата-центры становится невероятно простым!

Это важное изменение позволило нам повысить скорость работы, снизить затраты на сопровождение кода и, как следствие, повысить общую стабильность платформы.

4. Автоматизация управления нашим Lives-стеком

Пожалуй, одно из моих любимых технических приключений в Yubo.

Наш Lives-стек построен на пулах виртуальных машин, которые регистрируются в Consul с разными тегами в зависимости от используемых ресурсов ВМ и её аптайма. Изначально этот стек работал на bare-metal в дата-центре рядом с европейским магистральным каналом (backbone). Такой сугубо статичный провижининг bare-metal-машин означал, что мы держали серверы, которые попросту простаивали, когда трафик был низким. Чтобы контролировать затраты и масштабируемость этого стека, мы решили перенести его к нашему облачному провайдеру GCP.

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

Жизненный цикл ВМ мы придумали сами — опять же на основе ресурсов ВМ, аптайма, а также кастомных метрик.

Как мы это сделали? Argo Workflow!

Благодаря нашим моделям развёртывания Argo и управлению ресурсами GCP по модели GitOps на реализацию этого процесса ушло всего несколько минут.

Процесс управления жизненным циклом ВМ Lives-стека на Argo Workflow

Разумеется, я не буду вдаваться в детали всего стека, но как это работает на практике?

Когда нам нужно больше серверов, мы поднимаем алерт через GCP Cloud Monitoring. Этот алерт отправляется в топик GCP Pub/Sub, который, в свою очередь, читает Argo Events. Этот Pub/Sub и объявление этих алертов, конечно же, описаны в конфигурации наших дата-центров:

---
env: prod
region: europe-west9

topics:
  - name: lives-scaleup
    retention: 86400s

alerts:
  - name: lives-europe-west9-prod
    labels:
      env: prod
      machine_type: n2-highcpu-16
      version: v2.1
    strategy:
      notificationChannelStrategy:
        - notificationChannelNames:
            - projects/lives-project/notificationChannels/12345678900987654321
          renotifyInterval: 1800s
      autoClose: 1800s
    combiner: OR
    conditions:
    - conditionThreshold:
        aggregations:
        - alignmentPeriod: 600s
          crossSeriesReducer: REDUCE_MEAN
          perSeriesAligner: ALIGN_MAX
        comparison: COMPARISON_GT
        duration: 0s
        filter: resource.type = "gce_instance"
          AND metric.type = "compute.googleapis.com/instance/cpu/utilization"
          AND (metadata.system_labels.region = "europe-west9"
          AND metadata.user_labels.app = "lives"
          AND metadata.user_labels.env = "prod")
        thresholdValue: 0.70
        trigger:
          count: 1
      displayName: Lives CPU utilization
    notificationChannel: projects/lives-project/notificationChannels/12345678900987654321

Затем Argo Events запускает Argo workflow, передавая ему на вход метки нашего GCP-алерта (machine_type, version и так далее), чтобы создать новые виртуальные машины, которые автоматически зарегистрируются в Consul и станут доступны в пуле нашего Lives LoadBalancer.

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

Сначала срабатывает первый алерт, который позволяет вывести виртуальную машину из нашего «Lives Load Balancer», навесив на неё специальные метаданные и теги.

Затем, когда на сервере не остаётся ни одной открытой комнаты, поднимается второй алерт и запускается Argo workflow, удаляющий виртуальную машину.

Конечно, здесь описана не вся логика, но это хорошо иллюстрирует, насколько просто всё реализуется благодаря нашей инфраструктуре в модели полного GitOps.

5. Заключение

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

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

  • Разработчикам больше не нужно изучать внутреннее устройство Terraform или разбираться в низкоуровневых облачных ресурсах.

  • Они просто описывают свои потребности — базу данных, CI-пайплайн, топик Kafka — в одном YAML-блоке, а обо всём остальном заботится платформа.

  • Всё описывается декларативно, версионируется в Git и применяется автоматически через наши пайплайны GitOps.

Шаблонирование — это не просто оптимизация, это наш способ снизить операционную рутину. Со стандартными паттернами, зашитыми в переиспользуемые шаблоны, мы устранили граничные случаи, сократили время онбординга и радикально снизили издержки на сопровождение инфраструктуры.

Короче говоря: понятные конфиги для разработчиков — меньше работы для инфраструктурной команды. Вот как команда из пяти человек управляет инфраструктурой, обслуживающей десятки миллионов пользователей.

И по мере того как мы распространяем эту модель на ML, комплаенс и безопасность, наша убеждённость только крепнет: самая мощная инфраструктура — та, о которой почти не нужно думать.

Infrastructure as Template (IaT) — вот основа нашей автоматизации!

© 2026 meganuke