Kubernetes 集群上的粘性会话

Posted

技术标签:

【中文标题】Kubernetes 集群上的粘性会话【英文标题】:Sticky sessions on Kubernetes cluster 【发布时间】:2020-04-03 22:47:56 【问题描述】:

目前,我正在尝试使用两个 负载平衡器 在 Google Cloud 上创建一个 Kubernetes 集群:一个用于后端(在 Spring boot 中),另一个用于前端(在 Angular 中),其中每个服务 (负载均衡器)与 2 个副本(pod)通信。为此,我创建了以下入口:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-ingress
spec:
  rules:
    - http:
        paths:
          - path: /rest/v1/*
            backend:
              serviceName: sample-backend
              servicePort: 8082
          - path: /*
            backend:
              serviceName: sample-frontend
              servicePort: 80

上面提到的入口可以使前端应用程序与后端应用程序提供的 REST API 进行通信。但是,由于后端提供的身份验证机制,我必须创建粘性会话,以便每个用户都与同一个 POD 进行通信。澄清一下,如果一个用户在 POD #1 中进行身份验证,则 POD #2 将无法识别 cookie。

为了解决这个问题,我读到 Nginx-ingress 设法处理这种情况,我通过此处提供的步骤进行安装:https://kubernetes.github.io/ingress-nginx/deploy/ using Helm。

您可以在下面找到我正在尝试构建的架构的图表:

使用以下服务(我只是粘贴其中一项服务,另一项类似):

apiVersion: v1
kind: Service
metadata:
  name: sample-backend
spec:
  selector:
    app: sample
    tier: backend
  ports:
    - protocol: TCP
      port: 8082
      targetPort: 8082
  type: LoadBalancer

我声明了以下入口:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-nginx-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/affinity: cookie
    nginx.ingress.kubernetes.io/affinity-mode: persistent
    nginx.ingress.kubernetes.io/session-cookie-hash: sha1
    nginx.ingress.kubernetes.io/session-cookie-name: sample-cookie
spec:
  rules:
    - http:
        paths:
          - path: /rest/v1/*
            backend:
              serviceName: sample-backend
              servicePort: 8082
          - path: /*
            backend:
              serviceName: sample-frontend
              servicePort: 80

之后,我运行kubectl apply -f sample-nginx-ingress.yaml 来应用入口,它被创建并且它的状态是好的。但是,当我访问出现在“端点”列中的 URL 时,浏览器无法连接到该 URL。 我做错什么了吗?

编辑 1

** 更新服务和入口配置**

经过一些帮助,我设法通过 Ingress Nginx 访问了这些服务。上面这里有配置:

Nginx 入口

路径不应包含“”,这与默认 Kubernetes 入口不同,默认 Kubernetes 入口必须具有“”来路由我想要的路径。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "sample-cookie"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

spec:
  rules:
    - http:
        paths:
          - path: /rest/v1/
            backend:
              serviceName: sample-backend
              servicePort: 8082
          - path: /
            backend:
              serviceName: sample-frontend
              servicePort: 80

服务

此外,服务的类型不应是“LoadBalancer”,而是“ClusterIP”,如下所示:

apiVersion: v1
kind: Service
metadata:
  name: sample-backend
spec:
  selector:
    app: sample
    tier: backend
  ports:
    - protocol: TCP
      port: 8082
      targetPort: 8082
  type: ClusterIP

但是,我仍然无法在我的 Kubernetes 集群中实现粘性会话,一旦我仍然得到 403,甚至 cookie 名称都没有被替换,所以我猜注释没有按预期工作。

【问题讨论】:

你的Service是什么类型的?是LoadBalancer 还是NodePort 它们是负载均衡器。 那么您是从公开为 GCP 负载均衡器的端点的 ip 地址访问您的服务吗? ...这意味着您没有使用您的 Ingress ...为此,您必须在 GKE 上拥有 NodePort 类型的服务。 或者“端点”列是什么意思?您应该通过 Ingress Controller 访问您的服务。 @DawidKruk 你有:------------------------------------ ------------------------------------------------------- NGINX 入口控制器版本:0.26。 1 构建:git-2de5a893a 存储库:github.com/kubernetes/ingress-nginx nginx 版本:openresty/1.15.8.2 ------------------------------- ------------------------------------------------跨度> 【参考方案1】:

我调查了此事,并找到了解决您问题的方法。

要实现两条路径的粘性会话,您需要两个入口定义。

我创建了示例配置来向您展示整个过程:

重现步骤:

应用入口定义 创建部署 创建服务 创建入口 测试

我假设集群已配置并且工作正常。

应用入口定义

在您的基础架构上安装 Ingress 控制器之前,请按照此Ingress link 查找是否有任何必要的先决条件。

应用以下命令来提供所有必需的先决条件:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

运行以下命令以应用通用配置来创建服务:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml

创建部署

以下是响应特定服务上的 Ingress 流量的 2 个示例部署:

hello.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  selector:
    matchLabels:
      app: hello
      version: 1.0.0
  replicas: 5
  template:
    metadata:
      labels:
        app: hello
        version: 1.0.0
    spec:
      containers:
      - name: hello
        image: "gcr.io/google-samples/hello-app:1.0"
        env:
        - name: "PORT"
          value: "50001"

通过调用命令应用第一个部署配置:

$ kubectl apply -f hello.yaml

goodbye.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: goodbye
spec:
  selector:
    matchLabels:
      app: goodbye
      version: 2.0.0
  replicas: 5
  template:
    metadata:
      labels:
        app: goodbye
        version: 2.0.0
    spec:
      containers:
      - name: goodbye 
        image: "gcr.io/google-samples/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50001"

通过调用命令应用第二个部署配置:

$ kubectl apply -f goodbye.yaml

检查部署是否正确配置了 pod:

$ kubectl get deployments

它应该显示如下内容:

NAME      READY   UP-TO-DATE   AVAILABLE   AGE
goodbye   5/5     5            5           2m19s
hello     5/5     5            5           4m57s

创建服务

要连接到之前创建的 pod,您需要创建服务。每个服务都将分配给一个部署。以下是实现这一目标的 2 项服务:

hello-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: NodePort
  selector:
    app: hello
    version: 1.0.0
  ports:
  - name: hello-port
    protocol: TCP
    port: 50001
    targetPort: 50001

通过调用命令应用第一个服务配置:

$ kubectl apply -f hello-service.yaml

再见-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: goodbye-service
spec:
  type: NodePort
  selector:
    app: goodbye
    version: 2.0.0
  ports:
  - name: goodbye-port
    protocol: TCP
    port: 50001
    targetPort: 50001

通过调用命令应用第二个服务配置:

$ kubectl apply -f goodbye-service.yaml

请记住,在这两种配置中,类型:NodePort

检查服务是否创建成功:

$ kubectl get services

输出应该是这样的:

NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)           AGE
goodbye-service   NodePort    10.0.5.131   <none>        50001:32210/TCP   3s
hello-service     NodePort    10.0.8.13    <none>        50001:32118/TCP   8s

创建入口

要实现粘性会话,您需要创建 2 个入口定义。

定义如下:

hello-ingress.yaml:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hello-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "hello-cookie"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/affinity-mode: persistent
    nginx.ingress.kubernetes.io/session-cookie-hash: sha1
spec:
  rules:
  - host: DOMAIN.NAME
    http:
      paths:
      - path: /
        backend:
          serviceName: hello-service
          servicePort: hello-port

再见-ingress.yaml:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: goodbye-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "goodbye-cookie"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/affinity-mode: persistent
    nginx.ingress.kubernetes.io/session-cookie-hash: sha1
spec:
  rules:
  - host: DOMAIN.NAME
    http:
      paths:
      - path: /v2/
        backend:
          serviceName: goodbye-service
          servicePort: goodbye-port

请更改两个入口中的 DOMAIN.NAME 以适合您的情况。 我建议查看此Ingress Sticky session 链接。 两个 Ingress 都配置为仅 HTTP 流量。

应用它们调用命令:

$ kubectl apply -f hello-ingress.yaml

$ kubectl apply -f goodbye-ingress.yaml

检查是否应用了这两种配置:

$ kubectl get ingress

输出应该是这样的:

NAME              HOSTS        ADDRESS          PORTS   AGE
goodbye-ingress   DOMAIN.NAME   IP_ADDRESS      80      26m
hello-ingress     DOMAIN.NAME   IP_ADDRESS      80      26m

测试

打开浏览器并转到http://DOMAIN.NAME 输出应该是这样的:

Hello, world!
Version: 1.0.0
Hostname: hello-549db57dfd-4h8fb

Hostname: hello-549db57dfd-4h8fb 是 pod 的名称。刷新几次。

它应该保持不变。

要检查另一条路线是否正在运行,请转到http://DOMAIN.NAME/v2/ 输出应该是这样的:

Hello, world!
Version: 2.0.0
Hostname: goodbye-7b5798f754-pbkbg

Hostname: goodbye-7b5798f754-pbkbg 是 pod 的名称。刷新几次。

它应该保持不变。

确保 cookie 不会更改打开的开发人员工具(可能是 F12)并导航到带有 cookie 的位置。您可以重新加载页面以检查它们是否没有更改。

【讨论】:

感谢您的回答。我在集群中尝试了您的解决方案,但是,我不明白 DOMAIN.NAME 的用途。我想 DOMAIN.NAME 对于 Nginx Ingresses 是强制性的。所以,我放了一个像'stickyingress.example.com'这样的默认URL,和例子一样。但是,我无法在浏览器中连接到 stickyingress.example.com,但我能够连接到入口 URL,并且它按预期重定向到前端应用程序,但我在后端应用程序中得到 404(相当于您的再见服务)。我想我在这里误解了host参数的含义。 “DOMAIN.NAME”是让 Ingress 知道将流量路由到哪里。让我详细说明。它应该是acme.com 的形式,而不是像acme.com/something 这样的形式。 /something 应该在 path 中,所以在这种情况下,两个入口都应该有相同的 DOMAIN.NAME。在浏览器中输入 IP 地址不会向 Ingress 发送适当的消息,并且它不起作用。例如,我尝试使用 IP 地址连接到 Ingress,它说错误 404,但通过 DOMAIN.NAME 连接可以正常工作。 好的,知道了。由于我的解决方案部署在 Google Cloud 上,我是否应该配置 Google Cloud DNS 并在 DNS 中添加一个条目以映射 CNAME“DOMAIN.NAME”和 Nginx 入口控制器 IP?我这样做了,但浏览器仍然无法解析名称。 我所做的是使用我的入口资源kubectl get ing 的域名和IP 地址创建了A 类记录。此命令应显示您的 IP。 感谢@davidkruk,它成功了,我必须解决我的后端应用程序的一些问题,但我现在能够处理 nginx 入口注释以创建粘性会话。【参考方案2】:

我认为您的Service 配置错误。只需删除type: LoadBalancer,默认类型为ClusterIP

LoadBalancer:使用云提供商的负载平衡器将服务公开到外部。自动创建外部负载均衡器路由到的 NodePort 和 ClusterIP 服务。在此处查看更多信息:https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer。

apiVersion: v1
kind: Service
metadata:
  name: sample-backend
spec:
  selector:
    app: sample
    tier: backend
  ports:
    - protocol: TCP
      port: 8082
      targetPort: 8082

【讨论】:

不幸的是,它没有解决我的问题。我遇到了同样的问题:无法连接到入口 URL。实际上,删除入口注释足以成功连接到入口 URL,但我的问题依赖于会话亲和性。我也尝试在我的服务中指定sessionAffinity: "ClientIP",但无法解决将请求路由到同一个 pod 的问题。 嗯,很难说有什么问题。 nginx.ingress.kubernetes.io/session-cookie-hash你把这个带到哪里去了? kubernetes.github.io/ingress-nginx/examples/affinity/cookie 没有提及。 你是对的@Dávid,但我在一些博客文章和 *** 中看到了这个注释,但我会删除它,因为官方文档中没有提到它。感谢您的帮助! 您是否查看过此故障排除站点:kubernetes.github.io/ingress-nginx/troubleshooting?查看日志:kubectl get pods -n &lt;namespace-of-ingress-controller&gt;(获取 pod 的名称),然后是 kubectl logs -n &lt;namespace&gt; nginx-ingress-controller-67956bf89d-fv58j。您也可以尝试提高日志级别:kubectl edit deploy -n &lt;namespace-of-ingress-controller&gt; nginx-ingress-controller# Add --v=X to "- args", where X is an integer(将 X 设置为 5 用于调试模式)。 谢谢@Dávid,实际上,调试模式对于修复我的入口配置中的一些错误很重要。我将编辑问题以提供修复。

以上是关于Kubernetes 集群上的粘性会话的主要内容,如果未能解决你的问题,请参考以下文章

Kubernetes 实战 -- 泛 kubernates 导论

Kubernetes(本地)Metallb LoadBalancer 和粘性会话

kubernates 组件(第二集)

带有粘性会话的 Nodejs 集群

了解kubernates对象(第三集)

Websocket应用程序的nginx-ingress粘性会话