如何在 kubernetes 集群中使用 FQDN 连接到 gRPC 服务器?

How to connect to gRPC server using FQDN within kubernetes cluster?

我已经 Docker 在我的本地机器上设置了 Desktop Kubernetes 集群并且工作正常。 现在我正在尝试将 .Net Core gRPC 服务器和 .Net Core 控制台负载生成器部署到我的集群。

我正在为 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"
        }); ;
    }
}

和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 ["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: {}
---

问题

客户端无法使用此计算服务器-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")

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

任何人都可以让我知道我错过了什么或做错了什么吗?

TIA

您定义了您的服务:

clusterIP: None

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


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

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

如果您指定

clusterIP: None

您创建了一个无头服务。您基本上是在告诉 Kubernetes 您不想为该服务分配虚拟 IP。代理没有负载平衡,因为没有要负载平衡的 IP。

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

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

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


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

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

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