Всем привет, меня зовут Дмитрий Гаевский, я руковожу созданием внутренней платформы для разработчиков в Тинькофф. Последние два года стали знаковыми для российской ИТ-отрасли с точки зрения «распаковки» тем о таких платформах. Крупные технологические компании начали делиться опытом, и сегодня я расскажу о нашем кейсе.
Введение
Внутренние платформы — не новинка для мировой ИТ-индустрии, но подход к их разработке выкристаллизовался сравнительно недавно. Мне кажется поворотным моментом появление отчета Applying product management to internal platforms в ежегодном докладе Technology radar известной исследовательской и консалтинговой компании Thoughtworks.
С тех пор лидеры индустрии внимательно следят за разными подходами и стратегиями построения IDP и создают собственные. Свой путь прошел и Тинькофф.
Уверен, что в будущем мы сделаем много докладов о сложных переездах команд, о нюансах технических решений и о том, какие проблемы помогла решить платформа. Но сегодня я хочу поговорить о стратегии развития таких продуктов. О том, как стратегия решает проблемы масштабирования системы, удерживания TCO в разумных рамках, управления командами и разделения ответственности. Попробую накидать рецепт запуска платформы с нуля.
Итак, сегодня мы:
Немного погрузимся в историю.
Посмотрим на вводные перед запуском.
Проведем разведку и поймем, о чем нужно подумать до старта.
Отправимся в бой с тем, что есть.
Запустим платформу и начнем привлекать к ее разработке остальных.
Научимся справляться с ростом и менять архитектуру.
Предполагаю, что подробно объяснять, зачем нужны такие платформы, не нужно, но немного коснусь и этой темы. Если вы любите видео, а не тексты, можете посмотреть запись моего выступления про построение IDP.
Контекст
Давайте включим машину времени и вернемся в беззаботный 2013 год. Инжиниринг в Тинькофф тогда был сравнительно небольшим: в компании работало не больше 700 инженеров, включая продуктовых разработчиков, Ops-команды, QA, аналитиков и технологов.
ИТ-ландшафт тогда выглядел так: много вендорских систем вроде Oracle и Siebel, специализированные команды для обеспечения Ops вокруг них, отсутствие инструментов для повышения эффективности разработки и ее компетенций в части Ops-приложений.
В период с 2014 по 2019 год Тинькофф сильно вырос. В 2015 году в компании решили перейти от монопродуктовой стратегии — банковского обслуживания физлиц — к мультипродуктовой. Стать платформой, которая поможет решать все вопросы, связанные с деньгами: от обслуживания бизнеса до бронирования отелей и инвестирования.
Команд стало больше, и ответом на этот вызов стало развитие различных IaaS- и managed-сервисов. Self-service не было, ресурсы в системах предоставлялись в основном по заявкам. Появился большой DevOps-отдел c «DevOps-чемпионами» внутри каждой бизнес-линии. Казалось, кросс-опыление между ними и разработчиками поможет сформировать общую культуру разработки.
Но этого не произошло. Вот что мы имели к началу 2019 года:
2500 инженеров;
набор всех возможных CI/CD-практик и стеков технологий, который включал довольно экзотические Erlang, Haskell и другие;
один большой битбакет, где хранилось около 10 тысяч репозиториев, и десяток гитлабов, где хранилось еще около пяти тысяч;
три билд-системы, Jenkins, Gitlab CI/CD и Teamcity, где собиралась большая часть артефактов на агентах с шаренным контекстом;
примерно такая же ситуация с артефактами и мониторингом.
Все эти системы были не связаны друг с другом. Не было общего naming convention, и попытки привязки к бизнес-линиям не помогали.
Это привело к тому, что бизнес не мог качественно выполнить нужный change в разумные сроки. Обеспечивать нужный SLA бизнес-процессов даже в рамках систем, которые у нас были, тоже оказалось невозможно. Фактически наступил паралич инфраструктурных и DevOps-команд из-за огромного maintenance burden. Хотелки пользователей применялись архаично разными командами сопровождения, а отсутствие общих подходов приводило к погоне за удовлетворением сиюминутных требований DevOps/business и недальновидным решениям, которые увеличивали 2-day operations.
Если вам интересны детали, посмотрите доклад моего руководителя Станислава Халупа. В 2021 году он выступил на Kuber Conf и раскрыл исторический контекст гораздо подробнее и живее.
Итак, проблемы нужно было решать. Нам была необходима внутренняя платформа с «проторенными дорожками» по разработке, поставке и эксплуатации приложений. Центральное место для аудита как с точки зрения безопасности, так и с точки зрения владения любыми ресурсами приложений.
Платформа разработки
Переносимся в 2020 год. Мы поняли, что хотим создать платформу и применить к ее построению продуктовый подход. У нас была хорошая фора: мы не первыми шли по этому пути и могли проанализировать опыт других компаний.
Что такое внутренняя платформа? Мне нравится такое определение:
Платформа — это объединение self-service API, инструментов, сервисов, знаний и поддержки, организованных как привлекательный внутренний продукт
Запомним его и вернемся к нему чуть позже.
Постановка задачи
Перед командой стояли такие задачи:
Разработать Identity and access control Management сервис с поддержкой работы сервисных аккаунтов («мне нравится как в Google, там все удобно») и oauth-провайдером.
Создать «зонтик», под которым можно будет собрать управление любыми ресурсами, нужными для разработки и эксплуатации приложений.
Реализовать строгое разделение управления этими ресурсами в рамках multi-tenancy RBAC модели.
Заложить основу для интеграции в систему любых вендорских продуктов, в первую очередь Gitlab, Artifactory, {Vendor product name}.
Реализовать GitOps-подход по хранению as-a-code любых политик, связанных с изменением кодовой базы. В том числе ревью и пуш-рулы.
Разработать pluggable UI.
Наладить мониторинг, алертинг, дежурства, ранбуки.
Обеспечить поддержку пользователей в течение рабочего дня с SLA реакции на ответ до 15 минут.
На все про все — четыре месяца. В команде два UX, один лид, один аналитик, один фултайм-разработчик и три парттайм-разработчика для части с GitLab.
Я даже не буду называть размер Ops-команды, поднимавшей тогда инсталляцию GitLab на три тысячи пользователей. Он был очень скромным.
Задача в такой срок запустить сложную группу сервисов с высокими требованиями по качеству и доступности кажется невыполнимой? Взгляните на определение высоких нагрузок по новому!
Стратегия
Если применить стратегию и немного тактических маневров, задача перестает казаться невыполнимой. Мы приняли стратегические решения и проверили несколько предположений, о которых я расскажу ниже.
Для начала важно лучше понять продукт. Мы должны не только представлять, какие задачи будет решать платформа, но и видеть, какой она должна стать.
У Internal Developer-платформ есть несколько стадий зрелости. На каждой будет разное соотношение собственных систем и вендорских, а также managed и unmanaged user scenarios.
Для нас как для руководителей разработки и архитекторов важно понимать, как выстроить continuous architecture. Вот некоторые принципы.
Do not reinvent. Хорошая стратегия — взять за основу готовые решения везде, где это возможно. И где этого хватит до момента, когда бизнес-обоснование функциональности платформы изменится и маятник качнется в сторону своих решений.
Свои решения — это не только время на разработку, но и operations burden. Наша задача — повысить производительность, а не снизить ее.
Focus on user stories. Любая часть платформы — лишь имплементационная деталь. Мы не считали, что подсистемы платформы отлиты из металла, и поэтому не относились к части вендорских решений как к самостоятельным продуктам.
GitLab, например, в части RBAC-модели и некоторых встроенных фич попросту противоречил идеям платформы и позволял обойти ее ограничения. Не важно, что именно вы используете сейчас как VCS, в какой системе хранятся артефакты и какое у всего этого API. Важны только пользовательские сценарии, вокруг которых выстроена вся система.
Конечная цель была амбициозной: сделать не столько набор сервисов, сколько платформу, которая полностью забрала бы на себя всю когнитивную сложность по развертыванию и operations приложений. Иначе говоря, application-centric платформу, где в first-class citizen не managed-сервисы, а именно приложения, их контракты, зависимости и окружения, где они развернуты. Это значит, что пользовательские истории будут длинными и иногда будут затрагивать не один десяток систем, разрабатываемых разными командами. Это важно.
Operational excellence. Платформа станет ключевой зависимостью систем, которые использует компания. Чтобы оправдать доверие к платформенным командам, нужна операционная компетентность. И это касается всех систем PaaS, разрабатываемых всеми командами.
Доступность и качество предоставляемых систем всегда должны быть first-priority. Это влияет в том числе на продуктовое планирование внутри команд. Нужно четко планировать миграции пользователей на инструменты и сервисы платформы и корректно коммуницировать по каждому большому изменению, затрагивающему их работу.
Обязательно надо обращать внимание на циклические зависимости внутри платформы.
Documentation and support is vital. Большинство наших пользователей — разработчики и инженеры, которые понимают, что нужно делать. Но их поведение слабо отличается от поведения пользователей публичных платформ. Документация и саппорт всегда идут рядом. Если документацию сложно найти или понять, если она устарела, это всегда вернется бумерангом в саппорт, увеличивая нагрузку на инженеров поддержки. А на старте инженеры поддержки — это мы и платформенная команда.
А теперь поговорим о предположениях, которые мы сделали:
1. Число команд, разрабатывающих отдельные кирпичики платформы, вырастет.
Вряд ли платформу и все сервисы всегда будет разрабатывать одна core-команда. Внутренняя платформа вроде IDP — центр притяжения для всех заинтересованных в производственном процессе специалистов. Это разработчики инфраструктурных сервисов, систем мониторинга, application security и систем для нагрузочных тестов. Набор интегрируемых систем вырастет, как только вырастет объем кода и увеличится ее ценность.
2. Большинство платформенных команд будут не подотчетны.
Из первого предположения следует, что в разработку, вероятно, вовлечется огромное количество людей. Ситуацию осложняет, что все они люди с разными руководителями, к которым бизнес приходит с разными задачами и сроками.
Tinkoff’s Journey
Как мы применили все вышеперечисленное на практике? Сделаем первый подход к проблеме и посмотрим, что получилось. Давайте рассмотрим решение с двух сторон: архитектурной и управленческой.
Архитектура. Итак, нам нужно собрать все под зонтик платформы. Получится ядро, которое определит доменную модель, расширяемость платформы, способы интеграции в ее аутентификацию и авторизацию.
Множество команд, которые в процессе развития платформы подключатся к разработке, должны полностью владеть:
Кодом приложения.
Политиками доступа и полномочиями.
UI-модулем.
При этом их не должны касаться сложности, связанные с авторизацией и аутентификацией.
Очевидным решением, укладывающимся в модульность, может быть zero trust model. К тому моменту я несколько лет наблюдал за развитием компании ORY. Энергичный Эней создал стартап, разрабатывая решения для построения IAM. В арсенале были и отлаженный Hydra, и сырой Kratos (idM), и Oathkeeper — на мой взгляд, самый несовершенный и ненадежный.
План был такой:
Пишем «доменный» сервис, определяющий multi-tenancy, RBAC, группы и ACL групп.
Склеиваем получившийся доменный сервис с Hydra и Kratos.
Пишем SPA-приложение для аутентификации.
Берем Oathkeeper, переписываем, реализовываем свои аутентификаторы и авторизаторы для интроспекта identity. В качестве policy-движка берем небезызвестную OPA.
Пишем коннектор к GitLab, подписываем его на permission cобытия от IAM и апплаим нужную модель прав, выданных в IAM в GitLab.
Разрабатываем общий тулинг на основной стек (Golang).
В качестве VCS поднимаем инсталляцию GitLab, где у всех отбираем права мейнтейнеров.
Управление. Чтобы справиться с растущей сложностью системы, мы выработали стратегию.
Core-команда (интерфейсная) разрабатывает общие требования и гайдлайны по API. Принятие решений о том, как выглядит каждое конкретное API, централизовано и проходит ревью команды.
Мы разработали гайдлайны по встраиванию в систему и требования к предварительному анализу. Это набор требований и правил, подсказывающий, о чем нужно подумать перед интеграцией.
Технически процесс интеграции в платформу выглядел следующим образом:
Разрабатываем приложение с API, реализованным по спецификации.
Нужный бэкенд разворачиваем на on-prem кластере.
Вместе с приложением разворачиваем поставляемый командой PaaS IAP (identity and access control proxy). По сути, это опенсорсный Oathkeeper.
Пишем access-рулы и OPA-политики.
Ставим задачи дизайнеру, который будет рисовать разделы платформы.
Разрабатываем UI силами UI-команды PaaS.
Запуск. План позволил здорово срезать стоимость на старте и заложить хороший фундамент на будущее. Запуститься удалось с опозданием всего на месяц. Затем начался тяжелый и очень Ops-политический процесс миграции команд, дни, проведенные в саппорте, и ночи на клавиатуре. Но это уже совсем другая история.
Довольно долго управление осуществлялось так. Уже после запуска платформы и роста adoption нашей связки IAM + RBAC + GitLab + GitLab CI, как мы и предполагали вначале, начали подключаться команды разработки самого CoreTech:
систем нагрузочного тестирования;
Android-ферм;
браузерных ферм.
Со временем и managed-систем, разрабатываемых инфраструктурными командами:
Postgres, Cassandra, S3, Airflow;
внутренний стриминг платформы (Kafka).
First stage problems
На первой стадии мы столкнулись с проблемами, к которым не были готовы. Начнем с технической части.
Face Open Source dude. Open Source — это вам не это. Нужно всегда иметь в виду, что нести контрибьюты больно и долго.
В компоненте Hydra попросту не оказалось важных для нас RFC7521 и RFC7523. Это возможность в Oauth использовать в качестве гранта не полученный через браузер authorization code, а сгенерированный заранее security assertion. Им может быть в том числе особым образом сгенерированный и подписанный JWT-токен.
Робот не может открыть браузер и нажать кнопки. Похожим образом работают учетки в Google-сервисах и многих других облаках.
Фича большая, и, чтобы сделать ее, пришлось:
Курить много спецификации.
Писать много, очень много кода.
Пинать Энея, чтобы быстрее собрал свою новую кухню и наконец поревьюил. К отмазкам обычно прилагалась уважительная причина в виде разбросанных на кухне идеальных немецких досок.
В итоге нам пришлось одновременно поддерживать и собирать форки сразу всех опенсорсных компонентов, так как протягивание в апстрим изменений заняло восемь месяцев. Я уже не говорю об обратно-несовместимых миграциях, некоторых странностях в коде, иногда отсутствующих метриках в приложениях и других сложностях.
Complexity. Архитектура оказалась сложной для многих команд. Проблемы вызвали схема деплоймента с несколькими контейнерами, сложно конфигурируемые access-рулы и политики, а также огромный файл конфигурации самого API, плохо знакомого разработчикам.
Широкий набор фич открывал дорогу творчеству, поэтому команды часто отступали от гайдлайнов. Из-за этого накладные расходы на поддержку и сопровождение росли, а качество сервиса снижалось.
Особенно забавно, что многие команды оказались категорически не готовы писать политики сами. Главный аналитик взял на себя роль писателя политик и тестов на них — так у нас появился Policyman. REGO, так похожий на Datalog, который многие учили в универе, оказался современному поколению немного не по зубам.
Beyond corpstandarts
Мы подготовили свою API-спецификацию. Но наши предложения описывать объекты часто сталкивались с аргументами вида «дайте мне просто делать такой JSON, как я хочу», ведь «разработчик — это творческая профессия».
Из-за этого базовый набор сервисов и коннекторов с самого начала имел две версии: v1 и v2. А спецификацию на старте не имплементировал вообще никто. Торопимся же, куда мне эти ваши стандарты!
Естественно, появилось множество «костылей» и разных реализаций работы с API как на сервере, так и на фронте. У нас накопился гигантский техдолг, выплачивать который пришлось целых два месяца командой из восьми инженеров.
Несложно увидеть, что большая часть задач по управлению была на «ручном приводе». После третьей системы команда PaaS начала терять контроль над происходящим в функциональных командах. Вишенкой на торте стал post mortem, когда опубликованный одной из команд VS попросту перетер рулы, отдающие статику, и наша платформа на целый час стала headless.
Эти проблемы превратили работу команды Core PaaS в игру «Волк и яйца»: приходилось бегать от команды к команде, делая наставления и обучая пользоваться инструментами, траблшутить проблемы развертывания и рассказывать, как правильно продумывать продуктовые истории. Мы чувствовали себя музыкантами на гастролях, неохотно поющими одну и ту же популярную песню.
Тут мы вспомнили о наших принципах и стратегических целях внутренней платформы:
Focus on user stories, operational excellence, documentation and support.
Если продукт сфокусирован на user stories наших пользователей, которые мы старательно расписываем в стратегии и планах, как же нам добиться высоких SLA в этих историях в таком хаосе и завоевать доверие пользователей?
Slow down to speed up
Опытный взгляд сразу увидит не просто минусы, а зияющие дыры в получившейся архитектуре и техническом управлении ею. Успешное управление предполагает, что мы задаемся вопросами:
Какие ограничения для команд создают инструменты и как выполняются соглашения?
Как соблюдаются гайдлайны написания политик, правильного использования прокси и других движущихся частей?
Как мы мотивируем команды реализовывать принятые в API-стандарте паттерны?
Мы придумали, как запустить платформу с минимумом усилий. Но управление развитием любой платформы всегда содержит два важных элемента: ограничения, мешающие делать неправильно, и плюшки, мотивирующие делать правильно.
Мы же не хотим, чтобы однажды появился Нео, который будет интегрировать свой managed-сервис так, что посыпется вся матрица. Поэтому нельзя забывать про robustness principle. А это именно то, чем мы пожертвовали на старте.
Ограничения (Enforce). Важная составляющая ограничений — контроль. Не важно, какие классные правила мы придумали, если их соблюдение никто не контролирует.
Мы выбрали довольно очевидную точку контроля — это точки подключения наших приложений в UI/SDK и Gateway. Для встраивания в UI необходимо использовать фреймворки от команды, которые предоставляют безальтернативных клиентов для работы с API.
Эндпоинты необходимо регистрировать в единой точке композиции системы — большом репозитории со спецификациями, где каждой системе отведены директории со своими VS, политиками и полномочиями.
Ограничение: любое приложение UI или в SDK может выполнять API-вызовы исключительно к нашему GW на spirit.tcsbank.ru
У этой точки есть важный компонент: поресурсный роутинг. Мы делегируем управление приложениям не по неймспейсу API (условно, не api/v1/service/*), а по REST-ресурсам. Это позволяет контролировать появление любых новых эндпоинтов API, предоставляющих управление ресурсом, как со стороны соответствия спецификации, так и продуктово.
Появление точки контроля и повсеместных ревью и валидации снизило скорость поставки новых API. Это было необходимо, чтобы начать контролировать систему. Дальше мы начали думать, как одновременно упростить командам жизнь и контролировать конфигурацию.
На очереди identity and access control proxy — поставляемый нами компонент для идентификации субъектов и авторизации их действий.
Здесь мы улучшались так:
Ограничили правила матчинга урлов.
Расширили OPA-политики, по факту реализовав высокоуровневый DSL.
Разработали свои zero-configurable авторизаторы для прокси.
Интегрировали policy-движок прямо в прокси.
Убрали все вредные настройки, предоставляемые OpenSource-компонентом.
Ограничение: команды, разрабатывающие интегрирующиеся системы, используют предоставляемый командой DSL для описания политик для авторизации.
Конечным результатом стал один легко конфигурируемый образ приложения, который разворачивается в кубовом неймспейсе команды, разрабатывающей managed-сервис. В дальнейшем он же берет на себя функции рейт-лимитов и аудита.
Мотивация (Incentive). Если строить инструментарий только на ограничениях, у команд возникнет вопрос: «А зачем нам вообще быть частью одной платформы?» Ведь можно запилить все сбоку, предоставить свой API с собственным фронтом и интегрироваться через Oauth.
Чтобы этого не произошло, важно подумать о мотивации. Мы решили проблему, реализовав PaaS внутри PaaS. Так появился целый core-домен, набор платформенных сервисов для решения типичных задач managed-систем:
Движок управления квотами. UI и бэкенд, где определены зоны доступности. Он предоставляет механизмы 2FC-резервирования и утилизации квот. Команды заводят в системе свои типы ресурсов и репортят состояния наличия таких ресурсов, а движок репортит состояние изменения квот, заведение новых заявок и другие операции.
Operations-сервис. Позволяет репортить состояние выполнения асинхронных операций.
Единая система аудита. Здесь агрегируются и классифицируются все события. Дальше их можно использовать как для формирования security-ивентов, так и для ведения аудит-лога по действиям пользователей.
Единый компонент поддержки. Система, где регистрируются топики, ответственные и дежурные. По топикам поддержки предоставляется набор метрик — SLA на реакции и ответы с соответствующими дашбордами.
Центр уведомлений. Общая система с UI и бэкендом, где команды могут регистрировать типы событий в managed-сервисах, а затем хуком или событием в MQ триггерить отправку соответствующих событий как адресно, так и группам ответственных или подписчикам.
GitOps-движок. Позволяет командам регистрировать типы отслеживаемых событий в коде любых репозиториев. Например, по типам Kind, описанных в yaml-манифестах.
Фреймворки и библиотеки для реализации паттернов transactional outbox и saga. Клиенты для всего описанного выше.
Собственная дизайн-система и набор UI-компонентов, фиксирующих не только отдельные куски интерфейса, но и целые паттерны (managed-сервис с интеграцией с квотами, нотификациями и другими).
Так мы даем удобный набор сервисов для разработки как managed-систем, так и других. Например, Quality Gate или систем нагрузочного тестирования.
Одновременно с ростом сложности системы и вовлечением в разработку большого количества людей нужно делать не только платформу для разработчиков во всей компании, но и платформу внутри платформы с ограничениями и привлекательными фичами.
Итоги
1. Перед началом разработки мы вникли в продуктовую стратегию и спланировали, какие функциональные команды будут интегрировать свои системы, как они будут это делать и какими будут основные пользовательские сценарии работы с системой.
2. Мы оценили ресурсы, в том числе временные. Применили solution-подход, проанализировав доступные OpenSource-решения. Попробовали спрогнозировать, какие системы придется менять в будущем и какие компоненты мы заменим своими.
3. С ростом сложности и вовлеченности в разработку функциональных команд мы начали прорабатывать решение для сохранения целостности и контроля. И создали точку контроля за политиками, конфигурацией и ресурсами функциональных команд, а также платформенные системы для построения тесно интегрированных managed-сервисов.
4. Мы постепенно заменили ручные методы контроля на автоматизированные, особенно в части политик и API.
Это был рассказ о небольшой, но очень важной части работы по созданию внутренней платформы. Если вы хотите задать вопросы по теме статьи или поделиться своим опытом, пишите комментарии.