Какие 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/httpmiddleware не работает — нужен 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 и безопасность
- Умеет ранжировать риски по вероятности и влиянию