Пример микросервиса на Go
Пример микросервиса на Go
Предварительно рекомендую познакомиться с Docker.
Микросервис на языке Go демонстрирует принципы контейнеризации и быстрого развертывания приложений. Использование стандартных инструментов позволяет создать надежную среду разработки и эксплуатации. Следующие шаги могут включать добавление базы данных, настройку балансировки нагрузки или интеграцию с системой мониторинга.
Установка инструментов разработки
Процесс создания и запуска микросервиса начинается с подготовки окружения. Для работы потребуется установить компилятор языка Go, инструмент для управления сборками и программу для контейнеризации Docker.
Установка среды разработки происходит в несколько этапов. Сначала установите Go с официального сайта, добавьте go в PATH и проверьте:
go version
Разбор:
go versionпроверяет, что бинарник Go доступен вPATHи возвращает версию toolchain.- Команда полезна как первый smoke-check окружения: если она работает, можно переходить к
go modи сборке. - По выводу видно платформу (
windows/amd64,linux/amd64), что помогает диагностировать проблемы кросс-сборки.
Затем установите Docker Desktop (или Engine на Linux), запустите демон и проверьте:
docker --version
Разбор:
docker --versionпроверяет, что CLI Docker установлен и вызывается из текущего терминала.- Эта проверка не гарантирует, что демон запущен, но подтверждает наличие клиентской части.
- Если команда недоступна, дальше шаги со сборкой образа (
docker build) не сработают.
Пользователю следует убедиться, что оба инструмента доступны из командной строки. Отсутствие ошибок при запуске проверочных команд подтверждает готовность среды к дальнейшим действиям. В случае возникновения проблем с правами доступа или путями поиска программ требуется перезапуск терминала или повторная проверка настроек переменных окружения.
Создание структуры проекта
Файловая структура проекта должна соответствовать стандартам организации кода в экосистеме Go. Корневая директория содержит исходные файлы, конфигурационные скрипты и документацию. Внутри корня создается файл main.go, который служит точкой входа для приложения.
Первым действием создают каталог проекта и инициализируют модуль Go (go.mod):
mkdir my-microservice
cd my-microservice
go mod init my-microservice
Разбор:
mkdir my-microserviceсоздаёт отдельную директорию проекта, чтобы не смешивать файлы с другими задачами.cd my-microserviceпереводит терминал в корень будущего модуля.go mod init my-microserviceсоздаётgo.modи фиксирует модульный путь проекта.- После инициализации все зависимости и версии будут управляться через модульный механизм Go.
Структура файлов выглядит следующим образом:
my-microservice/— корневая папка проекта;main.go— основной файл с логикой сервера;Dockerfile— инструкция для сборки образа контейнера;go.mod— описание зависимостей модуля;go.sum— контрольные суммы зависимостей (генерируется автоматически).
Такая организация позволяет легко масштабировать проект, добавлять новые пакеты и поддерживать порядок в коде. Использование стандартной структуры упрощает работу других разработчиков и автоматизированных систем сборки.
Написание кода сервера
Расширенный разбор маршрутов, форм и шаблонов — в Веб на стандартной библиотеке Go.
Файл main.go содержит реализацию простого HTTP-сервера, который обрабатывает входящие запросы и возвращает ответ клиенту. Код написан с использованием стандартной библиотеки net/http, которая входит в состав базового дистрибутива Go.
Приложение регистрирует обработчик для корневой URL-адреса /. При поступлении запроса сервер выполняет функцию обработки, которая формирует текстовый ответ. Функция http.HandleFunc связывает путь с функцией-обработчиком. Вызов функции http.ListenAndServe запускает сервер на указанном порту.
Код ITЗагрузка примера кода…
Разбор:
package mainиfunc main()формируют исполняемую программу, а не библиотеку.helloHandler(w http.ResponseWriter, r *http.Request)— обработчик HTTP-запроса, гдеwиспользуется для ответа клиенту.http.HandleFunc("/", helloHandler)регистрирует маршрут/и привязывает его к функции-обработчику.http.ListenAndServe(":8080", nil)запускает сервер и блокирует поток, пока процесс не завершится или не возникнет ошибка.- Проверка
if err != nilобязательна, потому что запуск может провалиться, например при занятом порте.
В приведенном примере используется функция fmt.Fprintf для записи данных в объект ResponseWriter. Этот объект представляет собой поток вывода ответа HTTP. Строка "Привет! Это микросервис на Go..." отправляется браузеру пользователя.
Функция main инициирует цикл обработки событий. Метод ListenAndServe блокирует выполнение программы до момента остановки сервера. Параметр :8080 указывает порт, на котором будет слушаться трафик. Если порт занят или недоступен, система вернет ошибку, которую код выводит в консоль через fmt.Printf.
Проверка сборки локально:
go build
./main # Windows: main.exe
Разбор:
go buildкомпилирует проект в бинарник текущей платформы, не запуская его.- Запуск
./main(илиmain.exe) проверяет, что приложение работает как самостоятельный исполняемый файл. - Такой цикл удобен для production: сборка и запуск разделены, а артефакт можно упаковать в контейнер.
Конфигурация Dockerfile
Файл Dockerfile определяет инструкцию для создания образа контейнера. Он содержит последовательность шагов по подготовке окружения, копированию кода и настройке запуска. Использование многоступенчатой сборки уменьшает размер итогового образа, исключая лишние инструменты разработки из финальной версии.
Первый этап сборки использует официальный образ Go как основу для компиляции. Эта стадия называется builder. Она загружает образ с установленным компилятором и инструментами. Затем в образ копируются файлы проекта. Команда RUN go build -o /app/main . собирает исполняемый файл и помещает его в директорию /app.
Второй этап использует легкий образ Alpine Linux. Этот образ содержит только необходимые системные библиотеки, что делает его минимальным по размеру. Копирование исполняемого файла из предыдущего этапа в текущий образ происходит через команду COPY --from=builder /app/main /app/main.
Финальная инструкция CMD указывает, какую команду выполнять при запуске контейнера. В данном случае это запуск собранного бинарного файла /app/main. Образ также объявляет порт 8080 как открытый для внешних подключений.
Код ITЗагрузка примера кода…
Разбор:
FROM golang:1.21-alpine AS builderзадаёт build-stage с компилятором Go.COPY go.mod ./иRUN go mod downloadпозволяют кэшировать слой зависимостей и ускорять повторные сборки.RUN CGO_ENABLED=0 GOOS=linux go build ...собирает статический Linux-бинарник для финального контейнера.- Второй
FROM alpine:latestформирует runtime-stage с минимальным набором файлов. COPY --from=builder /app/main .переносит только бинарник, исключая исходники и инструменты сборки.EXPOSE 8080документирует сетевой порт сервиса, аCMD ["./main"]задаёт команду запуска контейнера.
Использование переменной окружения CGO_ENABLED=0 отключает связь с C-библиотеками, что позволяет создать полностью статический бинарный файл. Такой файл не требует наличия дополнительных библиотек в целевом образе и работает быстрее. Образ Alpine обеспечивает безопасность и экономию места на диске.
Актуальный шаблон multi-stage с построчным разбором и проверкой curl — Go-сервис в галерее Dockerfile; теория инструкций — Dockerfile.
Сборка и запуск контейнера
Сборка образа по Dockerfile (точка — текущая директория с файлом):
docker build -t my-go-service .
docker images
Разбор:
docker build -t my-go-service .собирает образ из текущего каталога и присваивает ему тегmy-go-service.- Точка в конце команды указывает build context: именно эти файлы доступны Docker во время сборки.
docker imagesпоказывает, что образ успешно создан и готов к запуску.
Запуск с пробросом порта и удалением контейнера после остановки (--rm):
docker run -p 8080:8080 --rm my-go-service
Разбор:
docker runсоздаёт и запускает контейнер из образаmy-go-service.-p 8080:8080связывает порт хоста с портом контейнера, чтобы сервис был доступен снаружи.--rmавтоматически удаляет контейнер после остановки и не оставляет "мусор" в списке контейнеров.
При запуске система выведет сообщение "Сервер запущен на порту 8080". Это подтверждает, что приложение успешно стартовало внутри изолированной среды. Контейнер остается активным и ожидает входящих запросов.
Если требуется остановить контейнер вручную, достаточно нажать комбинацию клавиш Ctrl+C в терминале. Система завершит процесс и удалит контейнер благодаря флагу --rm.
Проверка работы сервиса
Для проверки работоспособности микросервиса необходимо открыть веб-браузер и перейти по адресу http://localhost:8080. Браузер отправит HTTP-запрос на указанный порт. Сервер внутри контейнера обработает запрос и вернет текстовое сообщение.
На экране браузера отобразится следующий текст:
Привет! Это микросервис на Go, работающий внутри контейнера.
Разбор:
- Это ожидаемое тело HTTP-ответа от обработчика
/, возвращаемое функциейhelloHandler. - Появление текста подтверждает полный путь запроса: браузер -> Docker port mapping -> Go-сервер -> ответ клиенту.
- Если текст не появляется, проверяют контейнерные логи, проброс порта и статус процесса сервера.
Отсутствие ошибок загрузки страницы означает, что сеть между хост-машиной и контейнером настроена корректно. Порт 8080 открыт и доступен для внешнего подключения.
Альтернативная проверка без браузера — curl в терминале:
curl http://localhost:8080
Разбор:
curlвыполняет тот же HTTP-запрос, что и браузер, но в терминале и без GUI.- Команда удобна для автоматических проверок и скриптов в CI/CD.
- Совпадение ответа с браузером подтверждает, что API работает независимо от клиента.
Результат выполнения команды совпадает с тем, что отображается в браузере. Такая проверка подтверждает полную функциональность микросервиса и корректность настройки Docker.
Как развить этот пример до "боевого" сервиса
Текущий пример полезен как старт, а следующий шаг обычно связан с переходом от "один файл и один handler" к более устойчивой структуре. Ниже — практичный маршрут, который можно внедрять по частям.
Минимальная структура директорий
my-microservice/
cmd/
api/
main.go
internal/
httpapi/
handlers.go
middleware.go
service/
notes.go
storage/
memory.go
configs/
config.example.env
Dockerfile
go.mod
Разбор:
cmd/api/main.goхранит точку входа приложения и изолирует запуск от доменной логики.internal/httpapi,internal/service,internal/storageразделяют транспорт, бизнес-правила и слой хранения.- Такая структура уменьшает связанность, упрощает unit-тесты и замену реализации (например, in-memory на PostgreSQL).
- Папка
configsдержит примеры конфигурации и упрощает запуск в разных окружениях.
Такая декомпозиция отделяет транспортный слой (httpapi) от бизнес-логики (service) и реализации хранения (storage). Это ускоряет тестирование и упрощает замену хранения в памяти на базу данных.
Конфигурация через переменные окружения
В учебном коде порт задан строкой ":8080". Для реальных окружений удобнее читать настройки из переменных:
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
addr := ":" + port
Разбор:
os.Getenv("PORT")читает значение из переменной окружения и делает настройку внешней для кода.- Условие
if port == ""задаёт безопасный дефолт для локального запуска. addr := ":" + portформирует строку адреса дляListenAndServe.- Подход позволяет использовать один и тот же бинарник в dev, staging и production.
Это позволяет запускать один и тот же бинарник в разных средах без пересборки. В контейнерах и оркестраторах такой подход считается стандартом.
Health-check и readiness
Один endpoint "сервер жив" часто дополняют проверкой готовности зависимостей:
GET /healthz— процесс работает;GET /readyz— сервис готов принимать трафик (например, БД отвечает).
Разделение этих проверок снижает ложные алерты во время запуска и деплоя.
Graceful shutdown
Для корректной остановки полезно заменить ListenAndServe на http.Server и добавить обработку сигналов ОС. Тогда активные запросы успевают завершиться:
Код ITЗагрузка примера кода…
Разбор:
http.Serverдаёт больше контроля над жизненным циклом, чем простой вызовListenAndServe.- Сервер запускается в отдельной goroutine, чтобы главный поток мог слушать сигналы завершения.
signal.NotifyContextсоздаёт контекст, который закрывается приSIGINTилиSIGTERM.context.WithTimeout(..., 5*time.Second)ограничивает время graceful shutdown.srv.Shutdown(shutdownCtx)завершает приём новых соединений и даёт активным запросам время корректно закончиться.
Что добавить сразу после первого релиза
- Логирование запросов с временем обработки.
- Метрики (
/metrics) и базовые алерты. - Тесты на handlers через
httptest. - CI-шаг
go test ./...и сборка Docker-образа. - Линтеры (
go vet,staticcheck) в pipeline.
Дополнительные сниппеты с разбором
Middleware для request-id
func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := strconv.FormatInt(time.Now().UnixNano(), 10)
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r)
})
}
Разбор:
- Middleware оборачивает основной handler и добавляет к каждому ответу
X-Request-ID. - Идентификатор удобно использовать для трассировки в логах и при разборе инцидентов.
next.ServeHTTPпередаёт управление следующему обработчику в цепочке.- Сниппет легко расширить: передавать id в контекст и включать в structured logging.
JSON-ответ с корректным статусом
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
Разбор:
- Установка
Content-Typeсообщает клиенту, что тело ответа в формате JSON. WriteHeader(status)задаёт HTTP-код до записи тела.json.NewEncoder(w).Encode(v)сериализует структуру напрямую в поток ответа.- Вынесение в helper сокращает дублирование и стандартизирует ответы API.
Внутренние связи и следующий шаг
- Если нужен фреймворк и более удобная маршрутизация, переходите к Первой программе на Gin или Первой программе на Echo.
- Для доступа к СУБД используйте Работу с базами данных из Go.
- Для конкурентной обработки фоновых задач откройте Асинхронность и горутины.
- Для проверки handlers и API-поведения используйте Тестирование в Go.
Ключевые тезисы
- Минимальный микросервис на Go строится на
net/http, модулеgo.modи одном бинарнике. - Docker многоступенчатой сборкой уменьшает размер образа и упрощает деплой.
- Следующий шаг после демо-сервиса — конфигурация, graceful shutdown, тесты и наблюдаемость.
Мини-практикум
- Вынесите порт в
PORTи добавьте значение по умолчанию8080. - Добавьте endpoint
GET /healthzиGET /readyz. - Напишите один тест через
httptestна200 OKдля корневого маршрута.
Типичные ошибки
- Код смешивает transport, бизнес-логику и работу с данными в
main.go. - Образ контейнера собирается без multi-stage и становится тяжелым.
- Сервер останавливается резким
killбезShutdown(ctx).
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.