rfc7231#section-6.5.1 有关 Kubernetes 上 dotnet core 入口控制器 api 访问的问题

Joy*_*Joy 2 kubernetes google-kubernetes-engine .net-core asp.net-core-routing

我已将一个简单的 dotnet core 应用程序部署到 Kubernetes 中。暴露的服务如下

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: {}
Run Code Online (Sandbox Code Playgroud)

我使用的入口控制器是 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
Run Code Online (Sandbox Code Playgroud)

dotnet core 应用程序启动简单 -

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();
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是入口输出 -

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>
Run Code Online (Sandbox Code Playgroud)

下面是 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>
Run Code Online (Sandbox Code Playgroud)

问题是当我将入口规则路径更改为 only/并使用时访问 -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."
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Joy*_*Joy 5

原文:感谢@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();
            });
        }
Run Code Online (Sandbox Code Playgroud)

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

----------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

更新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(/|$)(.*)
Run Code Online (Sandbox Code Playgroud)

所以现在你可以同时做这两件事 -

curl 35.192.198.231/expense/weatherforecast
curl 35.192.198.231/expense/fakeapi
Run Code Online (Sandbox Code Playgroud)

它会将 url 重写并转发为 -

localhost:80/weatherforecast
localhost:80/fakeapi
Run Code Online (Sandbox Code Playgroud)

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