Текущая настройка

Я работаю над системой интеграции умного дома для своего дома около 7 лет. Система состоит из нескольких частей, среди которых веб-интерфейс, чат-бот и API-сервер (я подробно описал это ранее). По мере того, как система росла, я перестал уделять время CI/CD, предпочитая сосредоточиться на новых функциях и рефакторингах, а не на улучшениях Для разработчиков.

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

Вся система работает на маленьком Raspberry Pi 3B+, поэтому раньше, когда я хотел «выпустить» код, мне приходилось входить в систему, вытаскивать код, редактировать все переменные среды для переделки и запускать его с помощью «экран», чтобы гарантировать, что сеанс не умрет, когда я выйду из системы. Хотя это было нормально, когда это был просто API, сейчас это просто непрактично, когда приходится запускать несколько экранов для чат-бота, API и прокси-сервера, а также туннеля ngrok.

Первый шаг: засунуть его в монорепозиторий

Часть проблемы заключается в том, что вся система находится в отдельных репозиториях, которые зависят друг от друга и изменяются вместе. Новые функции пользовательского интерфейса или чат-бота требуют изменений в API. У меня часто бывает, что два или три кода VS работают одновременно на рабочих столах, и это непродуктивно. (Не помогает и то, что мое устройство — Macbook Air 2014 года выпуска — тоже на последнем издыхании!)

Так я перешел на Турборепо. Вместо нескольких репозиториев теперь у меня есть только один. Миграция не была сложной, я в основном следовал руководству. Я создал новый репозиторий karen и сначала заставил его работать только с API. Как только я был доволен этим, я перенес другие пакеты. Пример репозитория, который я использовал, поставляется с TSConfig и ESLint для всего монорепозитория, чтобы уменьшить дублирование настроек.

Это был отличный опыт, так как это большой бонус, имеющий только один набор настроек и конфигурации. Учитывая, что у меня часто нет большого количества времени для работы над этим проектом, подобные вещи часто отходят на второй план, особенно когда я быстро создаю прототип чего-то нового. Вскоре «новая» вещь становится полноценной частью системы, но вся ее конфигурация представляет собой беспорядок. Кроме того, некоторые пакеты редко обновляются, поэтому отстают. В целом я теперь на борту монорепозиториев, когда они следуют ключевому принципу:

«Код, который меняется вместе, должен жить вместе».

Далее: трубопроводы

В настоящее время мой процесс сборки заключается в том, чтобы зафиксировать код в Github, а затем перенести его на мой Raspberry Pi. Затем мне нужно создать четыре сеанса «экрана» для каждого продукта, а затем собрать все по отдельности.

Очевидным решением долгое время было создание образов Docker. В то время как руководящий принцип заключается в том, чтобы как можно больше работать локально, удобство использования облачного продукта для CI/CD было заманчивым.

Учитывая, что я уже использую Azure Pipelines на работе, я подумал, что это хороший шанс использовать CircleCI вместо этого и приобрести некоторые навыки. Помогло и то, что это бесплатно.

Создание пайплайна, создающего проект Node.js в образе Docker, — это уже не самая передовая история, как десять лет назад, — однако мне было немного сложно заставить Monorepo создавать отдельные репозитории в отдельных контейнерах.

Как и все великие разработчики, я украл часть кода вместо того, чтобы решать его самостоятельно. Я нашел этот невероятный скрипт Bash на Github, который делал почти все, что мне было нужно. Сначала он запускает задание для определения разницы между тем, что было создано ранее, и любыми новыми фиксациями на мастере. Затем это задание порождает дочернее задание для каждого пакета, которому требуется создание нового образа Docker. Он также проверит любые ранее неудачные сборки и повторит их.

После нескольких проблем (в какой-то момент я думаю, что Circle был взломан, и они полностью стерли всю предыдущую историю заданий — полностью испортив систему сборки на несколько дней) я все это собрал.

Я сделал несколько настроек — в основном исключения, чтобы не создавать задания для ESlint, TSConfig или веб-пакетов (веб-приложение построено на Netlify и размещается там, а не локально). Я также добавил немного больше журналов, просто чтобы помочь мне понять, что происходит.

Это оставило меня с конвейером «триггер-рабочие процессы», который запускал стандартный скрипт сборки config.yml в корне моего монорепозитория с заданием сборки Docker для каждого из моих контейнеров. Когда родительское задание решит, что нужен новый контейнер, оно сделает вызов API REST к API CircleCI, инициировав это задание.

Теперь результаты: контейнеры Docker

Теперь конвейеры работали и создавали задачи сборки для моих пакетов — им действительно нужно было что-то собрать. В настоящее время мой raspberry pi запускал отдельные проекты через git pull, npm build и npm run prod —действительно мрачные вещи. Я создал отдельный Dockerfile для каждого из пакетов. Эти образы Docker были очень простыми — всего несколько строк. Как видите, я не специалист по платформам.

Вы можете подумать, как вы работали над этой системой 7 лет и только сейчас собрали образы Docker?! Ответ находится в начале статьи — все это работает на Raspberry Pi. Около пяти лет назад я сделал аналогичный всплеск, чтобы запустить Docker, но обнаружил, что не могу запускать большинство программ на архитектурах ARM. Возможно, это было возможно, но я не был достаточно умен. Однако в прошлом году я наткнулся на кое-что прекрасное — arm32v7/node — образ Docker, чтобы заставить Node.js работать с архитектурой Raspberry Pi. После того, как у меня был этот базовый образ, все, что мне нужно было сделать, это заставить конвейеры создавать контейнеры и отправлять их в реестр контейнеров.

Все вместе: Docker Compose

Теперь у меня было несколько разных образов в реестре контейнеров (я использовал бесплатный: Canister). Я не хотел возвращаться к необходимости вытаскивать отдельные изображения. Docker Compose идеально подходит для этого. Я создал очень простой файл компоновки Docker на Raspberry Pi с настройкой «текущей» цели (то есть правильных версий каждого контейнера).

Учитывая, что для некоторых контейнеров существует безумное количество настроек (для API существует около 40 значений, позволяющих ему подключаться ко всем различным API и продуктам в доме!) В итоге я создал отдельные файлы .env для каждого. , и ссылаясь на них в файле compose.yml вместо массивного куска документа. Это сработало очень хорошо, так как полностью фокусирует файл на реальных запущенных версиях и позволяет зафиксировать его в системе управления версиями. В какой-то момент я хотел бы, чтобы какой-нибудь веб-хук вытащил последний файл YAML, но сейчас я просто прыгаю на поле и меняю одно или два значения, чтобы обновить его. Кроме того, все контейнеры возвращаются обратно, если Raspberry Pi по какой-то причине выключается, поэтому он намного более устойчив.

Этот конвейер CI/CD быстро улучшил общий процесс разработки проекта (и это здорово, поскольку я являюсь разработчиком). Кроме того, теперь я успеваю сделать гораздо больше за то время, что работаю над всем этим. Признаюсь, было обидно не добавлять ничего нового в течение двух месяцев, пока я собирал это. В этом году я смог выплюнуть гораздо больше вещей!