Configure external IP redirect inside the Nginx Ingress controller

Question

I would like to know how to configure the Nginx Ingress controller to redirect to a URL when calling the external IP address.

Ingress controller yaml

apiVersion: v1
kind: Service
metadata:
labels:
  helm.sh/chart: ingress-nginx-3.4.1
  app.kubernetes.io/name: ingress-nginx
  app.kubernetes.io/instance: ingress-nginx
  app.kubernetes.io/version: 0.40.2
  app.kubernetes.io/managed-by: Helm
  app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: NodePort
ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    port: 443
    protocol: TCP
    targetPort: https
externalIPs: 
- ip1
- ip2
- ip3
selector:
  app.kubernetes.io/name: ingress-nginx
  app.kubernetes.io/instance: ingress-nginx
  app.kubernetes.io/component: controller

If I enter my external IP into the browser I will of course end up on the Nginx 404 page. In this special case, I would like to set up a redirection to its specific domain e.g. google.com. I would like to increase the security of the public K8s cluster. I have not yet configured this particular case and would like to know if it is even possible to set up such a forwarding process. All configured Ingress resources released their service to the internet.


Solution 1:

Bare-metal environments lacks the commodity that traditional cloud environments provide where network load balancers are available and single K8s manifest is enough to provide single point of contact to the NGINX ingress controller. Of course we have to remember that internal redirect follows the ingress resource which then configures the ingress controller accordingly.

With External IP method the source IP of HTTP is not preserved therefore not recommended to use it despite it's apparent simplicity. So you will loose the information what is going with your requests (unless you will montor ingress controller)

To answer the question how to configure external IP redirect inside Nginx Ingress Controller we have to start from knowing what ingress actually is:

Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. Traffic routing is controlled by rules defined on the Ingress resource. There must be an Ingress controller to satisfy an Ingress. Only creating an Ingress resource has no effect.

To achieve expected results you should create two separate ingress objects (not controllers). First one with no host is specified so the rule applies to all inbound HTTP traffic through the IP address specified as described in the Ingress rules:

apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  annotations: 
    nginx.ingress.kubernetes.io/permanent-redirect: "https://www.google.com"
  name: ingress-redirect
spec: 
  rules: 
  - http: 
      paths: 
      - path: / 
        pathType: Prefix 
        backend: 
          serviceName: example
          servicePort: 12 

And the second ingress object with a host that is provided (for example, hello-world.info, so that the rules apply to that host. Like in the example below:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-host
spec: 
  rules: 
  - host: hello-world.info
    http: 
     paths: 
     - path: / 
       pathType: Prefix  
       backend: 
        serviceName: web
        servicePort: 80

As you can see below, the above configuration creates the following sections in the nginx-ingress-controller's nginx.conf. One is responsible for serving requests with specific Host: header, another is for serving requests w/o Host: header e.g. if request is sent to IP address directly:

enter http { 

# works when no Host header is provided

    ## start server _
    server {
            server_name _ ;

            listen 80 default_server reuseport backlog=511 ;
            listen 443 default_server reuseport backlog=511 ssl http2 ;

            location / {


                    set $namespace      "default";
                    set $ingress_name   "ingress-redirect";
                    set $service_name   "";
                    set $service_port   "";
                    set $location_path  "/";

                    return 301 https://www.google.com;


# works when specific Host header is provided

    ## start server hello-world.info
    server {
            server_name hello-world.info ;

            listen 80  ;
            listen 443  ssl http2 ;


            location / {

                    set $namespace      "default";
                    set $ingress_name   "ingress-host";
                    set $service_name   "web";
                    set $service_port   "80";
                    set $location_path  "/";

            }

    }
    ## end server hello-world.info

For testing purposes I have deployed quickly those two ingress objects and nginx pod:

➜ kubectl run --image nginx web   

And expose it to have serivce behind the second ingress object:

➜ kubectl expose pod web --port 80 

As you can see when curling the redirect works as expected:

➜  ~ curl $(minikube ip)  -I                            
HTTP/1.1 301 Moved Permanently
Server: nginx/1.19.0
Date: Tue, 27 Oct 2020 13:22:06 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://www.google.com

And if you curl the same address with the host name it redirects towards nginx pod:

➜  ~ curl $(minikube ip) -H "Host: hello-world.info"    
---
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
---

Also it is worth to mention about Kubernetes docs that provide also good example of Name based virtual hosting. It supports routing HTTP traffic to multiple host names at the same IP address. The example in the document tells the backing load balancer to route requests based on the Host header.

For example the Ingress mentioned in the documentation page above routes traffic requested for first.bar.com to service1, second.foo.comto service2, and any traffic to the IP address without a hostname defined in request (that is, without a request header being presented) to service3.