
Как масштабироваться, если новых нод не подвезут? Представьте, что вы управляете огромным высоконагруженным проектом, но находитесь не в облаке, а в on-premise. Никаких «запросить ещё одну ноду», никаких облачных щедрот — только то, что стоит в вашем серверном зале, и бюджет, который уже освоен. В таких условиях масштабироваться — всё равно что играть в «Тетрис» на харде: ресурсы ограничены, фигуры падают быстро и места всё меньше.
В этой статье мы — Илья Семёнов@ilc6 и Алексей Игнатов@AViIgnatov из Сбера, — расскажем, как мы построили собственный автоскейлер, который не запрашивает новые ресурсы, а перераспределяет существующие. Обойдёмся без магии облака, только логика, метрики и симуляция — и да, на DevOps Conf это вызвало оживлённые дискуссии.
Наша экспертиза — от разработки до архитектуры, в том числе высоконагруженных бэкендов, интеграционных, архитектурных и платформенных решений. Эта статья написана по мотивам доклада на DevOps Conf. Расскажем про наш собственный автоскейлер, который применяем к on-premise с инсталляцией, и какие решения для этого придумали.
Итак, поехали!
Разберёмся с базой
Как работает классический HPA и Cluster Autoscaler в облаках.

Масштаби��ование работает следующим образом. Kubernetes, используя deployment, управляет количеством запущенных подов. В процессе работы с подов собираются метрики, HPA периодически опрашивает метрики и даёт сигнал об изменении количества подов. Если свободного места для размещения новых подов нет, то в работу включается CA (cluster autoscaler) который обращается к облачному за получением новой ноды.
Horizontal Pod Autoscaler (HPA) использует метрики и, в зависимости от настроек, решает: нужно ли увеличить или уменьшить количество реплик (копий пода).
Если метрик недостаточно или подам негде размещаться — например, в кластере нет свободных ресурсов, — то в работу включается Cluster Autoscaler. Он обращается к облачному провайдеру с запросом на выделение новой ноды. После её запуска в ней размещаются новые поды. Таким образом, кластер масштабируется автоматически под текущую нагрузку.
В облаке при нехватке ресурсов можно просто запросить новую ноду у провайдера: она быстро выделится, и на ней развернутся новые поды. В on-premise-среде такой возможности нет. Если нода у вас уже есть — она физически размещена в ЦОДе, и просить её подключения бессмысленно: она либо уже задействована, либо пока свободна, но всё равно закреплена за вами. А если новой ноды ещё нет, то вы её и не получите — не купили, бюджет не заложен, поставки не приехали. Есть ещё вариант: занять ноды у соседней системы, но алгоритм такого временного перераспределения ресурсов достаточно сложен.
Поэтому автоскейлер on-premise не может работать по классическому принципу «запросил — получил — использовал — освободил». У него нет внешнего источника ресурсов. Единственный выход — маневрировать уже имеющимися ресурсами, перераспределяя их между разными бизнес-нагрузками. Других вариантов в on-premise просто нет: объём ресурсов ограничен и заранее определён.
Что должен делать on-premise автоскейлер?
Требования к автоскейлеру:
Оценка состояния всего проекта. Скейлер должен следить не только за нагрузкой на отдельные сервисы, но и за наличием свободных ресурсов в проекте. Если в целом сейчас в проекте свободных ресурсов достаточно, то отдельный сервис может использовать ресурсы сверх своей расчётной квоты. А если свободных ресурсов в проекте мало, то каждый сервис получит ресурсы строго в пределах своей квоты. То есть скейлер должен уметь менять своё поведение в зависимости от наличия свободных ресурсов в проекте.
Основная метрика для масштабирования отдельных рабочих нагрузок — потребление CPU. Масштабирование по потреблению процессора — наиболее предсказуемый и удобный способ: больше нагрузки — выше использование CPU, меньше — ниже. Память не даёт такой прозрачной зависимости.
Поддержка внешних метрик. Автоскейлер должен уметь использовать внешние метрики, для масштабирования как отдельных нагрузок, так и для выбора режима работы всего проекта. Например, в «дневном режиме» поднимать больше онлайн-сервисов, а в «ночном» режиме — больше сервисов пакетной обработки, чтобы менять поведение в зависимости от ситуации. Для каждого из режимов — свой профиль.
Уметь останавливать неприоритетные сервисы. Скейлер должен уметь уменьшать число реплик или полностью останавливать некритичные сервисы, если в проекте временно не хватает ресурсов.
Немного истории: монолит vs микросервисы
В чём причина перерасхода вычислительных ресурсов при переходе на микросервисную архитектуру? Когда всё размещалось на серверах приложений, нагрузка на монолитное приложение выглядела так:

На рисунке показана стандартная кривая ежедневной нагрузки практически любого фронтального приложения в e‑commerce или финтехе. Первый пик около 11 часов, второй — ближе к 17−19. Чем сложнее бизнес, тем сильнее, как правило, разнесены максимумы нагрузки разных операций внутри общей загрузки сервера приложений. Общий сервер приложений, на котором размещено монолитное приложение, усреднял нагрузку по группе операций, и потребление вычислительных ресурсов считалось как максимум скользящей суммы в течение дня:
Потребление = Max(Op1(t)+Op2(t) + Op3(t) ...)
Резерв был общим для всех видов операций, и это гораздо лучше, чем то, что произошло при переходе на микросервисную архитектуру. А произошло следующее:

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

Формула совсем не такая, как в монолите:
Потребление = Max(Op1) + Max(Op2) + Max(Op3) ...
Теперь необходимый объём ресурсов считается не как максимум скользящей суммы, а как сумма абсолютных максимумов по каждой бизнес-операции. Резерв, наоборот, разбился на много маленьких резервов:
Резерв = Res(Op1) + Res(Op2) + Res(Op3) …
Чем больше фрагментируется общий резерв ресурсов, и чем больше диапазоны изменения нагрузки в течении дня, тем больше приходится добавлять ресурсов каждому сервису. Один крупный резерв на группу сервисов внутри монолитного приложения позволял экономней расходовать вычислительные ресурсы. Разделение на микросервисы, с одной стороны, убрало «усреднение по группе операций», а с другой стороны, усложнило процедуры прогнозирования роста нагрузки и расчёта необходимых резервов. Нужно следить за большим количеством нагрузок и точнее и чаще пересчитывать резервы.
Попытка увеличить резервы у всех сервисов «на всякий случай», чтобы сгладить пики нагрузки, ведёт к перерасходу вычислительных ресурсов. При этом вы, как правило, видите, что кластер в целом загружен лишь на 10–15%, но не можете уменьшить резервы ни у одного из бизнес-сервисов, потому что боитесь: при кратковременном всплеске нагрузки на конкретный сервис ресурсов ему может не хватить.
Разница в необходимых ресурсах между монолитными и микросервисными приложениями при большом количестве сервисов может составить 200-300%:
Потребление = Max(Op1(t)+Op2(t) + Op3(t) ...
Резерв = общий
Потребление = Max(Op1) + Max(Op2) + Max(Op3) ...)
Резерв = Res(Op1) + Res(Op2) + Res(Op3) ...
Речь не только о влиянии этих формул, но именно разница между ними вносит существенный вклад в перерасход ресурсов.
Реализация автоскейлера для on-premisse
Хотелось этот перерасход устранить, придумав свою реализацию автоскейлера. Но принципиально менять реализацию HPA в K8s тоже не хотелось. Придумали надстроить базовые механизмы автомасштабирования следующим образом:

Используем привычную схему: Pod, Deployment, метрики. Настраиваем Horizontal Pod Autoscaler (HPA) на синтетическую метрику, в которую наш автоскейлер постоянно публикует рассчитанное количество реплик для этой нагрузки.
Таким образом, мы не меняем базовые механизмы Kubernetes, а просто усложняем его поведение через дополнительный компонент — наш on-premise автоскейлер. Алгоритм расчёта количества реплик для каждой нагрузки настраивается отдельно с помощью CRD ScaleTemplate, в котором задаются пороги масштабирования вверх и вниз и диапазоны количества реплик для каждого из возможных состояний проекта.
Для следующей нагрузки делаем всё то же самое. Даже если у вас в проекте 50 сервисов, 50 HPA и 50 синтетических метрик, алгоритм будет сам рассчитывать, как вести себя каждой нагрузке внутри проекта по вышеприведённой схеме.
apiVersion: autoscaler.dev/v1beta1 kind: ScaleTemplate metadata: name: egressgateway-app-prod-sp namespace: application-sp spec: usageLevels: - maxReplicas: 10 minReplicas: 1 name: low - maxReplicas: 5 minReplicas: 1 name: middle - maxReplicas: 3 minReplicas: 3 name: high behavior: scaleDown: stabilizationWindowSeconds: 17 scaleUp: stabilizationWindowSeconds: 12 targets: - name: egressgateway-app-prod-sp triggers: - metadata: cpuDown: '0.4' cpuUp: '0.6' memory: '9000' type: utilization
В зависимости от текущей нагрузки на весь проект, вы задаёте разное количество реплик. Это позволяет динамично управлять ресурсами: резерв становится общим, но используется согласованно.
Когда нагрузка приближается к критическому уровню, система приводит проект к заранее определённому и управляемому состоянию. Это защищает от ситуации, когда одна из нагрузок резко возрастает и вытесняет остальные. Ключевое — в пиковых условиях все сервисы работают сбалансировано, без перекосов и конфликтов за ресурсы.
Такой подход удобен и устраняет главное опасение, которое обычно возникает у команды сопровождения системы при обсуждении автомасштабирования в on-premise. Если всем выделять ресурсы с запасом, то автомасштабирование теряет смысл. Если общий пул ресурсов меньше чем сумма ресурсов по всем компонентам, то появляется риск, что один сервис начнёт потреблять их раньше других и нарушит баланс. Введённая политика даёт гарантию, что этого не произойдёт: ресурсы распределяются согласованно с учётом приоритетов и текущей нагрузки проекта: при низком общем потреблении распределение практически свободное, при высоком — политика распределения ужесточается, при максимальном потреблении система приходит в заранее зафиксированное состояние.
Управляем ресурсами с помощью CapacityPolicy

У нас есть два уровня расчётов: для отдельных сервисов и для всего проекта в целом. Этот процесс повторяется циклично: сначала система определяет текущий уровень загрузки проекта (высокий, средний, низкий), а затем рассчитывает, сколько реплик нужно каждой рабочей нагрузке. Управлять таким поведением можно через специальный ресурс — CapacityPolicy, который задаёт правила масштабирования для разных уровней загрузки.
apiVersion: autoscaler.dev/v1beta1 kind: ProjectCapacityPolicy metadata: name: pcp-aft namespace: application-sp spec: sources: - config: type: cpu name: cpu_var type: utilization usageLevels: - conditions: - lowerBound: '0.9' source: cpu_var upperBound: '1000m' name: high - conditions: - lowerBound: '0.7' source: cpu_var upperBound: '600m' name: middle - conditions: - lowerBound: '0.3' source: cpu_var upperBound: '350m' name: low
Система анализирует общее потребление ресурсов проекта и сопоставляет его с заданными порогами. Например, низкий уровень нагрузки — до 40%, средний — до 60%, высокий — до 85%. При достижении каждого уровня все сервисы переводятся в заранее заданное согласованное состояние. Также можно подключать внешние метрики — логика работы при этом остаётся прежней.
Для выбора режима управления отдельными рабочими нагрузками можно использовать внешние метрики по аналогии с KEDA. Это может быть не только потребление процессора, но и, допустим, глубина очередей, размер промежуточных таблиц и пр.
Главное преимущество — возможность использовать бизнес-метрики для управления проектом. Например, при всплеске вхо��ов в онлайн-приложение можно перевести весь проект в нужный режим работы. Такая управляющая метрика работает как коробка передач: вы выбираете «скорость» — общий режим проекта, а нагрузку на отдельные сервисы регулируете отдельно. Это позволяет гибко управлять проектом даже при фиксированной ресурсной квоте.

Самый простой способ применения — это смена дневного и ночного профиля, ведь днём активны одни сервисы, а ночью — другие. Также можно задать приоритеты: в рабочее время отдавать ресурсы онлайн-сервисам, а офлайн-задачи выполнять позже. В аварийной ситуации — быстро переключаться на критичный режим, где приоритетные сервисы получают больше ресурсов. Кажется, что схема простая и требует лишь незначительной настройки, без серьёзных изменений в инфраструктуре. Но, как часто бывает, настоящая сложность становится понятна только после первых шагов по внедрению.
Сложности больших проектов
Заметную экономию ресурсов можно получить только в крупных проектах — там множество сервисов, и перераспределение даёт реальный эффект. В небольших проектах с парой-тройкой сервисов экономия есть, но она малозаметна. В масштабных же системах применяется одна capacity policy на весь проект и множество scale templates для отдельных нагрузок. Посчитаем количество диапазонов в зависимости от сложности системы и профилей нагрузки.
Количество диапазонов для автомасштабирования = Количество сервисов × Количество уровней × Количество профилей
Если у вас 100 сервис��в, хотя бы три уровня нагрузки и, по меньшей мере, два профиля (дневной и ночной), то получится 600 диапазонов: 600 = 100 × 3 × 2.
Если задачу чуть-чуть усложнить, добавить сервисов, уровней и профилей, то количество диапазонов сильно вырастет:
3000 = 500 × 3 × 2
6000 = 500 × 4 × 3
То есть, идея красивая, рабочая, но понятно, что никто на самом деле не хотел бы рассчитывать это вручную. Можно упростить, но всё равно получается приличное количество диапазонов. Это оказалось препятствием к применению технологии.
Второй важный момент — живой проект всегда растёт и изменяется. Там, где новые версии сервисов выкатываются каждый день, важно регулярно пересчитывать или хотя бы проверять актуальность политик автомасштабирования. Но эта проверка не должна тормозить релизы — время на корректировку не должно влиять на сроки тестирования и вывода новых сервисов в прод.
Представьте, что вы преодолели все эти трудности, подготовили масштабную конфигурацию для большого проекта: определили диапазоны реплик, проверили алгоритм, оценили экономию ресурсов. Но в корпоративной среде этого недостаточно — здесь никто не принимает решения на основе чьих-то слов. Даже если вы уверены в корректности расчётов и эффективности схемы, вас попросят всё проверить. Нужно обосновать, что реплики рассчитаны правильно, а результат действительно даёт выгоду.
Первая мысль — проверять на стенде нагрузочного тестирования (НТ). Задача проверки автомасштабирования на стенде НТ похожа на поиск максимума, то есть надо последовательно подать все профили нагрузки по очереди и посмотреть, как система будет себя вести. Но её необходимо выполнять для всех вариантов нагрузок. Если считать, что есть три уровня нагрузки (минимальная, средняя, высокая), то, используя комбинаторную формулу, получаем:
При двух типах нагрузки (фронтальная и пакетная), количество сочетаний без повторений 2 из 6 — это 15 вариантов НТ.
Если добавить третий тип нагрузки, то количество сочетаний без повторений — 3 из 9. Это уже 84 варианта НТ.
Мало того, что нагрузочное тестирование достаточно дорого обходится в корпоративной среде и требует длительного времени, провести 84 НТ подряд физически невозможно. Это оказалось существенным препятствием для нашей идеи.
Тогда мы решили, что проверка на НТ-стенде — не вариант. И единственным способом решить задачу будет переход к моделированию работы системы. Новая идея — сделать цифровой двойник и проверять политики масштабирования на двойнике.
Цифровой двойник
Авиастроение использует цифровых двойников для контроля за работой авиационного двигателя. Бывают цифровые двойники газовых турбин, нефтяных скважин и много чего другого. Для изучения работы сложных систем построения моделей это крайне выгодное решение.
Мы решили, что единственный для нас выход — сделать цифровой двойник микросервисного проекта, но есть нюанс. К примеру, в авиационном двигателе количество компонентов внутри не меняется, а микросервисный проект — система динамическая. Каждый раз, когда мы добавляем новый компонент в наше приложение, мы меняем структуру проекта. Поэтому в нашем случае цифровой двойник должен состоять из:
цифровой модели каждого отдельного пода;
цифровой модели внешних ресурсов;
цифровой архитектуры проекта, то есть нам нужен текущий граф связи;
описания профилей нагрузок.
Модель пода
Почему именно под? Это наименьшая управляемая единица в Kubernetes. Под однозначно связан с релизным процессом, с компонентом в solution-архитектуре, и, самое главное, под имеет достаточное количество метрик для построения моделей, что нам и нужно.
При изменении функциональности новую версию пода мы можем локально протестировать для актуализации параметров модели.
Давайте посмотрим, как выглядит наш под, размещённый на кластере, и какие метрики его работы доступны в Kubernetes:

Здесь есть нода, под, трафик, который подаётся на под и выходит из него. Скорее всего, вы собираете какие-то метрики, у вас есть какое-то решение, вы их куда-то складываете.
В случае Istio observability у нас подключён сервис Istio Proxy. Мы можем собирать метрики, связанные с потреблением процессорного времени. Также у нас есть размеры и количество сообщений, запросов и ответов. В случае с gRPC у нас тоже есть метрики. Даже при использовании TCP нам есть, что собрать и попробовать проанализировать.
Из чего состоит наша модель:
имя и версия пода, чтобы понимать, с какой версией мы работаем;
лимит CPU;
определение входного и выходного трафиков.
Дальше мы переходим к статистической модели:
Функции преобразования трафика по параметрам количества вызовов и размера сообщений для каждого входа и выхода.
Функция потребления ресурсов процессора в зависимости от этого трафика (ради чего мы всё и затеяли).
Рассмотрим несколько вариантов и обсудим, как они между собой на самом деле связаны.
Вычислитель
Для примера возьмём самый простой случай.

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

Трафик поступает, обрабатывается и отправляется дальше. Количество сообщений на выходе прямо пропорционально количеству сообщений на входе: сколько запросов пришло — столько и передано на выход. Размер сообщения на выходе, как правило, линейно зависит от сообщения на входе. При преобразовании протокола мы можем получать на вход прокси бинарное сообщение, а на выход отдать текстовое или менять формат сообщения из экономичного JSON на избыточный XML.
Потребление процессорного времени в компоненте зависит от проходящего через него трафика. Для компонента прокси общий трафик будет равен сумме входящего и исходящего трафиков.

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

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

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

Мы использовали полиномиальную модель третьей степени и оценивали её качество с помощью коэффициента детерминирования (R²). Это позволяет понять, насколько хорошо предсказания совпадают с реальными значениями: чем ближе R² к единице, тем точнее модель.
Теперь, имея представление о том, как ведёт себя каждый отдельный компонент, можно переходить к следующему шагу: объединению всех моделей подов в модель проекта с помощью цифровой архитектуры.
Цифровая архитектура
Для хранения цифровой архитектуры есть несколько вариантов:
Выгрузка списка компонентов и графа связей из архитектурного фреймворка, хранящего архитектуру как код (например, из DocHub). Мы делаем это с помощью нашей корпоративной системы описания цифровой архитектуры.
Реверс-инжиниринг архитектуры по манифестам Kubernetes и Istio. Так называемая таблица смежности (граф зависимостей).
Реверс-инжиниринг архитектуры по журналам и данным мониторинга.
Построение solution-архитектуры проекта вручную. Не самый любимый вариант, но тоже рабочий.
Давайте посмотрим, как выглядит граф взаимодействий между компонентами в популярном фреймворке Kiali.

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

Для каждого из компонентов мы зафиксировали функции передачи трафика, функции отклика, определили полиномиальные зависимости потребления CPU от проходящего трафика, скопировали из манифестов лимиты процессора. Мы подаём трафик внутрь проекта и его отдаём. А ещё у нас есть три разные нагрузки, каждая со своей частотой и своим средним размером сообщения.
Алгоритм работы симулятора
Расчёт трафика «запросов», проходящего через каждый компонент, на основании входящего трафика и функций преобразования.
Расчёт отклика каждого внешнего ресурса на основании входящего трафика.
Расчёт трафика «ответов», проходящего через каждый компонент.
Расчёт потребления процессора в каждом компоненте — то, ради чего мы всё и затевали.
Расчёт необходимого количества реплик для политик автомасштабирования.
Таким образом, мы сняли ещё одно препятствие на пути к промышленному использованию нашего автоскейлера. Нам больше не нужно считать диапазоны реплик вручную, у нас есть для этого инструмент. Дальше мы планируем усложнить алгоритм расчёта, присвоить каждой транзакции условную стоимость и применять разные алгоритмы оптимизации.
Пример алгоритма

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

Дополнительно: что удалось найти в Google
Когда мы задались вопросом, как управлять автомасштабированием в проекте, мы догадывались, что едва ли нам первым пришло в голову пытаться моделировать что-либо в Kubernetes. Обнаружили, что в мире существуют другие решения:
Проект CloudSim позволяет эмулировать площадки, виртуальные машины, кластеры Kubernetes и отдельные нагрузки. Вся симуляция программируется на языке Java. Нужно написать мета-приложение — приложение для приложения. CloudSim — действующий проект, им можно воспользоваться, если у вас действительно большие задачи, в том числе не связанные с Kubernetes.
Проект SimKube позволяет эмулировать поведение кластера Kubernetes на основе метрик и трасс, что уже ближе к нашей задаче.
Проект KWOK (Kubernetes WithOut Kubelet) позволяет эмулировать жизненный цикл Node, подов и других ресурсов K8s, также на основе метрик и трасс. Это достаточно популярное решение, о котором периодически рассказывают в мире.
Выводы
Решая задачу, мы пришли к выводам:
Реальное управление ресурсами в кластерах Kubernetes в on-premise-инсталляциях требует больше, чем стандартные возможности горизонтального масштабирования и получения новых ресурсов.
Система, динамически изменяющая свою конфигурацию на основе политик автомасштабирования, нуждается в инструменте для проверки правильности настройки этих политик. По-хорошему, изменяя любой компонент в проекте или граф связей, мы должны быстро рассчитать, как это изменение повлияет на трафик в смежных компонентах и во всём проекте. Дальше, используя разработанный симулятор, можно переходить от привычного нагрузочного тестирования к быстрому нагрузочному моделированию.
Kubernetes и Istio предоставляют достаточное количество телеметрии для построения цифрового двойника прикладного проекта. В рамках нашей задачи этого достаточно использования Istio Observability.
Хотите обсудить практики масштабирования в on-premise и облаке, автоматизацию, метрики и реальные примеры? Присоединяйтесь к нам на DevOps Conf — расскажем, как проектировать автомасштабирование для тысяч сервисов, управлять ресурсами гибко и осознанно, а также как превратить нагрузку в модель. Будет практично, плотно и по-настоящему DevOps'но. Следите за анонсами и приходите!
