Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert multiple path rewrite rules from default.conf to ingress-nginx.yaml #11456

Open
vienleidl opened this issue Jun 12, 2024 · 16 comments
Open
Labels
kind/support Categorizes issue or PR as a support question. lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. needs-priority needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one.

Comments

@vienleidl
Copy link

What happened:
I'm trying to convert the NGINX configuration file of a nginx reverse proxy container in Azure Container Apps (ACA) to the ingress-nginx for Kubernetes on Azure Kubernetes Service (AKS). The nginx container works well on ACA with the default.conf file.

However, when converting to ingress-nginx.yaml file, it seems to me that the ingress-nginx doesn't route the traffic to the api service while hitting the http://example.southeastasia.cloudapp.azure.com/app/api. So, I don't see any incoming requests to the api pod in its logs.

What you expected to happen:
I guess the route to /app overrides the /app/api. It means the ingress-nginx controller sends all requests with the path /app/api to the app service only. But I'm not sure if my ingress-nginx.yaml is correct for making it worked as expected.

NGINX Ingress controller version:

-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.10.1
  Build:         4fb5aac1dd3669daa3a14d9de3e3cdb371b4c518
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.25.3
-------------------------------------------------------------------------------

Kubernetes version:

Client Version: v1.29.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.4

Environment:

  • Cloud provider or hardware configuration: Azure Kubernetes Service (AKS)
  • OS (e.g. from /etc/os-release): AzureLinux
  • Kernel (e.g. uname -a): n/a
  • Install tools: kubectl, helm
  • Basic cluster related info:
NAME                                  STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE            KERNEL-VERSION     CONTAINER-RUNTIME
aks-systempool1-39537598-vmss000000   Ready    <none>   28h   v1.29.4   10.0.8.91     <none>        CBL-Mariner/Linux   5.15.153.1-2.cm2   containerd://1.6.26
aks-userpool1-19086842-vmss000000     Ready    <none>   28h   v1.29.4   10.0.8.10     <none>        CBL-Mariner/Linux   5.15.153.1-2.cm2   containerd://1.6.26
  • How was the ingress-nginx-controller installed:
NAME                                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART
                                                APP VERSION
aks-managed-azure-monitor-metrics       kube-system     1563            2024-06-12 08:23:31.456922911 +0000 UTC deployed        azure-monitor-metrics-addon-0.1.0-6529ca7328d20e449db7da30dcc5ee505289999f
cert-manager                            ingress-nginx   1               2024-06-11 14:09:37.7655219 +0700 +07   deployed        cert-manager-v1.14.5                                                        v1.14.5
ingress-nginx                           ingress-nginx   1               2024-06-11 13:10:37.0523875 +0700 +07   deployed        ingress-nginx-4.10.1                                                        1.10.1
  • Current State of the controller:
Name:         nginx
Labels:       app.kubernetes.io/component=controller
              app.kubernetes.io/instance=ingress-nginx
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=ingress-nginx
              app.kubernetes.io/part-of=ingress-nginx
              app.kubernetes.io/version=1.10.1
              helm.sh/chart=ingress-nginx-4.10.1
Annotations:  meta.helm.sh/release-name: ingress-nginx
              meta.helm.sh/release-namespace: ingress-nginx
Controller:   k8s.io/ingress-nginx
Events:       <none>
  • Current state of ingress object, if applicable: n/a
  • Others: n/a

How to reproduce this issue:

Create a new AKS cluster on Azure:

A public AKS cluster has 2 node pools with Azure CNI (overlay) network and the new namespace is ingress-nginx.

Using helm to install the Ingress Controller

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm repo list
helm install ingress-nginx ingress-nginx `
  --repo https://kubernetes.github.io/ingress-nginx `
  --namespace ingress-nginx `
  --set controller.config.http2=true `
  --set controller.config.http2-push="on" `
  --set controller.config.http2-push-preload="on" `
  --set controller.ingressClassByName=true `
  --set controller.ingressClassResource.controllerValue=k8s.io/ingress-nginx `
  --set controller.ingressClassResource.enabled=true `
  --set controller.ingressClassResource.name=nginx `
  --set controller.service.externalTrafficPolicy=Local `
  --set controller.setAsDefaultIngress=true

Create needed ingress resources with one-stop-shop YAML file:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-root
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-buffer-size: "256k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
    nginx.ingress.kubernetes.io/client-max-body-size: "100m"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: root
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-app
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /app/(.*) /$1 break;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /app/
        pathType: Exact
        backend:
          service:
            name: app
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-api
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /api/(.*) /$1 break;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /app/api
        pathType: Exact
        backend:
          service:
            name: api
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component1
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component1(@0.0)?/?(.*) /$2 break;
      proxy_set_header        X-Forwarded-Host        $host;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /component/component1(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: component1
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component2
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component2(@0.0)?/?(.*) /$2 break;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /component/component2(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: component2
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component3
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component3(@0.0)?/?(.*) /$2 break;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /component/component3(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: component3
            port:
              number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component4
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component4-([^@]*?)(@0.0)?/?(.*) $1/$3 break;
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.southeastasia.cloudapp.azure.com
    secretName: tls-secret
  rules:
  - host: example.southeastasia.cloudapp.azure.com
    http:
      paths:
      - path: /component/component4-[^@]*?(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: component4
            port:
              number: 8080

Anything else we need to know:

default.conf

server {
listen 80;
server_name nginx.xxx-xxx.southeastasia.azurecontainerapps.io;
client_max_body_size 0;

# Root path
location / {
    proxy_pass http://root.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

# /app/
location /app/ {
    rewrite /app/(.*) /$1 break;
    proxy_pass http://app.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

# /app/api
location /app/api {
    rewrite /api/(.*) /$1 break;
    proxy_pass http://api.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

# /component/component1
location ~ /component/component1(@0.0)? {
	rewrite /component/component1(@0.0)?/?(.*) /$2 break;
	proxy_pass http://component1.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

# /component/component2
location ~ /component/component2(@0.0)? {
	rewrite /component/component2(@0.0)?/?(.*) /$2 break;	
	proxy_pass http://component2.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
    proxy_set_header        X-Forwarded-Host        $host;
}

# /component/component3
location ~ /component/component3(@0.0)? {
    rewrite /component/component3(@0.0)?/?(.*) /$2 break;
    proxy_pass http://component3.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

# /component/component4
location ~ /component/component4-[^@]*?(@0.0)? {
    rewrite /component/component4-([^@]*?)(@0.0)?/?(.*) $1/$3 break;
    proxy_pass http://component4.internal.xxx-xxx.southeastasia.azurecontainerapps.io;
}

}

@vienleidl vienleidl added the kind/bug Categorizes issue or PR as related to a bug. label Jun 12, 2024
@k8s-ci-robot k8s-ci-robot added the needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. label Jun 12, 2024
@k8s-ci-robot
Copy link
Contributor

This issue is currently awaiting triage.

If Ingress contributors determines this is a relevant issue, they will accept it by applying the triage/accepted label and provide further guidance.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@longwuyuan
Copy link
Contributor

/remoe-kind bug
/kind support
The pathType filed matters a lot in the case you describe.
Look at the nginx.conf inside the controller pod and there will be more clarity on routing decisions.

@k8s-ci-robot k8s-ci-robot added the kind/support Categorizes issue or PR as a support question. label Jun 12, 2024
@vienleidl
Copy link
Author

vienleidl commented Jun 12, 2024

/remoe-kind bug /kind support The pathType filed matters a lot in the case you describe. Look at the nginx.conf inside the controller pod and there will be more clarity on routing decisions.

I tried to change them to Prefix, Exact or ImplementationSpecific, but it still doesn't work. Thanks for your suggestion! I did have a look at the nginx.conf file and it's weird that the location blocks are something like below:

location ~* "^/component/component3(@0.0)?"
location ~* "^/component/component2(@0.0)?"
location ~* "^/component/component1(@0.0)?"
location ~* "^/app/api(.*)"
location ~* "^/app/(.*)"
location ~* "^/" 

For more details:

## start server example.southeastasia.cloudapp.azure.com
        server {
                server_name example.southeastasia.cloudapp.azure.com ;
                http2 on;
                listen 80  ;
                listen [::]:80  ;
                listen 443  ssl;
                listen [::]:443  ssl;
                set $proxy_upstream_name "-";
                ssl_certificate_by_lua_block {
                        certificate.call()
                }

                location ~* "^/component/component3(@0.0)?" {
                        set $namespace      "ingress-nginx";
                        set $ingress_name   "ingress-nginx-component3";
                        set $service_name   "component3";
                        set $service_port   "8080";
                        set $location_path  "/component/component3(@0.0)?";
                        set $global_rate_limit_exceeding n;
                        rewrite_by_lua_block {
                                lua_ingress.rewrite({
                                        force_ssl_redirect = false,
                                        ssl_redirect = true,
                                        force_no_ssl_redirect = false,
                                        preserve_trailing_slash = false,
                                        use_port_in_redirects = false,
                                        global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } },
                                })
                                balancer.rewrite()
                                plugins.run()
                        }
                        # be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
                        # will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
                        # other authentication method such as basic auth or external auth useless - all requests will be allowed.
                        #access_by_lua_block {
                        #}
                        header_filter_by_lua_block {
                                lua_ingress.header()
                                plugins.run()
                        }
                        body_filter_by_lua_block {
                                plugins.run()
                        }
                        log_by_lua_block {
                                balancer.log()
                                plugins.run()
                        }
                        rewrite_log on;
                        port_in_redirect off;
                        set $balancer_ewma_score -1;
                        set $proxy_upstream_name "ingress-nginx-component3-8080";
                        set $proxy_host          $proxy_upstream_name;
                        set $pass_access_scheme  $scheme;
                        set $pass_server_port    $server_port;
                        set $best_http_host      $http_host;
                        set $pass_port           $pass_server_port;
                        set $proxy_alternative_upstream_name "";
                        client_max_body_size                    1m;
                        proxy_set_header Host                   $best_http_host;
                        # Pass the extracted client certificate to the backend
                        # Allow websocket connections
                        proxy_set_header                        Upgrade           $http_upgrade;
                        proxy_set_header                        Connection        $connection_upgrade;
                        proxy_set_header X-Request-ID           $req_id;
                        proxy_set_header X-Real-IP              $remote_addr;
                        proxy_set_header X-Forwarded-For        $remote_addr;
                        proxy_set_header X-Forwarded-Host       $best_http_host;
                        proxy_set_header X-Forwarded-Port       $pass_port;
                        proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
                        proxy_set_header X-Forwarded-Scheme     $pass_access_scheme;
                        proxy_set_header X-Scheme               $pass_access_scheme;
                        # Pass the original X-Forwarded-For
                        proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
                        # mitigate HTTPoxy Vulnerability
                        # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                        proxy_set_header Proxy                  "";
                        # Custom headers to proxied server
                        proxy_connect_timeout                   5s;
                        proxy_send_timeout                      60s;
                        proxy_read_timeout                      60s;
                        proxy_buffering                         off;
                        proxy_buffer_size                       4k;
                        proxy_buffers                           4 4k;
                        proxy_max_temp_file_size                1024m;
                        proxy_request_buffering                 on;
                        proxy_http_version                      1.1;
                        proxy_cookie_domain                     off;
                        proxy_cookie_path                       off;
                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_timeout             0;
                        proxy_next_upstream_tries               3;

                        rewrite /component/component3(@0.0)?/?(.*) /$2 break;

                        rewrite "(?i)/component/component3(@0.0)?" /$2 break;
                        proxy_pass http://upstream_balancer;
                        proxy_redirect                          off;
                }

While the following server block is something like this for backend:

# Global filters

        ## start server _
        server {
                server_name _ ;
                http2 on;
                listen 80 default_server reuseport backlog=4096 ;
                listen [::]:80 default_server reuseport backlog=4096 ;
                listen 443 default_server reuseport backlog=4096 ssl;
                listen [::]:443 default_server reuseport backlog=4096 ssl;
                set $proxy_upstream_name "-";
                ssl_reject_handshake off;
                ssl_certificate_by_lua_block {
                        certificate.call()
                }

                location / {
                        set $namespace      "";
                        set $ingress_name   "";
                        set $service_name   "";
                        set $service_port   "";
                        set $location_path  "";
                        set $global_rate_limit_exceeding n;
                        rewrite_by_lua_block {
                                lua_ingress.rewrite({
                                        force_ssl_redirect = false,
                                        ssl_redirect = false,
                                        force_no_ssl_redirect = false,
                                        preserve_trailing_slash = false,
                                        use_port_in_redirects = false,
                                        global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } },
                                })
                                balancer.rewrite()
                                plugins.run()
                        }
                        # be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
                        # will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
                        # other authentication method such as basic auth or external auth useless - all requests will be allowed.
                        #access_by_lua_block {
                        #}
                        header_filter_by_lua_block {
                                lua_ingress.header()
                                plugins.run()
                        }
                        body_filter_by_lua_block {
                                plugins.run()
                        }
                        log_by_lua_block {
                                balancer.log()
                                plugins.run()
                        }
                        access_log off;
                        port_in_redirect off;
                        set $balancer_ewma_score -1;
                        set $proxy_upstream_name "upstream-default-backend";
                        set $proxy_host          $proxy_upstream_name;
                        set $pass_access_scheme  $scheme;
                        set $pass_server_port    $server_port;
                        set $best_http_host      $http_host;
                        set $pass_port           $pass_server_port;
                        set $proxy_alternative_upstream_name "";
                        client_max_body_size                    1m;
                        proxy_set_header Host                   $best_http_host;
                        # Pass the extracted client certificate to the backend
                        # Allow websocket connections
                        proxy_set_header                        Upgrade           $http_upgrade;
                        proxy_set_header                        Connection        $connection_upgrade;
                        proxy_set_header X-Request-ID           $req_id;
                        proxy_set_header X-Real-IP              $remote_addr;
                        proxy_set_header X-Forwarded-For        $remote_addr;
                        proxy_set_header X-Forwarded-Host       $best_http_host;
                        proxy_set_header X-Forwarded-Port       $pass_port;
                        proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
                        proxy_set_header X-Forwarded-Scheme     $pass_access_scheme;
                        proxy_set_header X-Scheme               $pass_access_scheme;
                        # Pass the original X-Forwarded-For
                        proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
                        # mitigate HTTPoxy Vulnerability
                        # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                        proxy_set_header Proxy                  "";
                        # Custom headers to proxied server
                        proxy_connect_timeout                   5s;
                        proxy_send_timeout                      60s;
                        proxy_read_timeout                      60s;
                        proxy_buffering                         off;
                        proxy_buffer_size                       4k;
                        proxy_buffers                           4 4k;
                        proxy_max_temp_file_size                1024m;
                        proxy_request_buffering                 on;
                        proxy_http_version                      1.1;
                        proxy_cookie_domain                     off;
                        proxy_cookie_path                       off;
                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_timeout             0;
                        proxy_next_upstream_tries               3;
                        proxy_pass http://upstream_balancer;
                        proxy_redirect                          off;
                }
                # health checks in cloud providers require the use of port 80
                location /healthz {
                        access_log off;
                        return 200;
                }
                # this is required to avoid error if nginx is being monitored
                # with an external software (like sysdig)
                location /nginx_status {
                        allow 127.0.0.1;
                        allow ::1;
                        deny all;
                        access_log off;
                        stub_status on;
                }

        }
        ## end server _

@longwuyuan
Copy link
Contributor

/remove-kind bug

Factors at play are ;

  • rewrite directive in snippet
  • route with pathType Prefix and path as /
  • routes with subpaths
  • special chars in path
  • No info on path validation

I think you should try ;

  • avoid snippets completely
  • use different FQDN like app.domain.org and api.domain.org

@k8s-ci-robot k8s-ci-robot removed the kind/bug Categorizes issue or PR as related to a bug. label Jun 12, 2024
@Gacko
Copy link
Member

Gacko commented Jun 12, 2024

use different FQDN like app.domain.org and api.domain.org

... or merge your Ingress resources into a single one as this would make it clearer, especially because some annotations/configurations affect not only a single path but all locations of that host in the resulting nginx.conf.

@vienleidl
Copy link
Author

  • use different FQDN like app.domain.org and api.domain.org

Sorry that I don't get what you meant! Could you please share more details about your idea? Actually, I did try to merge into one central Ingress, but I couldn't make it worked with the rewrite-target for all paths.

@vienleidl
Copy link
Author

vienleidl commented Jun 12, 2024

  • avoid snippets completely

I replaced the configuration-snippets with rewrite-target, but I got the same issue.

  • use different FQDN like app.domain.org and api.domain.org

It would be better if I could use one domain for all backend services behind the Ingress Controller. Because there are a lot of backend pods/services in the architecture, using the same FQDN facing to the Internet users.

@longwuyuan
Copy link
Contributor

I suggested that you use different fqdn for each backend service/pod because it is the most commonly used approach after many man-hours of trial & error.

@Gacko suggested the alternative to organize your rules in one ingress as that is the other best-practice.

You have created a sceneario where you want to have common hostname and also the beginning part of the path as common among different microservices. That is just bad-design AFAIK.

If you had a unique FIRST part like /app, /api1, /api2, /api2, /api3 etc etc. , even that would make @Gacko's suggestion work for you. Instead I see that you have "/component" as common to all services first and then on top of that you have some regex after that in regex group2. Finally to add more complication you have a requirement to use the rewrite functionality. So this means you have a choice to do whatever you want, but whatever you choose will bring its own set of trade-offs. Fighting with the order of the location blocks and fighting with the regex prefixes nginx configures for location blocks, is a consequence of choosing the design you have.

@vienleidl
Copy link
Author

vienleidl commented Jun 12, 2024

@longwuyuan I agreed with you that the design is not good enough and not optimal. It would be better if the path is unique or just different from each other. But in the technical point of view, it's weird to me that kind of default.conf file of NGINX reverse proxy works on both of Azure Container Apps (Envoy proxy) and Azure App Services (Docker Compose). I think there is something needed to adjust for making the Ingress Controller worked.

@longwuyuan
Copy link
Contributor

  • The first rule you showed, that has path as slash "/" and pathType as "Prefix, is going to be a match any & all requests with FQDN in the request URL as example.southeastasia.cloudapp.azure.com .

  • So do NOT expect any request with hostname example.southeastasia.cloudapp.azure.com to match ANY other rule.

  • You can try to add one rule at a time and send a request to verify, instead of creating all the rules together. That way you can know what breaks.

@vienleidl
Copy link
Author

  • The first rule you showed, that has path as slash "/" and pathType as "Prefix, is going to be a match any & all requests with FQDN in the request URL as example.southeastasia.cloudapp.azure.com .
  • So do NOT expect any request with hostname example.southeastasia.cloudapp.azure.com to match ANY other rule.
  • You can try to add one rule at a time and send a request to verify, instead of creating all the rules together. That way you can know what breaks.

Thanks for your reply! It helps me a lot. And yes, I was thinking the same that the root path might override the other paths, so then I did test one by one. The test result was that I could reach to the /app and other /component/component1=>4, but there was no traffic to the /app/api pod. I'm wondering how it could work well on the other platforms like ACA and App Services.

@crinjes
Copy link

crinjes commented Jun 17, 2024

  • The first rule you showed, that has path as slash "/" and pathType as "Prefix, is going to be a match any & all requests with FQDN in the request URL as example.southeastasia.cloudapp.azure.com .
  • So do NOT expect any request with hostname example.southeastasia.cloudapp.azure.com to match ANY other rule.

Not true, see https://nginx.org/en/docs/http/ngx_http_core_module.html#location. Prefix locations are checked, the longest match is remembered, then regex locations are check in order (sorted by ingress-nginx by length reverse). First regex match wins, otherwise longest remembered prefix is chosen.

This works:

kind create cluster
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/http-svc.yaml

cat << EOF | kubectl apply -f -
apiVersion: v1
data:
  X-Ingress-Name: \$ingress_name
kind: ConfigMap
metadata:
  name: proxy-headers
  namespace: ingress-nginx
---
apiVersion: v1
data:
  allow-snippet-annotations: "true"
  proxy-set-headers: "ingress-nginx/proxy-headers"
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
EOF

kubectl apply -f ingresses.yaml
ingresses.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-root
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-buffer-size: "256k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
    nginx.ingress.kubernetes.io/client-max-body-size: "100m"
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-app
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /app/(.*) /$1 break;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /app/
        pathType: Prefix
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-api
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /api/(.*) /$1 break;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /app/api
        pathType: Prefix
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component1
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component1(@0.0)?/?(.*) /$2 break;
      proxy_set_header        X-Forwarded-Host        $host;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /component/component1(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component2
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component2(@0.0)?/?(.*) /$2 break;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /component/component2(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component3
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component3(@0.0)?/?(.*) /$2 break;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /component/component3(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: http-svc
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx-component4
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite /component/component4-([^@]*?)(@0.0)?/?(.*) $1/$3 break;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /component/component4-[^@]*?(@0.0)?
        pathType: ImplementationSpecific
        backend:
          service:
            name: http-svc
            port:
              number: 80
POD_NAME=$(kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -l app.kubernetes.io/component=controller -o NAME)

# ingress-nginx-root
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/ | grep x-ingress-name

# ingress-nginx-app
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/app/ | grep x-ingress-name

# ingress-nginx-api
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/app/api | grep x-ingress-name

# ingress-nginx-component1
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/component1 | grep x-ingress-name
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/[email protected] | grep x-ingress-name

# ingress-nginx-component2
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/component2 | grep x-ingress-name
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/[email protected] | grep x-ingress-name

# ingress-nginx-component4
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/component4- | grep x-ingress-name
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/component4-asdf | grep x-ingress-name
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/[email protected] | grep x-ingress-name
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -H 'Host: example.com' http://localhost/component/[email protected] | grep x-ingress-name

Some notes:

  • allow-snippet-annotations: "true" config must be set to use snippets
  • at least one of the ingress configs needs to have the nginx.ingress.kubernetes.io/use-regex: "true" annotation for regex to work
  • those rewrite seem messy. /app/api remains /app/api but /app/api/ is /, for example
  • you chose pathType: Exact for something that should have been Prefix according to the rewrites. Doesn't really matter though, once you use-regex, the ingress controller treats every path as a regex.

@longwuyuan
Copy link
Contributor

@crinjes thanks for pointing out. @vienleidl plz update if the comments from @crinjes solves your problem.

@vienleidl
Copy link
Author

  X-Ingress-Name: \$ingress_name

You gave me a hope and I'm happy to hear that😊. I've already set up as your suggestion. But I have a question about the $ingress_name in the ingresses.yaml file. Should I repalce it with something?

After applied, all ingress resources are pointed to the private IP address and the port 80 instead of the public IP and the port 8080. And it seems to me that it doesn't work. The error of web browser is ERR_CONNECTION_CLOSED

image

image

kubectl exec -it -n ingress-nginx pod/ingress-nginx-controller-xxx-xxx-- curl -H 'Host: example.southeastasia.cloudapp.azure.com' http://localhost/

Hostname: http-svc-xxx-xxx

Pod Information:
        node name:      aks-userpool1-xxx-xxx
        pod name:       http-svc-xxx-xxx
        pod namespace:  ingress-nginx
        pod IP: 10.0.8.16

Server values:
        server_version=nginx: 1.14.2 - lua: 10015

Request Information:
        client_address=10.0.8.27
        method=GET
        real path=/
        query=
        request_version=1.1
        request_scheme=http
        request_uri=http://example.southeastasia.cloudapp.azure.com:8080/

Request Headers:
        accept=*/*
        host=example.southeastasia.cloudapp.azure.com
        user-agent=curl/8.5.0
        x-forwarded-for=::1
        x-forwarded-host=example.southeastasia.cloudapp.azure.com
        x-forwarded-port=80
        x-forwarded-proto=http
        x-forwarded-scheme=http
        x-ingress-name=\ingress-nginx-root
        x-real-ip=::1
        x-request-id=2fbf2cbf3a2e0f764e3f782ea69fa924
        x-scheme=http

Request Body:
        -no body in request-
kubectl exec -it -n ingress-nginx pod/ingress-nginx-controller-xxx-xxx-- curl -H 'Host: example.southeastasia.cloudapp.azure.com' http://localhost/app/

Hostname: http-svc-xxx-xxx

Pod Information:
        node name:      aks-userpool1-xxx-xxx
        pod name:       http-svc-xxx-xxx
        pod namespace:  ingress-nginx
        pod IP: 10.0.8.16

Server values:
        server_version=nginx: 1.14.2 - lua: 10015

Request Information:
        client_address=10.0.8.27
        method=GET
        real path=/app/
        query=
        request_version=1.1
        request_scheme=http
        request_uri=http://example.southeastasia.cloudapp.azure.com:8080/app/

Request Headers:
        accept=*/*
        host=example.southeastasia.cloudapp.azure.com
        user-agent=curl/8.5.0
        x-forwarded-for=::1
        x-forwarded-host=example.southeastasia.cloudapp.azure.com
        x-forwarded-port=80
        x-forwarded-proto=http
        x-forwarded-scheme=http
        x-ingress-name=\ingress-nginx-root
        x-real-ip=::1
        x-request-id=6dea76a0e03ef1825682cb6de0361ec5
        x-scheme=http

Request Body:
        -no body in request-
kubectl exec -it -n ingress-nginx pod/ingress-nginx-controller-xxx-xxx -- curl -H 'Host: example.southeastasia.cloudapp.azure.com' http://localhost/app/api

Hostname: http-svc-xxx-xxx

Pod Information:
        node name:      aks-userpool1-xxx-xxx
        pod name:       http-svc-xxx-xxx
        pod namespace:  ingress-nginx
        pod IP: 10.0.8.16

Server values:
        server_version=nginx: 1.14.2 - lua: 10015

Request Information:
        client_address=10.0.8.27
        method=GET
        real path=/app/api
        query=
        request_version=1.1
        request_scheme=http
        request_uri=http://example.southeastasia.cloudapp.azure.com:8080/app/api

Request Headers:
        accept=*/*
        host=example.southeastasia.cloudapp.azure.com
        user-agent=curl/8.5.0
        x-forwarded-for=::1
        x-forwarded-host=example.southeastasia.cloudapp.azure.com
        x-forwarded-port=80
        x-forwarded-proto=http
        x-forwarded-scheme=http
        x-ingress-name=\ingress-nginx-root
        x-real-ip=::1
        x-request-id=9811f4a03b25fefb84be4e0fee9323b8
        x-scheme=http

Request Body:
        -no body in request-

@longwuyuan
Copy link
Contributor

all responses are from same pod with ip 10.0.8.16. expected result is response from different pods

Copy link

This is stale, but we won't close it automatically, just bare in mind the maintainers may be busy with other tasks and will reach your issue ASAP. If you have any question or request to prioritize this, please reach #ingress-nginx-dev on Kubernetes Slack.

@github-actions github-actions bot added the lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. label Jul 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/support Categorizes issue or PR as a support question. lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. needs-priority needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one.
Projects
Development

No branches or pull requests

5 participants