DEV Community

Cover image for Kubernetes-101: Ingress
Mattias Fjellström
Mattias Fjellström

Posted on • Originally published at mattias.engineer

Kubernetes-101: Ingress

In this article we will look at the Ingress resource. An Ingress exposes a Service on HTTP or HTTPS outside of the Kubernetes cluster. This sounds suspiciously much like what we already did using only a Service itself. The Ingress can do much more than what we could do with only a Service.

To actually make the Ingress resource work you must have an Ingress controller in your cluster. In this article we will use the Nginx Ingress controller in a Minikube cluster.

A schematic view of the role of the Ingress resource is shown in the following image:

ingress

As in the illustration the Ingress resource can configure multiple routing rules that send traffic to different Services in your Kubernetes cluster. The routing rules can look at the hostname and the path to determine where traffic should be sent. The connection between the outside world and the Kubernetes cluster is usually a load balancer of some sort (labeled LB in the figure above), e.g. an Application Load Balancer in AWS, but it does not have to be.

Ingress controllers

There are a number of Ingress controllers available. There are three Ingress controllers that are officially supported by the Kubernetes project: AWS, GCE, and the Nginx ingress controller.

I am using Minikube on my laptop, so for me it makes sense to use the Nginx Ingress controller. The Nginx Ingress controller uses Nginx behind the scenes to handle the functionality that the Ingress resources provide.

The process of how to install the Nginx Ingress controller on Minikube involves using the minikube addons command:

$ minikube addons enable ingress

You can view the list of minikube maintainers at: https://github.com/kubernetes/minikube/blob/master/OWNERS
💡  After the addon is enabled, please run "minikube tunnel" and your ingress resources would be available at "127.0.0.1"
    ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v1.2.1
    ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
🔎  Verifying ingress addon...
🌟  The 'ingress' addon is enabled
Enter fullscreen mode Exit fullscreen mode

After running this command we can see that the Nginx Ingress controller is installed in the ingress-nginx Namespace:

$ kubernetes get pods -n ingress-nginx

NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-98mcb        0/1     Completed   0          107s
ingress-nginx-admission-patch-rkjj6         0/1     Completed   0          107s
ingress-nginx-controller-5959f988fd-kw6jk   1/1     Running     0          107s
Enter fullscreen mode Exit fullscreen mode

The installation process for other Ingress controllers will differ from what I showed you here. In general the process involves installing the controller using Helm. Read the documentation for the Ingress controller that you need to install. A list of some of the available Ingress controllers can be found in the official documentation.

Ingress

We now have an Ingress controller installed in our cluster. We can now create Ingress resources that work! Note that if we didn't have an Ingress controller we could still create Ingress resources, but they would not provide any functionality.

A basic Kubernetes manifest for an Ingress looks like this:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-service
                port:
                  number: 80
Enter fullscreen mode Exit fullscreen mode

As with most manifests this one has apiVersion, kind, metadata, and spec. In metadata we see our first example of annotations.

An annotation is similar to labels, but are more commonly used to configure settings for resources. In the example above there is one annotation named nginx.ingress.kubernetes.io/rewrite-target with the value /. This annotation tells the Nginx ingress-controller to rewrite the target path to / for incoming requests. This particular annotation only makes sense to the Nginx ingress-controller. There are more advanced things you can do using this annotation, but we keep things simple in this article.

Most of the magic with an Ingress is configured in .spec.rules where we have our routing rules. This is where we define what traffic this Ingress resource listens to as well as where it will send this traffic in our Kubernetes cluster. In the example above I have a single rule for the host example.com. If I owned this domain and I point it at my Kubernetes cluster, then traffic reaching my cluster would be handled by this Ingress resource. Inside my routing rule I match on a single route, the root route /. Traffic destined to example.com/ will match this routing rule and this path, and the traffic will be sent to what I define inside of .spec.rules[0].http.paths[0].backend. In this case I have specified that the traffic should be sent to a Service named my-service on port 80.

There are a lot more you can do with these routing rules, for instance you can have rules for different hosts and different path prefixes. See the official documentation for some additional examples.

Once we have written down our Kubernetes manifest we can create the Ingress resource using kubectl apply:

$ kubectl apply -f ingress.yaml

ingress.networking.k8s.io/my-ingress created
Enter fullscreen mode Exit fullscreen mode

We can query our cluster for all Ingress resources with kubectl get ingresses (or kubectl get ingress):

$ kubectl get ingresses

NAME         CLASS   HOSTS         ADDRESS        PORTS   AGE
my-ingress   nginx   example.com   192.168.49.2   80      32s
Enter fullscreen mode Exit fullscreen mode

We could also use the short for for ingress/ingresses which is ing:

$ kubectl get ing

NAME         CLASS   HOSTS         ADDRESS        PORTS   AGE
my-ingress   nginx   example.com   192.168.49.2   80      40s
Enter fullscreen mode Exit fullscreen mode

To view details about a specific Ingress resource we can use kubectl describe:

$ kubectl describe ingress my-ingress

Name:             my-ingress
Labels:           <none>
Namespace:        default
Address:          192.168.49.2
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host         Path  Backends
  ----         ----  --------
  example.com
               /   my-service:80 (<error: endpoints "my-service" not found>)
Annotations:   nginx.ingress.kubernetes.io/rewrite-target: /
Events:
  Type    Reason  Age                From                      Message
  ----    ------  ----               ----                      -------
  Normal  Sync    25s (x2 over 54s)  nginx-ingress-controller  Scheduled for sync
Enter fullscreen mode Exit fullscreen mode

This particular Ingress does not work because I have not created the Service that the Ingress points to, so I will delete it using kubectl delete:

$ kubectl delete -f ingress.yaml

ingress.networking.k8s.io "my-ingress" deleted
Enter fullscreen mode Exit fullscreen mode

Sample application

To test the Ingress resource I will first set up a sample application consisting of a Deployment and a Service. The application uses the Nginx image, but do not confuse this instance of Nginx with the Nginx Ingress controller! The full Kubernetes manifests for my application looks like this:

# application.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
Enter fullscreen mode Exit fullscreen mode

I create my application using kubectl apply:

$ kubectl apply -f application.yaml

deployment.apps/nginx-deployment created
service/nginx-service created
Enter fullscreen mode Exit fullscreen mode

Creating an Ingress resource

Let's now add an Ingress resource:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 80
Enter fullscreen mode Exit fullscreen mode
  • The name of this resource is defined in .metadata.name and is set to nginx-ingress.
  • There is a single annotation added in .metadata.annotations, it is named nginx.ingress.kubernetes.io/rewrite-target and has the value /. This annotations says that the target is always rewritten to / no matter what path I try to go to.
  • There is a single rule defined in .spec.rules. This rule is for the host hello-world.info.
  • There is a single path defined for the hello-world.info rule in .spec.rules[0].http.paths, this is for the / path. This path will send the traffic to the service named nginx-service on port 80.

To make the host hello-world.info work I must either own the domain (I don't!) or I can edit my /etc/hosts file to route traffic destined to hello-world.info to go to my localhost instead. I add the following line to /etc/hosts:

127.0.0.1 hello-world.info
Enter fullscreen mode Exit fullscreen mode

I create my Ingress resource with kubectl apply:

$ kubectl apply -f ingress.yaml

ingress.networking.k8s.io/nginx-ingress created
Enter fullscreen mode Exit fullscreen mode

After a while I can see that the Ingress resource is ready:

$ kubectl get ing

NAME            CLASS   HOSTS              ADDRESS        PORTS   AGE
nginx-ingress   nginx   hello-world.info   192.168.49.2   80      19s
Enter fullscreen mode Exit fullscreen mode

Verifying that the Ingress works

Now all that is left to do is to test that my Ingress works. Since I am on a Mac and using Minikube there are some additional steps I need to do, as you might remember from an earlier article. I need to tunnel traffic:

$ minikube tunnel

✅  Tunnel successfully started

📌  NOTE: Please do not close this terminal as this process must stay alive for the tunnel to be accessible ...

❗  The service/ingress nginx-ingress requires privileged ports to be exposed: [80 443]
🔑  sudo permission will be asked for it.
🏃  Starting tunnel for service nginx-ingress.
Enter fullscreen mode Exit fullscreen mode

In a separate terminal window I run curl to verify that my Ingress works:

$ curl hello-world.info

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

It works!

HTTPS traffic with TLS certificate

What we have seen so far in this article is HTTP traffic. In the real world we would rather use HTTPS traffic. You can configure an Ingress to use a TLS certificate to allow it to terminate HTTPS traffic from the outside world. The first step is to add the TLS certificate as a Secret resource in your Kubernetes cluster and then reference it from your ingress resource.

For example, we can define a secret of type kubernetes.io/tls with two keys (tls.crt and tls.key):

apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
  name: my-tls-certificate
data:
  tls.crt: <base64 encoded cert>
  tls.key: <base64 encoded key>
Enter fullscreen mode Exit fullscreen mode

Then we can create an Ingress resource where we reference the Secret in .spec.tls[0].secretName for the corresponding hostname (i.e. the certificate must match the hostname specified in .spec.tls[*].hosts):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  tls:
    - hosts:
        - hello-world.info
      secretName: my-tls-certificate # reference to the secret
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 80
Enter fullscreen mode Exit fullscreen mode

Summary

In this article we created and worked with the Ingress resource. An Ingress can safely be considered to be a more advanced component of the Kubernetes world, since it often involves external resources such as cloud load balancers and secrets such as TLS certificates.

We have only scratched the surface of what the Ingress resource has to offer, but it is enough to get us started to learn more. We now understand that to work with Ingress resources we must first install an Ingress controller. We know that there are many Ingress controllers to choose from, and what we choose depends on where we run our cluster and what needs we have. We saw concrete examples of using the Nginx controller in a local Minikube cluster. We also saw what it takes to set up an Ingress with a HTTPS-listener.

In the next article I will look at three important concepts related to security in our Kubernetes clusters: Service Accounts, security contexts, and NetworkPolicies. See you there!

Top comments (0)