rfc7231#section-6.5.1 关于 Kubernetes 上 dotnet 核心入口控制器 api 访问的问题

Posted

技术标签:

【中文标题】rfc7231#section-6.5.1 关于 Kubernetes 上 dotnet 核心入口控制器 api 访问的问题【英文标题】:rfc7231#section-6.5.1 issue on dotnet core ingress controller api access on Kubernetes 【发布时间】:2020-05-05 00:33:08 【问题描述】:

我已经在 Kubernetes 中部署了一个简单的 dotnet 核心应用程序。暴露的服务如下

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2020-01-17T18:07:23Z"
  labels:
    app.kubernetes.io/instance: expo-api
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: expo-api
    app.kubernetes.io/version: 0.0.4
    helm.sh/chart: expo-api-0.0.4
  name: expo-api-service
  namespace: default
  resourceVersion: "997971"
  selfLink: /api/v1/namespaces/default/services/expo-api-service
  uid: 144b9d1d-87d2-4096-9851-9563266b2099
spec:
  clusterIP: 10.12.0.122
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app.kubernetes.io/instance: expo-api
    app.kubernetes.io/name: expo-api
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: 

我使用的入口控制器是 nginx 入口控制器,简单入口规则设置如下 -

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: "true"
  creationTimestamp: "2020-01-17T18:07:24Z"
  generation: 3
  labels:
    app.kubernetes.io/instance: expo-api
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: expo-api
    app.kubernetes.io/version: 0.0.4
    helm.sh/chart: expo-api-0.0.4
  name: expo-api
  namespace: default
  resourceVersion: "1004650"
  selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/expo-api
  uid: efef4e15-ed0a-417f-8b34-4e0f46cb1e70
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: expo-api-service
          servicePort: 80
        path: /expense
status:
  loadBalancer:
    ingress:
    - ip: 34.70.45.62

具有简单启动的dotnet核心应用程序 -

public class Startup
    
        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        
            services.AddControllers();
        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllers();
            );
        
    

这是入口输出 -

Name:             expo-api
Namespace:        default
Address:          34.70.45.62
Default backend:  default-http-backend:80 (10.8.0.9:8080)
Rules:
  Host  Path  Backends
  ----  ----  --------
  *     
        /expense   expo-api-service:80 (10.8.0.26:80,10.8.0.27:80,10.8.1.14:80)
Annotations:
  kubernetes.io/ingress.class:            nginx
  nginx.ingress.kubernetes.io/use-regex:  true
Events:                                   <none>

下面是 nginx 入口控制器设置 -

Name:                     nginx-nginx-ingress-controller
Namespace:                default
Labels:                   app=nginx-ingress
                          chart=nginx-ingress-1.29.2
                          component=controller
                          heritage=Helm
                          release=nginx
Annotations:              <none>
Selector:                 app=nginx-ingress,component=controller,release=nginx
Type:                     LoadBalancer
IP:                       10.12.0.107
LoadBalancer Ingress:     34.66.164.70
Port:                     http  80/TCP
TargetPort:               http/TCP
NodePort:                 http  30144/TCP
Endpoints:                10.8.1.6:80
Port:                     https  443/TCP
TargetPort:               https/TCP
NodePort:                 https  30469/TCP
Endpoints:                10.8.1.6:443
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

问题是当我将入口规则路径更改为仅/ 并使用 -curl 34.66.164.70/weatherforecast 访问时,它工作得非常好。

但是,当我将入口路径更改为 /expense 并尝试使用 - curl 34.66.164.70/expense/weatherforecast 访问时。输出是一个错误 -


  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "|4dec8cf0-4fddb4d168cb9569.",
  "errors": 
    "id": [
      "The value 'weatherforecast' is not valid."
    ]
  

我无法理解这背后的问题。它是从 dotnet core 端出现还是从 Kubernetes 出现?如果 dotnet 可能是什么分辨率,如果在 Kubernetes 上,预期的分辨率是什么。

【问题讨论】:

【参考方案1】:

原创:感谢@heyzling洞察力,我找到了解决方案。我将应用程序路径更改为代码startup.cs。问题是 Api 最初并不期望所有控制器都有路由前缀。因此它给出了错误。所以我不得不在 startup.cs 中做一点改动来添加 app.UsePathBase("/expense") 。以下是我添加的配置 -

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            app.UsePathBase("/expense"); // this is the added configuration which identifies the ingress path rule individually. 
            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllers();
            );
        

理想情况下,我觉得这不是一个好的设计,因为 kubernetes 入口和 dotnet 核心路由应该彼此一无所知。理想情况下,它们不应相互依赖以遵守路由规则。如果有人有更好的解决方案?请发帖。以上解决了我的目的,但我对此并不满意。

----------------------------------------------------------------------------------

更新 2:感谢@heyzling。我终于找到了解决方案 - 看起来它必须重写 url 并将 dotnet 代码期望的实际 API url 转发给正在运行的 docker 映像。

这是代码示例 -

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
  labels:
    app.kubernetes.io/name: expo-api
  name: expo-api
  namespace: default
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: expo-api-service
          servicePort: 80
          path: /expense(/|$)(.*)

所以现在你可以两者兼得 -

curl 35.192.198.231/expense/weatherforecast
curl 35.192.198.231/expense/fakeapi

它会将 url 重写并转发为 -

localhost:80/weatherforecast
localhost:80/fakeapi

在容器内。因此它按预期工作。通过这种方式,我们 DO NOT 不再需要 app.UsePathBase("/expense") 并且 dotnet core 和 ingress 都不必相互了解任何信息。

【讨论】:

我试图避免这个答案。我完全同意应用程序不应该知道和关心基础设施细节。我用可能的解决方案更新了我的第一个答案,希望对您有所帮助。 @heyzling 非常感谢,您的回答给了我解决这个问题的想法。查看更新 2【参考方案2】:

更新 1

我在您的 Ingress 对象中看不到 nginx.ingress.kubernetes.io/rewrite-target 注释。不能说是不是故意跳过的。

如果此注释不存在,您的应用会收到“GET: /expense/weatherforecast”。如果这是你想要的,一切都很好。但是,如果您希望您的应用接收“GET:/weatherforecast”,您应该将nginx.ingress.kubernetes.io/rewrite-target: / 添加到您的 Ingress 注释中。

更新 2

Ingress-nginx 文档有关于“重写”注释的文章: https://kubernetes.github.io/ingress-nginx/examples/rewrite/#rewrite-target

有一个非常简洁的例子可以帮助理解如何暴露/expense/weatherforecast 端点。但不幸的是,我也无法实现/expense 曝光。我尝试了更复杂的正则表达式,并尝试了该文章中的“app-root”注释,但没有任何效果 - Ingress 总是为 /expense 端点返回 404。

我找不到任何关于如何处理根端点和相对端点的有用信息,所以我开始即兴创作。我发现您可以使用两个支持的路径来使其工作。不知道这是错误还是功能:)。

遵循入口规范在我的测试应用上效果很好。它可以正确处理/expense/expense/weatherforecast

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # nginx.ingress.kubernetes.io/use-regex: "true" # can be true or false, no matter
    nginx.ingress.kubernetes.io/rewrite-target: "/$1"
  name: app
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: app
          servicePort: 80
        path: "/expense/(.+)" # handle relative path
      - backend: 
          serviceName: app
          servicePort: 80
        path: "/expense" # handle root

【讨论】:

您在更新时提到了一些有趣的事情。我添加了重写注释我没有收到错误。但是我确实收到了 404 。这是路径规则为/expense 感谢您的见解,我解决了很多问题。这是双向操作,需要运出。需要对代码库进行轻微更改,这需要 aspnet 核心的路径前缀才能了解路由中的路径前缀是必需的。我会发布我的解决方案 另一件事我不需要nginx.ingress.kubernetes.io/rewrite-target: /,因为我添加了路由前缀来识别来自应用程序内部的路径。我不确定这是否是一个好的解决方案,理想情况下我希望在 kubernetes 本身内处理它。我不知道该怎么做。如果您有任何解决方案,请告诉我。 你能把你的研究链接放在可能帮助一些正在经历这类问题的未来观点的人吗?我尚未对此进行测试,但会向您报告您的解决方案

以上是关于rfc7231#section-6.5.1 关于 Kubernetes 上 dotnet 核心入口控制器 api 访问的问题的主要内容,如果未能解决你的问题,请参考以下文章

新 HTTP 规范 (RFC 7231) 中的“欺骗性请求路由”是啥意思?

尝试发布到 API 时,我在 Flutter 中收到“状态”:400 错误

HTTP状态码

HTTP 两次 DELETE 同一个资源应该返回什么

不再以讹传讹,GET和POST的真正区别(转)

HTTP/2 中的 HTTP 语义