Skip to content

Kubernetes Ingress Configurations and Static Assets

Estimated time to read: 11 minutes

  • Originally Written: July, 2020

There are a number of benefits to using an ingress.

  • Centralized SSL termination
  • Rule based routing
  • Reduction in IP address usage

However you may run into potential issues with routing rules for some applications and designs. I haven't found a good consolidated document on the problems and fixes so have started documenting what we've found.

First let's recap the Kubernetes Ingress resource.

An 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.​​​​​​​​​​​​​​


Here's an example configuration.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: guestbook-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /guestbook
        backend:
          serviceName: guestbook
          servicePort: 80
      - path: /sockshop
        backend:
          serviceName: sockshop
          servicePort: 80

You can access the services through the IP address of the Ingress Controller, in this case 10.113.105.202. The paths (/guestbook and /sockshop) are converted into the nginx.conf configuration on the controller.

When It Works Correctly

To see a basic example we can use the Kubernetes Guestbook example application.

https://github.com/kubernetes/examples/blob/master/guestbook/all-in-one/guestbook-all-in-one.yaml

Save the following to a file e.g. ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: guestbook
  namespace: guestbook
spec:
  rules:
  - http:
      paths:
        - path: /
          backend:
            serviceName: frontend
            servicePort: 80

Deploy this application to your cluster

kubectl apply -f guestbook-all-in-one.yaml

kubectl apply -f ingress.yaml

Once it's running you should have access to the guestbook application through the ingress controller e.g. http://10.113.105.202/

If you open the developer console in your browser you should see a number of files downloaded as the page loads.

Let's see how it works

In this example, the path: / configuration in the ingress YAML file acts as a wildcard, therefore matching all assets that are required (index.html, controllers.js, guestbook.php).

Info

In Kubernetes 1.18 there are ingress enhancements (Exact and Prefix keywords) to provide more granular control.

https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/

Since there's a match, the controller will forward these to the associated service, frontend.

The frontend webserver, Apache, has been configured as default and is serving content from the root directory, /, so returns the requested files.

More than One Application

Question

It's great that the guestbook is working however what happens if we want to deploy a second application? Should we also use the root path?

rules:
  - http:
      paths:
        - path: /
          backend:
            serviceName: frontend
            servicePort: 80
        - path: /
          backend:
            serviceName: sockshop
            servicePort: 80

Question

How could we direct traffic to the right application if the same path points to two different services?

In this case we might want to configure a specific path per application.

rules:
  - http:
      paths:
        - path: /guestbook
          backend:
            serviceName: frontend
            servicePort: 80
        - path: /sockshop
          backend:
            serviceName: sockshop
            servicePort: 80

Here's the result when we try to reach the guestbook with our new path, /guestbook

As you can see, when it reaches the ingress controller it matches the path and so the traffic is sent to the web server. However, the web server is serving content from the root directory and we are trying to access files located in the subdirectory, /guestbook. If you monitor the logs you should see a /guestbook 404 since the content can't be located.

Rewriting the Location

We need to specify /guestbook in the browser but also need to have the web server understand what we need. We can use the NGINX Ingress rewrite-target annotation in Kubernetes to overcome this issue.

The annotations in the Kubernetes Ingress allow us to include additional configuration required for our environment. In this case, we can use the ingress YAML file to provide NGINX configurations which will be added to the nginx.conf file automatically.

Info

This may be different depending on which ingress controller you are using

Using the rewrite-target annotation we can specify the target URI where the traffic must be redirected.

https://kubernetes.github.io/ingress-nginx/examples/rewrite/

So in our example we first match the path, /guestbook, and then NGINX will rewrite it to / before sending it to the web server. Since it's now rewritten to the root directory, /, the index.html file is returned correctly as was the case in the first scenario.

However there's still a problem as you can see below.



It turns out that within index.html is a reference to a few other static assets (CSS and JS files). When the page loads it tries to download the required files but does not provide any subdirectory to indicate that this is for the guestbook application. It tries to access 10.113.105.202/controllers.js, however the path we've configured is looking for 10.113.105.202/guestbook.

A 404 error is returned since there's no rule to match controllers.js and forward to the web server.

Solving the problem

To overcome this issue you can update the rewrite-target to only append the second capture group. Here's an updated example.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: my-cluster.my-domain.com
    http:
      paths:
      - backend:
          service:
            name: guestbook
            port:
              number: 80
        path: /guestbook(/|$)(.*)
        pathType: Prefix

The two key lines to note are:

nginx.ingress.kubernetes.io/rewrite-target: /$2

This annotation tells the Nginx ingress controller to rewrite the URL of incoming requests. /$2 refers to the second group in the regular expression, denoted by (.*) in the path. In essence, it means "change the URL of the incoming request to the value captured in the second group of the regular expression".

path: /guestbook(/|$)(.*)

This is a regular expression defining the paths that this ingress rule will apply to. Any URL path that starts with /guestbook will match this rule. The (/|$) means "either a slash or the end of the line". The (.*) means "any number of any characters", which is a catch-all for anything that follows /guestbook/ in the URL. Essentially, it captures any path that starts with /guestbook/ or simply /guestbook.

So, for example, if an incoming request URL is /guestbook/page1, the ingress rule will rewrite the target URL as /page1 before routing it to the appropriate service.

Info

Sometimes you might find it works when accessing https://my-domain.com/guestbook/ (note the trailing slash) but not https://my-domain.com/guestbook.

URL paths are hierarchical, similar to file paths in a file system. When you have a trailing slash like https://my-domain.com/guestbook/, it's interpreted as a directory and the browser will request https://my-domain.com/guestbook/controller.js rather than just controllers.js.

When it's received at the Kubernetes Ingress, the ingress rule will rewrite the target from guestbook/controller.js to /controllers.js before sending it to the server which returns controllers.js.

There are some other options outlined below to solve this problem however see if the rewrite syntax above will work as this may be the easiest option.

Another Option: Serve Static Content from a Different Location

The first option is to separate the static content from the main HTML files and host them externally.

You can see an example of this in the guestbook application by looking at the bootstrap.min.css file. Bootstrap is a popular front-end open source toolkit, and so is hosted on a CDN and available for anyone to use.

When the page loads, the Bootstrap CSS file is downloaded directly from the CDN. This means we don't need to worry about ingress or web server rules to server this file.

The drawback of this approach is having to manage these files being hosted somewhere else.

Another Option: Modify the Application Code


The second option is to modify the application code to prepend the path when loading the assets . In this example we would update the HTML to reference the file, guestbook/controllers.js.

When the browser attempts to download the file it will request the path, 10.113.105.202/guestbook/controllers.js. We have a rule that matches /guestbook so the ingress controller will rewrite this to the root directory, /, and request controllers.js from the web server.

Info

In the example flow diagram above you'll see the path is updated to path: /guestbook/* to match all our files and subpaths.

The drawback of this approach is having to update all the code, which in some cases can be a very large task.

Another Option: Change the Apache Serving Directory


The final option we'll look at is to modify the configuration on the web server, in this case Apache.

From the testing and examples above, we know that we need a subdirectory, /guestbook, for this application. However when we added this the web server returned a 404 since it was serving content from the root directory, /, and couldn't find the guestbook subdirectory files.

To solve this problem we can tell Apache to stop serving content from /, and instead use /guestbook as the root directory.

In the apache2.conf configuration file you need to modify the DocumentRoot so it reads:

DocumentRoot /var/www/html/guestbook

On Ubuntu you should be able to find apache2.conf in the /etc/apache2/ directory.

Update this file and then restart Apache if needed. You should also remove the rewrite-target annotation from your ingress as we are no longer serving content from root, /

If everything has worked you should now be able to access your guestbook application successfully and all static assets loaded correctly.

Comments