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:
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
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
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
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
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
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
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
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
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
I create my application using kubectl apply
:
$ kubectl apply -f application.yaml
deployment.apps/nginx-deployment created
service/nginx-service created
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
- The name of this resource is defined in
.metadata.name
and is set tonginx-ingress
. - There is a single annotation added in
.metadata.annotations
, it is namednginx.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 hosthello-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 namednginx-service
on port80
.
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
I create my Ingress resource with kubectl apply
:
$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/nginx-ingress created
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
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.
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>
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>
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
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!