12.2 Creating and using Ingress objects
The previous section explained the basics of Ingress objects and controllers, and how to install the Nginx ingress controller. In this section, you’ll learn how to use an Ingress to expose the services of the Kiada suite.
Before you create your first Ingress object, you must deploy the pods and services of the Kiada suite. If you followed the exercises in the previous chapter, they should already be there. If not, you can create them by creating the kiada
namespace and then applying all manifests in the the Chapter12/SETUP/
directory with the following command:
$ kubectl apply -f SETUP/ --recursive
12.2.1 Exposing a service through an Ingress
An Ingress object references one or more Service objects. Your first Ingress object exposes the kiada
service, which you created in the previous chapter. Before you create the Ingress, refresh your memory by looking at the service manifest in the following listing.
Listing 12.1 The kiada service manifest
apiVersion: v1
kind: Service
metadata:
name: kiada
spec:
type: ClusterIP
selector:
app: kiada
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
The Service type is ClusterIP
because the service itself doesn’t need to be directly accessible to clients outside the cluster, since the Ingress will take care of that. Although the service exposes ports 80
and 443
, the Ingress will forward traffic only to port 80.
Creating the Ingress object
The Ingress object manifest is shown in the following listing. You can find it in the file Chapter12/ing.kiada-example-com.yaml
in the book’s code repository.
Listing 12.2 An Ingress object exposing the kiada service at kiada.example.com
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kiada-example-com
spec:
rules:
- host: kiada.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kiada
port:
number: 80
The manifest in the listing defines an Ingress object named kiada-example-com
. While you can give the object any name you want, it’s recommended that the name reflect the host and/or path(s) specified in the ingress rules.
WARNING
In Google Kubernetes Engine, the Ingress name mustn’t contain dots, otherwise the following error message will be displayed in the events associated with the Ingress object: Error syncing to GCP: error running load balancer syncing routine: invalid loadbalancer name
.
The Ingress object in the listing defines a single rule. The rule states that all requests for the host kiada.example.com
should be forwarded to port 80
of the kiada
service, regardless of the requested path (as indicated by the path
and pathType
fields). This is illustrated in the following figure.
Figure 12.4 How the kiada-example-com Ingress object configures external traffic routing
Inspecting an Ingress object to get its public IP address
After creating the Ingress object with kubectl apply
, you can see its basic information by listing Ingress objects in the current namespace with kubectl get ingresses
as follows:
$ kubectl get ingresses
NAME CLASS HOSTS ADDRESS PORTS AGE
kiada-example-com nginx kiada.example.com 11.22.33.44 80 30s
NOTE
You can use ing
as a shorthand for ingress
.
To see the Ingress object in detail, use the kubectl describe
command as follows:
$ kubectl describe ing kiada-example-com
Name: kiada-example-com
Namespace: default
Address: 11.22.33.44
Default backend: default-http-backend:80 (172.17.0.15:8080)
Rules:
Host Path Backends
---- ---- --------
kiada.example.com
/ kiada:80 (172.17.0.4:8080,172.17.0.5:8080,172.17.0.9:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 5m6s (x2 over 5m28s) nginx-ingress-controller Scheduled for sync
As you can see, the kubectl describe
command lists all the rules in the Ingress object. For each rule, not only is the name of the target service shown, but also its endpoints. If you see an error message related to the default backend, ignore it for now. You’ll fix it later.
Both kubectl get
and kubectl describe
display the IP address of the ingress. This is the IP address of the L7 load balancer or reverse proxy to which clients should send requests. In the example output, the IP address is 11.22.33.44
and the port is 80
.
NOTE
The address may not be displayed immediately. This is very common when the cluster is running in the cloud. If the address isn’t displayed after several minutes, it means that no ingress controller has processed the Ingress object. Check if the controller is running. Since a cluster can run multiple ingress controllers, it’s possible that they’ll all ignore your Ingress object if you don’t specify which of them should process it. Check the documentation of your chosen ingress controller to find out if you need to add the kubernetes.io/ingress.class
annotation or set the spec.ingressClassName
field in the Ingress object. You’ll learn more about this field later.
You can also find the IP address in the Ingress object’s status
field as follows:
$ kubectl get ing kiada -o yaml
...
status:
loadBalancer:
ingress:
- ip: 11.22.33.44
NOTE
Sometimes the displayed address can be misleading. For example, if you use Minikube and start the cluster in a VM, the ingress address will show up as localhost
, but that’s only true from the VM’s perspective. The actual ingress address is the IP address of the VM, which you can get with the minikube ip
command.
Adding the ingress IP to the DNS
After you add an Ingress to a production cluster, the next step is to add a record to your Internet domain’s DNS server. In these examples, we assume that you own the domain example.com
. To allow external clients to access your service through the ingress, you configure the DNS server to resolve the domain name kiada.example.com
to the ingress IP 11.22.33.44
.
In a local development cluster, you don’t have to deal with DNS servers. Since you’re only accessing the service from your own computer, you can get it to resolve the address by other means. This is explained next, along with instructions on how to access the service through the ingress.
Accessing services through the ingress
Since ingresses use virtual hosting to figure out where to forward the request, you won’t get the desired result by simply sending an HTTP request to the Ingress’ IP address and port. You need to make sure that the Host
header in the HTTP request matches one of the rules in the Ingress object.
To achieve this, you must tell the HTTP client to send the request to the host kiada.example.com
. However, this requires resolving the host to the Ingress IP. If you use curl
, you can do this without having to configure your DNS server or your local /etc/hosts
file. Let’s take 11.22.33.44
as the ingress IP. You can access the kiada
service through the ingress with the following command:
$ curl --resolve kiada.example.com:80:11.22.33.44 http://kiada.example.com -v
* Added kiada.example.com:80:11.22.33.44 to DNS cache
* Hostname kiada.example.com was found in DNS cache
* Trying 11.22.33.44:80...
* Connected to kiada.example.com (11.22.33.44) port 80 (#0)
> GET / HTTP/1.1
> Host: kiada.example.com
> User-Agent: curl/7.76.1
> Accept: */*
...
The --resolve
option adds the hostname kiada.example.com
to the DNS cache. This ensures that kiada.example.com
resolves to the ingress IP. Curl then opens the connection to the ingress and sends the HTTP request. The Host
header in the request is set to kiada.example.com
and this allows the ingress to forward the request to the correct service.
Of course, if you want to use your web browser instead, you can’t use the --resolve
option. Instead, you can add the following entry to your /etc/hosts
file.
11.22.33.44 kiada.example.com
NOTE
On Windows, the hosts file is usually located at C:\Windows\System32\Drivers\etc\hosts
.
You can now access the service at http://kiada.example.com with your web browser or curl
without having to use the --resolve
option to map the hostname to the IP.
12.2.2 Path-based ingress traffic routing
An Ingress object can contain many rules and therefore map multiple hosts and paths to multiple services. You’ve already created an Ingress for the kiada
service. Now you’ll create one for the quote
and quiz
services.
The Ingress object for these two services makes them available through the same host: api.example.com
. The path in the HTTP request determines which service receives each request. As you can see in the following figure, all requests with the path /quote
are forwarded to the quote
service, and all requests whose path starts with /questions
are forwarded to the quiz
service.
Figure 12.5 Path-based ingress traffic routing
The following listing shows the Ingress manifest.
Listing 12.3 Ingress mapping request paths to different services
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-example-com
spec:
rules:
- host: api.example.com
http:
paths:
- path: /quote
pathType: Exact
backend:
service:
name: quote
port:
name: http
- path: /questions
pathType: Prefix
backend:
service:
name: quiz
port:
name: http
In the Ingress object shown in the listing, a single rule with two paths is defined. The rule matches HTTP requests with the host api.example.com
. In this rule, the paths
array contains two entries. The first matches requests that ask for the /quote
path and forwards them to the port named http
in the quote
Service object. The second entry matches all requests whose first path element is /questions
and forwards them to the port http
of the quiz
service.
NOTE
By default, no URL rewriting is performed by the ingress proxy. If the client requests the path /quote
, the path in the request that the proxy makes to the backend service is also /quote
. In some ingress implementations, you can change this by specifying a URL rewrite rule in the Ingress object.
After you create the Ingress object from the manifest in the previous listing, you can access the two services it exposes as follows (replace the IP with that of your ingress):
$ curl --resolve api.example.com:80:11.22.33.44 api.example.com/quote
$ curl --resolve api.example.com:80:11.22.33.44 api.example.com/questions/random
If you want to access these services with your web browser, add api.example.com
to the line you added earlier to your /etc/hosts
file. It should now look like this:
11.22.33.44 kiada.example.com api.example.com
Understanding how the path is matched
Did you notice the difference between the pathType
fields in the two entries in the previous listing? The pathType
field specifies how the path in the request is matched with the paths in the ingress rule. The three supported values are summarized in the following table.
Table 12.1 Supported values in the pathType field
PathType | Description |
---|---|
Exact | The requested URL path must exactly match the path specified in the ingress rule. |
Prefix | The requested URL path must begin with the path specified in the ingress rule, element by element. |
ImplementationSpecific | Path matching depends on the implementation of the ingress controller. |
If multiple paths are specified in the ingress rule and the path in the request matches more than one path in the rule, priority is given to paths with the Exact
path type.
Matching paths using the Exact path type
The following table shows examples of how matching works when pathType
is set to Exact
.
Table 12.2 Request paths matched when pathType is Exact
Path in rule | Matches request path | Doesn’t match |
---|---|---|
/ | / | /foo /bar |
/foo | /foo | /foo/ /bar |
/foo/ | /foo/ | /foo /foo/bar /bar |
/FOO | /FOO | /foo |
As you can see from the examples in the table, the matching works as you’d expect. It’s case sensitive, and the path in the request must exactly match the path
specified in the ingress rule.
Matching paths using the Prefix path type
When pathType
is set to Prefix
, things aren’t as you might expect. Consider the examples in the following table.
Table 12.3 Request paths matched when pathType is Prefix
Path in rule | Matches request paths | Doesn’t match |
---|---|---|
/ | All paths; for example: / /foo /foo/ |
|
/foo or /foo/ |
/foo /foo/ /foo/bar |
/foobar /bar |
/FOO | /FOO | /foo |
The request path isn’t treated as a string and checked to see if it begins with the specified prefix. Instead, both the path in the rule and the request path are split by /
and then each element of the request path is compared to the corresponding element of the prefix. Take the path /foo
, for example. It matches the request path /foo/bar
, but not /foobar
. It also doesn’t match the request path /fooxyz/bar
.
When matching, it doesn’t matter if the path in the rule or the one in the request ends with a forward slash. As with the Exact
path type, matching is case sensitive.
Matching paths using the ImplementationSpecific path type
The ImplementationSpecific
path type is, as the name implies, dependent on the implementation of the ingress controller. With this path type, each controller can set its own rules for matching the request path. For example, in GKE you can use wildcards in the path. Instead of using the Prefix
type and setting the path to /foo
, you can set the type to ImplementationSpecific
and the path to /foo/*
.
12.2.3 Using multiple rules in an Ingress object
In the previous sections you created two Ingress objects to access the Kiada suite services. In most Ingress implementations, each Ingress object requires its own public IP address, so you’re now probably using two public IP addresses. Since this is potentially costly, it’s better to consolidate the Ingress objects into one.
Creating an Ingress object with multiple rules
Because an Ingress object can contain multiple rules, it’s trivial to combine multiple objects into one. All you have to do is take the rules and put them into the same Ingress object, as shown in the following listing. You can find the manifest in the file ing.kiada.yaml
.
Listing 12.4 Ingress exposing multiple services on different hosts
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kiada
spec:
rules:
- host: kiada.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kiada
port:
name: http
- host: api.example.com
http:
paths:
- path: /quote
pathType: Exact
backend:
service:
name: quote
port:
name: http
- path: /questions
pathType: Prefix
backend:
service:
name: quiz
port:
name: http
This single Ingress object handles all traffic for all services in the Kiada suite yet only requires a single public IP address.
The Ingress object uses virtual hosts to route traffic to the backend services. If the value of the Host
header in the request is kiada.example.com
, the request is forwarded to the kiada
service. If the header value is api.example.com
, the request is routed to one of the other two services, depending on the requested path. The Ingress and the associated Service objects are shown in the next figure.
Figure 12.6 An Ingress object covering all services of the Kiada suite
You can delete the two Ingress objects you created earlier and replace them with the one in the previous listing. Then you can try to access all three services through this ingress. Since this is a new Ingress object, its IP address is most likely not the same as before. So you need to update the DNS, the /etc/hosts
file, or the --resolve
option when you run the curl
command again.
Using wildcards in the host field
The host
field in the ingress rules supports the use of wildcards. This allows you to capture all requests sent to a host that matches *.example.com
and forward them to your services. The following table shows how wildcard matching works.
Table 12.4 Examples of using wildcards in the ingress rule’s host field
Host | Matches request hosts | Doesn’t match |
---|---|---|
kiada.example.com | kiada.example.com | example.com api.example.com foo.kiada.example.com |
*.example.com | kiada.example.com api.example.com foo.example.com |
example.com foo.kiada.example.com |
Look at the example with the wildcard. As you can see, *.example.com
matches kiada.example.com
, but it doesn’t match foo.kiada.example.com
or example.com
. This is because a wildcard only covers a single element of the DNS name.
As with rule paths, a rule that exactly matches the host in the request takes precedence over rules with host wildcards.
NOTE
You can also omit the host
field altogether to make the rule match any host.
12.2.4 Setting the default backend
If the client request doesn’t match any rules defined in the Ingress object, the response 404 Not Found
is normally returned. However, you can also define a default backend to which the ingress should forward the request if no rules are matched. The default backend serves as a catch-all rule.
The following figure shows the default backend in the context of the other rules in the Ingress object.
Figure 12.7 The default backend handles requests that match no Ingress rule
As you can see in the figure, a service named fun404
is used as the default backend. Let’s add it to the kiada
Ingress object.
Specifying the default backend in an Ingress object
You specify the default backend in the spec.defaultBackend
field, as shown in the following listing (the full manifest can be found in the ing.kiada.defaultBackend.yaml
file).
Listing 12.5 Specifying the default backend in the Ingress object
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kiada
spec:
defaultBackend:
service:
name: fun404
port:
name: http
rules:
...
In the listing, you can see that setting the default backend isn’t much different from setting the backend in the rules. Just as you specify the name
and port
of the backend service in each rule, you also specify the name and port of the default backend service in the service
field under spec.defaultBackend
.
Creating the service and pod for the default backend
The kiada
Ingress object is configured to forward requests that don’t match any rules to a service called fun404
. You need to create this service and the underlying pod. You can find an object manifest with both object definitions in the file all.my-default-backend
.yaml. The contents of the file are shown in the following listing.
Listing 12.6 The Pod and Service object manifests for the default ingress backend
apiVersion: v1
kind: Pod
metadata:
name: fun404
labels:
app: fun404
spec:
containers:
- name: server
image: luksa/static-http-server
args:
- --listen-port=8080
- --response-code=404
- --text=This isn't the URL you're looking for.
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: fun404
labels:
app: fun404
spec:
selector:
app: fun404
ports:
- name: http
port: 80
targetPort: http
After applying both the Ingress object manifest and the Pod and Service object manifest, you can test the default backend by sending a request that doesn’t match any of the rules in the ingress. For example:
$ curl api.example.com/unknown-path --resolve api.example.com:80:11.22.33.44
This isn't the URL you're looking for.
As expected, the response text matches what you configured in the fun404
pod. Of course, instead of using the default backend to return a custom 404
status, you can use it to forward all requests to default to a service of your choice.
You can even create an Ingress object with only a default backend and no rules to forward all external traffic to a single service. If you’re wondering why you’d do this using an Ingress object and not by simply setting the service type to LoadBalancer, it’s because ingresses can provide additional HTTP features that services can’t. One example is securing the communication between the client and the service with Transport Layer Security (TLS), which is explained next.