Создание современного REST API с помощью Go — часть 4

Эта статья является четвертой в серии, в которой шаг за шагом описываются все аспекты реализации современного микросервиса REST API:

  1. Определение первой модели данных SQL с помощью sqlc
  2. Внедрение REST API с помощью Gin
  3. Настройка с помощью Viper
  4. Сборка и запуск в контейнере
  5. Контейнерные тесты

Весь код серии доступен по адресу https://github.com/bquenin/modern-go-rest-api-tutorial.

Каковы наши среды выполнения?

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

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

Производственная среда

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

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

  • Создание образа производственного контейнера,
  • Запуск этого образа в стеке, имитирующем производственную среду, с помощью docker-compose.

Размер имеет значение

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

Вот почему малогабаритные операционные системы, такие как Alpine, очень кстати. Alpine — это очень компактный дистрибутив Linux, предназначенный для запуска контейнерных приложений и стремящийся к минимально возможной занимаемой площади. Размер сжатого образа чуть больше 2 МБ.

Go — идеальный выбор для создания контейнерных приложений. Действительно, тот факт, что двоичные файлы связаны статически, позволяет очень легко упаковать их в контейнер. Вы даже можете создать образ, содержащий только бинарный файл go (С нуля), и он все равно будет работать!

Здесь мы используем Alpine в качестве базового образа, потому что очень полезно иметь оболочку и другие команды при отладке работающего контейнера и подключении к нему.

Создание образа контейнера

Существует несколько способов создания образа контейнера, совместимого с OCI, но в этом руководстве мы будем использовать Docker. Чтобы образ контейнера оставался небольшим, мы будем использовать многоэтапный процесс сборки:

  1. Сборка бинарного файла go: мы используем официальный образ докера Go. Чтобы включить кэширование, мы сначала загружаем модули go. Это создаст отдельный слой, который можно повторно использовать в сборках, что значительно сократит время сборки. Затем мы копируем исходный код и собираем бинарный файл Go.
  2. Создание производственного образа. Мы используем официальный образ Alpine и КОПИРОВАТЬбинарный файл go, который мы только что создали--from предыдущего слоя.

Получившийся образ минимален, так как содержит только Alpine OS и бинарный файл go. Ни один из артефактов сборки (исходный код, импортированные пакеты и т. д.) не присутствует в финальном образе.

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

Определение нашего стека сервисов

Теперь, когда у нас есть контейнер, мы хотим запустить его в среде, максимально приближенной к производственной среде. Для этого мы будем использовать docker-compose. Compose — это инструмент для определения и запуска многоконтейнерных приложений Docker. С Compose вы используете файл YAML для настройки служб вашего приложения. Затем вы создаете и запускаете все службы из вашей конфигурации с помощью одной команды.

Нашему стеку нужны только 2 сервиса: база данных Postgres и наш микросервис. Мы можем описать этот стек с помощью Compose следующим образом:

микросервис

Это микросервис нашего приложения.

Он включает раздел build, который сообщает Compose о необходимости сборки контейнера при запуске стека. Если вы изменили исходный код, будет создан новый образ, отражающий эти изменения.

Как упоминалось в предыдущей статье, мы настраиваем наше приложение, используя переменные environment, чтобы указать имя хоста и пароль базы данных Postgres. Обратите внимание, что DNS-имя службы происходит от имени службы, определенного в стеке. В этом случае Postgres доступен по DNS-имени postgres.

По умолчанию контейнеры не экспортируют свои порты на локальный хост. Поэтому нам нужно указать сопоставление портов для доступа к сервису через порт 8080. Вы можете узнать больше о Compose networking здесь.

Постгрес

Это наша служба базы данных Postgres. Мы используем официальный образ из хаба Docker.

Что касается другой службы, мы настраиваем ее, используя переменные среды, чтобы указать пароль. В производственной среде эту переменную среды может предоставить диспетчер конфигурации или другие средства, такие как секрет Kubernetes.

Чтобы инициализировать нашу схему базы данных, мы полагаемся на соглашение о контейнерной базе данных и монтируем наш schema.sql в папку /docker-entrypoint-initdb.d. Подробнее об этом механизме можно прочитать здесь.

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

Примечание о безопасности

В этом руководстве мы указываем пароль базы данных, используя переменные среды в файле компоновки Docker. Пароль базы данных по умолчанию проверяется в репозитории исходного кода для удобства. Однако помните, никогда не проверяйте конфиденциальную информацию в репозитории исходного кода.

В нашем случае конвейер развертывания может установить переменную среды пароля базы данных, выполнив подстановку переменных в файле Compose. Еще более безопасным подходом было бы использование инструмента управления секретами, такого как Hashicorp Vault, в сочетании с секретами Kubernetes или секретами Docker.

Среда разработки

Давайте теперь посмотрим на среду разработки. Единственное принципиальное отличие состоит в том, что мы не хотим, чтобы наш микросервис работал в стеке. Мы хотим запустить наш сервис, используя нашу любимую IDE или другие средства.

Нам все еще нужно, чтобы база данных Postgres работала и была доступна. Действительно, рабочий сервис Postgres не экспортирует никаких портов, поэтому подключение к нему из-за пределов стека невозможно. Нам нужно выставить этот порт в режиме разработки.

Надеюсь, мы сможем выразить эти различия, просто создав еще один файл стека:

Мы можем запустить наш стек в разработку, объединив этот файл с производственным стеком. Количество реплик для микрослужбы установлено равным 0, поэтому в стеке не работает ни один экземпляр, а служба Postgres предоставляет хосту свой порт.

Нам нужно только запустить наш экземпляр микросервиса и настроить его, используя переменные среды, для подключения к экземпляру Postgres из стека (доступного на локальном хосте: 5432).

Собираем все вместе

Теперь, когда у нас есть стеки, нам нужно использовать удобные команды, чтобы иметь возможность запускать и останавливать их. Для этого воспользуемся старым добрым Makefile:

Теперь вы можете использовать следующую команду:

  • make prod: чтобы запустить производственную среду, в которой вы можете подключиться к микросервису на локальном хосте: 8080,
  • make dev: для запуска среды разработки, где вы можете подключиться к базе данных на локальном хосте: 5432.
  • make stop: чтобы изящно остановить любой из стеков.

Что дальше?

Теперь, когда мы рассмотрели среду производства и разработки, мы рассмотрим как писать и контейнеризовать наши интеграционные тесты!