首页 > 解决方案 > 如何在 Kubernetes 集群中使用 FQDN 连接到 gRPC 服务器?

问题描述

我在本地机器上安装了 Docker Desktop Kubernetes 集群,它运行良好。现在我正在尝试将 .Net Core gRPC 服务器和 .Net core Console 负载生成器部署到我的集群。

我正在为 gRPC 应用程序使用 VisualStudio(2019) 的默认模板

服务器:

原型文件

syntax = "proto3";

option csharp_namespace = "KubernetesLoadSample";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

.net 核心 gRPC 应用程序

public class GreeterService : Greeter.GreeterBase
{
    private readonly ILogger<GreeterService> _logger;
    public GreeterService(ILogger<GreeterService> logger)
    {
        _logger = logger;
    }

    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        _logger.LogInformation("Compute started");
        double result = 0;
        for (int i = 0; i < 10000; i++)
        {
            for (int j = 0; j < i; j++)
            {
                result += Math.Sqrt(i) + Math.Sqrt(j);
            }
        }
        return Task.FromResult(new HelloReply
        {
            Message = "Completed"
        }); ;
    }
}

和 DockerFile 这个项目如下,

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /src
COPY ["KubernetesLoadSample.csproj", "KubernetesLoadSample/"]
RUN dotnet restore "KubernetesLoadSample/KubernetesLoadSample.csproj"

WORKDIR "/src/KubernetesLoadSample"
COPY . .
RUN dotnet build "KubernetesLoadSample.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "KubernetesLoadSample.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "KubernetesLoadSample.dll"]

我能够使用在本地检查此图像

PS C:\Users\user> docker run -it -p 8000:80 kubernetesloadsample:latest
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app
info: KubernetesLoadSample.GreeterService[0]
      Compute started // called from BloomRPC Client

客户

客户端是一个 .net 控制台应用程序,它在循环中调用服务器

    static async Task Main(string[] args)
    {
        var grpcServer = Environment.GetEnvironmentVariable("GRPC_SERVER");
        Channel channel = new Channel($"{grpcServer}", ChannelCredentials.Insecure);

        Console.WriteLine($"Sending load to port {grpcServer}");
        while(true)
        {
            try
            {
                var client = new Greeter.GreeterClient(channel);
                var reply = await client.SayHelloAsync(
                                  new HelloRequest { Name = "GreeterClient" });

                Console.WriteLine("result: " + reply.Message);
                await Task.Delay(1000);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{DateTime.UtcNow} : tried to connect : {grpcServer}  Crashed : {ex.Message}");
            }
        }
    }

客户端的 Docker 文件:

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /src
COPY ["GrpcClientConsole.csproj", "GrpcClientConsole/"]
RUN dotnet restore "GrpcClientConsole/GrpcClientConsole.csproj"

WORKDIR "/src/GrpcClientConsole"
COPY . .
RUN dotnet build "GrpcClientConsole.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "GrpcClientConsole.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "GrpcClientConsole.dll"]

和部署文件如下,

---
apiVersion: v1
kind: Namespace
metadata:
  name: core-load
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  name: compute-server
  namespace: core-load
spec:
  replicas: 4
  selector:
    matchLabels:
      app: compute-server-svc
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: compute-server-svc
    spec:
      containers:
      - env:
        image: kubernetesloadsample:latest
        imagePullPolicy: Never
        name: compute-server-svc
        ports:
        - containerPort: 80
          name: grpc
        resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
  name: compute-server-svc
  namespace: core-load
spec:
  clusterIP: None
  
  ports:
  - name: grpc
    port: 5000
    targetPort: 80
    protocol: TCP
  selector:
    app: compute-server-svc
---
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  name: compute-client
  namespace: core-load
spec:
  replicas: 1
  selector:
  
    matchLabels:
      app: compute-client
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: compute-client
    spec:
      containers:
      - env:
        - name: GRPC_SERVER
          value: compute-server-svc.core-load.svc.cluster.local:5000
        image: grpc-client-console:latest
        imagePullPolicy: Never
        name: compute-client
        resources: {}
status: {}
---

问题

客户端无法使用此 compute-server-svc.core-load.svc.cluster.local:5000 名称连接 gRPC 服务器。我也试过 compute-server-svc.core-load 这个,但面临以下问题

PS E:\study\core\k8sgrpc\KubernetesLoadSample> k get pods -n core-load
NAME                              READY   STATUS    RESTARTS   AGE
compute-client-bff5f666-cjwf5     1/1     Running   0          15s
compute-server-545567f589-5blkv   1/1     Running   0          15s
compute-server-545567f589-bv4r2   1/1     Running   0          15s
compute-server-545567f589-mdp2x   1/1     Running   0          15s
compute-server-545567f589-wdff5   1/1     Running   0          15s
PS E:\study\core\k8sgrpc\KubernetesLoadSample> k logs compute-client-bff5f666-cjwf5 -n  core-load --tail 5
07/09/2021 17:18:35 : tried to connect : compute-server-svc.core-load.svc.cluster.local:5000 Crashed : Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")
07/09/2021 17:18:35 : tried to connect : compute-server-svc.core-load.svc.cluster.local:5000 Crashed : Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")
07/09/2021 17:18:35 : tried to connect : compute-server-svc.core-load.svc.cluster.local:5000 Crashed : Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")
07/09/2021 17:18:35 : tried to connect : compute-server-svc.core-load.svc.cluster.local:5000 Crashed : Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")
07/09/2021 17:18:35 : tried to connect : compute-server-svc.core-load.svc.cluster.local:5000 Crashed : Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")

我没有从与此类似的 stackoverflow 问题中得到任何解决方案,所以我创建了这个。

谁能让我知道我错过了什么或做错了什么?

TIA

标签: c#kubernetes.net-coregrpckubernetes-service

解决方案


您使用以下内容定义了您的服务:

clusterIP: None

用于创建无头服务。这可能是问题的原因,因此删除它可以解决您的错误。


当您创建ClusterIP类型服务(这是默认类型)时,Kubernetes 会自动为服务分配一个虚拟 IP(也称为集群 IP,如类型所暗示的那样),然后用于代理与相关服务选择的 Pod 的通信。

这意味着有一个“新”IP 地址(仅从集群内部可见),不同于分配给服务后面的 Pod(或单个 Pod)的各种 IP,然后通过某种负载平衡将流量路由到站在后面的豆荚。

如果您指定

clusterIP: None

你创建了一个无头服务。您基本上是在告诉 Kubernetes,您不希望将虚拟 IP 分配给服务。代理没有负载平衡,因为没有 IP 来进行负载平衡。

相反,DNS 配置将为服务后面(选择)的每个 Pod 返回 A 记录(IP 地址)。

如果您的应用程序需要发现服务背后的每个 Pod,然后自己使用 IP 地址做任何他们想做的事情,这将很有用。

可能是为了通过内部实现进行负载平衡,可能是因为不同的 Pod(在同一个服务后面)用于不同的事情..或者可能是因为这些 Pod 中的每一个都想发现其他 Pod(考虑多实例主要应用程序,例如例如 Kafka 或 Zookeeper)


我不确定您的问题到底是什么,这可能取决于该特定应用程序如何解析主机名..但您不应该使用无头服务,除非您有必要决定选择哪个 Pod通过您要联系的 svc。

与虚拟 IP 相比,使用 DNS 循环进行负载平衡也(几乎总是)不是一个好主意。因为应用程序可以缓存 DNS 解析,并且如果 Pod 然后更改 IP 地址(因为 Pod 是短暂的,它们随时更改 IP 地址例如,它们重新启动),在到达它们时可能存在网络问题......等等。

文档中有大量信息: https ://kubernetes.io/docs/concepts/services-networking/service/


推荐阅读