
Привет, Хабр! У платформы VK Cloud есть продукт, который позволяет компаниям частично или полностью перенести свою инфраструктуру не в публичное, а в частное облако. То есть хранить все в своем ЦОД и под личным контролем — но пользоваться при этом интерфейсом и инструментами, разработанными VK Tech.
В этой статье расскажем, как работает платформа VK Private Cloud и чем на самом деле она отличается от публичного облака. Будет много технических примеров, деталей и конфигураций и минимум общих описаний — только для уточнения нюансов. А также подробности о новой версии 4.3.
Как мы создавали VK Private Cloud
И публичное, и частное облако VK Tech построены на базе OpenStack: в свое время мы сделали форк от версии Ocata и вот уже который год его развиваем:
не устроил фронт — написали свой;
не понравился стоковый способ управления политиками в OpenStack — сделали свой, более гибкий механизм;
недостаточно гибк�� работает механизм создания и использования резервных копий — пишем свой.
При этом то, что мы форкнулись от версии Ocata, не означает, что мы на этой версии застряли. Мы берем оттуда только самые полезные и нужные наработки и добавляем их в платформу после тщательного тестирования. Часть компонентов у нас вообще заменена на собственную реализацию. А что попадает в VK Cloud — наследуется и в VK Private Cloud.
Исторически сначала разрабатывалось только публичное облако. Но когда на горизонте появился запрос на приватные инсталляции, началось развитие On-Premise-направления. Задача была сложной и многогранной: сделать автономный, самостоятельно разворачивающийся продукт на уровне качества предшественника.
Первые шаги
Первая версия VK Private Cloud (1.X) была собрана в качестве proof of concept — проверки возможности установки платформы как on-premise. По сути, и все: развернули, попробовали — всё работает, ничего интересного.
А вот вторая версия (2.X) уже была интересней, так как мы пытались разворачивать продукт через kolla-ansible.
Если очень грубо: к автоматике kolla-ansible мы просто применяли наши версии пакетов и Docker-образов и пробовали это запустить. Подход вполне себе работал. Но процессы установки и обновления оставляли желать лучшего: вылезли фундаментальные проблемы kolla-установки со сложностью апдейтов, ну и дополнительный Docker-слой не добавлял общей стабильности. Ставилось это неделями, и обновлять нужно было крайне аккуратно, так как при любом неверном движении можно было легко «организовать» простой. Благо наши инженеры набили руку, и в целом ничего курьезного не произошло.
Еще с kolla-ansible у нас не получалась полноценная On-Premise-инсталляция. Часть экспертизы терялась из-за смены IaC, часть наших кастомных фич просто не доезжала из-за необходимости пересборки под kolla-образы. В общем, это не тот продукт, который мы хотели бы видеть.
В третьей версии (3.X) мы решили не только использовать наши исправленные версии сервисов OpenStack, наполненные собственными наработками, но и применить IaC-код от нашей команды SRE. Он называется ansible-openstack и на тот момент содержал около 80 плейбуков и более сотни ролей. Все это непрерывно совершенствуется как горизонтально (появляется больше сервисов и плейбуков), так и вертикально (улучшаются уже существующие плейбуки в сторону уменьшения простоя, дополнительных проверок и прочее).
В этот ansible-openstack пришлось вносить много правок, чтобы всё заработало в On-Premise. Тогдашний IaC-код больше был заточен на поддержание существующей инсталляции публичного облака, а мы с приватным больше были нацелены на быстрое развертывание на чистую инфраструктуру. На момент нашего прихода в IaC даже не было логики для установки с нуля — упор делался на поддержание уже работающей инсталляции.
За эти годы чего мы только не повидали: начиная от относительно простых расхардкодов завязки на инфраструктуру VK Tech вроде IP-адресов DNS-сервера и заканчивая долгими разъяснениями командам разработки:
о том, что у нас более длинный цикл выпуска релиза и «патчи первого дня» нам не подходят (как в старые добрые времена);
о том, что внутри компаний немного другая философия использования — DDoS'ить и майнить там не будут, но зато вполне могут потребовать прокинуть кастомные CA или попросить повышенный уровень аудита;
о том, что сыпать дебаг-логами нашим клиентам не нужно — они интересны только вам (разработчикам), а у компаний это просто будет занимать диски бесполезным (и немалым) грузом; и многое другое.
В конце концов мы смогли договориться и сработаться: коллеги из публичного облака приносили нам классные штуки по поддержке огромных и высоконагруженных инсталляций продукта, а мы смогли организовать относительно быстрое развертывание платформы в новых ЦОД или даже регионах.
Как все работает сейчас
Мы разворачиваем продукт на чистые серверы (с нуля) десятки раз за рабочий день. Каждый коллега может развернуть персональную инсталляцию и либо проверить там отработку новых версий приложений, либо просто провести самые смелые эксперименты. При этом всегда можно накатить на стенд самый свежий код — как из своей ветки, так и из стабильного бранча. А если что-то совсем сломалось — можно пересоздать стенд с нуля.

Если раньше новые регионы запускались около года, то теперь это вопрос пары месяцев. Сама установка занимает редко больше суток, а остальное время тратится на работы до и после: закупку и подключение серверов и регрессионное тестирование.
Текущая, четвертая, версия (4.X) связана с переходом на РЕД ОС. Мы в VK Private Cloud оказались более легкими на подъем — все-таки за нами нет инфраструктуры на несколько ЦОД’ов. Поэтому мы в каком-то смысле ушли вперед именно в переходе с CentOS 7 — на тот момент до окончания его поддержки оставался примерно год.
Запуск VK Private Cloud на РЕД ОС позволил поймать и отладить все проблемы миграции, и в целом все счастливы: и VK Cloud следом начал переводить серверы на эту ОС, а мы получили возможность организовать отдельную сборку VK Secure Cloud.
Наполняемость фичами идет по фильтру спроса. Новая функциональность сначала выходит на VK Cloud, и, если она там «стрельнет» и покажет высокий спрос, мы добавляем ее в VK Private Cloud. Например, Managed Kubernetes востребован, условно, каждым пятым клиентом — значит, отправляется к нам. А какая-нибудь нишевая фича (например, AI API — «машинное зрение» и «синтезатор речи») попадет в on-premise, только когда будет явный запрос рынка.
По сути, сейчас мы имеем код, артефакты и документацию из публичного облака, упакованные в отдельный продукт. Тот же интерфейс, те же возможности — только все хранится на стороне клиента.
Получилось так, что обе версии во многом похожи: мы перенесли в приватную те технические особенности, которые делают надежным и удобным публичную. В последней все еще больше опций, но основные возможности схожи — прямо сейчас вы можете протестировать и оценить. Такая двойная ориентация продукта довольно уникальна — в основном на рынке вы можете увидеть только полностью публичную версию с парадигмой «есть только такая версия, а поставить к вам мы не можем, даже посмотреть нельзя». Либо предлагают только приватную версию, которую без дополнительного общения и подтверждения платежеспособности вам даже пощупать не дадут.
Особенности разработки
Догфудинг
Догфудинг в ИТ — это практика использования сотрудниками компании собственных продуктов и услуг. Где-то на моменте начала разработки третьей версии мы поняли, что, чтобы разворачивать и сносить для переразворота нашу текущую версию, нам нужно много, очень много физических серверов. Раньше под любую сложную задачу или плановое тестирование выделялось 12 и более машин, и из-за большого числа желающих за ними часто приходилось стоять в очереди. А еще приходилось интегрировать их в наши механизмы безопасности и инвентаризации VK Tech. Это долго и бесполезно для нас, так как при установке в ЦОД другой компании будут совсем другие механизмы ИБ и учета.
Где-то тогда возникла идея развернуть большой стенд VK Private Cloud, на котором можно ставить маленькие, персональные и автономные стенды.

Как это организовано:
Часть физических серверов мы выделили для установки текущей, стабильной версии (на тот момент 2.X на kolla-ansible).
Прокидывали на вычислительные узлы поддержку nested virtualization.
Внутри создавали виртуальную инфраструктуру (маршрутизаторы, сети и сами ВМ).
На ней разворачивали тестируемую версию продукта.
Назвали мы эту штуку Undercloud — «Подоблако».
Многие не верили, что это заработает, — но оно заработало, и мы уже не представляем иного способа работы. Конечно, не все возможности системы так можно проверить (например, не получится протестировать автоэвакуацию или работу с GPU), но 90% изменений Undercloud успешно обрабатывает: развертывание происходит очень близко к реальным боевым условиям, вложенная виртуализация позволяет разворачивать любые IaaS/PaaS-сущности, а базовые компоненты (БД, IAM, балансировщики) вообще непривередливы к такому способу установки.
Это очень хорошо сказалось на скорости развертывания новых стендов — ручных операций осталось минимум, и теперь балом правят Terraform и Ansible. Развернуть стенд можно просто по кнопке с минимальным набором параметров вида «как назвать стенд» и «из каких веток разворачиваться».
То, что не покрывает данный способ развертывания, мы закрываем оставшимися физическими серверами (т. н. HW-стенды). Это:
периодические end-2-end тесты;
тесты на производительность;
фичи, которые возможно реализовать только на физическом железе (например GPU), и тому подобные работы.
Очередь на физические серверы очень сильно уменьшилась, и они сейчас даже иногда простаивают.
Сама концепция «облака в облаке» в итоге переросла в отдельный внутренний облачный провайдер со своим бюджетом и командой SRE. Undercloud был переведен со второй версии на последнюю стабильную четвертую, и мы обновляем его под нагрузкой по мере выхода новых релизов.
Эта концепция также очень заинтересовала разработчиков сервисов VK Cloud. У них появилась возможность не просто написать код и ждать, когда его где-то там развернут и проверят, а самим поднять максимально подходящий стенд.
Для понимания: сейчас у нас только на подобных стендах создано около 1000 ВМ, на которые тратится ресурсов порядка 11 тысяч vCPU, 32 ТБ ОЗУ и 350 ТБ SSD. И по мере снижения порога входа это число только растет.

Автоматизация
Из-за того, что продукт большой, ставится долго, а ошибки или поломки в инфраструктуре могут остановить разработку целой команды, мы изо всех сил автоматизируем процессы. Одна из таких автоматизаций — GitOps. Мониторинг нашей инфраструктуры сделан именно на нём.
Так как нам важна только сигнализация о текущих проблемах, развертывание инфраструктуры мониторинга сделано на чистом GitOps. Это позволяет вносить изменения и добавлять что-то новое исключительно коммитами в Git. Также это избавляет от лишних инструкций, ручных действий и ускоряет модернизацию при необходимости.
Для раскатки стендов и тестирования продукта используется Jenkins. Стенд может раскатываться до 14 часов, а потом еще тесты на нем могут идти от пары часов до пары суток — Jenkins оказался более удобным вариантом, чем GitLab CI.
Для конфигурирования Jenkins мы используем плагин Jenkins Configuration as Code, что позволяет хранить все настройки в Git и применять GitOps. При создании merge request в код конфигурации он прогоняется на тестовом инстансе. Результат виден как pipeline в merge request, а после ревью и слияния с основной веткой конфигурация автоматически приезжает на основной и запасной инстансы, которые живут в разных облаках.
Код самих Jenkins Job мы храним как Groovy Pipeline в Git, а настройки джобов — тоже в Git с использованием Jenkins Job Builder. Это позволяет тестировать новый код, добавляемый в Groovy Pipeline, с помощью GitLab CI. В случае вывода коммита из draft весь код прогоняется на Jenkins с имитацией развертывания стенда. Кроме того, это позволяет выкатывать настройки джобов в любой момент.
В качестве агентов Jenkins используются поды, которые запускаются в двух разных Kubernetes-кластерах, находящихся в разных облаках.
Еще один плюс этого решения: в любой момент можно поднять себе точную копию Jenkins и поэкспериментировать с ним, не боясь, что это затронет других коллег.
Тестирование
В процессе разработки важно проверять работоспособность продукта при добавлении нового функционала и быть уверенным, что внесенные изменения не ломают то, что уже хорошо работало.
Продукт большой и сложный, состоит из множества компонентов, и в зависимости от типа установки некоторые из них могут быть отключены. Поэтому проверять работоспособность необходимо автоматически и покомпонентно. Для этого мы разрабатываем и используем функциональное API-тестирование (автотесты) на базе Tempest. При добавлении новых фич разрабатываются и новые тесты. А при автоматической раскатке внутренних стендов (в случае успешной установки) автоматически запускаются проверки.
Вариантов тестов довольно много, и полный набор может выполняться около двух дней. Поэтому при раскатке нового стенда с изменениями в рамках определенной задачи запускается минимальный набор, который выявляет базовую работоспособность продукта.
Наличие успешно пройденных тестов является необходимым критерием при ревью и внесении изменений в основную ветку разработки. Кроме того, на регулярных ночных стендах, создаваемых из основной ветки, запускается полный набор автотестов.
Отчеты и результаты тестирования хранятся некоторое время даже после удаления стендов в Allure Report.

Итого:
Полные автотесты мы прогоняем на т. н. ночных стендах, где собирается полный отчет обо всех выявленных проблемах.
Упрощенный набор прогоняем на персональных стендах для прохождения этапа ревью.
Даже если изменения что-то сломали и на упрощенных автотестах этой проблемы не видно, она обнаружится буквально после очередного прохождения ночных тестов, и мы сможем оперативно все исправить. Можно даже первоначальную задачу с коллеги не снимать и решить все в рамках нее.
Подробнее о том, как выбирали и адаптировали стек для автоматического тестирования, можно почитать в статье Павла Балахонова.
Трудности с продуктом
Работая с VK Private Cloud, мы точно поняли, почему другие продукты не идут в On-Premise и остаются только в формате SaaS — потому что это совсем непросто. Из самого очевидного: приходится обогащать частное облако той функциональностью, которые в публичном никогда не пригодилась.
К этому относится, например, необходимость поддержки работы с кастомным CA. Продукт разворачивается в интранете компании, и там любят расшифровывать трафик для анализа потенциальных угроз. Именно поэтому пришлось обеспечивать механизм распространения кастомных CA по всем серверам и последующего добавления их в «доверенные».
Особенно больно это в Java-приложениях (благо их у нас немного), в контейнеризированных сервисах (а их у нас много) и, самое болезненное, в образах сервисных ВМ. Проблему с контейнерами мы решили путем стандартизации сборки образа, где всегда предусмотрена ручка для прокидывания списка доверенных сертификатов. А вот сервисные образы сложнее: тут либо заставлять каждого клиента пересобирать их для добавления CA, либо решать задачу средствами Nova по типу механизма cloud-init. В итоге мы закрыли этот вопрос созданием собственного сервиса templater — он расширяет возможности cloud-init и делает ряд стандартных, заранее заложенных операций на конечных ВМ.
С автономными стендами тоже возникло интересное обстоятельство: просто развернуть систему в изолированной среде без привязки к инфраструктуре VK Tech можно, но тогда часть фич просто не будет работать. Непонятно, как проверять интеграцию с серверами идентификации (типа Active Directory) или как отправлять письма пользователям с одноразовыми паролями и прочими сообщениями. Поэтому мы вместе с продуктом поставляем так называемые тестовые компоненты — ряд маленьких сервисов, которые заменяют корпоративные аналоги в изолированных установках.
Вот эти сервисы:
FreeIPA — опционально ставится для обеспечения синхронизации пользователей и групп. Имитирует контроллер домена и подобные сервисы.
Postfix — опционально ставится для обеспечения отправки email. Имитирует почтовые сервисы компании.
MinIO — опционально ставится для обеспечения интеграции с S3 (хранение Glance-образов, бэкапов ВМ и XaaS-приложений). Имитирует корпоративный S3-сервер.
Все три сервиса входят в поставку, но с пометкой «ТОЛЬКО ДЛЯ ТЕСТИРОВАНИЯ». На продакшен-установках они, конечно, должны заменяться на корпоративные решения.
И еще была большая проблема с заполнением Ansible-инвентори — она огромна, 16 166 строк.
Мы сделали шаблонизатор на ytt, где все эти файлы генерируем из специального файла minimal.yml, а в нем содержится только самое-самое важное, что нужно менять при новой установке (IP-адреса, FQDN, хостнеймы и всякие флажки включения/выключения чего-нибудь).
Этот файл значительно меньше — 1586 строк.
Как это выглядит:
Мы на уровне разработки правим параметры в инвентори, а если видим, что параметр будет точно меняться от клиента к клиенту, выносим его в minimal.yml.
Далее собираем весь наш шаблон в один бинарник под названием invgen.
Передаем его клиенту.
Клиент сначала запускает его для получения файла minimal.yml:
$ ./invgen-linux-amd64 config-example > minimal.yml.Затем заполняет его своими параметрами.
Когда все заполнено — генерирует инвентори:
$ ./invgen-linux-amd64 generate
На выходе он получает полноценную Ansible-инвентори, где ключевые параметры подставлены автоматически и создано много групповых и хостовых переменных.
Вы можете спросить, почему мы не сделали то же самое на основе Jinja2. На это есть несколько причин:
Нам нужна более сложная логика обработки значений из minimal.yml, чем простое наследование. Нужно валидировать, что там внес админ клиента, и иногда кастомизировать его запрос (например, вырезать из полного URL только короткое имя). И еще много подобных манипуляций, которые в Jinja2 превращаются в сложный набор действий.
При использовании Jinja2 получается цепочка наследования: переменная ведет в другую, та в третью и так далее. С ytt добавляется конечное значение, и это легче обслуживать после развертывания.
Мы хотим видеть конечный вид инвентори при ревью MR, поэтому после каждого коммита генерируется примерный результат. Это очень помогает в плане наглядности — сразу видно, что поменяли в логике шаблона и как это отразится на генерации. В Jinja2 такую наглядность реализовать сложнее — опять же из-за наследования.
Мы хотели получить один бинарник. С Jinja2 пришлось бы устанавливать дополнительные Python-библиотеки и молиться, чтобы их более новая версия ничего не сломала в нашей логике.
Ytt мы кастомизировали для генерации секретов — не просто паролей длиной N символов, а RSA-ключей, SSL-сертификатов, Ceph-кейрингов, паролей в base64. Научить этому Jinja2 — задача значительно более сложная.
Ytt в плане обслуживания тоже непростая вещь: с её библиотеками и нашими запросами инструмент разросся до полноценного решения со своими тонкостями и профильными экспертами в команде. Но мы в целом им довольны, и без него было бы хуже.

Вариативность
Одним клиентам нужен чистый IaaS, и они не готовы переплачивать за PaaS. Другим нужна возможность создавать БД и Kubernetes-кластеры по кнопке, но не нужны XaaS-функциональность и наша реализация балансировщиков нагрузки. Третьи хотят не использовать Ceph, а подключать в качестве хранилища виртуальных дисков внешние СХД от стороннего вендора, и так далее.
Все это переросло в специальные ручки, которые при установке и обновлении наполняют Ansible-инвентори необходимыми переменными и запускают ряд плейбуков. В итоге в текущей версии таких ручек 17 штук.
Но не все так просто: одни фичи могут конфликтовать с другими (Neutron ↔ Sprut), некоторые требуют обязательного включения других как «зависимости» (Magnum → Octavia), третьи вообще не являются явной фичей, но нужны для работы прочих (Barbican).

Сделать свободный конструктор и полностью протестировать все возможные конфигурации не представляется возможным — и в целом не нужно. Поэтому мы поставляем только «золотые» конфигурации: опрашиваем потенциальных клиентов о том, какая функциональность им нужна, и из этого делаем около 10 наборов. Их мы именуем «Вариациями», проставляя номер вида V3 («Вариация 3»). В такую вариацию, например, включены все IaaS-сервисы плюс XaaS и DBaaS, работающие на Sprut и на внешних СХД. На них и целится наша разработка: мы проверяем, чтобы всё в таком составе стабильно ставилось и работало, проводим тестирование на этих конфигурациях.
Чтобы конечный клиент не запутался во всех функциональностях, мы добавили валидатор на соотношение включенных фич. Если он включит что-то лишнее, мы не дадим сгенерировать инвентори и выдадим сообщение вроде «Такая конфигурация не поддерживается». И это правда: мы действительно не знаем, что там в итоге получится, будет ли оно работать и установится ли вообще.

Получилась интересная концепция выпуска релиза: если нужно добавить какую-то новую фичу, к нам должен поступить запрос на нее. Таким образом, мы и обеспечиваем предложением рынок, и сами из релиза в релиз с размеренной скоростью внедряем новое.
Воплощение Multi-AZ
Одной из востребованных, крупных и сложных фич в новом релизе оказалась возможность установки продукта в нескольких availability zones (дата-центрах). На этапе декомпозиции задача была разбита более чем на 40 подзадач и пополнялась новыми в процессе.
Поскольку разработка и тестирование ведутся на виртуальных стендах в Undercloud/Corpcloud, самыми проблемными и долгоиграющими оказались работы по подготовке внутреннего виртуального стенда с имитацией нескольких management-сетей и нескольких дата-центров «как у клиентов».
Изначально архитектурно в нашей Ansible-инвентори не была заложена работа с несколькими дата-центрами или AZ и, соответственно, сетями. Поэтому первой проблемой стала смена формата данных со string (содержащей одну сеть или количество серверов) на массивы и словари, что повлекло доработку и переработку нашего шаблонизатора на ytt.
После смены формата данных для сетей и количества серверов мы столкнулись с проблемой Terraform-скриптов создания виртуальных машин для подготовки тестовых стендов — они также не были рассчитаны на разный набор сетей и разбивку ВМ по дата-центрам. В результате был переработан подход с пробросом и переиспользованием информации о сетях и наборах ВМ из инвентори в Terraform. А еще появились Ansible-плейбуки подготовки стенда, расположенные в отдельном репозитории.
К счастью, большая часть ролей Ansible не потребовала существенных изменений. Например, после всех манипуляций с подготовкой стенда, доработкой инвентори, сетями и маршрутизацией PaaS-компоненты заработали как в single, так и в multi-AZ. Это стало приятным бонусом.
VK Private Cloud 4.3
Сейчас у нас выходит релиз 4.3, и мы взяли на себя две взаимоисключающие цели: сделать продукт и как можно более компактным (новая редакция VK Private Cloud Light), и максимально наполненным.
В сторону «маленьких» — у нас есть несколько вариантов установки.
Например, совсем недавно появился запрос на маленькие инсталляции системы виртуализации (ниша таких продуктов, как VMware или Hyper-V), и мы ужали установку с масштаба сотен серверов до нескольких штук. Без отказоустойчивости, лишних PaaS'ов, с совмещенными на одном сервере слоями управления и виртуализации. Но зато всё это умещается на нескольких машинах и вполне выполняет свою IaaS-роль.
Серверы ролей Controller (где располагается управление) и Compute (где физически запускаются ВМ) мы объединили в одну роль, назвав ее Hybrid.

Не остался в стороне и другой путь — на максимальное наполнение, отказоустойчивость и геораспределение.
В публичном VK Cloud накоплен огромный опыт расположения ресурсов в нескольких ЦОД'ах, и через что мы только не прошли:
массовую горячую миграцию ВМ вместе с виртуальными дисками в связи с переездом;
перерубание оптики экскаватором;
выходы из строя целых стоек по питанию.
Все это вылилось в опыт обеспечения максимально возможной надежности «что бы ни случилось».
В 4.3 появилась реализация 3-AZ — развертывание в трех ЦОД. Теперь полностью официально, из коробки и со встроенным опытом предотвращения различных угроз прерыванию работы облачных серверов.

Для сравнения вот вам еще пример обычного стенда с одной зоной (для размещения в одном дата-центре):

Еще нам удалось вырезать IaaS-слой и оставить только систему идентификации и аудита. Это нужно для развития смежных продуктов VK Tech — например, S3-совместимого объектного хранилища. Оно работает на той же основе, и при необходимости установки обоих продуктов мы вполне можем ужиться на одной «базе».
Еще мы добавили VDI as a Service, или, как он у нас называется, Cloud Desktop. В публичной облаке сервис работает уже довольно давно, и теперь он есть и в VK Private Cloud. С VDI можно обеспечить тысячи пользователей высокопроизводительными рабочими станциями для повседневных задач, не тратя деньги на выделение каждому работнику дорогостоящего ПК.

В стоковой поставке мы предлагаем рабочие станции на базе Windows Server 2022, Astra Linux и РЕД ОС. Но ничего не мешает запустить любую другую десктопную ОС или кастомизировать существующие до неузнаваемости — главное, чтобы серверная часть Cloud Desktop (такая же работает на VK Play в облачном гейминге) там запустилась.
Ну и конечно, vGPU для десктопов. Вряд ли кто-то будет играть в игры — но вот запуск тяжелых графических приложений (например, AutoCAD) явно будет востребован. Поэтому в комплекте с VDI идет и поддержка vGPU: админ на стороне клиента может выделить несколько десктопных ВМ с подключением к видеокарте сервера и разделить ее между несколькими пользователями. Или отдать целиком одному — тут мы е��о не ограничиваем. Учитывая ситуацию на рынке видеокарт, это лучший способ и сэкономить на оборудовании, и не застопорить работу компании.

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