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

Let's expose our Knative routing to the external world.

Knative Serving subsystem coordinates autoscaling of a service and its pods, and its routing. It maintains automatically generated internal routes for containers to use to communicate with Knative services, plus ingress routes — traffic that comes in from the external world.

But to make this work we need to external users' traffic to find its way into your Knative Kubernetes cluster.


In this article we set up DNS and allow public HTTP traffic into our Knative applications. We will deploy a fresh GKE cluster, install Knative, deploy an application, and then move to adding a static IP to our Knative gateway, setting up public DNS with CloudFlare, configuring Knative for our public domain, and redeploying our application. Exciting times. Let's do things on the internet.

Fresh Knative on GKE

Create a Google Kubernetes cluster (GKE) and install Knative (following along to the documentation instructions that are similar to below):

gcloud components update
gcloud auth login

gcloud config set project knative-experiments
export CLUSTER_NAME=knative
export CLUSTER_REGION=us-west1
export CLUSTER_ZONE=us-west1-c

gcloud container clusters create $CLUSTER_NAME \
  --region=$CLUSTER_ZONE \
  --cluster-version=latest \
  --machine-type=n1-standard-2 \
  --enable-autoscaling --min-nodes=1 --max-nodes=5 \
  --enable-autorepair \
  --scopes=service-control,service-management,compute-rw,storage-ro,cloud-platform,logging-write,monitoring-write,pubsub,datastore \
  --num-nodes=3

Your local kubectl will be automatically configured for your new cluster:

$ kubectl get nodes
NAME                                     STATUS   ROLES    AGE   VERSION
gke-knative-default-pool-db8990a0-3bls   Ready    <none>   57s   v1.10.7-gke.6
gke-knative-default-pool-db8990a0-hf7t   Ready    <none>   57s   v1.10.7-gke.6
gke-knative-default-pool-db8990a0-t2hz   Ready    <none>   57s   v1.10.7-gke.6

Grant cluster-admin permissions to current user:

kubectl create clusterrolebinding cluster-admin-binding \
  --clusterrole=cluster-admin \
  --user=$(gcloud config get-value core/account)

Install Knative (which includes Istio):

knctl install --exclude-monitoring

This command will block until all istio/knative pods are running successfully.

Deploy an application

We can quickly check that our GKE Knative is operational by deploying a pre-built Docker image:

knctl namespace create -n helloworld

knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev1

Since we have not yet setup public DNS into our Knative/Istio/Kubernetes cluster, we are limited to interacting with our application via knctl curl or its equivalent curl command:

$ knctl curl --service hello -n helloworld
Running: curl '-sS' '-H' 'Host: hello.helloworld.example.com' 'http://104.198.109.254:80'

Hello World: Rev1!

By default Knative uses a dummy example.com base domain for all routes. We'll replace this with your own working domain soon.

In the rest of this article we will add a static IP into our Knative routing, set up DNS to the static IP, and redeploy our application with its public route.

Allocating Static IP to Knative

At the time of this article, instructions for creating a GCP IP address and mapping it to our Knative cluster is located at https://github.com/knative/docs/blob/master/serving/gke-assigning-static-ip-address.md

GCP automatically allocates public IP addresses to each node in our GKE cluster, so the Knative ingress gateway (where we want our inbound traffic to arrive) already appears to have an External IP:

$ kubectl get svc knative-ingressgateway --namespace istio-system
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                                      AGE
knative-ingressgateway   LoadBalancer   10.19.248.251   104.198.109.254   80:32380/TCP,443:32390/TCP,32400:32400/TCP   2m

$ knctl ingress list
Ingresses

Name                    Addresses        Ports  Age
knative-ingressgateway  104.198.109.254  80     2m
                                         443
                                         32400

The External IP value 104.198.109.254 matches the IP from the knctl curl output above.

But we cannot use this dynamic IP for our DNS as GCP makes no promises that the IP address will be the same over time.

Instead, we need to create and allocate a static persistent IP address to our Knative ingress gateway.

First, create a regional IP address into the same region as your GKE cluster.

$ gcloud compute addresses create --region $CLUSTER_REGION knative-ingress
$ gcloud compute addresses list
NAME             REGION    ADDRESS         STATUS
knative-ingress  us-west1  35.233.215.214  RESERVED

We now want to explicitly configure GCP to bind this static IP to the node running the Knative ingress gateway.

kubectl patch svc knative-ingressgateway --namespace istio-system --patch \
  $(gcloud compute addresses describe \
       knative-ingress --region $CLUSTER_REGION --format json | \
       jq -cr "{spec: {loadBalancerIP: .address}}")

Confirm that the gateway is bound and curl example traffic routes successfully (in my example the expected IP is 35.233.215.214):

$ kubectl describe svc knative-ingressgateway --namespace istio-system
...
Events:
  Type    Reason                Age                  From                Message
  ----    ------                ----                 ----                -------
  Normal  EnsuringLoadBalancer  62s (x2 over 3m45s)  service-controller  Ensuring load balancer
  Normal  LoadbalancerIP        62s                  service-controller  -> 35.233.215.214
  Normal  EnsuredLoadBalancer   13s (x2 over 3m5s)   service-controller  Ensured load balancer

$ gcloud compute addresses list
NAME             REGION    ADDRESS         STATUS
knative-ingress  us-west1  35.233.215.214  IN_USE

$ knctl ingress list
Ingresses

Name                    Addresses       Ports  Age
knative-ingressgateway  35.233.215.214  80     13m
                                        443
                                        32400

$ knctl curl --service hello -n helloworld
Running: curl '-sS' '-H' 'Host: hello.helloworld.example.com' 'http://35.233.215.214:80'

Hello World: Rev1!

GCP/GKE now promises that this static 35.233.215.214 will always be bound to the Knative ingress gateway. We can now try out public routing through a hostname.

Knative Domains

Before we setup our custom domain, we can use free services like https://sslip.io. Any subdomains of 35.233.215.214.sslip.io will always resolve to 35.233.215.214.

Update Knative to use your own .sslip.io as your default base domain for all routes:

knctl domain create -d 35.233.215.214.sslip.io --default

Or try the following to dynamically construct the .sslip.io hostname:

knctl domain create --default \
    -d "$(gcloud compute addresses describe knative-ingress --region $CLUSTER_REGION --format json | jq -cr .address).sslip.io"

Your Knative applications do not need to be redeployed. Since we updated the default base domain, our "hello" application's route is updated automatically:

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

Name   Traffic         All Traffic Assigned  Ready  Domain                                    Age
hello  100% -> hello:  true                  true   hello.helloworld.35.233.215.214.sslip.io  21m

We can now view our application in a web browser or via normal curl commands:

$ curl hello.helloworld.35.233.215.214.sslip.io
Hello World: Rev1!

Custom DNS

You can now setup your own bespoke DNS using the static IP.

In my example below I've created an A record for knative-ingress.starkandwayne.com, and then a wildcard CNAME to the A name. This allows me to change the knative-ingress record once (for example, to a new static IP or a different load balancer) and all application CNAMEs will be automatically adjusted.

DNS A record to static IP and CNAME records to the A record

I can update my Knative default domain:

knctl domain create --default --domain knative.starkandwayne.com

Once the DNS has propogated, I can now reach my application by my custom base domain:

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

Summary

In this article we deployed a new Kubernetes cluster on GKE, installed Knative with default load balancer support, associated a static IP with Knative's ingress gateway, and set up our own DNS to reference the static IP.

This gives us the magical:

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

In the next article we will craft beautiful domains, such as hello.starkandwayne.com, to replace the programmatically generated (read: ugly) domain hello.helloworld.knative.starkandwayne.com above.