Docker容器间gRPC客户端无法连接服务端问题求助
问题描述
连接gRPC服务端时遇到如下错误:
rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 172.20.0.3:50051: connect: connection refused
客户端与服务端运行在不同Docker容器中,相关代码与配置细节如下:
服务端代码
grpcServer := grpc.NewServer() walletpb.RegisterWalletServiceServer(grpcServer, grpcapi.NewWalletServiceServer(configApp)) // Ideally should be interface go func() { listen, err := net.Listen("tcp", "0.0.0.0:50051") util.FatalIfError(err) util.FatalIfError(grpcServer.Serve(listen)) }()
客户端代码
var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err := grpc.Dial("app:50051", opts...) if err != nil { util.FatalIfError(err) } defer conn.Close() // Initialize the WalletService client client := walletpb.NewWalletServiceClient(conn) // Timeout after 5 seconds if the server doesn't respond ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Call the GetBalance method and receive a response response, err := client.GetBalance(ctx, &walletpb.BalanceRequest{UserId: "user123"}) util.FatalIfError(err)
Dockerfile.backend
FROM golang:1.20 as builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -v -o backend ./cmd/backend/main.go FROM alpine:3.18 COPY --from=builder /app/backend /backend CMD ["/backend"]
Dockerfile.client
FROM golang:1.20 as builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -v -o client ./cmd/client/main.go FROM alpine:3.18 COPY --from=builder /app/client /client CMD ["/client"]
docker-compose.yml
version: "3.8" services: app: build: context: . dockerfile: Dockerfile.backend networks: - mynetwork ports: - "8080:8080" - "50051:50051" depends_on: - redis client: build: context: . dockerfile: Dockerfile.client ports: - "8081:8081" - "50051:50051" networks: - mynetwork depends_on: - app redis: image: "redis:7.2-alpine" networks: - mynetwork ports: - "6379:6379" networks: mynetwork: driver: bridge
从错误信息来看,服务端地址(172.20.0.3:50051)是正确的,但仍无法连接,恳请帮忙排查可能遗漏的细节。
补充信息
util.FatalIfError函数
func FatalIfError(err error) { if err != nil { log.Fatal().Err(err).Send() } }
wallet.proto文件
service WalletService { rpc GetBalance (BalanceRequest) returns (BalanceResponse); } message BalanceRequest { string user_id = 1; } message BalanceResponse { double balance = 1; }
问题排查与解决建议
1. 端口冲突问题
客户端容器的docker-compose.yml中映射了50051:50051端口,这会和服务端的端口映射冲突,导致服务端的50051端口可能无法正常监听。
- 解决方法:删除客户端服务的
50051:50051端口映射,容器内部无需将该端口暴露给宿主机,只要在Docker网络内可访问即可。
2. depends_on不保证服务就绪
depends_on: - app仅保证客户端容器在app容器之后启动,但不保证app的gRPC服务已经完全启动并监听端口。客户端启动时可能服务端还未准备好,导致连接被拒绝。
- 解决方法:
- 在客户端代码中添加
grpc.WithBlock()选项,让grpc.Dial阻塞直到连接成功或超时; - 实现连接重试逻辑,比如循环尝试连接直到成功;
- 使用健康检查工具(如wait-for-it),确保服务端gRPC服务就绪后再启动客户端。
- 在客户端代码中添加
3. 服务端gRPC服务启动的异步风险
服务端代码将gRPC服务启动放在goroutine中,如果主goroutine提前退出,会导致gRPC服务还未启动就终止。
- 解决方法:检查主程序是否有阻塞逻辑(如HTTP服务启动、信号监听等),确保主goroutine不会提前退出,保证gRPC服务能持续运行。
4. 网络连通性验证
在客户端容器中执行命令测试连通性:
# 进入客户端容器 docker exec -it <client-container-name> sh # 测试telnet连接服务端 telnet app 50051
如果无法连通,检查Docker网络mynetwork是否正常,两个容器是否都加入了该网络。
5. 服务端端口监听确认
在服务端容器中执行命令确认端口监听状态:
docker exec -it <app-container-name> sh netstat -tulpn | grep 50051
如果没有监听记录,说明服务端的gRPC服务未正常启动,查看容器日志排查启动错误。
内容的提问来源于stack exchange,提问作者Avag Sargsyan
相关产品推荐
相关产品推荐

