Kubernetes 集群中两个服务之间使用 Ingress 作为 API 网关的通信

Posted

技术标签:

【中文标题】Kubernetes 集群中两个服务之间使用 Ingress 作为 API 网关的通信【英文标题】:Communication Between Two Services in Kubernetes Cluster Using Ingress as API Gateway 【发布时间】:2021-07-03 19:46:20 【问题描述】:

我在尝试在 Kubernetes 集群中的两个服务之间进行通信时遇到问题。我们使用 kong 入口对象作为“api 网关”来重新路由 http 从简单的 Angular 前端调用以将其发送到 .NET Core 3.1 API 控制器接口后端。

在这两个 ClusterIP 服务前面有一个入口控制器,用于从我们的 kubernetes 集群接收外部 http(s) 调用以启动前端服务。此入口显示在此处:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
  namespace: kong
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: app.***.*******.com     <<  Obfuscated
      http:
        paths:
            - path: /
              backend:
                serviceName: frontend-service
                servicePort: 80

第一个服务称为“前端服务”,这是一个简单的 Angular 9 前端,允许我输入 http 字符串并将这些字符串提交到后端。 清单 yaml 文件如下所示。请注意,由于各种原因,图像名称被混淆了。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: kong
  labels:
    app: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      imagePullSecrets:
      - name: regcred
      containers:
      - name: frontend
        image: ***********/*******************:****  << Obfuscated
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: kong
  name: frontend-service
spec:
  type: ClusterIP  
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP

第二个服务是一个简单的 .NET Core 3.1 API 接口,它在到达控制器时打印回一些文本。后端服务称为“dataapi”,其中有一个名为 ValuesController 的简单控制器。

用于此的清单 yaml 文件如下所示。

  replicas: 1
  selector:
    matchLabels:
      app: dataapi
  template:
    metadata:
      labels:
        app: dataapi
    spec:
      imagePullSecrets:
      - name: regcred
      containers:
      - name: dataapi
        image: ***********/*******************:****  << Obfuscated
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: dataapi
  namespace: kong
  labels:
    app: dataapi
spec:
  ports:
  - port: 80
    name: http
    targetPort: 80
  selector:
    app: dataapi

我们使用 kong 入口作为代理将传入的 http 调用重定向到 dataapi 服务。此清单文件如下所示:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kong-gateway
  namespace: kong
spec:
  ingressClassName: kong
  rules:
  - http:
      paths:
      - path: /dataapi
        pathType: Prefix
        backend:
          service:
            name: dataapi
            port:
              number: 80

执行“kubectl get all”会产生以下输出:

kubectl get all

NAME                                READY   STATUS    RESTARTS   AGE
pod/dataapi-dbc8bbb69-mzmdc         1/1     Running   0          2d2h
pod/frontend-5d5ffcdfb7-kqxq9       1/1     Running   0          65m
pod/ingress-kong-56f8f44fd5-rwr9j   2/2     Running   0          6d

NAME                              TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                      AGE
service/dataapi                   ClusterIP      10.128.72.137    <none>         80/TCP,443/TCP               2d2h
service/frontend-service          ClusterIP      10.128.44.109    <none>         80/TCP                       2d
service/kong-proxy                LoadBalancer   10.128.246.165   XX.XX.XX.XX    80:31289/TCP,443:31202/TCP   6d
service/kong-validation-webhook   ClusterIP      10.128.138.44    <none>         443/TCP                      6d

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dataapi        1/1     1            1           2d2h
deployment.apps/frontend       1/1     1            1           2d
deployment.apps/ingress-kong   1/1     1            1           6d

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/dataapi-dbc8bbb69         1         1         1       2d2h
replicaset.apps/frontend-59bf9c75dc       0         0         0       25h
replicaset.apps/ingress-kong-56f8f44fd5   1         1         1       6d

'kubectl get ingresses' 给出:

NAME                    CLASS    HOSTS  (Obfuscated)
ingress-nginx           <none>   ***.******.com,**.********.com,**.****.com,**.******.com  + 1 more...        xx.xx.xxx.xx   80      6d                                                                             ADDRESS        PORTS   AGE
kong-gateway            kong     *                                                                            xx.xx.xxx.xx   80      2d2h

从前端,期望是构造http字符串:

http://kong-proxy/dataapi/api/values

将在后端输入我们的“值”控制器并从该控制器返回文本字符串。

这两个服务都在同一个 kubernetes 集群上运行,这里使用的是 Linode。我们的想法是,它是两种类型为 ClusterIP 的两个服务之间的“集群内”通信。

Chrome 控制台报错是:

zone-evergreen.js:2828 GET http://kong-proxy/dataapi/api/values net::ERR_NAME_NOT_RESOLVED

请注意,我们发现了一个与我们类似的 *** issue,并且该结果中的建议是将“default.svc.cluster.local”添加到 http 字符串中,如下所示:

http://kong-proxy.default.svc.cluster.local/dataapi/api/values

这不起作用。我们还将服务的命名空间 kong 替换为默认值,如下所示:

http://kong-proxy.kong.svc.cluster.local/dataapi/api/values

产生与上述相同的错误。

我是否缺少关键步骤?非常感谢任何建议!

*************** Eric Gagnon 的回复更新 ******************

再次感谢 Eric 的回复。这是我和我的同事根据您的建议尝试过的方法

    Pod dns 配置错误:检查 pod 的第一个名称服务器是否等于 'kube-dns' svc ip 以及搜索是否以 kong.svc.cluster.local 开头:
kubectl exec -i -t -n kong frontend-simple-deployment-7b8b9cfb44-f2shk -- cat /etc/resolv.conf

nameserver 10.128.0.10
search kong.svc.cluster.local svc.cluster.local cluster.local members.linode.com
options ndots:5

kubectl get -n kube-system svc 

NAME       TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.128.0.10   <none>        53/UDP,53/TCP,9153/TCP   55d

kubectl describe -n kube-system svc kube-dns

Name:              kube-dns
Namespace:         kube-system
Labels:            k8s-app=kube-dns
                   kubernetes.io/cluster-service=true
                   kubernetes.io/name=KubeDNS
Annotations:       lke.linode.com/caplke-version: v1.19.9-001
                   prometheus.io/port: 9153
                   prometheus.io/scrape: true
Selector:          k8s-app=kube-dns
Type:              ClusterIP
IP:                10.128.0.10
Port:              dns  53/UDP
TargetPort:        53/UDP
Endpoints:         10.2.4.10:53,10.2.4.14:53
Port:              dns-tcp  53/TCP
TargetPort:        53/TCP
Endpoints:         10.2.4.10:53,10.2.4.14:53
Port:              metrics  9153/TCP
TargetPort:        9153/TCP
Endpoints:         10.2.4.10:9153,10.2.4.14:9153
Session Affinity:  None
Events:            <none>    
    App 未使用 pod dns:在 Node 中,将 dns.getServers() 输出到控制台
I do not understand where and how to do this.  We tried to add DNS directly inside our Angular frontend app, but we found out it is not possible to add this.
    Kong-proxy 不喜欢某些东西:设置日志记录调试、多次点击应用程序以及 grep 日志。

我们在这里尝试了两个测试。首先,我们的 kong-proxy 服务可以从入口控制器访问。请注意,这不是我们简单的前端应用程序。它只不过是一个将 http 字符串传递给我们设置的公共网关的代理。这确实有效。我们通过以下方式公开了这一点:

http://gateway.cwg.stratbore.com/test/api/test

["Successfully pinged Test controller!!"]

kubectl logs -n kong ingress-kong-56f8f44fd5-rwr9j | grep test 

10.2.4.11 - - [16/Apr/2021:16:03:42 +0000] "GET /test/api/test HTTP/1.1" 200 52 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/89.0.4389.128 Safari/537.36"

So this works.

但是当我们尝试从与后端在同一集群中运行的简单前端界面执行此操作时:

它不适用于文本框中显示的文本。此命令不会添加任何新内容:

kubectl logs -n kong ingress-kong-56f8f44fd5-rwr9j | grep test 

前端返回错误。

但如果我们添加这个 http 文本:

kong-ingress pod 被击中:

kubectl logs -n kong ingress-kong-56f8f44fd5-rwr9j | grep test 

10.2.4.11 - - [16/Apr/2021:16:03:42 +0000] "GET /test/api/test HTTP/1.1" 200 52 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
10.2.4.11 - - [17/Apr/2021:16:55:50 +0000] "GET /test/api/test HTTP/1.1" 200 52 "http://app-basic.cwg.stratbore.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"

但前端返回错误。

所以在这一点上,我们已经尝试了很多事情来让我们的前端应用程序成功地向我们的后端发送一个 http 并得到响应,但我们没有成功。我还尝试了与我们的前端应用程序一起打包的 nginx.conf 文件的各种配置,但也没有运气。

我将把所有这些打包到一个 github 项目中。谢谢。

【问题讨论】:

【参考方案1】:

克里斯,

我没有使用过 linode 或 kong,也不知道你的前端实际上是做什么的,所以我只指出我能看到的:

最简单的dns检查是curl(或ping、dig等):

http://[dataapi 的 pod ip]:80 来自主机节点 http://[kong-proxy svc 的内部 ip]/dataapi/api/values 来自主机节点(或另一个 pod - 见下文)

nginx入口控制器上的默认路径匹配是pathPrefix,所以你的nginx入口路径:/和nginx.ingress.kubernetes.io/rewrite-target:/实际上匹配所有内容并重写为/。如果您正确指定所有 ingress 以便它们优先于“/”,这可能不是问题。

您说“使用 kong 入口作为代理来重定向传入”,只是想确保您正在代理(而不是重定向客户端)。

chrome 是否只是从前端服务转发其上游错误?外部客户端不应该能够解析集群的 url(除非您已将本地计算机加入集群的网络或执行了其他一些花哨的技巧)。默认情况下,dns 只在集群内有效。

cluster dns 一般遵循 [service name].[namespace name].svc.cluster.local。如果 dns 集群 dns 工作正常,那么从集群中的 pod 使用 curl、ping、wget 等并将其指向该 svc 会将其发送到集群 svc ip,而不是外部 ip。

您的 dataapi 服务是否配置为响应 /dataapi/api/values 或者它不关心 uri 是什么?

如果您没有任何网络策略限制命名空间内的流量,您应该能够在同一命名空间中创建一个测试 pod,并直接 curl 服务 dns 和 pod ip:

apiVersion: v1
kind: Pod
metadata:
  name: curl-test
  namespace: kong
spec:
  containers:
  - name: curl-test
    image: buildpack-deps
    imagePullPolicy: Always
    command:
    - "curl"
    - "-v"
    - "http://dataapi:80/dataapi/api/values"
  #nodeSelector:
  #  kubernetes.io/hostname: [a more different node's hostname]

pod 应该尝试从集群进行 dns 解析。所以它应该找到dataapi的svc ip和curl端口80路径/dataapi/api/values。服务 IP 是虚拟的,因此它们实际上不是“可访问的”。相反,iptables 将它们路由到 pod ip,它具有实际的网络端点并且是可寻址的。

一旦完成,只需检查日志:kubectl logs curl-test,然后将其删除。

如果失败,日志中的失败性质应该会告诉您是 dns 还是链接问题。如果它有效,那么您可能没有集群 dns 问题。但是您可能存在节点间通信问题。要对此进行测试,您可以运行与上述相同的清单,但取消注释节点选择器字段以强制它在与您的 kong-proxy pod 不同的节点上运行。这是一种手动方法,但可以快速进行故障排除。只需冲洗并根据需要对其他节点重复。

当然,可能不是这些,但希望这有助于解决问题。

【讨论】:

感谢 Eric 的详细解答。我和我的同事正在根据您的上述建议尝试各种方法。但是使用您的 pod 取得了一些进展。我可以使用上面的 pod 连接到 dataapi 服务,也可以连接到他们称之为 dataapi 的 kong-proxy 服务。那很好,谢谢。所以剩下的问题是我们无法成功地将'dataapi:80/dataapi/api/values'从前端服务传递给dataapi服务。前端是 kubernetes 集群内的一个 pod。谢谢,任何其他建议表示赞赏。 -克里斯 听起来 dns 和 http 工作正常。希望登录 nginx 控制器和 kong 代理可以对此有所了解。您是要从浏览器/外部客户端访问 [external url]/dataapi/api/values,还是前端服务 pod 创建自己的对 kong-proxy/dataapi/api/values 的请求以响应来自客户端的下游请求? 再次感谢您的回复埃里克!前端服务正在创建自己的对 kong-proxy 的请求(我们没有引用外部 URL)。我们同意 DNS 和 HTTP 似乎工作正常。我们的 Angular 应用程序中是否有一些配置和/或环境变量允许在 kubernetes 集群内广播 http 请求?应用程序的 dockerization 中是否有某些东西可能会阻止它?再次感谢您的帮助。 几种可能: 1. Pod dns 配置错误:检查 pod 的第一个名称服务器是否等于 kube-dns svc ip 以及搜索是否以 kong.svc.cluster.local 开头:kubectl exec -i -t -n kong frontend-5d5ffcdfb7-kqxq9 -- cat /etc/resolv.conf kubectl get -n kube-system svc 2. 应用程序不使用 pod dns:在节点中,将 dns.getServers() 输出到控制台 3. Kong-proxy 没有像这样:设置日志调试,多次点击应用程序,然后 grep 日志:(kubectl logs -n kong ingress-kong-56f8f44fd5-rwr9j | grep dataapi) 如果您看到条目,至少它已经走到了这一步...... Eric,根据您的最后建议,我们已在上述原始帖子的底部添加了更多信息。再次,非常感谢您的所有帮助!我们即将把我们所有的代码放到一个 github 项目中。我想我们已经很接近了。【参考方案2】:

在 Eric G 的大量帮助(谢谢!)和阅读this previous *** 之后,我终于解决了这个问题。正如此链接中的答案所示,我们的前端 pod 在 Web 浏览器中为我们的应用程序提供服务,该浏览器对 Kubernetes 集群一无所知

正如链接所示,我们在 nginx 入口中添加了另一条规则,以成功地将我们的 http 请求路由到正确的服务

    - host: gateway.*******.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: gateway-service
                port:
                  number: 80

然后从我们的 Angular 前端,我们发送 HTTP 请求如下:

...
http.get<string>("http://gateway.*******.com/api/name_of_contoller');
...

我们终于能够以我们想要的方式与我们的后端服务进行通信。前端和后端都在同一个 Kubernetes 集群中。

【讨论】:

以上是关于Kubernetes 集群中两个服务之间使用 Ingress 作为 API 网关的通信的主要内容,如果未能解决你的问题,请参考以下文章

Kubernetes:我应该使用 HTTPS 在服务之间进行通信吗

Kubernetes集群中微服务之间如何进行JWT认证

Kubernetes如何管理暴露服务

Kubernetes如何管理暴露服务

Kubernetes如何管理暴露服务

[K8s]Kubernetes-服务负载均衡联网(上)