This is the fourth in a collection of articles as I figure out what's what with Knative for Kubernetes. The full set of articles are:

In the first article we deployed an application service and used its internal routing to test. In the previous third article we setup a static IP to our ingress gateway, and set up some wildcard DNS. This allowed Knative to generate hostnames for each application service, in the functional format "appname.namespace.basedomain.whatever". We can agree that we're not going public with these generated hostnames. Not as many people would be addicted to a site called moneymaker.production.facebook.com.

We want pretty hostnames – starkandwayne.com – crisp and efficient.


In the previous article we set up a common wildcard custom domain (knative.starkandwayne.com in my example) to a long-lived static IP that was bound to the ingress gateway. But the generated route for our helloworld application -- hello.helloworld.knative.starkandwayne.com wasn't exactly how we'd like it to be for public consumption. We want hello.starkandwayne.com or maybe use a whole new domain like sayhellowithknative.com.

In this article we'll add a custom route to our Knative application service. To do this we will break out from the high-level knctl CLI and interact with Knative/Istio using kubectl and some poeticly aesthetic YAML.

A Note on Terminology

OMG, I can be confused about when/how to use the terms hostname, route, and domain within Knative/knctl/Kubernetes/DNS. You can be confused too for now until I too have their usage clear in my mind. I'm sorry.

Generated routes

From the previous article, we have configured a custom base domain for all routes:

$ knctl domain list
Domains

Name                       Default
knative.starkandwayne.com  true

Therefore, Knative generates a route hostname for each service based on its service name and namespace within Kubernetes.

$ knctl routes list -n helloworld
Routes in namespace 'helloworld'

Name   Traffic         Ready  Domain                                      Age
hello  100% -> hello:  true   hello.helloworld.knative.starkandwayne.com  2m

All generate routes are guaranteed to be unique by Knative. All routes are guaranteed to be ugly by me.

We will set up some prettier custom routes in this article.

Internal routes

In addition to the public routes above, Knative maintains internal routes for each service.

These are recorded by /status/domainInternal in a routes CRD:

$ kubectl get routes.serving.knative.dev -n helloworld hello -o yaml
...
status:
  domain: hello.helloworld.knative.starkandwayne.com
  domainInternal: hello.helloworld.svc.cluster.local
  ...

Regardless of any changes to public routes, the domainInternal will be a consistent internal hostname.

Our solution for custom routes will be to create a Knative virtual service that rewrites requests from our pretty custom domains, into the domainInternal internal route for our service.

Custom route to internal route

Let's configure two routes - hello.knative.starkandwayne.com and hello.starkandwayne.com.

The former will "just work" because we already have wildcard DNS set up for *.knative.starkandwayne.com.

The latter hello.starkandwayne.com will require additional DNS configuration. This is the same for any new domains like hellothere.com.

Create a file hello-entry-route.yaml:

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: hello-entry-route
  namespace: helloworld
spec:
  gateways:
  - knative-shared-gateway.knative-serving.svc.cluster.local
  hosts:
  - hello.knative.starkandwayne.com
  - hello.starkandwayne.com
  http:
  - match:
    - uri:
        prefix: "/"
    rewrite:
      authority: hello.helloworld.svc.cluster.local
    route:
    - destination:
        host: knative-ingressgateway.istio-system.svc.cluster.local
      weight: 100

Apply it to create or update:

kubectl apply -f hello-entry-route.yaml

Give it a moment and try out the already-enabled wildcard hostname:

$ curl hello.knative.starkandwayne.com
Hello World: Rev1!

Configure custom DNS

Before we can try out hello.starkandwayne.com or any other custom hostname we need to add a CNAME DNS entry to our ingress gateway:

Add CNAME for your custom route

Now that we have both the external DNS entry and the internal Knative/Istio routing, we can try out our custom route:

$ curl hello.starkandwayne.com
Hello World: Rev1!

Routing Paths To Additional Services

Our current routing will rewrite any requests to hello.knative.starkandwayne.com and hello.starkandwayne.com and forward them to the internal route hello.helloworld.svc.cluster.local. The internal service will receive the same path, headers, data, etc.

We can also split the incoming traffic to multiple application services. For example, we could route any traffic to hello.starkandwayne.com/blog or its children to a blog application.

Deploy An Additional Service

kubectl create ns blog

knctl deploy \
      --namespace blog \
      --service hello-blog \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET="Blog Readers"

We are impatient operators who want to know, "Is it done yet?" Fortunately, we can watch Kubernetes events to see the action unfold.

$ kubectl get events -n blog --watch-only
NAMESPACE   LAST SEEN   TYPE     REASON    KIND   MESSAGE
blog        1s          Normal   Created   Pod    Created container
blog   1s    Normal   Started   Pod   Started container
blog   1s    Normal   Pulling   Pod   pulling image "gcr.io/knative-releases/github.com/knative/serving/cmd/queue@sha256:65b4824c5f21134a1b8373cbc2be8d431744f06caf118a309f94b6514cd6f40c"
blog   2s    Normal   Pulled   Pod   Successfully pulled image "gcr.io/knative-releases/github.com/knative/serving/cmd/queue@sha256:65b4824c5f21134a1b8373cbc2be8d431744f06caf118a309f94b6514cd6f40c"
blog   1s    Normal   Created   Pod   Created container
blog   1s    Normal   Started   Pod   Started container
blog   1s    Normal   Pulled   Pod   Container image "docker.io/istio/proxyv2:1.0.0" already present on machine
blog   1s    Normal   Created   Pod   Created container
blog   1s    Normal   Started   Pod   Started container
blog   0s    Warning   Unhealthy   Pod   Readiness probe failed: Get http://10.16.1.13:8022/health: dial tcp 10.16.1.13:8022: getsockopt: connection refused
blog   0s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready
blog   0s    Normal   ConfigurationReady   Configuration   Configuration becomes ready
blog   0s    Normal   LatestReadyUpdate   Configuration   LatestReadyRevisionName updated to "hello-blog-00001"
blog   0s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready
blog   0s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready
blog   0s    Normal   Created   Route   Created VirtualService "hello-blog"
blog   1s    Normal   Updated   Route   Updated status for route "hello-blog"
blog   0s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready
blog   0s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready
blog   1s    Normal   RevisionReady   Revision   Revision becomes ready upon endpoint "hello-blog-00001-service" becoming ready

I'd prefer an event that said "everything is ready; go go go!", but the above events will have to do for now.

$ kubectl get events -n blog --watch-only
Running: curl '-sS' '-H' 'Host: hello-blog.blog.knative.starkandwayne.com' 'http://35.233.215.214:80'

Hello World: Blog Readers!

$ curl hello-blog.blog.knative.starkandwayne.com
Hello World: Blog Readers!

Our new application will also have an internal route hello-blog.blog.svc.cluster.local.

Splitting Incoming Traffic

We can now update our hello-entry-route route (an Istio VirtualService custom resource) to split /blog traffic to our new application service.

Update tmp/hello-entry-route.yaml and insert a new /spec/http to match a /uri/prefix: /blog:

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: hello-entry-route
  namespace: helloworld
spec:
  gateways:
  - knative-shared-gateway.knative-serving.svc.cluster.local
  hosts:
  - hello.knative.starkandwayne.com
  - hello.starkandwayne.com
  http:
  - match:
    - uri:
        prefix: /blog
    rewrite:
      authority: hello-blog.blog.svc.cluster.local
    route:
    - destination:
        host: knative-ingressgateway.istio-system.svc.cluster.local
      weight: 100
  - match:
    - uri:
        prefix: /
    rewrite:
      authority: hello.helloworld.svc.cluster.local
    route:
    - destination:
        host: knative-ingressgateway.istio-system.svc.cluster.local
      weight: 100

Apply it to update:

kubectl apply -f hello-entry-route.yaml

For a demonstration of the Istio routing in action:

$ curl hello.starkandwayne.com
Hello World: Rev1!

$ curl hello.starkandwayne.com/blog
Hello World: Blog Readers!

Summary

After four articles, we've come a long way. We can now deploy any source code, build using Dockerfile or Cloud Foundry buildpack (or any build template you want), from local source code or Git repository, into an autoscaled Kubernetes deployment, with a beautiful custom hostname of our own choosing.