Exposing your application to the public (Ingress)

Exposing your application to the public (Ingress)

What is Ingress?

Ingress in IT terms are traffic coming from outside the network (or in our case the cluster) that wants to reach one of our internal services.

Previously we deployed an application hosted in Pod and have a Service pointing to it. But the Service was only internally accessible, like APIs that are going to be accessed from front-ends. But what about front-ends themselves that needs to be accessed by users from outside the cluster? The solution would be to forward all communication from port 80 on the host machine, to port 80 in the container/Pod.

But typically you want to run multiple applications inside a Kubernetes cluster. May be as simple as multiple websites, or as complex as a microservices solution. And typically these applications are hosted in different Pods/containers. The problem will arise when nodes in a cluster can only use one port 80 (or 443) to handle http, and that port can be assigned to one application. So the solution here is to have another application that handles all requests coming on port 80 and then route them internally to different applications using rules that checks for host name or path. That is the Ingress controller.

Thanks to Ahmet Alp Balkan for the diagram
Thanks to for the diagram

So routing requests is the main function of the Ingress controller, though it usually can also provide these features:

  • SSL termination
  • Authentication
  • IP whitelisting
  • Client rate limiting (throttling)
  • Logging and monitoring
  • Response caching
  • Web application firewall
  • GZIP compression
  • Servicing static content

Also one thing to note, Ingress deployment, varies by where your cluster is deployed. For example:

  • In your localhost cluster, you will need just one Pod for ingress that serves traffic of one port of your machine to the cluster (which is the sample below).
  • In your bare metal - multiple nodes cluster: you may want to deploy the Ingress controller as a DaemonSet (will describe it shortly below) in order to make sure that any traffic reaches port 80 of any node will be picked up by the Ingress.
  • In cloud providers: it’s handled differently, cause each cloud provider have the capability to use it’s own external IPs/load balancers and services. Some providers comes with NGINX Ingress controller like Google Kubernetes Engine. And here is how to do it on Azure.

The below are Kubernetes terms that needs to be described first before trying to understand how to deploy Ingress:

DaemonSets

In Kubernetes, DaemonSets is similar to ReplicaSets but instead of defining number of replicas, it will always create a Pod per cluster node. So for a cluster with 4 nodes, it will guarantee that a Pod exists in every node even when new nodes are added later.

NodePort

When we define a Service in Kubernetes, one of the available service types is NodePort which allow the service to be exposed on a specific port of the host machine. For example if this service has 2 Pods in 2 different nodes, having NodePort: 80 makes the service exposed on port 80 of both physical nodes. So accessing one node with it’s external IP:80 will get to that port. Of course this comes with the limitation that port 80 of the host must not be used by multiple Pods.

Using DaemonSets and NodePort together is useful when we want to make sure that in all cluster nodes, port 80 is always handled by an ingress controller. So using them together will work in both localhost (1 node) scenario, and in multiple nodes bare metal (on premise), and may be on cloud, provided there is something that allows traffic to reach nodes.

Using NGINX

One of the most popular ingress servers is NGINX. In the following step, we are going to deploy nginx ingress as a DaemonSet and NodePort service (it opens 1 random port on your host machine) that direct traffic to inside your cluster.

Run the following commands sequentially to setup the NGINX ingress controller as a DaemonSet: (Source)

kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/common/ns-and-sa.yaml

kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/common/default-server-secret.yaml

kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/common/nginx-config.yaml

kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/rbac/rbac.yaml

kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/daemon-set/nginx-ingress.yaml

Run the following command to make sure the pod is running:

kubectl get pods --namespace=nginx-ingress

And open your dashboard and make sure you have this service under the namespace ingress-nginx

Note the random port assigned to the service after port 80. This is the nodePort.

You can open in your browser http://localhost:{port}/ to see the following nginx 404 page to make sure we are able to reach the nginx ingress.

Ideally you want the random port we used above to be port 80, but the default range allowed in Kubernetes is 30000–32767. To change this port range and allow 80, you will need to alter kubeadm config file on the master node. Which is too advanced for the purpose of this article. But if you want to do it, you can check (this issue).

Add Ingress rule

Now let’s add a rule in the ingress to direct the traffic to the service we created in the previous article.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: mysample-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: localhost
    http:
      paths:
      - path: /
        backend:
          serviceName: mysample-service
          servicePort: 80

Now if you refresh the browser it will serve our sample service.

Important Note

Please note there are multiple types of NGINX controllers, we used one that is managed by NGINX Inc., while there is another one NGINX managed by Kubernetes. Note the annotation that we used in the previous resource is unique to one of them.

For more information on how to write rules, check:

Istio Ingress

Another alternative to NGINX ingress, is to use a service mesh. Istio and Linkerd are popular service meshes that takes control of all the cluster network traffic including ingress. It will need more future articles to cover the use of Istio.