惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
N
News and Events Feed by Topic
S
Schneier on Security
G
GRAHAM CLULEY
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
The Last Watchdog
The Last Watchdog
Vercel News
Vercel News
The Cloudflare Blog
C
Check Point Blog
Help Net Security
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
NISL@THU
NISL@THU
S
Security @ Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
AWS News Blog
AWS News Blog
Cloudbric
Cloudbric
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Vulnerabilities – Threatpost
S
Secure Thoughts

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как желание написать простой CRUD привело к созданию целой видеоплатформы
Вадим Климов · 2026-06-23 · via Все публикации подряд на Хабре

Средний

16 мин

0

  • Введение

  • Подготовка к работе

  • Начало работы

  • Когда одного CRUD'а мало

  • Начинаем делать реальный проект

  • Перед разделением на микросервисы

  • Разделение на микросервисы

  • Создание клиентских приложений

  • Добавление мониторинга

  • Перевод проекта в Kubernetes: dev- и cloud-стенды

  • Cloud Deploymen

  • Итоги

Введение

Статья о том, как безобидная инициатива постепенно приобрела эффект снежного кома и со временем превратилась в видеоплатформу, которая в последствии стала DevOps-проектом.

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

Из языков программирования я был наиболее знаком с Python, поэтому следовало выбрать фрэймворк под этот язык. Выбор стоял между Django и FastAPI. Django чаще используется для создания монолитных приложений, которое уже изначально идет с БД и админкой. Нам же нужно было настроить и понять работу с базами данных самостоятельно, а так же работать не строго в рамках границ одного монолита, - а с возможностью выхода за эти рамки при необходимости. Таким образом выбор пал на FastAPI.

Подготовка к работе

Заниматься работой над этим проектом я решил в наиболее удобной для этого среде, - на свободный SSD-диск на 256 ГБ установил Linux Ububntu, и в ней настроил все, что необходимо для работы, - установил среду разработки и сам FastAPI. Изучать фреймворк решил по книге "FastAPI веб-разработка на Python", автор Билл Любанович.

Начало работы

Изначально я просто делал всё по книге, - списывал оттуда код, вникал, устранял проблемы. Некоторые примеры, используемые в книге заметно отличались от актуального Python-стека, поэтому часть кода приходилось адаптировать под современные версии Python и библиотек. Практически сразу - с момента создания своего первого эндпоинта я начал использовать Git, и сразу же решил обернуть код в Docker. Потому, что не было ни одной причины этого не сделать. Позднее на примерах кода из книги по созданию списка исследований и работы с ним - был сделан полноценный CRUD, на что программа книги и была рассчитана. Так я впервые познакомился с разделением приложения на уровни: web-уровень принимает HTTP-запросы и возвращает ответы, service-уровень содержит бизнес-логику, а data-уровень отвечает за взаимодействие с базами данных.

В исходном варианте данные хранились в SQLite. И я подумал, что будет перспективным перейти с SQLite на PostgreSQL, таким образом я познакомился с SQLAlchemy, - ORM-библиотекой, позволяющей работать с различными СУБД через единый интерфейс. Так как проект у меня был в Docker, а PostgreSQL запускался в отдельном контейнере, - с этого момента я начал работать с docker-compose. Признаюсь, что переход с SQLite на PostgreSQL дался мне не совсем легко. Это было связано с тем, что пришлось существенно перерабатывать data-уровень: если раньше там использовались raw SQL-запросы, то теперь появились ORM-модели и работа через session SQLAlchemy. Начали применяться Alembic-миграции.

На это у меня ушел примерно месяц, за который у меня получилось сделать первый бэкенд своими руками. Я узнал о разных библиотеках - Pydantic(валидация входящих данных), Pytest(тестирование Python-кода;), HTTPX(http-клиент для тестирования API и взаимодействия между сервисами). Научился исправлять ошибки, погрузился в тестирование. Так узнал, что в FastAPI есть swagger, в котором удобно проверять работу API, подёргал ручки с помощью Postman и узнал что в нём этот процесс можно автоматизировать. Особо интересным мне показалось интергационное тестирование - это тестирование уровней и их взаимосвязи друг с другом. Увидел перспективы автоматического тестирования, сделал первый автотест.

Когда одного CRUD'а мало

После того, как у меня получился базовый CRUD, пришло понимание, что хочется чего-то больше похожего на реальное настоящее приложение, а не учебный проект. Для базового CRUD'а нет смысла, допустим, настраивать мониторинг потому, - что там нечего мониторить. К нему не создать интересное клиент-приложение, такой проект нельзя разместить в портфолио потому, что "как-то не серьёзно". Возникла задача создать что-то боевое и настоящее, чем реально можно пользоваться.

Было бы неплохо создать настоящую инфраструктуру, на примере который можно было бы изучить и применить брокеры сообщений и кеширование, применить репликацию. С технологиями я уже определился, - можно было бы добавить в проект Redis и RabbitMQ (со вторым познакомился на работе). Но вопрос, - какие задачи выполняет сервис, в котором применяются данные технологии, и какие из этих задач актуальны в качестве идеи для проекта, разрабатываемого в домашних условиях?

Из возможных вариантов были следующие:

  • система обработки файлов и документов

  • система уведомлений

  • маркетплейс

  • облачный сервис

  • стриминговая платформа

  • видеоплатформа

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

  • система обработки файлов и документов - не достаточно масштабно

  • система уведомлений - влечёт за собой вопрос "кого и о чем уведомлять?"

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

  • облачные сервис - в сети есть готовые открытые решения, зачем делать свой, если можно просто поднять тот же NextCloud?

  • стриминговая платформа - стоит понять - кому и зачем, нужно стримить и смотреть стримы у вас дома?

Осталась только видеоплатформа для загрузки и просмотра видео на подобие Ютьюба или Рутьюба.

Начинаем делать реальный проект

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

В этот момент я понял, что для упорядочивания работы мне необходимо документировать свои действия, ставить и описывать цели, описывать результаты и проводить анализ своих действий. Это значит, что необходим какой-то инструмент, который позволяет это делать - система по работе с заявками и документацией. Нужно где-то документировать и описывать свой проект, API должно быть описано, должна быть схема проекта, должно быть описано как его тестировать и запускать, должны быть описаны различные нюансы и особенности которые могут иметь значение. После недолгих раздумий и выбора сервиса решил использовать RedMine потому, что я с ним очень хорошо знаком. Поднял прямо на самом ПК с убунтой на борту, настроил автоматические бэкапы базы данных на внешний носитель, так как блочные устройства имеют свойство периодически выходить из строя, а вся информация из тикетов хранится как раз в БД. Данное решение позволило мне существенно упорядочить работу над проектом, я смог таким образом ставить перед собой конкретные цели того, что мне нужно сделать. Большие задачи я разбивал на большое количество мелких задач и решал их все по-отдельности. Данный способ дал мне определённость в том, что я буду делать сегодня и завтра, в том что мне предстоит в принципе сделать и позволил мне наладить тайм-менеджмент. По решённым задачам я смог отслеживать свой прогресс и составлять дальнейшие планы. В заявках оставлял комментарии с важной информацией, описывал нововведения/изменения, ветку и коммит из Git с датами.

Вернёмся к проекту, - после настройки системы заявок я добавил функционал работы с видео, API начало быть не просто заглушками - а реально работающими ручками. Видео стало загружаться и храниться пока что в локальном storage на ПК, на этапе загрузки стал возвращаться статус загрузки - ready/failed. Я создал тестовую страницу html-страницу для просмотра видео, чтобы наблюдать за работой сервиса, отслеживать ошибки через DevTools. Загруженное видео начало быть готовым для просмотра в качестве 360p/720p/1080p. Был реализован HLS (HTTP Live Streaming), - деление потока видео при просмотре на сегменты 2-10 секунд в формате .ts или .m4s. Ffmpeg стал генерировать завершённый статический HLS-плейлист - который управляет очередностью сегментов. Таким образом браузер при просмотре видео подгружает сегменты видео при просмотре, а не всё видео целиком. Создаётся Thumbnail - превью-картинка к видео. С помощью Range(http-механизм частичной загрузки файла) осуществляется перемотка видео в плеере. Вот так был реализован работающий HLS VOD - просмотр видео по запросу, где видео загружается сегментами. VOD означает "Video On Demand" - видео по запросу.

Перед разделением на микросервисы

Ранее я хотел на собственном опыте познакомиться с использованием RabbitMQ и Redis в проекте, решил сделать это уже сейчас:

  • Redis используетлся для обеспечения идемпотентности обработки видео и координации работы нескольких processing-worker’ов. Если одно и то же видео повторно попадёт в очередь задач, Redis через distributed lock позволяет избежать параллельной обработки одного и того же файла несколькими воркерами одновременно.

  • RabbitMQ используется как брокер сообщений и очередь задач для обработки видео. Это позволяет сделать загрузку асинхронной - пользователь не ждёт завершения transcoding во время upload-запроса, а обработка выполняется processing-worker’ами в фоне. Такой подход также позволяет независимо масштабировать количество воркеров в зависимости от нагрузки. Дополнительно через RabbitMQ реализованы retry-механизмы и DLQ (dead-letter queues) для повторной обработки failed-задач.

На данном этапе бэкенд видеоплатформы был еще похож на MVP, но далее следовало большое количество доработок, ходе которых я начал внедрять практики и решения, характерные для production-проектов. Была обеспечена идемпотентность на уровне БД даже без Redis, реализована асинхронная загрузка видео, введена JWT-авторизация пользователей, реализована возможность подключения S3-хранилища и использования CDN, сделано понятное логирование.

Интересной задачей оказалась добавление на платформу возможности стриминга. Нужно было создать новые эндпоинты для контроля сессии, научить платформу принимать live-поток и осуществлять транскодирование, генерировать hls-сегменты на лету и проводить очистку устаревших сегментов с диска после определённого TTL, чтобы не забивалось пространство.

С появлением стриминга у меня появилось три новых docker-контейнера:

  • ingest nginx - принимает live-поток в RTMP

  • origin nginx - раздаёт поток для просмотра

  • live-cleaner - проводит очистку чанков после завершения стрима

Я думал, что может стать проблемой реализация DVR (Digital Video Recorder) - в контексте видеоплатформы это возможность перемотки и паузы во время прямого эфира, перепросмотра стрима, - но оказалось, что это не проблема. По опыту работы с видеонаблюдением у меня было представление, что для хранения и просмотра записей нам необходим рекордер, - сервер со специальным ПО, который принимает и записывает видеопоток. Но чуть глубже разобравшись с вопросом мне стало понятно, что рекордер как таковой - это программный менеджер, который управляет процессом принятия видеопотока, нарезания его на сегменты, составление манифеста/плейлиста - который позволяет плееру понимать, как можно перематывать видео. В нашем случае функцию такого менеджера выполняет сам бекенд, а записи можно сохранять на S3-хранилище. То есть - я думал что рекордер нам будет нужен в качестве отдельного программного решения, которое нужно будет разработать но - оказалось, что в этом нет необходимости. В нашем случае возможность отмотки видео назад зависит от TTL хранения сегментов этого видео, который можно изменить как нам угодно или вовсе убрать. В итоге стриминг был успешно реализован.

Разделение на микросервисы

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

Разделению предшествовала многоступенчатая подготовка, задачи с наиболее понятными мотивами были бы сформулированы таким образом:

  • фиксация архитектурных границ сервисов, - определение окончательных зон ответственности каждого сервиса

  • подготовка каждого сервиса к изоляции, - убрана жёсткая связанность бизнес-логики с сервисом авторизации, подготовка storage-контракта к переходу на S3 чтобы это не требовало изменения бизнес-логики

  • сквозная корреляция запросов - один request_id проходит через все сервисы, в итоге в логах каждого сервиса можно отследить запрос по единому request_id

  • разделение API и Delivery - API не отдаёт контент а возвращает ссылку, контент отдаёт origin nginx

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

  • processing-worker - асинхронный обработчик видео

  • upload-service - сервис загрузки видео

  • video-api - основной сервис метаданных

  • outbox-publisher - читает записи событий из БД и публикует их в RabbitMQ

  • video-events-consumer - читает события из RabbitMQ, обновляет статусы в БД

  • live-api - управляет live-сессиями

  • live-cleaner - удаляет неактуальный контент из live-storage

  • identity-service - сервис авторизации

  • dlq-replayer - осуществляет retry failed-событий возвращая их в outbox-publisher

  • migrate - одноразовый job для применения Alembic-миграций

  • ingest nginx - принимает RTMP-поток стрима, пишет live HLS-файлы

  • origin nginx - раздаёт HLS-плейлисты, контент

  • RabbitMQ - брокер сообщейний

  • Redis - runtime-хранилище для locks/coordination

  • PostgreSQL(Master+Replica) - база данных Complete_K8S_Stend.png

Схема взаимодействия микросервисов

Схема взаимодействия микросервисов

После разделения платформы на микросервисы появилась проблема согласованности между базой данных и RabbitMQ. Например, сервис мог успешно сохранить изменения в PostgreSQL, но не успеть отправить событие в очередь из-за ошибки или рестарта контейнера.Для решения этой проблемы был реализован outbox pattern. События сначала сохраняются в отдельную outbox-таблицу внутри той же транзакции PostgreSQL, что и изменения основных данных. После этого отдельный сервис "outbox-publisher" считывает события из таблицы и публикует их в RabbitMQ.Этот подход подход позволяет избежать потери событий и обеспечивает более надёжное взаимодействие между микросервисами.

Создание клиентских приложений

До текущего момента тестирование функционала происходило посредством Unit- и E2E - тестов, также с помощью тестовой html-страницы. Возникло желание создать MVP-клиент, который будет работать с API, чтобы посмотреть на сколько оно работоспособно в реальных условиях. Из опыта создания клиентских приложений у меня совершенно не было опыта создания сайтов, но был небольшой опыт создания мобильных приложений под Android. Ранее я использовал Flutter для создания собственного приложения для списка покупок, когда не смог подобрать себе подходящее приложение из тех, что уже были(тогда мне было нужно, чтобы приложение не было перегружено лишним функционалом и имело всё необходимое). В данном случае Flutter позволил бы обеспечить мультиплатформенность, - так как один и тот же код с небольшими правками можно использовать для создания приложения под Android, IOS и веб-сайта, если использовать Flutter Web.

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

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

Экраны приложения

Экраны приложения

С веб-версией я пришёл к выводу, что для ведоплатформы всё же оптимальнее использовать Next.js, а не Flutter Web - так как последний больше про веб-версию мобильного приложения. А Next.js проще работать с API и HLS-стримингом. Сайт тоже был сделан, почти в той же стилистике и с теми страницами и функционалом что и приложение. Из самых больших отличий - стриминг на сайте происходит через OBS. Пришлось доработать API, чтобы оно корректно отдавало ссылку на стрим и ключ для OBS.

Таким образом появилось возможность мультиплатформенной поддержки сервиса.

Добавление мониторинга

Локально на ПК, где велась разработка, проект запускался через docker-compose, количество контейнеров было около 18. Наверное было бы неплохо, чтобы можно было всё это эффективно наблюдать и быстро понимать, где возникают проблемы. Так я задумался о мониторинге, который в последствии был обеспечен с помощью Prometeus+Grafana. Ну и само-собой - куда же без логов? Так как полнотекстовым поиском в логах моих сервисов искать особо нечего, решил что в Elastic для нас нет необходимости, будем использовать Loki.

Так у нас появилось аж 4 дашборда:

  • Video Platform Overview - показывает состояние всех сервисов, + панель с логами по основных из них (web, upload, video, live, identity)

  • Video Platform Pipeline - показывает работу пайплайна из сервисов по загрузке видео, + их логи (processing-worker, outbox-publisher, video-events-consumer, live-cleaner)

  • Video Platform API - показывает нагрузку на API-сервисы, ошибки, количество запросов в секунду, + логи

  • Video Platform SLO - показывает стабильность пайплайнов, нагрузку на систему, где "застревают"

Перевод проекта в Kubernetes: dev- и cloud-стенды

Наверное у читателя может возникнуть вопрос, - а зачем в данном проекте и в данном случае K8s?

Отвечу честно, - он здесь незачем:

  • у нас нет мультисервера на несколько машин

  • автоматическое масштабирование таких сервисов как processing-worker, live-api, origin nginx без реального запуска проекта в прод в нашем случае будет скорее учебной задачей

  • для нас пока что не критичен отказ сервиса на пару секунд И исходя из этого было бы практичнее оставить проект в docker и может быть использовать Docker Swarm по необходимости, но мне хотелось пощупать руками K8s, поэтому я захотел взяться за эту задачу.

Если не вдаваться в подробности, - переход в K8s благополучно удался. В ходе его реализации я на практике столкнулся с рядом проблем:

  • приведение к рабочему виду переменных окружения, - из .env нужно было разделить ConfigMap и secrets

  • установить правильный порядок старта сервисов, - у меня изначально Alembic-миграции(отдельный job) запускался раньше БД, и миграции не осуществлялись. Теперь job ждёт доступности БД

  • решение проблем с портами и маршрутизацией - сервисы должны быть доступны не только внутри Pod, но и снаружи Kubernetes-кластера. Для внешнего доступа используются NodePort/Ingress-механизмы, а маршрутизация в итоге строится по цепочке: Ingress → Service → targetPort → containerPort.

  • мониторинг так же пришлось перерабатывать под K8s, точнее сделан отдельный мониторинг под K8s-версию стенда

На этом этапе я задумался о том, насколько проект готов к эксплуатации, и рассмотрел возможность развернуть отдельное окружение для друзей. Наверное было бы неплохой идеей арендовать облако и задеплоить туда весь проект, попутно применив на практике Terraform и подключив реальное S3-хранилище, - неплохая практика. После того, как произвёл расчет конфигурации облака, пришло понимание того, что production-like стенд является довольно ресурсоёмким, а аренда на несколько месяцев, не говоря уже о постоянной доступности сервиса, - весьма затратное занятие (около 60-120$ в месяц). Тогда мне вспомнился рассказ одного знакомого DevOPS'a о том, как он собрал дома системный блок под сервер, и появилось понимание мотивов, - почему он это сделал. Для того, чтобы обеспечить постоянную доступность моей видеоплатформы на длительный срок, с финансовой точки зрения было бы выгоднее собрать под неё свой сервер. Оба варианта, - с облачной инфраструктурой и собственным сервером подразумевали под собой финансовые издержки, которые на данный момент не были бы в должной мере целесообразными. Поэтому с "closed beta" я пока решил повременить.

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

В него вошли сервисы:

  • identity-service

  • upload-service

  • processing-worker

  • video-api

  • migrate

  • db-master

  • web

  • origin

  • rabbitmq

  • redis

Он тоже был развернут в Kubernetes, для управления конфигурацией на этот раз использовался Helm. Для этого стенда также был создан мониторинг Prometeus+Grafana+Loki. Создана сборка мобильного приложения и сайта для работы с ним. По факту, по сравнению с полной версией, которую можно именовать "локальный dev-стенд", - мы убрали возможность стриминга.

Если в локальном dev-стенде конфигурация описывалась через manifests-файлы, к появлению cloud-стенда уже стало понятно, что поддерживать большое количество manifests не удобно.
Cloud-окружению потребовалось:

  • отдельная конфигурация ingress nginx

  • другие images tages

  • другой набор сервисов

  • отдельные secrets и variables

  • возможность переиспользовать шаблоны

Cloud deployment был переведён в Helm. Он позволил шаблонизировать K8s-конфигурацию и управлять окружением(пока одним) через values-файлы без дублирования manifests. В последствии поддержка cloud-стенда стала существенно проще.

Cloud Deployment

Ранее я уже выполнял ручной деплой на виртуальную машину и VPS с использованием Docker Compose, однако теперь мне возникла необходимость разобраться на практике, как выглядит автоматизированный cloud deployment. После сравнения цен и конфигураций у различных cloud-провайдеров арендовал подходящую VPS-конфигурацию в Timeweb Cloud. Для подготовки облачного окружения использовался Terraform, это позволило описать инфраструктуру в виде кода и с возможностью воспроизведения развернуть необходимые cloud-ресурсы.

В качестве CI/CD-платформы использовался GitLab. Pipeline был разделён на несколько stages:

  • test

  • build

  • security

  • deploy

  • smoke

Успешно пройденные stages из списка выше

Успешно пройденные stages из списка выше

Перейдём к боли и слезам, - пайплайны постоянно сыпались по самым разным причинам. До того момента, как весь pipeline прошел успешно и все stages были завершены без ошибок, произошло около 15-17 падений. Ошибки самыми были разнообразными, ниже описаны некоторые из них(разбиты по категориям).

CI/CD и GitLab:

  • Старый/неработающий GitLab Runner, - создавал на ПК новый и регистрировал

  • CI/CD variables были Protected - REGISTRY_PULL_USER и REGISTRY_PULL_TOKEN не передавались в pipeline потому, что ветка была не protected

  • Smoke-test проверял неправильный endpoint - "curl http://$PROD_DOMAIN/health" давал 404, потому что реальный route ещё не готов, временно сделали smoke non-blocking

  • Rollout checks были слишком жёсткими для первого деплоя - "rollout status deployment/web --timeout=180s" падал раньше, чем сервисы успевали

Работа с GitLab Registry и private images:

  • K8s не мог скачать private images из GitLab Registry - были ошибки 403 Forbidden при pull образов, решили через Project Access Token и imagePullSecret

  • imagePullSecrets не применялись к Pod’ам - добавили imagePullSecrets в templates и дополнительно привязали secret к default ServiceAccount

  • Неверный image tag PostgreSQL - bitnami/postgresql:16 не существовал/не тянулся, использовали конкретный рабочий tag "16.4.0"

Helm и K8s deployment:

  • Helm конфликтовал с уже существующими ресурсами - Namespace и Secrets были созданы вручную/CI отдельно, а Helm не мог “импортировать” их без своих labels/annotations, - дали Helm управлять ресурсами самому

  • Job "migrate" нельзя обновлять как обычный Deployment - помогло удаление старого "migrate job" перед "helm upgrade"

  • Старые failed deploy’ы мешали новым - остатки namespace/resources/jobs приводили к конфликтам, решено иметь процедуру cleanup перед повторным деплоем

K8s networking и ingress:

  • Ingress class не соответствовал кластеру - Chart был настроен на nginx, а в k3s используется traefik, из-за этого запросы давали 404, учли ingress-controller конкретного кластера

Так как в облачном стенде на момент деплоя еще не были реализованы port forwarding и маршрутизация, - API стенда пока что были недоступны, и smoke-тест был по-сути заглушкой. Но позже это было исправлено, и smoke стал делать health-check сервисов.

После того как весь pipeline начал проходить успешно, успешно проходил smoke-тест, к облачному стенду было подключено реально S3-хранилище. Это было сделано, чтобы убедиться, что система способна работать комплексно. CDN'ы я подключать не стал, - это более актуально для полного реального продакшен-стенда.

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

Итоги

Всё время работы над проектом мною правило желание изучать что-то новое посредством применения этого на практике.

Если кого-то интересует, использовал ли я нейросети - да, использовал для автоматизации рутины. Приоритетом моей работы не был кодинг как таковой, - более интересными были изучение фреймворка FastAPI, DevOPS-методологии и применение различных технологий. Первый CRUD я написал руками, а по мере роста проекта начал активно использовать ИИ. Основной интерес для меня представляли DevOps-практики, поэтому ИИ выступал скорее инструментом автоматизации рутинной разработки.

На данный момент работа над видеоплатформой завершена, подготовлена документация и мониторинги. Проект готов к развёртыванию, подключению S3-хранилища и CDN. За время работы он разросся настолько, что дальнейшее развитие уже потребует не только доработки бэкенда с инфраструктурой, но и работой над сайтом и приложением, - то есть пользовательской частью в целом.

Создание бизнеса/стартапа мне не интересны, поэтому выкатывать в публичный прод я его не планирую. Но он вполне себе подходит на выполнение роли испытательного полигона для изучения технологий и отработки практических навыков.

За время работы над проектом произошло несколько интересных событий, - однажды перестали быть доступны образы с Docker Hub, пришлось переключаться на Yandex Container Registry. Были ужесточены требования к сайтам и сервисам в интернете. Названия и вывески стали требовать на русском языке, а у меня платформа на английском. На фоне всех этих событий спешить пока не хочется.

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