Teil 2: Viele Wege führen in die Cloud

Knative Tutorial: Deep Dive in Knative Build

Dr. Nic Williams

© Shutterstock / Avesun (modifiziert)

Mit Knative können Serverless-Plattformen erstellt werden, die auf Kubernetes basieren. Doch wie genau funktioniert das eigentlich alles? Wie skaliert meine Anwendung auf 0? Wie läuft das Deployment ab? Diese und weitere Fragen beantwortet Dr. Nic Williams, CEO von Stark & Wayne, in diesem umfangreichen Tutorial zu Knative.

Der vorliegende Artikel ist Teil des Knative Tutorials von Dr. Nic Williams, CEO von Stark & Wayne, und ist im englischsprachigen Original auf dem Blog des Unternehmens erschienen.

Knative hat drei High-Level-Subsysteme:

  • Serving für die Koordination der automatischen Skalierung eines Service (inkl. seiner Pods) sowie dessen Routing.
  • Build, das eine Toolchain für die Konvertierung maßgeschneiderten Anwendungscodes zu einem (temporären) Container Image.
  • Eventing für das Erstellen lose gekoppelter Anwendungsservices durch eine Abstrahierung öffentlicher bzw. untergeordneter Events.

Im ersten Teil unseres Knative Tutorials haben wir mit dem Deployment einer Anwendung begonnen, die bereits innerhalb eines Container Images erstellt wurde. In diesem Teil wollen wir uns nun ansehen, wie wir Knative Build nutzen können, um unsere Anwendung während des Deployment-Prozesses in ein Container Image umzuwandeln. Hierfür werden wir sowohl ein Dockerfile und ein Buildpack von Cloud Foundry verwenden. Außerdem führen wir das Deployment einmal vom lokalen Dateisystem und einmal von einem Git Repository aus durch.

Aller Anfang ist einfach

Bislang ging es noch relativ simpel zu. Knative in irgendeine Kubernetes-Umgebung zu installieren, ein bereits existierendes Container Image auszuführen und via curl damit zu interagieren war ein Kinderspiel. Der vorherige Artikel lässt sich wie folgt zusammenfassen:

knctl install [--node-ports] [--exclude-monitoring]
knctl deploy --service  --image 
knctl curl --service 

Die beiden letzteren Schritte, also das Deployment eines Images in einen laufenden Kubernetes Pod, der über jedes Deployment hinweg versioniert wurde, und das Interagieren mit dem Image via HTTP Routing, war eine Demonstration von Knative Serving.

Knative unterstützt aber auch ein flexibles Subsystem für das Erstellen von Container Images, die dann als Kubernetes Pod ausgeführt werden. Knative Build ist, wie bereits erwähnt, sehr flexibel. Die Use Cases umfassen unter anderem

  • den Upload eines lokalen Verzeichnisses und Builds aus einem expliziten Dockerfile heraus,
  • den Upload eines lokalen Verzeichnisses und Builds unter Zuhilfenahme eines Buildpacks
  • und die oben erwähnten Build-Sequenzen von oben, wenn die Quelle nicht-lokales Git Repository ist.
Serverless Architecture Whitepaper

LIVING IN A POST-CONTAINER WORLD
Free: Brand new Serverless Architecture Whitepaper

Stay on top of the latest trends with the help of our Serverless Architecture Whitepaper. Get deep insights on serverless platforms, cloud-native architecture, cloud services, the Kubernetes ecosystem (Knative, Istio etc.) and much more!

Namespaces

Im ersten Artikel habe ich in jeden knctl-Befehl explizit --namespace helloworld integriert. Diese Ausdrücklichkeit finde ich sehr ansprechend. Andere sehen das vielleicht anders und möchten einen individuellen Namespace erstellen, um die Befehle kürzer zu gestalten. Es gibt hierfür die Möglichkeit, knctl zu modifizieren, sodass der Befehl einen „aktuellen Namespace“ hat:

kubectl create ns my-simple-app
export KNCTL_NAMESPACE=my-simple-app

Alle knctl-Befehle werden diesen Namespace annehmen:

$ knctl service list
Services in namespace 'my-simple-app'

Name  Domain  Annotations  Age

0 services

Für unsere Low-Level-kubectl-Befehle können wir einfach $KNCTL_NAMESPACE wiederverwenden:

kubectl get pods -n $KNCTL_NAMESPACE

Upload eines lokalen Verzeichnisses mit Dockerfile

Ein Nebenprodukt der Build-Sequenzen aller Beispiele mit Knative Build sind Container Images (ein geheimes Codewort für „Docker Image“). Diese Images müssen irgendwo gehostet werden, wofür etwa der Docker Hub, die GCP Container Registry, die Azure Container Registry oder eine lokale bzw. Do-it-Yourself Registry wie Harbor.

Knative muss nun zunächst mit dem Ort und den Secrets der Container Registry konfiguriert werden, wozu wir knctl basic-auth-secret create innerhalb aller zugehörigen Kubernetes Namespaces verwenden. Für den Docker Hub verwenden wir die Flag --docker-hub:

knctl basic-auth-secret create -s registry --docker-hub -u  -p 

Für die GCP Container Registry verwenden wir die Flag --gcr:

knctl basic-auth-secret create -s registry --gcr -u  -p 

Alle restlichen Container Registrys, die keine praktische Flag haben, werden die Flags --type und --url verwendet:

knctl basic-auth-secret create -s registry --type docker --url https://registry.domain.com/ -u  -p 

Als nächstes müssen wir die Container Registry Secrets in einen Kubernetes-Service-Account mappen. Dieser Account wird die oben genannten Informationen den Pods zur Verfügung stellen, die von Knative Build genutzt werden.

knctl service-account create --service-account build -s registry

Der folgende Code zeigt den Service Account bzw. die schließlich darin gespeicherten Informationen:

$ kubectl get serviceaccount -n $KNCTL_NAMESPACE
NAME      SECRETS   AGE
build     2         37s
default   1         3h

Damit sind wir an einem Punkt angelangt, an dem wir das Knative-Build-Subsystem zum Erstellen neuer Container Images autorisieren können. Lasst uns als Beispiel eine Go-Anwendung klonen und sie aus ihrem lokalen Verzeichnis auf einem Docker Hub Image-Namen deployen:

git clone https://github.com/cppforlife/simple-app
cd simple-app

DOCKER_IMAGE=index.docker.io//knative-simple-app

knctl deploy \
    --service simple-app \
    --directory=$PWD \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG="Built from local directory using Dockerfile"

 

Ein Gedanke: Das explizit mit einem Namen versehene Container Image ist ein temporäres Nebenprodukt der Build-Serve-Sequenz und der Name selbst ist mir ziemlich egal. Dennoch muss ich einen expliziten Namen vergeben. 🤔

 

Die Ausgabe, die der Befehl knctl deploy ausspuckt, wird in etwa so aussehen:

Name  simple-app

Waiting for new revision to be created...

Tagging new revision 'simple-app-00001' as 'latest'

Tagging new revision 'simple-app-00001' as 'previous'

[2018-10-15T13:18:31+10:00] Uploading source code...

[2018-10-15T13:19:59+10:00] Finished uploading source code...

Watching build logs...

build-step-build-and-push | INFO[0000] Downloading base image golang:1.10.1
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.547607       1 metadata.go:142] while reading 'google-dockercfg' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.550268       1 metadata.go:159] while reading 'google-dockercfg-url' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | INFO[0001] Executing 0 build triggers
build-step-build-and-push | INFO[0001] Extracting layer 0
build-step-build-and-push | INFO[0003] Extracting layer 1
build-step-build-and-push | INFO[0004] Extracting layer 2
build-step-build-and-push | INFO[0004] Extracting layer 3
build-step-build-and-push | INFO[0007] Extracting layer 4
build-step-build-and-push | INFO[0010] Extracting layer 5
build-step-build-and-push | INFO[0015] Extracting layer 6
build-step-build-and-push | INFO[0015] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0027] WORKDIR /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] cmd: workdir
build-step-build-and-push | INFO[0027] Changed working directory to /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] Creating directory /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] COPY . .
build-step-build-and-push | INFO[0027] RUN CGO_ENABLED=0 GOOS=linux go build -v -o app
build-step-build-and-push | INFO[0027] cmd: /bin/sh
build-step-build-and-push | INFO[0027] args: [-c CGO_ENABLED=0 GOOS=linux go build -v -o app]
build-step-build-and-push | net
build-step-build-and-push | vendor/golang_org/x/net/lex/httplex
build-step-build-and-push | vendor/golang_org/x/net/proxy
build-step-build-and-push | net/textproto
build-step-build-and-push | crypto/x509
build-step-build-and-push | crypto/tls
build-step-build-and-push | net/http/httptrace
build-step-build-and-push | net/http
build-step-build-and-push | github.com/mchmarny/simple-app
build-step-build-and-push | INFO[0030] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0034] Storing source image from stage 0 at path /kaniko/stages/0
build-step-build-and-push | INFO[0038] trying to extract to /kaniko/0
build-step-build-and-push | INFO[0038] Extracting layer 0
build-step-build-and-push | INFO[0040] Extracting layer 1
build-step-build-and-push | INFO[0041] Extracting layer 2
build-step-build-and-push | INFO[0041] Extracting layer 3
build-step-build-and-push | INFO[0043] Extracting layer 4
build-step-build-and-push | INFO[0046] Extracting layer 5
build-step-build-and-push | INFO[0051] Extracting layer 6
build-step-build-and-push | INFO[0051] Extracting layer 7
build-step-build-and-push | INFO[0051] Deleting filesystem...
build-step-build-and-push | INFO[0053] No base image, nothing to extract
build-step-build-and-push | INFO[0053] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0062] COPY --from=0 /go/src/github.com/mchmarny/simple-app/app .
build-step-build-and-push | INFO[0063] Taking snapshot of files...
build-step-build-and-push | INFO[0063] EXPOSE 8080
build-step-build-and-push | INFO[0063] cmd: EXPOSE
build-step-build-and-push | INFO[0063] Adding exposed port: 8080/tcp
build-step-build-and-push | INFO[0063] ENTRYPOINT ["/app"]
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.751338       1 metadata.go:142] while reading 'google-dockercfg' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.753927       1 metadata.go:159] while reading 'google-dockercfg-url' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | 2018/10/15 03:21:06 pushed blob sha256:72a682eea3309941d5e8e6f993a07ae4d33a413b8b7fa2762f8e969310b5996a
build-step-build-and-push | 2018/10/15 03:21:07 pushed blob sha256:9c24aa788ba416c5e1e631d8af3e3115519ad7ca0f659ac10f40682524c6d9cd
build-step-build-and-push | 2018/10/15 03:21:07 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:b5823ead77d9544998b5bc844f049d1a7dfb0aefe7461b74b3e4f67fb5481fa1 size: 428
nop | Nothing to push

Succeeded

Knative Build: Debugging

Der Befehl knctl deploy zeigt aktuell weder interne Fehler noch Warnungen von Knative Build. Es kann einem also passieren, dass man den Text Waiting for new revision to be created... stundenlang ansehen kann, ohne dass etwas passiert.

Kail ist eine Möglichkeit für das Debugging, es streamt die Logs des Knative-Build-Subsystems:

kail -n knative-build

Der Nachteil ist, dass man danach endlose Logs nach Fehlern durchsuchen muss. Zum Beispiel nach so etwas: "msg":"Failed the resource specific validation{error 25 0 serviceaccounts \"build\" not found}"

Builds mit Buildpacks

Die Herangehensweisen von Cloud Foundry und Heroku, Container Images zu bauen, sind in meinen Augen sehr gut. Glücklicherweise werden sie von Knative Build durch die Verwendung eines individuellen Build Templates unterstützt. Zunächst müssen wir dafür das Build Template mit dem Namen buildpack im aktiven Namespace registrieren:

kubectl -n $KNCTL_NAMESPACE apply -f https://raw.githubusercontent.com/knative/build-templates/master/buildpack/buildpack.yaml

Um es dann auch nutzen zu können, muss die Flag --template buildpack hinzugefügt werden. Weitere Variablen für die Umgebung, die vom Build Template (oder in diesem Fall der Buildpack-Sequenz) genutzt werden, können via --template-env NAME=value weitergegeben werden. Als Beispiel sei hier das Cloud Foundry Go Buildpack genannt, das $GOPACKNAME benätigt (siehe auch den Eintrag in der entsprechenden Dokumentation):

knctl deploy \
    --service simple-app \
    --directory=$PWD \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG="Built from local directory using Buildpack template" \
    --template buildpack \
    --template-env GOPACKAGENAME=main

Die Ausgabe entspricht ziemlich genau der eines Cloud Foundry Buildpacks:

Name  simple-app

Waiting for new revision (after revision 'simple-app-00001') to be created...

Tagging new revision 'simple-app-00002' as 'latest'

Tagging older revision 'simple-app-00001' as 'previous'

[2018-10-15T13:40:41+10:00] Uploading source code...

[2018-10-15T13:42:08+10:00] Finished uploading source code...

Watching build logs...

build-step-build | -----> Go Buildpack version 1.8.26
build-step-build | -----> Installing godep 80
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/godep/godep-v80-linux-x64-cflinuxfs2-06cdb761.tgz]
build-step-build | -----> Installing glide 0.13.1
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/glide/glide-v0.13.1-linux-x64-cflinuxfs2-aab48c6b.tgz]
build-step-build | -----> Installing dep 0.5.0
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/dep/dep-v0.5.0-linux-x64-cflinuxfs2-52c14116.tgz]
build-step-build | -----> Installing go 1.8.7
build-step-build |        Download [https://buildpacks.cloudfoundry.org/dependencies/go/go1.8.7.linux-amd64-cflinuxfs2-fff10274.tar.gz]
build-step-build |        **WARNING** Installing package '.' (default)
build-step-build | -----> Running: go install -tags cloudfoundry -buildmode pie .
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:1124eb40dd68654b8ca8f5d9ec7e439988a4be752a58c8f4e06d60ab1589abdb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:6be38da025345ffb57d1ddfcdc5a2bc052be5b9491825f648b49913d51e41acb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:a5733e6358eec8957e81b1eb93d48ef94d649d65c69a6b1ac49f616a34a74ac1
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:21324a9f04e76c93078f3a782e3198d2dded46e4ec77958ddd64f701aecb69c0
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:efa2d34b82bc07588a1a8fd4526322257408109547ee089a792b3f51c383f8e6
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:d495696b33936c79216ec8178726b9fbe915fafbffdd0911a7fdabce4297d9a4
build-step-export | 2018/10/15 03:48:00 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:e5ef1d4d255b4bcbb38d4b43bb6302423c33e6eeabd0e20d5fda4e5ce4c46668 size: 1082
nop | Nothing to push

Die Anwendung wurde, wie wir nun sehen können, redeployt:

$ knctl curl -s simple-app
Built from local directory using Buildpack template

Privates Git Secret

In den bisher gezeigten Beispielen haben wir den Quellcode unserer Anwendung vom lokalen Rechner hochgeladen und dann ein temporäres Docker Image erstellt (entweder aus einem Dockerfile oder einem Cloud Foundry Buildpack), bevor wir die Anwendung ausgeführt haben. Knative kann, wie bereits erwähnt, den Quellcode auch aus einem Git Repository beziehen. Aus technischer Perspektive unterstützt Knative Build aktuell übrigens ausschließlich das Fetchen von Quelltext aus einem Git Repository. Das Feature, mit dem sich ein lokales Verzeichnis hochladen lässt, gibt es nur im knctl CLI von Dmitriy.

Knative Build wird für das Fetchen des Git Repositorys fit gemacht, indem die Flag --directory=$PWD durch die Flags --git-url und --git-revision ersetzt wird. Ist das Repository privat, müssen die SSH-Log-in-Daten im Service-Account (den wir im Beispiel oben build genannt haben) eingetragen werden. Ein kleines Helferlein beim Erstellen des Secrets kubernetes.io/ssh-auth ist der Befehl knctl ssh-auth-secret create.

$ knctl ssh-auth-secret create --secret git --github --private-key "$(cat ~/.ssh/id_rsa)"
Name  git
Type  kubernetes.io/ssh-auth

$ kubectl get secrets -n $KNCTL_NAMESPACE
NAME                  TYPE                                  DATA   AGE
...
git                   kubernetes.io/ssh-auth                1      5m
registry              kubernetes.io/basic-auth              2      3h

Als nächstes muss das git-Secret dem Service-Account build hinzugefügt werden. Zum Zeitpunkt, da dieses Tutorial entstand, hat knctl noch keinen Befehl wie etwa knctl serviceaccounts create unterstützt. Stattdessen müssen wir den alten Service-Account löschen und einen neuen erstellen:

kubectl delete serviceaccounts -n $KNCTL_NAMESPACE build
knctl service-account create --service-account build -s registry -s git

Deployment von Git

Um von Git aus zu deployen, entfernen wir --directory und fügen --git-url bzw. --git-revision hinzu:

knctl deploy \
    --service simple-app \
    --git-url git@github.com:cppforlife/simple-app.git \
    --git-revision master \
    --service-account build \
    --image ${DOCKER_IMAGE:?required} \
    --env SIMPLE_MSG="Built from Git repo using Buildpack template" \
    --template buildpack \
    --template-env GOPACKAGENAME=main

Fazit

Der Befehl knctl deploy ist eine nette Ergänzung für Knative, um neue Container Images vor dem Deployment zu erstellen. Er erlaubt das Building aus lokalen Quellverzeichnissen oder direkt aus einem Git Repository, Dockerfile oder Cloud Foundry Buildpacks heraus. Zudem gibt es die Unterstützung verschiedener temporärer Docker Registrys.

Geschrieben von
Dr. Nic Williams
Dr. Nic Williams
CEO of Stark & Wayne. User and evangelist of Cloud Foundry, Kubernetes, Concourse CI, and BOSH. Author of books Concourse Tutorial and Ultimate Guide to BOSH. Awarded Cloud Foundry Champion 2018. Twitter: @drnic
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: