Knative – was ist das eigentlich?

21.12.2021

Seit Anfang November ist Knative nun offiziell in der Version 1.0 erhältlich. Welche Features es mit sich bringt und wieso Du es vielleicht auch verwenden solltest, zeige ich Dir in diesem Blog-Beitrag.

Knative – grob zusammengefasst

Knative ist eine quelloffene Erweiterung des klassischen Kubernetes, die nativ auf diesem aufsetzt und ein serverless Kubernetes ermöglicht. Es soll dazu dienen, dass sich Entwickler ausschließlich um die Implementierung ihrer Services kümmern müssen und lästige Nebenaktivitäten übernommen werden. Da Knative auf Kubernetes basiert, ist dieses ebenfalls unabhängig von Sprache und Framework.

 

Aktuell erweitert Knative Kubernetes um drei relevante Komponente, jedoch ist für die Zukunft geplant, noch weitere Komponente bieten zu können. Die Komponenten sind Build, Serve und Event.1

Vergleich zu reinem Kubernetes

Das reine Kubernetes bietet für viele Funktionen zwar Unterstützung, indem es die benötigten Ports liefert, jedoch ist hier der Entwickler stets gezwungen auf Third-Party-Software zurückzugreifen, oder gar eigene Software zu schreiben. An dieser Stelle setzt Knative an und bietet mit den Komponenten Serve und Event zwei wichtige Lösungen.

Serve

In reinem Kubernetes ist es den Containern bzw. Microservices nur möglich miteinander zu kommunizieren, wenn auf externe Software wie z.B. Istio zugegriffen wird. Knative löst dieses Problem mit der Komponente Serve. Diese beinhaltet Features wie Intelligent Routing, Autoscaling und auch Snapshots. Ermöglicht wird durch Knative sogar das sogenannte Scale-to-Zero. Anders als bei Kubernetes, welches einen Pod löscht sobald dieser auf null Replicas reduziert wird, bleibt der Pod bestehen, verbraucht aber keine Systemressourcen. Dies spart – da es sich bei Knative, um eine serverlose Umgebung handelt – auch einiges an Kosten, denn bei vielen Cloudanbietern zahlt man nach dem System pay-by-use. Werden keine Ressourcen verbraucht, entstehen keine Kosten.2

Event

Die Komponente Event liefert einen build-in Eventlistener und es kann, anders als bei reinem Kubernetes, auf zusätzliche Software verzichtet werden. Dieser Bestandteil ermöglicht es beispielsweise Trigger zu definieren.3

Build

Um mit Kubernetes arbeiten zu können, muss Code geschrieben, ein Container-Image erstellt werden und anschließend in einem Registry zur Verfügung gestellt werden, sodass Kubernetes nun über ein YAML-File darauf zugreifen kann. An dieser Stelle greift Knative ein. Knative ermöglicht all diese Schritte direkt aus dem Kubernetes-Cluster heraus auszuführen. Diese Komponente trägt den Namen Build, ist inzwischen allerdings von Knative abgekoppelt und Teil eines eigenen Projekts namens Tekton.4

Hello World – ein einfaches Beispiel

Um Dir zu zeigen, wie so ein Vorgang abläuft, habe ich den Prozess vom Installieren bis zum Ausführen eines Services durchgemacht und diesen dokumentiert. Dazu habe ich bereits einige Vorbereitungen getroffen und cURL, Docker, Minikube, kubectl, sowie Java und Maven installiert.

Vorbereitung, Setup und Installation

Zuerst starten wir unseren Minikube:

minikube start

Haben wir unseren Minikube gestartet installieren wir Knative via YAML-Files. Da es sich bei Knative um ein Open Source Projekt handelt, finden wir diese auf GitHub:

kubectl apply -f https://github.com/knative/serving/releases/download/v0.26.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.26.0/serving-core.yaml

Danach installieren wir ein Networking Layer, um später mit unserem Service über das Internet zu kommunizieren. Hier wählen wir den von Knative empfohlenen Kourier:

#installieren:
kubectl apply -f https://github.com/knative/net-kourier/releases/download/v0.26.0/kourier.yaml

#als Standard festlegen:
kubectl patch configmap/config-network \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"ingress.class":"kourier.ingress.networking.knative.dev"}}'

#Test:
kubectl --namespace kourier-system get service kourier

Nachdem wir Knative und ein Networking Layer installiert haben, prüfen wir ob die Installation erfolgreich war:

kubectl get pods -n knative-serving

Wenn alles geklappt hat, sollte der Output ähnlich wie dieser aussehen:

Hier werden die Knative Pods unserer VM angezeigt. Im Idealfall sollte hier unter anderem der Kourier-Controller-Pod laufen.

Dann konfigurieren wir unsere DNS um zukünftig cURL-Commands auch ohne Host-Header nutzen zu können. Um dies zu tun, müssen wir – da wir Minikube lokal installiert haben – zuerst in einem neuen Konsolen-Fenster einen Tunnel erstellen um auf den Loadbalancer zugreifen zu können:

minikube tunnel

Läuft unser Tunnel, können wir unsere DNS-Einstellungen konfigurieren. Hierzu nutzen wir den von Knative zur Verfügung gestellten Kubernetes-Job MagicDNS:

kubectl apply -f https://github.com/knative/serving/releases/download/v0.26.0/serving-default-domain.yaml

Erstellen unserer App

Nachdem alle Vorbereitungen abgeschlossen sind, können wir endlich anfangen. Zuerst erstellen wir ein neues Web-Projekt. Um Zeit zu sparen und das ganze etwas zu vereinfachen, nutzen wir ein Template:

curl https://start.spring.io/starter.zip \
   -d dependencies=web \
   -d name=helloworld \
   -d artifactId=helloworld \
   -o helloworld.zip
unzip helloworld.zip

Nun öffnen wir die soeben erstellte Java-Datei und legen den Inhalt unserer Applikation fest, in meinem Falle ein einfaches Hello-World:

package com.example.helloworld;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class HelloworldApplication {

@Value("${TARGET:doubleSlash}")
String target;

@RestController
class HelloworldController {
    @GetMapping("/")
    String hello() {
        return "Hello " + target + "!";
    }
}
public static void main(String[] args) {
    SpringApplication.run(HelloworldApplication.class, args);
    }
}

Anschließend testen wir unsere Hello-World-App erst einmal lokal:

./mvnw package && java -jar target/helloworld-0.0.1-SNAPSHOT.jar

#der Output unserer App sollte nun unter http://localhost:8080/ sichtbar sein!
#hierzu einfach die localhost-Adresse im Browser öffnen, oder mit curl pingen.
#in unserem Fall steht dort nun "Hello doubleSlash!"

Macht unsere App, das was wir wollen, können wir ein Container-Image erzeugen. Hierzu erstellen wir als Erstes ein Dockerfile. Wichtig hierbei ist, dass unser File kein Suffix – wie z.B. ‚.txt‘ – hat:

# Use official maven/Java 11 image to create build artifact: https://hub.docker.com/_/maven
FROM maven:3.8-jdk-11 as builder

# Copy local code to container image.
WORKDIR /app
COPY pom.xml .
COPY src ./src

# Build release artifact.
RUN mvn package -DskipTests

# Use Official OpenJDK image for lean production stage of multi-stage build.
# https://hub.docker.com/_/openjdk
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM openjdk:11-jre

# Copy jar to the production image from builder stage.
COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar

# Run web service on container startup.
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"

Danach bauen wir über Docker einen Container und laden diesen dann in unser Docker-Registry. Hierbei entspricht {username} Deinem Docker Hub Username:

# Build
docker build -t {username}/helloworld-java-spring .
# Push
docker push {username}/helloworld-java-spring

Nun deployen wir unsere App in ein Cluster. Hierzu erstellen wir zuerst ein YAML-File namens ’service.yaml‘ mit folgendem Inhalt. Auch hier entspricht {username} Deinem Username:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-java-spring
  namespace: default
spec:
  template:
    spec:
      containers:
        - image: docker.io/{username}/helloworld-java-spring
          env:
            - name: TARGET
              value: "World"

Dann rufen wir das YAML-File auf und deployen unsere App:

kubectl apply -f service.yaml

Ob alles funktioneirt hat können wir prüfen, indem wir unseren Service aufrufen. Dazu ermitteln wir zunächst seine URL:

kubectl get ksvc helloworld-java-spring  --output=custom-columns=NAME:.metadata.name,URL:.status.url

Anschließend pingen wir unseren Service – wobei {URL} der URL entspricht, die wir im vorherigen Schritt zurück bekommen haben – oder öffnen die URL einfach direkt im Browser:

curl {URL}

#Unseren Service via 'curl' zu pingen kann einige Sekunden dauern. Schneller ist hier definitiv den Link im Browser zu öffnen!
#Im Vergleich zu unserem lokalen Versuch steht hier nun nicht mehr "Hello doubleSlash!"
#als Antwort, sondern "Hello World!" - genau so wie wir es in unserem Code festgelegt haben!

Scale-to-Zero

Wenn wir unseren Pod nun beobachten, sehen wir genauere Details zu dessen Status und wie das Autoscale-Feature der Komponente Serve abläuft:

kubectl get pods -l serving.knative.dev/service=helloworld-java-spring -w

# Zuerst sehen wir, dass unser Pod läuft und Traffic besteht,
# da wir diesen im vorherigen Schritt angepingt haben:

NAME                                                       READY   STATUS    RESTARTS   AGE
helloworld-java-spring-00003-deployment-776b9c79bc-2rbwt   2/2     Running   0          32s

# Wenn wir nun den Traffic abbrechen, also unseren Service nicht mehr pingen,
# sehen wir, dass unsere Pods terminiert werden. Es sind 0 Pods aktiv,
# aber dennoch werden die Pods nicht vollständig gelöscht.
# Dieses Feature nennt sich Scale-to-Zero:

helloworld-java-spring-00003-deployment-776b9c79bc-2rbwt   2/2     Terminating   0          80s
helloworld-java-spring-00003-deployment-776b9c79bc-2rbwt   0/2     Terminating   0          112s

# Entsteht nun wieder Traffic, da wir zum Beispiel unseren Service pingen,
# baut Knative erneut einen Container für uns:

helloworld-java-spring-00003-deployment-776b9c79bc-qj7bv   0/2     ContainerCreating   0          0s
helloworld-java-spring-00003-deployment-776b9c79bc-qj7bv   1/2     Running             0          3s
helloworld-java-spring-00003-deployment-776b9c79bc-qj7bv   2/2     Running             0          18s

Benötigen wir unseren Service nicht mehr, lässt sich dieser einfach löschen:

kubectl delete -f service.yaml

Genauere Details zu den von mir ausgeführten Schritten und weitere Features wie Traffic Splitting und Eventing findest Du hier.

 

Fazit

Solltest Du bereits Kubernetes nutzen, bietet Knative nur Vorteile für Dich. Es setzt nativ auf Kubernetes auf, benötigt somit keinerlei Umstellungen oder ähnliches und liefert zusätzlich nützliche Features. Darüber hinaus bietet Knative ein eigenes Command Line Interface, welches die benötigten Schritte beim Arbeiten mit Knative noch einfacher macht. So kannst Du beispielsweise auf das Erstellen von YAML-Files für das Deployment verzichten. Zudem werden, da es sich um ein brandneues OpenSource-Projekt handelt, stetig weitere Features entwickelt und hinzugefügt.


Quellen:

1 Dev-Insider: https://www.dev-insider.de/was-ist-knative-a-917621/

2 Red Hat: https://www.redhat.com/en/topics/microservices/what-is-knative

3 IBM Technology: https://www.ibm.com/cloud/learn/knative

4 brainDOSE: https://braindose.blog/2020/08/13/differences-between-function-serverless-knative/

5 Knative: https://knative.dev/docs/serving/samples/hello-world/helloworld-java-spring/

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*