Как стать автором
Поиск
Написать публикацию
Обновить
58.58
iSpring
Платформа для корпоративного обучения

Докеризация сборки проекта на всех уровнях

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров5.1K

Всем привет, на связи Вадим Макеров, бэкенд-разработчик iSpring. Успешная воспроизводимая сборка проекта является критическим фактором в поддержке и развитии проекта. При большом количестве проектов и технологических стеков гарантировать воспроизводимость сборки — «собралось однажды, соберется всегда» — сложнее.

О том, как реализовать идемпотентность сборки, я рассказывал в рамках митапа в офисе iSpring в 2023 году. Эта статья — текстовая версия моего доклада.

Моделируем

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

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

Здесь возникает первая проблема

Проблема №1 — Доработка старых проектов

По требованию отдела ИБ нужно доработать несколько проектов, которые уже давно не трогали.

Алгоритм внесения изменений:

  • Клонировать репозиторий проекта

  • Собрать проект локально (необходимо для выгрузки библиотек проекта)

  • Внести изменения

  • Скомпилировать, прогнать тесты, запустить линтер

  • Закоммитить изменения

После начала работ над первым проектом пошли первые неприятности:

Отсутствие специфических инструментов

Некоторые проекты, помимо общего набора инструментов, могут требовать специфические, которые не установлены у большинства разработчиков.

Новый линтер — старый проект

После внесения доработок запуск линтера выдал следующее

Линтер обнаружил замечания, которые раньше не замечал. Мы уверены, что раньше их не было, т.к. линтер запускается в CI/CD перед релизом изменений в проекта.

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

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

В итоге

Имеем сложности сборки проекта и замечания линтера, которые нужно править.

Проблема №2 — Обновление инструмента

Если быть точным — обратно несовместимое обновление инструмента в проекте.

Приведу пример реальной ситуации с обновлением инструментов:

Обновляется кодогенератор с версии v1  на v2, версии между собой несовместимы.

Команда A и B имеют локально установленные версии v1 инструмента.

В рамках доработок проекта команда A решает использовать инструмент v2.

Команда A ставит себе инструмент локально и дорабатывает проект под использование версии v2.

В виду производственной необходимости команде B необходимо доработать проект вместо команды A.

Команда B не способна собрать проект без указаний команды A об обновлении инструмента.

В итоге

Команда B не способна собрать проект без обращения к команде A или самостоятельных поисков и изучении нужной версии инструмента.

Ситуация осложняется при большом количестве команд, дорабатывающих разные проекты.

Проблема №3 — Отсутствие инструмента локально

Этого тезиса я касался в секции доработки старых проектов, хочу отдельно разобрать эту проблему.

Отсутствие заранее подготовленного инструмента локально вынуждает разработчика идти по шагам:

  • Искать в документации к проекту версию инструмента и как её установить. Но вряд ли это будет там описано 

  • Отвлекать других разработчиков

  • Искать в интернете нужный инструмент и подбирать версию

В итоге

  • Разработчик дольше вливается в разработку

  • Нет гарантий, что разработчик установит нужную версию инструмента

Проблематика

Вышеописанные примеры являются следствием системных недоработок

Версии инструментов не зафиксированы

  • Неизвестно какая версия используется в проекте

    • Усложняется первичная настройка

  • При обновлении инструментов нужно как-то уведомить другие команды/отделы

Локальное влияние

  • Локальный сетап непредсказуемо влияет на сборку

  • Появляются сайд-эффекты

Решение

Одним из решений может быть контейнеризация сборки

Под контейнеризацией я подразумеваю использование Docker и Docker-образов с необходимыми инструментами

Контейнеризация сборки - не единственный способ решения описанных проблем.
Решений существует множество, таких как Nix-shell, к примеру. Нам хотелось идти в контейнеризацию и изолированность инструментов, поэтому мы выбрали контейнеры и Docker.

Основные плюсы, которые привносит докеризация сборки:

Портативность

Docker-образы легко переносятся между машинами разработчиков, распространяются через registry docker.hub или другие registry.

К тому же, образы легко переносятся в локальное registry организации при необходимости изоляции CI/CD от внешних факторов

Консистентное окружение

Инструмент запускается в настроенном под него окружении, не требуя настройки локального окружения(переменные окружения, пути к исполняемым утилитам) и не конфликтуя с локальными утилитами разработчика.

Изоляция

Использование утилит изолированно в контейнере, что дает дополнительную безопасность локального окружения разработчика и CI/CD.

Запускаемые инструменты изолированы и не могут влиять на хост‑машину разработчика.

(Заметка на полях: у зловредных инструментов есть множество способов выбраться, но инструменту запущенному в докер контейнера навредить хост‑машине сложнее — чем будучи запущенным на хосте напрямую)

Версионирование

Docker‑образы идеально версионируются, в качестве тэга позволяя выставить любую строку. Можно использовать semver, день выпуска версии инструмента или просто хэш коммита из GIT.

Выбор инструмента

В CI/CD мы уже используем сборку в контейнерах посредством макро-образа со всеми утилитами.

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

Таким образом, имеем следующее требование:

Сборка проекта должна проходить одинаково — локально и в CI/CD.

Makefile + Docker

Можно описать сборку в Makefile, где использовать контейнеры определенных образов для сборки

Пример:

all: build test check


.PHONY: build
build:
   @docker run --rm -it \
       -w ${PWD} \
       -v ${PWD}:${PWD} \
       -e GOCACHE=/app/cache/go-build \
       -v /app/cache/go-build \
       golang:1.22 \
       build -v ./cmd/app -o ./bin/app


.PHONY: test
test:
   @docker run --rm -it \
       -w ${PWD} \
       -v ${PWD}:${PWD} \
       -e GOCACHE=/app/cache/go-build \
       -v /app/cache/go-build \
       golang:1.22 \
       test ./...


.PHONY: check
check:
   @docker run --rm -it \
       -w ${PWD} \
       -v ${PWD}:${PWD} \
       -e GOCACHE=/app/cache/go-build \
       -v /app/cache/go-build \
       golangci/golangci-lint:v1.56 \
       golangci-lint run

Плюсами я бы выделил:

  • Простота — такой Makefile написать достаточно просто

  • Интуитивность — в таком Makefile понятно что каждая команда делает

Минусы же:

  • Нет возможности переиспользовать слои от других этапов сборки, как в классическом Dockerfile

  • Не очень удобно оперировать docker run, когда нужно писать более сложные команды

Dev containers

Dev containers расширение для VS Code (JetBrains также поддержали в своих продуктах) позволяющее запустить IDE внутри контейнера с заранее подготовленным окружением для разработки.

Минусом такого подхода является монолитный образ для IDE и невозможность оперировать такими контейнерами на CI/CD.

DevConainer больше похож на описание окружения, а не утилиту сборки. Из-за чего с ним возникают неудобство в оперировании путями кэша, экспорта кэша в CI/CD и так далее.

Earthly.dev

Earthly позволяет описывать сборку в формате Eaethfile похожему на Makefile + Dockerfile

(пример с README.md проекта)

# Earthfile
VERSION 0.8
FROM golang:1.15-alpine3.13
RUN apk --update --no-cache add git
WORKDIR /go-example

all:
  BUILD +lint
  BUILD +docker

build:
  COPY main.go .
  RUN go build -o build/go-example main.go
  SAVE ARTIFACT build/go-example AS LOCAL build/go-example

lint:
  RUN go get golang.org/x/lint/golint
  COPY main.go .
  RUN golint -set_exit_status ./...

docker:
  COPY +build/go-example .
  ENTRYPOINT ["/go-example/go-example"]
  SAVE IMAGE go-example:latest

Плюсами Earthly я бы выделил:

  • Все команды выполняются в контейнерах

  • Использует Buildkit

Минусы же для нас оказались более значительными

  • Форсирование своей инфраструктуры

    • Используются кастомные версии Buildkit, что является еще одной точкой отказа в долговременной поддержке

  • Возможны Breaking-changes в инструменте

    • Версия v0.8.14 на момент написания статьи

Собственный инструмент

BrewKit

Решили сделать собственный инструмент: BrewKit

(да-да, написали свой велосипед)

Выделяющими качествами BrewKit являются:

  • Сборка в контейнерах

    • Команды не выполняются локально

  • Исходники копируются в стадию сборки и результаты стадии явно экспортируются или используются дальше

    • Неправильное использование утилит не позволит удалить или испортить локальные файлы

    • Если зависимые файлы для стадии не изменились — стадия будет пропущена

  • В качестве движка сборки используется BuildKit

  • Описание сборки в Jsonnet — мощное расширение над классическим JSON

Архитектура

BrewKit обращается к docker-демону с конкретными командами сборки в рамках определенных образов.

Демо работы инструмента

https://asciinema.org/a/q09d6OZyAiGNz1QEFyrPLxPTi

Выводы

С контейнеризацией сборки теперь:

  • Проще контроль за используемыми инструментами и их версиями

  • Упрощенное обновление инструментов

  • Улучшение Developer Experience

    • Разработчик быстрее вливается в разработку проекта

Дополнение к выводам спустя 1 год

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

Brewkit → OpenSource

BrewKit выложен в opensource под лицензией MIT. Как и обещалось на митапе.
Теперь BrewKit можно опробовать у себя. В REDME.md лежит пример быстрого старта, а в docs/ лежит больше деталей о его внутренней реализации.

Обновления упростились

Недавно вышел go 1.22 и наше обновление на него было простым и быстрым.
Раньше у нас обновление проекта на новую версию Go, линтера и кодогенераторов занимал часа 4 на проект. С введением контейнеризации сборки — обновление каждого проекта занимает полчаса(в реальности даже меньше).

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

Теги:
Хабы:
Всего голосов 8: ↑8 и ↓0+10
Комментарии7

Публикации

Информация

Сайт
www.ispring.ru
Дата регистрации
Дата основания
2001
Численность
201–500 человек
Местоположение
Россия
Представитель
Приёмко Андрей