You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 03:08:17