Knative, smoothly, on K3S

I am starting today a new series of articles parallel to the Kubernetes one, dedicated to Knative which I discovered a short time ago thanks to SΓ©bastien Blanc (opens new window) (RedHat) which made last month several exciting presentations (opens new window)

# πŸ‡¬πŸ‡§ Knative, the tool that makes Kubernetes lovable

Knative in one sentence: it is an abstraction thought to make Kubernetes more "developer-friendly".

In other words, this tool will allow you to simplify your development workflow ("I don't care how it works, I just want to deploy my applications").

Knative is composed of two main elements:

  • Knative Serving for deploying applications (what interests us today)
  • Knative Eventing for event management (we will come back to this in future articles)

In the first articles in this series, I would deal with Knative Serving.

# Installation

As for the previous articles, to understand how it works, I like to install everything on my machine (πŸ‘‹ little important reminder, I wrote everything with a "developer vision"), so once again we will run all this locally and on K3S using Multipass.

The documentation (opens new window) of Knative is pretty well done, so I'm pretty much just following it and applying it in a Multipass context.

# K3S cluster creation

Knative provides its own network layer, so we need to install K3S without Traefik

  • Create a 2k-cluster directory
  • Create a config directory inside the 2k-cluster directory (we'll store in it the configuration file of our cluster: k3s.yaml)
  • Next, inside 2k-cluster, type the following commands:

# mount the config directory
multipass mount config ${vm_name}:config
# install k3s
multipass --verbose exec ${vm_name} -- sudo -- bash <<EOF
curl -sfL | sh -s - --disable traefik

# get the IP address of the Multipass VM
IP=$(multipass info ${vm_name} | grep IPv4 | awk '{print $2}')

# get the cluster configuration file
multipass exec ${vm_name} sudo cat /etc/rancher/k3s/k3s.yaml > config/k3s.yaml
sed -i '' "s/$IP/" config/k3s.yaml

βœ‹ I'm under OSX, if you're using Linux, replace sed -i '' "s/$IP/" config/k3s.yaml by sed -i "s/$IP/" config/k3s.yaml. Or use coreutils (opens new window) for OSX

Before resume, be sure that all the system pods of K3S are "loaded" by using this command:

export KUBECONFIG=$PWD/config/k3s.yaml
kubectl get pods --namespace kube-system

You should get something like this:

NAME                                      READY   STATUS    RESTARTS   AGE
local-path-provisioner-58fb86bdfd-zhc2l   1/1     Running   0          10m
metrics-server-6d684c7b5-4dw68            1/1     Running   0          10m
coredns-6c6bb68b64-2d6zk                  1/1     Running   0          10m

So, now, we have a running K3S cluster, and we can deploy Knative on it.

# Deploy Knative Serving component

πŸ‘‹ Knative Serving needs an Ingress Gateway to route request to the services. Usually, Istio is used, but some other are possible. We'll use Kourier (opens new window) which is a lightweight alternative, and it's a good thing when running on K3S.

multipass --verbose exec ${vm_name} -- sudo -- bash <<EOF
# Install Knative Serving
kubectl apply --filename "${knative_version}/serving-crds.yaml"
kubectl apply --filename "${knative_version}/serving-core.yaml"

# Install and configure Kourier
kubectl apply --filename${knative_version}/third_party/kourier-latest/kourier.yaml
kubectl patch configmap/config-network --namespace knative-serving --type merge --patch '{"data":{"ingress.class":""}}'

Check the status of the pods (be sure that all have a Running status):

kubectl get pods --namespace knative-serving
kubectl get pods --namespace kourier-system

Then you can veriffy that Kourier has obtained an external IP for the LoadBalancer:

kubectl --namespace kourier-system get service kourier

If all is allright, you should get this:

NAME      TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
kourier   LoadBalancer   80:32717/TCP,443:32167/TCP   62m

The external IP matches your cluster IP

Now, it's time to apply a last setting. To manage the DNS part, we'll use the "easy": the (opens new window) service:

multipass --verbose exec ${vm_name} -- sudo -- bash <<EOF
# Configure DNS
kubectl apply --filename "${knative_version}/serving-default-domain.yaml"

Do a last test:

kubectl get pods --namespace knative-serving

The default-domain pod should have a Completed status:

NAME                          READY   STATUS      RESTARTS   AGE
webhook-577576647-z5254       1/1     Running     0          87m
activator-65fc4d666-6c4nf     1/1     Running     0          87m
autoscaler-74b4bb97bd-x5m2d   1/1     Running     0          87m
controller-6b6978c965-jc79d   1/1     Running     0          87m
default-domain-rxfhj          0/1     Completed   0          21s

There it's done. Now we have a running K3S cluster with Knative. It's time to have fun.

πŸ–οΈ I created a project to easily reproduce the installation process: (opens new window)

# First deployment to Knative

We are going to create a NodeJS. Create a new directory named amazing-web-app and add the following files to this directory:

  • index.js
  • package.json
  • Dockerfile
  • service.yaml

# index.js

const http = require('http')
const port = 8080

let index_page = `
<!doctype html>
    <meta charset="utf-8">
        body {
          background-color: ${process.env.BACKGROUND_COLOR};
        h1 {
          font-family: "Source Sans Pro"; font-weight: 300; font-size: 100px;
          display: block;
          color: #${process.env.COLOR};

const requestHandler = (request, response) => {
  response.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'})

const server = http.createServer(requestHandler)

server.listen(port, (err) => {
  if (err) {
    return console.log('😑 something bad happened', err)
  console.log(`🌍 server is listening on ${port}`)

# package.json

  "main": "index.js",
  "scripts": {
    "start": "node index.js"

# Dockerfile

FROM node:12-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . ./
CMD [ "npm", "start" ]

# service.yaml

kind: Service
  name: k-amazing-web-app
  namespace: default
      - image:
        - name: MESSAGE
          value: "I πŸ’š Knative"
        - name: BACKGROUND_COLOR
          value: "lemonchiffon"
        - name: COLOR
          value: "darkslateblue"

# Deployment

You have probably noticed that the yaml manifest is a lot simpler than usual. It's one of the good things you gain with Knative (and you'll see that we can do even better).

To deploy our webapp, you first need build the Docker image of it:

echo ${docker_pwd} | docker login --username ${docker_user} --password-stdin

docker build -t ${docker_user}/k-amazing-web-app .
docker push ${docker_user}/k-amazing-web-app

Once this is done, you can simply deploy:

kubectl apply --filename service.yaml

πŸŽ‰ That's all!

Type the below command:

kubectl get ksvc

You should get:

NAME                URL

we've deployed on the default namespace

Use this URL with your browser:

alt kantaive01

# A more lovable deployment

Create a new directory sparkling-web-app and copy the following files of the former example:

  • index.js
  • package.json
  • Dockerfile

πŸ–οΈ you don't need the service.yaml file anymore πŸ€”

To do the job without the yaml file, we are going to use kn, the CLI of Knative.

You can find the Knative CLI project here: (opens new window) and the installation process here: (opens new window).

# Deployment (without the yaml file)

Of course, you need to build, before, the Docker image of the webapp:

echo ${docker_pwd} | docker login --username ${docker_user} --password-stdin

docker build -t ${docker_user}/sparkling-web-app .
docker push ${docker_user}/sparkling-web-app

And now, you can deploy (in a new namespace this time). We use the kn service create command:

kubectl create namespace k-apps

kn service create sparkling-web-app \
--namespace k-apps \
--env MESSAGE="I πŸ’š Knative" \
--env BACKGROUND_COLOR="mediumslateblue" \
--env COLOR="lightyellow" \

You will obtain the following output:

Creating service 'sparkling-web-app' in namespace 'k-apps':

  0.294s The Route is still working to reflect the latest desired specification.
  0.461s Configuration "sparkling-web-app" is waiting for a Revision to become ready.
  6.200s ...
  6.270s Ingress has not yet been reconciled.
  6.458s Ready to serve.

Service 'sparkling-web-app' created to latest revision 'sparkling-web-app-cwxky-1' is available at URL:

Now, use the URL in your browser:

alt kantaive02

πŸŽ‰ it' simple! πŸ˜‰

# Deployment again (again without the yaml file)

To deploy again any updates, we'll use the kn service update command:

kn service update sparkling-web-app \
--namespace k-apps \
--env MESSAGE="I πŸ’šπŸ–οΈ Knative" \
--env BACKGROUND_COLOR="crimson" \
--env COLOR="lightyellow" \

this time, we didn't change the source code but the value of the environment variables.

You will obtain the following output:

Updating Service 'sparkling-web-app' in namespace 'k-apps':

  5.301s Traffic is not yet migrated to the latest revision.
  5.382s Ingress has not yet been reconciled.
  5.721s Ready to serve.

Service 'sparkling-web-app' updated to latest revision 'sparkling-web-app-ltxwx-2' i
s available at URL:

Use the URL in your browser:

alt kantaive03

πŸ˜ƒ As you can see, the use of Knative extremely simplify the use of Kubernetes. In the next blog posts, will see more features of this tool.

You can find the source code of the 2 examples here:

Last Articles