Начало шаблона для быстрого «заземления» PHP-разработчиков в Go
15 лет мы делали бэкенд на PHP. И вот однажды было принято стратегическое решение: сначала переписать самые высоконагруженные места на Go, а потом разрабатывать новые сервисы на нём.
Представьте: вы хотите рассказать про новый язык команде из 40 разработчиков, которые настолько хорошо готовят PHP, что собрали на нём многопоточную систему реального времени и высокой доступности. В худшем случае вас сожгут, в лучшем — прислушаются, но продолжат делать как раньше. Это если вводить язык насильно.
Поэтому мы решили сначала устроить хакатон, а потом позвать на тёмную сторону всех желающих. Если бы их набралось хотя бы человек десять, то со временем они показали бы всем остальным личным примером, как это круто.
Расскажу по шагам, как повторить такой опыт у вас. И про результаты нашего внедрения. И про то, что случается в голове у PHP-разработчика при виде нового языка.
Мы начали с подготовки шаблона и CI/CD, который позволяет задеплоиться за 15 секунд. Чтобы его написать самому, нужно где-то недели две. Мы сделали его заранее.
Шаг 1. Рассказать про Golang
У нас часто и много проводятся внутренние семинары. Кто-то хочет что-то рассказать, пишет объявление, собирает людей и рассказывает. Бывает, это особенности регулирования авиатрафика в России, бывает, рассказ про путешествие, но куда чаще речь идёт про какую-то технологию или фичу, которая была реализована. И может быть полезна ещё кому-то. Так вот, мы собрали внутренний семинар по Go, где рассказали про преимущества языка. Быстрый (компилируемый без бубна: PHP тоже можно скомпилировать, но сложнее), многопоточный из коробки, довольно просто поддерживаемый, можно писать без фреймворка прямо в блокноте, то есть нет геморроя с зависимостями третьего уровня, которые тащит за собой развитый фреймворк. Деплой за 15 секунд. И вроде даже нет никаких очевидных недостатков именно в нашей ситуации. Предлагаем попробовать. Это было как рассказ о другом мире: вот язык, смотрите, чем он отличается, на следующих выходных будет хакатон для тех, кому интересно.
Шаг 2. Документация
За три дня до хакатона рассылаем ссылку на получасовой вводный курс по синтаксису, основным типам данных и функциональности. И базовую документацию. Вот что получили разработчики:
golang.org/doc
tour.golang.org
Плюс контакты Go-senior внутри.
Шаг 3. Группы
Дальше мы предложили разбиться на группы и принести задачи, которые хотелось бы здесь и сейчас реализовать на новом языке. Сразу предупредили, что хакатон будет в офисе в нерабочее время (в субботу), дело добровольное, но можно будет решить боевую задачу и поучиться на практике в почти парном режиме с человеком, который знает нюансы языка. Это заинтересовало 40 человек, и они начали биться на группы и набирать задачи. С группами получилось не у всех: многие пришли с задачей только на себя одного, группы были по два, реже — по три человека. Сами задачи были сверху бэклогов или относились к дублированию какого-то тормозящего места.
Конечной целью было сделать задачу на хакатоне, которую потом можно будет докрутить немного и «продать» владельцу продукта (руководителю направления). В духе: «Вот смотрите, оно раньше 40 секунд работало, а теперь за две всё делает». Эта часть — важнейшая для стратегического успеха внедрения языка в командах: нужна маленькая победа в самом начале.
Шаг 4. Невидимая работа
Подготовили шаблон, про который я говорил выше, под нашу инфраструктуру, который позволял бы запускаться сразу без плясок по сборке и подключению всего нужного. Этот шаблон как бы заменяет фреймворк. Выкладывать его особого смысла нет: повторюсь, он очень заточен именно под нас.
Перечислю лишь функционал, который был заложен в шаблон:
- Интеграция с k8s.
- Экспорт метрик в prometheus.
- Конфигурация через переменные окружения.
- Http-сервер.
- Opentracing.
- Логгер.
У нас уже задолго велась работа по изучению Kubernetes и в качестве решения мы выбрали OpenShift, который предлагал большие возможности по кастомизации и настройке CI/CD. Так мы заточили шаблон сразу под k8s. Созданное приложение в OpenShift создает репозиторий, собирает скелет приложения, сервис в k8s, применяет политики безопасности, собирает бинарник, деплоит в k8s и меньше чем через минуту сервис доступен в сети. Так хакатон стал еще и мероприятием, на котором разработчиков познакомили с нашей будущей PaaS.
Также подготовили дашборд в Grafana с основными метриками, настроили экспорт логов для Kibana и агенты для Jaeger. Для каждого сервиса генерируются ссылки на сервисы, по которым можно сразу получить всю необходимую информацию.
Дальше нужно было договориться про доступы к базам данных, которые нужны для решения задач участниками хакатона. Это неправильно, но мы подключались к боевым базам в read-only на время работы. Это ещё причина, почему суббота — нагрузка в этот день наименьшая.
Предупредили кухню (напомню, она у нас бесплатная на перекусы — творог-фрукты, но не на горячую еду) про мероприятие. Еду подвезли в офис, чтобы мы не отвлекались.
Шаг 5. Сам хакатон
По очевидным причинам (семья, отдых) некоторые не смогли в ту субботу, поэтому мы разбили на заходы по 15 человек.
Рассказали про нашу PaaS-платформу, как создавать из шаблона приложение, как пользоваться шаблоном. Это каркас, в котором есть обёртки для сбора метрик, хелс чеков и метрик, подключения к базе данных и так далее, показали, как подключать всякое разное. Можно было всё удалить на месте или заранее (шаблон тоже раздали до хакатона). Это готовое приложение, которое надо расширять.
Рассказали про деплой и особенности разработки под Kubernetes (конфигурирование, логирование, метрики, работа с ready-пробами, выделение ресурсов по процессору и памяти и т. д.). Попробовали задеплоить «Hello, world!».
Ребята сами создали сервисы. Приятно удивились простоте и удобству использования шаблона. Кто-то уже успел попрактиковаться на Golang, кто-то просто пролистал документацию.
У нас не было цели показать, как писать хорошо, нужно было получить грубый функциональный код и показать, как всё быстро может уйти в препрод, как всё быстро работает и как легко вносить правки.
План был такой: команды пишут, если есть вопросы — отвечаем. Потом сессия ревью и помощь по тому, как можно на Golang сделать что-то правильнее по архитектуре.
Из когнитивных искажений PHP-мидлов стоит отметить «старый» способ мышления в одном потоке. В Go хорошая встроенная конкурентная модель и многопоточность «из коробки», чего не было на PHP. Код получается очень читаемый, чего тоже мало кто ждал.
Мы на практике не всегда используем многопоточность. Зависит от задачи. Но под капотом она тоже есть, и, где нужно, скорость растёт за счёт неё. Например, на любой входящий запрос REST создаётся отдельная горутина. Golang может обрабатывать несколько запросов одновременно, когда большинство других языков (включая PHP) по умолчанию строят их в очередь и обрабатывают в один поток на каждый воркер.
Вторая особенность — большая боль с возвратом ошибок. Если многопоточность — это просто другая архитектура и к ней легко привыкнуть за час-два, то в случае с ошибками при переходе с PHP — это изменение всей модели мышления. Возврат ошибок из функций — основная парадигма в Go. В PHP всё привычно оборачивают try-catch. А тут требуется обработать явно в теле функции. Некоторых людей зверски бомбило от этого «замедления», они активно сопротивлялись. Всё потому, что подход довольно близок к TDD, а TDD требует терпения во время написания кода, но зато прост в отладке. Это увеличивает код по строчкам «если — ошибка», зато один раз пишешь и не возвращаешься. Это очень важно в архитектуре микросервисов, потому что микросервисы растут как грибы и один разработчик может написать десятки микросервисов. Постоянно переключать контекст и дебажить ошибки — это очень дорого и неэффективно. Если сделать все правильно, то приложение будет работать годами, так же эффективно, как и во время разработки. А на PHP в такой же ситуации велика вероятность, что при неправильном написании кода будут постоянно всплывать новые ошибки, после чего надо будет отлаживать всё заново.
Получились портянки в main. Когда функциональная часть была запущена — уже разбирались, как лучше это разбить по пакетам. Потом было ревью. Когда тебе помогает Go-senior, это сразу очень полезно. Кому-то показали, как постоянные походы в базу заменить на кеш в оперативной памяти. Кому-то помогли переписать код в параллельное исполнение. Где-то были мелкие затыки вроде того, что HTTP-сервер на PHP традиционно поднимается и умирает, а тут остаётся висеть.
Шаг 6. Репозиторий
В конце хакатона код складывался в репозиторий, где каждый мог посмотреть код соседних команд и отправить пулл-реквест. Это тоже часть обучения, и в течение недели эти реквесты шли довольно активно.
Итоги
Восемь часов хакатона в два потока по плюс-минус 15 человек (напомню, из 70 разработчиков 40 хотели попробовать Go и получили документацию и шаблон, но не все могли в один день или хотели участвовать физически).
Четверть сервисов давали ожидаемый результат сразу. Ещё часть либо требовала долгих доработок со стороны монолита PHP, либо просто была обширнее восьми часов. Ещё примерно половина сервисов была доделана до прода позже.
В итоге все познакомились с языком, платформой OpenShift, как их женить вместе и так далее. Увидели шаблон приложения и новый CI/CD, получили весь набор инструментов, чтобы дальше решать свои задачи на Go.
Дальше «продавали» фичи владельцам продуктов. Мы отвечали на вопросы по архитектуре от руководителей команд.
В течение следующего месяца примерно все вместе дописывали шаблон (который мини-фреймворк) под потребности подразделений. В итоге он стал побольше. Да, ещё одна особенность шаблона: вся конфигурация — через переменные окружения, то есть при локальной разработке и при переходе на препрод-прод всё идеально одинаково работает. Докер этим и хорош.
Из хакатона пошёл в массы не только Go. Появились шаблоны в PaaS-платформе для PHP и Node.js, но всё равно при прочих равных разработчики чаще выбирают Go как язык для микросервиса. В итоге на практике все новые микросервисы прода, написанные после хакатона, написаны именно на Go. То есть мы распиливаем монолит и сразу всё новое пишем под Kubernetes. К концу года переедем туда почти полностью.
Те, кто не пришёл, расспросили коллег, что это было. Потом подходили и спрашивали по языку, сделали несколько задач в фоновом режиме (к основной работе), дальше положили код в репозиторий и получали пулл-реквесты от других только что обученных разработчиков и от Go-специалистов. Интересовались книгами. То есть мы были ещё несколько недель техподдержкой второй линии.
Для тех, кто хотел продолжать обучение, создали группу в Телеграме с книгами и прочим. Также организовали гильдию Golang-разработчиков, где еженедельно обсуждаем возникшие вопросы в работе, разрабатываем общие компоненты, делимся хорошими практиками. Вступить может любой разработчик.
Самое большое следствие — через месяц мы переписали один из самых нагруженных участков, и он стал работать примерно в шесть тысяч раз быстрее в проде. Но про это я лучше расскажу отдельно: там не только особенности языка, но и реализация многопоточности, пара архитектурных улучшений и немного шаманства. Самое главное — вот наш опыт знакомства с языком, и его в целом легко повторить у вас, если есть хотя бы один Go-специалист, который потратит пару недель на обучение.
Другие посты про нашу разработку: история 15-летнего монолита, продуктовый подход к кухне, что должен знать разработчик про бизнес, особенности возврата авиабилетов.