Creating a Kubernetes Nginx Ingress Controller and create a rule to a sample application

Whenever you are creating an application that you want to expose to the outside world, it's always smart to control the flow towards the application behind it. That's why Kubernetes has something called Kubernetes Ingress. But what is it?

Kubernetes Ingress

Kubernetes Ingress allows you to expose HTTP and HTTPS routes from outside the cluster to services within the cluster. The traffic routing is then controlled by rules defined in the ingress sources.

For this article, I will explain how you can get started on creating your own Nginx Ingress Controller. Of course this is not the only possibility, so feel free to check other ingress controllers such as Istio, HAProxy, Traefik, …

Some advantages of using an ingress controller:

  • Rate limiting, Timeouts, …
  • Authentication
  • Content based routing

Sample Hello World application

Before we create our controller, let's get started on creating a simple demo application. The only thing our application will do is process the HTTP request, wait a couple of seconds and return a "Hello World" response.

Creating our sample app

I decided to create this application in Node.js. So if you have npm and node installed, run the following commands:

npm init -y
npm i express --save

Whereafter you can create an index.js file with the following content:

const express = require('express')
const app = express()
const port = 3000

app.get('/', async (req, res) => {
  console.log('Got request, waiting a bit');
  await delay(15 * 1000);
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

const delay = async (timeout = 1000) => {
  return new Promise((resolve, reject) => setTimeout(resolve, timeout));
}

Packaging it as a container

Since everything is created in terms of application code, we can package it all up into a Docker container by creating a Dockerfile:

Dockerfile

FROM node:latest

WORKDIR /usr/src/app

# Install deps
RUN apt-get update

# Create Certificate
RUN apt-get install ca-certificates

# Install Package.json dependendencies
COPY package.json .
RUN npm install

# Copy Source Code
ADD . /usr/src/app

CMD [ "npm", "run", "start" ]
EXPOSE 3000

That we can build with (choose one for your use-case):

# Local build (for local use)
# Note: when using minikube, make sure to run `eval $(minikube docker-env)` to build images in minikube context
docker build -t local/node-sample-helloworld .

# Remote build (to push to docker repository)
docker build -t thebillkidy/node-sample-helloworld .
docker push thebillkidy/node-sample-helloworld

Running it on Kubernetes

Once it is build, we can now run it on our Kubernetes cluster. For that we create a Deployment YAML file:

kubernetes.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: d-node-sample-helloworld
spec:
  selector:
    matchLabels:
      app: node-sample-helloworld
  replicas: 1
  template:
    metadata:
      labels:
        app: node-sample-helloworld
    spec:
      containers:
      - name: main
        image: thebillkidy/node-sample-helloworld:latest # if local, utilize local/node-sample-helloworld
        imagePullPolicy: Always # if local, utilize Never
        ports:
        - containerPort: 3000

That we can apply with kubectl apply -f kubernetes.yaml and should now show the following after running kubectl get deployments -A:

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
d-node-sample-helloworld   1/1     1            1           37s

Kubernetes is getting more popular everyday and it's no wonder why! When you are running applications on-premise or in-cloud, the possibility of having the applications in a portable way is a strong one! Removing the friction for scaling-out your application when you are ready for it, or even bursting scenarios.

Nginx Ingress Controller

We now have a simple Hello World application running, but it's only available internally! What we could do now is expose it through Kubernetes and a LoadBalancer, but let's actually utilize our Ingress Controller here! So let's get started creating this Ingress Controller.

Installation

The first step that we should do is create the NGINX Ingress controller. For this we can follow these steps:

Note: before the stable/nginx-ingress chart was utilized. But this is now deprecated!
# ref: https://github.com/kubernetes/ingress-nginx (repo)
# ref: https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx (chart)

# 1. Create namespace
kubectl create namespace ingress-nginx

# 2. Add the repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# 3. Update the repo
helm repo update

# 4. Install nginx-ingress through Helm
helm install ingress-controller ingress-nginx/ingress-nginx --namespace ingress-nginx

Once we ran the above, we should now be able to access the ingress controller by loading the external IP (kubectl -n ingress-nginx get svc).

Note: when working with Minikube or others, we can utilize kubectl port-forward svc/ingress-controller-ingress-nginx-controller --namespace ingress-nginx --address 0.0.0.0 8000:80 and access it on http://localhost:8000

We are now ready to expose our application!

Exposing our Application

Once an ingress controller is created, we need to expose our application internally:

kubectl expose deployment d-node-sample-helloworld --name svc-node-sample-helloworld

and configure our Ingress controller to route traffic to it as defined in the Kubernetes Ingress API. By creating a YAML file:

ingress-node-sample-helloworld.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-node-sample-helloworld
  annotations:
    # Target URI where the traffic must be redirected
    # More info: https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/rewrite/README.md
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    # Uncomment the below to only allow traffic from this domain and route based on it
    # - host: my-host # your domain name with A record pointing to the nginx-ingress-controller IP
    - http:
        paths:
        - path: / # Everything on this path will be redirected to the rewrite-target
          backend:
            serviceName: svc-node-sample-helloworld # the exposed svc name and port
            servicePort: 3000

Which we apply with kubectl apply -f ingress-node-sample-helloworld.yaml

Now once this is applied, we should be able to execute a cURL request to access our application! So let's try this:

# Execute a GET request with the specified host and IP
# Note: the Host should match what is written in spec.rules.host
curl -k -X "GET" -H "Host: my-host" http://YOUR_IP

Or we can also open it in our browser and navigate to http://YOUR_IP

Note: If this is not working, check kubectl describe ing and make sure that the configuration is correct

Conclusion

In this article a demonstration was made on how you can set up your own ingress controller for Kubernetes. This is of course a small step in the entire chain of use cases, where most often you want to do more such as rate limiting, or even monitoring it.

The next article will explain more in-depth how you are able to start monitoring what we have just set-up through Prometheus and visualize all of it in Grafana.