Knative – was ist das eigentlich?
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:
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.
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/