Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 22 из 25 ~18 мин +50 XP

Развертывание Connect-сервисов

деплой connect — последний рубеж пути: контейнер, оркестратор, ingress с TLS и HTTP/2 на границе. Соберём мультистейдж-образ на distroless, настроим окружение и лимиты рантайма, подключим readiness и graceful shutdown к жизненному циклу пода, выставим сервис через ingress с HTTP/2 и выровняем таймауты, лимиты тела и сжатие по всей цепочке клиент, ingress, хендлер.

Содержание урока · 10 разделов

Сервис, который отлично работает на localhost, в проде встречает оркестратор с пробами, ingress с TLS, лимиты тела, таймауты прокси и SIGTERM среди ночи. Деплой connect собирает всё, что ты построил за курс: один mux с healthz и метриками из девятнадцатого урока, graceful shutdown, h2c для gRPC-жанров, лимиты из четырнадцатого, метрики из семнадцатого. Задача урока — выровнять всю цепочку: клиент, ingress, под, хендлер — так, чтобы ни одно звено не резало то, что нужно соседнему. Если собрать частые поисковые запросы — connect docker, grpc docker, connect деплой, — получится ровно чек-лист этого урока, по которому деплой connect проходит без сюрпризов: образ, окружение, пробы, остановка, TLS, HTTP/2, таймауты, лимиты. План: мультистейдж Dockerfile и почему CGO_ENABLED=0, окружение 12-factor и лимиты рантайма, пробы и жизненный цикл пода, ingress с TLS на границе и HTTP/2 внутрь, и финальное выравнивание таймаутов, лимитов тела и сжатия по метрикам p99.

Мультистейдж-образ: build-слой против runtime-слоя#

Docker-образ для Go-сервиса собирается двумя этажами. Первый — builder на полном golang-образе: go mod download выносится в отдельный слой после копирования только go.mod и go.sum — пока зависимости не менялись, слой берётся из кэша и никакие пакеты не перекачиваются; затем сборка с CGO_ENABLED=0: чисто Go-бинарник без линковки с libc работает на любом минимальном базовом образе, вплоть до scratch. Второй этаж — runtime: distroless/static с nonroot-пользователем или alpine, куда копируется только бинарник. Итог — десятки мегабайт вместо гигабайта тулчейна, меньше поверхность атаки, быстрее pull на нодах. connect-специфика тут минимальна, но одна привычка важна: кодогенерация buf generate выполняется в builder-слое или раньше в CI — в runtime-образе buf не нужен, сгенерированные файлы уже вкомпилированы. EXPOSE документирует порт 8080, ENTRYPOINT запускает бинарник; HEALTHCHECK в Dockerfile полезен для docker-compose и standalone Docker, но Kubernetes его игнорирует — там правят бал пробы манифеста, о них ниже.

Dockerfile: два этажа

GO
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/notes ./cmd/notes

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/notes /notes
EXPOSE 8080
USER nonroot
ENTRYPOINT ["/notes"]

Кэш по слоям: пока go.mod не менялся, download берётся из кэша и сборка начинается с копирования кода. nonroot в distroless — процесс не root; порт 8080 больше 1024, привилегии не нужны. Флаг -trimpath убирает локальные пути из бинарника. В runtime-образе нет ни Go, ни buf, ни shell — только бинарник и минимум системных слоёв.

Зачем CGO_ENABLED=0 при сборке образа Connect-сервиса?

Окружение: 12-factor и лимиты рантайма#

Конфигурация живёт в окружении, не в образе: один и тот же артефакт катится через dev, staging и prod, различаясь переменными — PORT, адрес базы, уровень логов, базовый URL из пятого урока. Секреты приходят из хранилища оркестратора в переменные или смонтированные файлы, никогда — слоями образа. Два рантайм-лимитера, которые в контейнерах кусаются регулярно. GOMAXPROCS по умолчанию равен числу ядер ноды, а не CPU-лимиту пода: при лимите в полтора ядра планировщик Go гоняет десятки потоков, конкуренция и троттлинг пляшут, и p99 проседает без видимых причин — лечит uber-go/automaxprocs или явная установка переменной. GOMEMLIMIT ставь чуть ниже memory-лимита пода, чтобы GC начал убираться до OOMKill, а не после. Для connect-сервиса это не теория: метрики из семнадцатого урока — первый индикатор, что CPU-троттлинг или GC-давление начались, и связка го-профилей с платформенными лимитами экономит часы дебага. Логи пиши в stdout структурно — сборщик платформы заберёт их сам, файлы и ротации в контейнере не нужны. Флаги командной строки оставь для редких переопределений, env — для всего остального: так пайплайн деплоя остаётся одинаковым для всех сервисов платформы независимо от языка.

Пробы и жизненный цикл: связка с graceful shutdown#

Пробы из девятнадцатого урока становятся контрактом с оркестратором. Liveness на /healthz отвечает «процесс жив» без походов к зависимостям — ошибка здесь перезапускает под, и зависшая база не должна устраивать рестарт-шторм. Readiness на /readyz проверяет готовность принимать трафик — лёгкий пинг базы с таймаутом в полсекунды, потому что readiness дёргают часто. StartupProbe даёт долгую инициализацию, не убивая под до первого успеха. Жизненный цикл при выкатке: под исключается из эндпоинтов сервиса — readiness снимается, — затем получает SIGTERM; graceful shutdown из девятнадцатого урока перестаёт принимать новые вызовы и ждёт активные до grace-дедлайна. Правило выравнивания: terminationGracePeriodSeconds манифеста должен быть больше Shutdown-дедлайна с запасом на закрытие зависимостей, иначе оркестратор убьёт под посреди доезда unary-вызовов. Долгие bidi-стримы сами не закончатся — их гасит дедлайн Shutdown, а клиент обязан переживать обрыв ретраем или переподключением: деплой и клиентская вежливость из пятого урока — одна система, а не два отдельных дела. StartupProbe особенно полезен, если сервис на старте прогревает кэши или ждёт миграций: без него liveness убьёт под раньше, чем тот успеет встать.

Пробы и grace в манифесте

GO
containers:
  - name: notes
    ports:
      - containerPort: 8080
    readinessProbe:
      httpGet: { path: /readyz, port: 8080 }
      periodSeconds: 5
    livenessProbe:
      httpGet: { path: /healthz, port: 8080 }
      periodSeconds: 10
    lifecycle:
      preStop:
        exec: { command: ["sleep", "5"] }
terminationGracePeriodSeconds: 35

preStop с паузой 5 секунд — время, чтобы исключение пода из эндпоинтов разъехалось по балансировщикам до SIGTERM; 35 секунд grace — больше Shutdown-дедлайна в 25-30 из девятнадцатого урока и больше p99 unary из метрик. Согласование трёх чисел — дедлайн Shutdown, grace пода, таймаут ingress — главная арифметика этого урока.

Что случится с вызовами при SIGTERM?

Прочитайте код и выберите, что он выведет:

<-ctx.Done() // SIGTERM от оркестратора
shutdownCtx, _ := context.WithTimeout(context.Background(), 30*time.Second)
srv.Shutdown(shutdownCtx)
// живы: пачка unary-вызовов и один bidi-стрим из восьмого урока
// terminationGracePeriodSeconds: 35

Ingress: TLS на границе, HTTP/2 внутрь#

Топология TLS из тринадцатого урока в деплое принимает конкретные формы. Вариант по умолчанию: TLS терминируется на ingress — сертификаты выпускает cert-manager или платформенный секрет, — внутрь кластера идёт plaintext, и тогда для gRPC-жанров обязателен h2c из девятнадцатого урока плюс ingress, который проксирует HTTP/2, а не даунгрейдит до HTTP/1.1: в ingress-nginx это аннотация backend-protocol со значением GRPC, в рукописном nginx — grpc_pass вместо proxy_pass. Connect-жанру хватает HTTP/1.1, но держи h2c всегда: жанры не выключены, пока живы старые gRPC-клиенты из двадцатого урока. Второй вариант — end-to-end TLS до пода: http.Server с TLSConfig из тринадцатого урока, ingress в TCP-режиме или mesh; безопаснее для комплаенса, тяжелее в сертификатном хозяйстве. Трейлеры: чистый gRPC-жанр требует HTTP/2-трейлеров через всю цепочку — ещё одна причина не даунгрейдить; Connect-протокол обходится без трейлеров, и это его прод-плюс: промежуточные прокси ломают его реже. Проверка после выката: grpcurl через ingress на gRPC-жанр и curl на Connect-жанр — оба должны ответить на одном хосте. Адрес сервера внутри кластера — обычный DNS сервиса; баланс по подам делает платформа, но долгие gRPC-соединения требуют аккуратности: headless-сервис или клиентские ретраи, иначе все соединения залипнут на первом поде.

За ingress, который терминирует TLS и даунгрейдит до HTTP/1.1, живут оба жанра — Connect и gRPC. Что сломается?

Таймауты, лимиты и сжатие: выравнивание цепочки#

У каждой прослойки свои ручки, и их надо свести в одну арифметику. Таймаут ingress больше p99 unary из метрик семнадцатого урока, иначе прокси режет доезжающие вызовы раньше сервера. Shutdown-дедлайн меньше terminationGracePeriodSeconds — правило из блока о пробах. Лимит тела — два барьера: WithReadMaxBytes из четырнадцатого урока в хендлере и client_max_body_size в ingress; из двух сработает меньший, поэтому сделай их согласованными, чтобы клиент получал честный код resource_exhausted, а не загадочный 413 от прокси. Сжатие — классическая двойная работа: ingress с gzip на RPC-пути пережимает то, что хендлер уже сжал по порогу WithCompressMinBytes; выключи прокси-компрессию для RPC-путей и оставь решение хендлеру — порог считает размер сообщения, а не HTTP-тела. Метрики скрейпятся с /metrics того же mux — отдельный порт не нужен, но проверь, что ingress не публикует /metrics и /healthz наружу: служебные роуты — внутренние, публикуется только RPC-путь. Дедлайны per-call остаются в контексте из пятого урока — платформенные таймауты их не заменяют. Наконец, drainage: балансировщик должен переставать слать новые длинные соединения раньше, чем под умрёт, — иначе SIGTERM придёт соединению, которому ещё жить и жить.

Чек-лист деплоя Connect-сервиса#

СлойНастройкаПрактика
Образмультистейдж, distroless, nonroot, CGO_ENABLED=0десятки МБ, без тулчейна и shell
ОкружениеPORT, DSN, GOMAXPROCS, GOMEMLIMITконфиг в env, секреты из хранилища оркестратора
Пробыliveness /healthz, readiness /readyz, startupProbeбез походов к зависимостям в liveness
ОстановкаShutdown 25-30 с меньше terminationGracePeriodSecondspreStop sleep на разъезд эндпоинтов
TLSтерминация на ingress, cert-managerh2c внутрь, HTTP/2-проксирование для gRPC-жанров
Таймаутыtimeout ingress больше p99 unaryдедлайны per-call в контексте из пятого урока
Лимиты телаWithReadMaxBytes и client_max_body_size согласованыкоды resource_exhausted вместо 413 от прокси
Сжатиеgzip ingress выключен на RPC-путиWithCompressMinBytes решает в хендлере
Наблюдаемость/metrics на mux, скрейп платформынаружу публикуется только RPC-путь

Собери пробы манифеста

Впиши пути проб: живость — без зависимостей, готовность — с лёгкой проверкой базы.

livenessProbe: httpGet: path: / port: 8080 readinessProbe: httpGet: path: / port: 8080 periodSeconds: 5

Путь релиза

Расставь путь релиза от коммита до трафика.

Строки

Ваша программа

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

Карточки: деплой

Термин

Мультистейдж-образ

нажмите, чтобы перевернуть
Определение

Builder на golang-образе: go mod download кэшируется слоем, CGO_ENABLED=0 даёт статический бинарник; runtime — distroless/static nonroot. buf generate живёт в CI, в runtime-образе тулчейна нет.

1 / 6

Что дальше по курсу#

Деплой замкнул дугу от proto до прода: образ, пробы, остановка, граница TLS и выровненные лимиты. Впереди — структура проекта: реальный шаблон с каталогами proto, gen и service в двадцать третьем уроке, типичные ошибки и отладка curl-режимом в двадцать четвёртом, и итоговый проект, соберущий сервис задач целиком. Для повторения: первый сервис и локальный запуск — в третьем уроке, TLS и сертификаты — в тринадцатом, лимиты и сжатие — в четырнадцатом, метрики и p99 — в семнадцатом, mux, h2c и graceful shutdown — в девятнадцатом, JSON-поверхность и GET-кэширование — в двадцать первом. Программа всех двадцати пяти уроков — на странице курса.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • деплой connect начинается с образа: мультистейдж Docker, CGO_ENABLED=0, distroless nonroot — без тулчейна и shell; buf generate живёт в CI.
  • Конфиг в окружении по 12-factor; GOMAXPROCS через automaxprocs, GOMEMLIMIT ниже лимита пода — иначе троттлинг и OOMKill.
  • liveness /healthz без зависимостей, readiness /readyz лёгкий, startupProbe для долгого старта; readiness связан с ротацией трафика.
  • Shutdown-дедлайн меньше terminationGracePeriodSeconds; preStop sleep разъезжает эндпоинты; стримы гасит дедлайн Shutdown.
  • TLS на границе с cert-manager, h2c внутрь, HTTP/2-проксирование ingress для gRPC-жанров; Connect-жанр живёт на HTTP/1.1.
  • Таймаут ingress больше p99; лимиты тела согласованы с WithReadMaxBytes; компрессию решает хендлер, прокси-gzip выключен.
  • Один порт и mux — один набор проб, одно ingress-правило, один скрейп; админ-порт только по сетевой политике.
  • Проверка деплоя: docker run локально с curl-пробами, затем grpcurl и curl через ingress — оба жанра отвечают на одном хосте.

Домашнее задание

Упакуй notes-сервис и прогони его через всю цепочку. Шаг первый: мультистейдж Dockerfile из урока, docker build, docker run с публикацией 8080 — curl на healthz и JSON-вызов на процедуру из двадцать первого урока. Шаг второй: docker-compose с HEALTHCHECK и остановкой — запусти долгий bidi-стрим из восьмого урока, сделай stop контейнера и зафиксируй, что unary доезжают, а стрим гасится дедлайном Shutdown. Шаг третий: манифест с пробами и terminationGracePeriodSeconds 35, больше Shutdown-дедлайна; в kind или minikube сделай выкатку новой версии и понаблюдай за обрывами через метрики — их быть не должно для unary. Шаг четвёртый: ingress с TLS через cert-manager или self-signed и backend-protocol GRPC; grpcurl через ingress на gRPC-жанр и curl на Connect-жанр — оба должны ответить на одном хосте. Шаг пятый: выключи HTTP/2-проксирование и наблюдай падение grpcurl-жанра, верни обратно; затем проверь двойную компрессию, поставив gzip на ingress, и выпиши в заметки чек-лист выравнивания таймаутов и лимитов по p99 из семнадцатого урока.

Личные заметкиПусто
Хранятся только в вашем браузере (Connect · Развертывание Connect-сервисов)

Вопросы и ответы по уроку

HEALTHCHECK в Dockerfile или пробы в манифесте?

HEALTHCHECK работает в docker-compose и standalone Docker, Kubernetes его игнорирует — там правят бал liveness, readiness и startup пробы манифеста. Если деплоишь и туда, и туда, держи один /healthz-эндпоинт для обеих систем: разница только в декларации, а не в коде сервиса.

Нужен ли service mesh для Connect?

Не обязателен: Connect живёт на обычном HTTP, и mesh-сайдкары проксируют его без специфики. Если нужен mTLS между подами, mesh даёт его без правок кода; без mesh — end-to-end TLS из тринадцатого урока на http.Server с TLSConfig.

Сколько реплик и какой autoscaler?

Начни с двух реплик и HPA по CPU после того, как GOMAXPROCS выровнен, иначе метрика врёт. Соединения gRPC длинные — scale-out новых реплик работает нормально, а scale-in рвёт стримы: держи graceful shutdown из девятнадцатого урока и drainage на балансировщике.

Термины урока

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

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Fyne25 уроков~13 чпрактика25 уроков уже доступны

Fyne: десктопные и мобильные GUI на Go

Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.

  • Создавать окна и интерфейсы из виджетов
  • Управлять компоновкой: Box, Grid, Form
  • Рисовать примитивами canvas и строить свои виджеты
  • Связывать данные через binding