gRPC-GoSeniorExperience

Какие production или build/runtime-риски есть у gRPC-Go: воспроизводимость, совместимость, debugging, ресурсы или supply chain?

Ключевые риски: нефиксированные версии protoc-плагинов ломают воспроизводимость, смешение старого и нового protobuf-рантайма вызывает panic, незакрытые стримы дают goroutine leak, а supply chain требует govulncheck в CI.

Production и build/runtime-риски gRPC-Go

gRPC-Go — зрелая библиотека, но у неё есть ряд специфических рисков, с которыми сталкиваются команды в production.

1. Воспроизводимость сборки (Build Reproducibility)

gRPC-Go активно использует кодогенерацию через protoc-gen-go и protoc-gen-go-grpc. Версии плагинов и компилятора protoc должны быть зафиксированы в репозитории, иначе разные разработчики получат несовместимый сгенерированный код.

  • Фиксируйте версии в tools.go через blank import и go.sum.
  • Используйте buf CLI (buf.gen.yaml) с явными версиями плагинов вместо «голого» protoc.
  • CI должен запускать buf generate и проверять, что diff пустой (protoc-generated files committed).
# buf.gen.yaml
version: v2
plugins:
  - remote: buf.build/protocolbuffers/go:v1.34.2
    out: gen
    opt: paths=source_relative
  - remote: buf.build/grpc/go:v1.4.0
    out: gen
    opt: paths=source_relative

2. Совместимость API и wire-совместимость

gRPC-Go следует Semantic Versioning, однако пакет google.golang.org/grpc содержит нестабильные internal-пакеты, которые периодически ломают компиляцию при мажорных апгрейдах. Типичные сценарии:

  • Переход с grpc v1.x на v2 сломал интерфейс ServiceDesc.
  • Протобуф-рантайм google.golang.org/protobuf и старый github.com/golang/protobuf — разные модули; смешивание версий вызывает panic при регистрации типов.
  • Wire-формат Protocol Buffers обратно совместим, но добавление required-полей или удаление полей без следования правилам из buf lint ломает клиентов.

3. Debugging в production

HTTP/2 multiplexing делает трассировку сложнее, чем HTTP/1.1:

  • Нет curl-дебаггинга — нужен grpcurl или grpc_cli.
  • Трейсинг требует явного включения OpenTelemetry: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc.
  • Стандартный net/http middleware не работает — нужен gRPC interceptor.
  • Без channelz (встроенный в gRPC-Go инструмент) невозможно понять состояние соединений в runtime.
import "google.golang.org/grpc/channelz/service"

// Регистрация channelz на отдельном порту
channelzServer := grpc.NewServer()
service.RegisterChannelzServiceToServer(channelzServer)
go channelzServer.Serve(lis)

4. Ресурсы: goroutine leak и memory

  • Каждый stream создаёт горутины на чтение/запись. Незакрытые стримы = goroutine leak.
  • Дефолтный лимит на размер сообщения — 4 МБ на получение. При превышении клиент получает ResourceExhausted без ясного сообщения.
  • Keepalive-параметры по умолчанию не идеальны для NAT/firewall-сред: соединения могут молча умирать.
  • Connection pool gRPC-Go не масштабируется горизонтально по умолчанию — один ClientConn = один TCP-коннект к одному адресу.

5. Supply Chain риски

  • gRPC-Go тянет транзитивные зависимости: golang.org/x/net, golang.org/x/sys, google.golang.org/protobuf и другие. Любая из них может иметь CVE.
  • Используйте govulncheck ./... в CI для проверки уязвимостей.
  • Плагины protoc устанавливаются через go install — фиксируйте через tools.go + go.sum, чтобы не тянуть произвольные версии из интернета.
# Проверка уязвимостей
govulncheck ./...

# Проверка устаревших зависимостей
go list -m -u all | grep '\['

Подводные камни

  • Смешивание github.com/golang/protobuf и google.golang.org/protobuf в одном бинаре вызывает panic при регистрации дублирующихся типов.
  • Незафиксированные версии protoc-gen-go-grpc в CI приводят к тому, что сгенерированный код отличается у разных разработчиков.
  • Дефолтный MaxRecvMsgSize = 4 МБ молча отрезает большие ответы на клиенте без человекочитаемой ошибки.
  • Без явного context.WithTimeout потоки могут висеть бесконечно, накапливая goroutine-утечки.
  • Channelz не включён по умолчанию в production-билдах — команды теряют видимость состояния соединений.
  • HTTP/2 требует TLS в большинстве proxy (nginx, Envoy в H2C-режиме требует явной настройки) — без этого gRPC молча деградирует до ошибок.
  • govulncheck нужно запускать именно после go mod tidy, иначе он проверяет устаревший граф зависимостей.
  • buf lint не проверяет backward compatibility на уровне бизнес-семантики — только синтаксис proto; дополнительно нужен buf breaking.

What hurts your answer

  • Говорить только о запуске gRPC-Go, но не об эксплуатации
  • Не упоминать observability, обновления, безопасность и rollback
  • Описывать риски абстрактно, без способов их снижать

What they're listening for

  • Видит production-риски gRPC-Go
  • Говорит про monitoring, rollout, rollback и безопасность
  • Умеет ранжировать риски по вероятности и влиянию

Related topics