Kitabı oku: «K8s Applications mit MicroK8S auf Raspberry PI»

Yazı tipi:

K8s Applications

mit MicroK8S

auf Raspberry PI

Beispiele für das Entwickeln und Betreiben von Anwendungen auf einem MicroK8s Kubernetes Cluster auf Raspberry PI Basis.

Alfred Sabitzer

https://www.linkedin.com/in/alfred-sabitzer-805312183/

https://www.xing.com/profile/Alfred_Sabitzer/cv

Vorwort

Die Idee eine „Cloud“ zu bauen entstand während der Corona-Zeit. Anstatt „Fernsehen“ könnte man doch was Sinnvolles ausprobieren. Außerdem ist der vorhandene Raspberry 4 bereits überlastet, und kann schwerlich neue Dienste anbieten. Da wäre doch ein System, das skalierbar ist, beliebig viele Services anbieten kann usw. doch genau das Richtige. Außerdem sind Microservices leichter auf neue Hardware portierbar! Ich erinnere mich noch genau, wie aufwändig der Umstieg vom Raspberry 2 auf den Raspberry 3 und dann auf den Raspberry 4 war. Ich musste jedes mal aufs Neue nachdenken, wie den die Installation genau geht, habe viel Zeit investiert und erreichte am Ende bestenfalls, dass ich nachher dieselben Dienste nutzen konnte wie vorher:)

Darum: eine Cloud muss her.

Generell: Der Bau einer Cloud ist ein komplexes Unternehmen (am einfachsten ist es, wenn man sich in einer bereits existenten Cloud einmietet – da gibt es genug Anbieter am Markt).

Dieses Buch beschreibt die einzelnen Schritte, die ich durchgeführt habe um mir meine eigene Cloud zu bauen. Hier werden aufbauend auf dem Hardwareaufbau im Buch "Bau einer K8s bare-metal-cloud mit RaspberryPI" ISBN 978-3-7534-9851-4 unterschiedliche Konzepte und Ideen beschrieben. Der Bau von Microservices, das Einrichten einer CI/CD-Strecke, kpt, kustomize, Servicemesh usw, wird anhand von Beispielen soweit möglich erklärt.

Hier finden sich viele Codefragmente, die eine Idee geben können, wie jemand das bei sich zu Hause selbst tun könnte. Diese Codeteile erheben keinen Anspruch auf Funktionalität oder dergleichen. Die Verwendung erfolgt auf eigenes Risiko.

Generell würde mich der Austausch mit anderen „Cloud“-Bauern interessieren. Ideen, Verbesserungen, Anregungen udglm. sind herzlich willkommen. Der Auto ist erreichbar unter microk8s.raspberry@slainte.at erreichbar.

Die in diesem Buch angeführten Programmteile gibt es online auf https://gitlab.com/Alfred-Sabitzer/k8sapp sowie auf https://gitlab.com/Alfred-Sabitzer/ldap .

Einleitung

Nach dem Aufsetzen des microK8s-Cluster auf RaspberryPI und prinzipiellem Einrichten der Infrastruktur (siehe „Bau einer K8s bare-metal-cloud mit RaspberryPI“), dient diese Beschreibung um die Schritte, Versuche und Fehlversuche zu dokumentieren, die ich beim Entwicklen, Einrichten und Betreiben von Anwendungen im k8s-Cluster unternommen habe. Es werden die folgenden Teile beschrieben

Infrastrukturservices

Repository

NTP

LDAP

Secretmanagement

Monitoringservices

Prometheus

Grafana

Alert-Agent

Storage und Backup

Longhorn

Externe Storage

Webservice – Stateful

Joomla

PostgresDB

Gitlab

Build-Pipelines

GitRunner

Buildautomatisierung

Eigene einfache Buildpipeline

kpt

kustomize

Servicemesh Linkerd

Alle Services werden als yaml-File beschrieben. Die Sourcen befinden sich auf https://gitlab.com/Alfred-Sabitzer/ldap sowie auf https://gitlab.com/Alfred-Sabitzer/k8sapp

Leider ist nicht alles am Raspberry mit arm-Architekture verfügbar. Somit kann ein K8S-Cluster auf Raspberry Basis nicht wirklich mit einem Cluster auf Intel-Basis (oder noch besser in einer wirklichen Cloud wie Google oder Amazon) verglichen werden. Es ist dennoch eine gute und kostengünstige Basis um die Prinzipien kennen zu lernen, und Hands-On einen Cluster vom Scratch aufzubauen.

Der hier beschriebene Cluster baut auf dem Buch


"Bau einer K8s bare-metal-cloud mit RaspberryPI" ISBN 978-3-7534-9851-4 auf.

Gutes Gelingen!

Architektur des Clusters

Inspiration:

https://de.wikipedia.org/wiki/Raspberry_Pi

https://www.picocluster.com/

Das System besteht aus einem RaspberryPI-Cluster mit microk8s. Die Raspberry-Version ist 4 mit 8GB RAM und einer 64GB SDRAM. Der Cluster besteht aus 5 Nodes. Jeder Node hat zusätzlich noch eine externe 110GB USB-SSD-Platte die als Clusterstorage konfiguriert ist.

Abbildung 1: k8s-Cluster


PC ist die Abkürzung für "pico-cluster". Es gibt pc1 bis pc5.

Die SSD-Disken werden mit Longhorn verwaltet. Es steht folgender Speicherplatz für PVC'S (permanent volume claims) zur Verfügung:

Abbildung 2: Longhorn Clusterstorage

Physikalisch sieht das aus wie folgt:

Abbildung 3: pico-Cluster von vorne

Man sieht hier den Cluster von vorne mit der Stromversorgung und dem Nway-Switch.

Abbildung 4: pico-Cluster von der Seite

Hier erkennt man den Cluster mit dem eingebauten Lüfter.

Abbildung 5: pico-Cluster von hinten

Hier kann man gut die Cluster-Storage erkennen. Aus thermischen Gründen befinden sich die SSD-Platten ausserhalb des Pico-Clusters.

Der Aufbau wird in „Bau einer K8s bare-metal-cloud mit RaspberryPI“ beschrieben.

Ein Übersicht gibt folgendes Bild:

Abbildung 6: MicroK8S Cluster - Gesamtsystem

Namespaces

Inspiration:

https://microk8s.io/docs/registry-built-in

Um den Cluster richtig betreiben zu können, sind auch Namespaces wichtig. Es sind (neben den technischen Namespaces, die mit k8s automatisch angelegt werden) noch die folgenden Namespaces vorhanden.

---

apiVersion: v1

kind: Namespace

metadata:

labels:

kubernetes.io/metadata.name: slainte

name: slainte

spec:

finalizers:

- kubernetes

---

apiVersion: v1

kind: Namespace

metadata:

labels:

kubernetes.io/metadata.name: default

name: default

spec:

finalizers:

- kubernetes

---

apiVersion: v1

kind: Namespace

metadata:

labels:

kubernetes.io/metadata.name: admin

name: admin

spec:

finalizers:

- kubernetes

---

Slainte ist quasi die Produktion (von aussen erreichbar). Default ist ein Test-URL der auch von aussen erreichbar ist. Die Services in admin haben keinen Ingress (sind von aussen nicht erreichbar), werden aber idR. als LoadBalancer-Services bereitgestellt.

IP-Adressen

Inspiration:

https://cloud.google.com/kubernetes-engine/docs/concepts/network-overview

Die im Cluster verfügbaren Bereiche sind:


Im Konkreten:

k8s.slainte.at 87.243.186.29

default.k8s.slainte.at 87.243.186.29

Aus dem Cluster selbst:

alfred@pc1:~/yaml$ k get svc --all-namespaces | grep -v '<none>'

NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

ingress ingress LoadBalancer 10.152.183.126 192.168.0.210 443:31287/TCP,80:31681/TCP 22h

kube-system kubernetes-dashboard LoadBalancer 10.152.183.150 192.168.0.211 443:31449/TCP 22h

longhorn-system longhorn-frontend LoadBalancer 10.152.183.199 192.168.0.212 80:31444/TCP 22h

container-registry registry LoadBalancer 10.152.183.207 192.168.0.213 5000:32000/TCP 22h

admin chrony-udp-svc LoadBalancer 10.152.183.77 192.168.0.240 123:32658/UDP 40m

admin pgadmin-svc LoadBalancer 10.152.183.201 192.168.0.241 5432:30954/TCP,80:30675/TCP,443:30676/TCP 19m

Source Repository

Inspiration:

https://microk8s.io/docs/registry-built-in

Eine Software-Registry wird benötigt, wenn man selbst entwickelte Anwendungen lokal speicher und verwenden möchte. Prinzipiell könnte man auch öffentliche Registrys nehmen, aber wir haben ja einen k8s-cluster.

alfred@pc1:~$ microk8s enable registry:size=40Gi

Addon storage is already enabled.

Enabling the private registry

Applying registry manifest

namespace/container-registry created

persistentvolumeclaim/registry-claim created

deployment.apps/registry created

service/registry created

configmap/local-registry-hosting configured

The registry is enabled

The size of the persistent volume is 40Gi

alfred@pc1:~$

Nun ist die Registry vorhanden. Das Cluster-Setup ist wie folgt:

alfred@pc1:~$ microk8s status

microk8s is running

high-availability: yes

datastore master nodes: 192.168.0.202:19001 192.168.0.203:19001 192.168.0.204:19001

datastore standby nodes: 192.168.0.201:19001 192.168.0.205:19001

addons:

enabled:

dashboard # The Kubernetes dashboard

dns # CoreDNS

ha-clusalfred@pc1:~$ microk8s status

microk8s is running

high-availability: yes

datastore master nodes: 192.168.0.202:19001 192.168.0.203:19001 192.168.0.204:19001

datastore standby nodes: 192.168.0.201:19001 192.168.0.205:19001

addons:

enabled:

dashboard # The Kubernetes dashboard

dns # CoreDNS

ha-cluster # Configure high availability on the current node

helm3 # Helm 3 - Kubernetes package manager

ingress # Ingress controller for external access

metallb # Loadbalancer for your Kubernetes cluster

metrics-server # K8s Metrics Server for API access to service metrics

prometheus # Prometheus operator for monitoring and logging

rbac # Role-Based Access Control for authorisation

registry # Private image registry exposed on localhost:5000

storage # Storage class; allocates storage from host directory

disabled:

helm # Helm 2 - the package manager for Kubernetes

host-access # Allow Pods connecting to Host services smoothly

linkerd # Linkerd is a service mesh for Kubernetes and other frameworks

portainer # Portainer UI for your Kubernetes cluster

traefik # traefik Ingress controller for external access

alfred@pc1:~$

Abbildung 7: Registry Clusterdisk

Das Volume wurde als ClusterDisk angelegt. Die Storageclass longhorn ist als default eingerichtet.

Das Skript zum Einrichten der Registry ist wie folgt:

#!/bin/bash

############################################################################################

# $Date: 2021-11-23 21:37:19 +0100 (Di, 23. Nov 2021) $

# $Revision: 1285 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K13_registry.sh $

# $Id: K13_registry.sh 1285 2021-11-23 20:37:19Z alfred $

#

# Schnell-Installation microk8s - Installation praktischer AddOns

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

# Voraussetzung: Sauber installierte Nodes, Verbundener Cluster

sname=$(basename "$0")

app="mikrok8s/install/${sname}"

pf=\$"Revision: "

sf=" "\$

fr="\$Revision: 1285 $"

revision=${fr#*"$pf"}

revision=${revision%"$sf"*}

xd=(`date '+%Y-%m-%d'`)

wd="${HOME}/copy/${app}/${xd}/r${revision}"

id="/opt/cluster/${app}/${xd}/r${revision}"

rm -f -R ${wd}

mkdir -p ${wd}

#

# Zu diesem Zeitpunkt sollte es eine Default-Storage-Class geben, wo das abgelegt wird.

ansible pc1 -m shell -a 'microk8s enable registry:size=40Gi'

ansible pc -m shell -a 'microk8s status --wait-ready'

#

# Adaptieren der Services

#

cat <<EOF > ${wd}/do_registry.sh

#!/bin/bash

#

# \$Date: 2021-11-23 21:37:19 +0100 (Di, 23. Nov 2021) $

# \$Revision: 1285 $

# \$Author: alfred $

# \$HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K13_registry.sh $

# \$Id: K13_registry.sh 1285 2021-11-23 20:37:19Z alfred $

#

# Ändern des Services auf Loadbalancer

#

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

# registry - 192.168.0.213

microk8s kubectl -n container-registry get service registry -o yaml > ${id}/registry-svc.yaml

sed 's/NodePort/LoadBalancer/' ${id}/registry-svc.yaml > ${id}/new-registry-svc.yaml

microk8s kubectl apply -f ${id}/new-registry-svc.yaml --validate=false

EOF

#

chmod 755 ${wd}/do_registry.sh

ansible pc1 -m shell -a ${id}'/do_registry.sh ' > ${wd}'/do_registry.log'

#

cat <<EOF > ${wd}/do_nodes.sh

#!/bin/bash

#

# \$Date: 2021-11-23 21:37:19 +0100 (Di, 23. Nov 2021) $

# \$Revision: 1285 $

# \$Author: alfred $

# \$HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K13_registry.sh $

# \$Id: K13_registry.sh 1285 2021-11-23 20:37:19Z alfred $

#

# Eintragen der Nodes in die hosts-Datei

#

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

sudo sed --in-place '/docker.registry/d' /etc/hosts

microk8s kubectl -n container-registry get service registry -o yaml > ${id}/nodes-registry-svc.yaml

ip=\$(cat ${id}/nodes-registry-svc.yaml | grep -i " ip: " | awk '{print \$3 }')

text="\${ip} docker.registry"

sudo sed -i "$ a \${text}" /etc/hosts

#

EOF

#

chmod 755 ${wd}/do_nodes.sh

ansible pc -m shell -a ${id}'/do_nodes.sh '

#

# Und jetzt die Repository-Info

#

cat <<EOF > ${wd}/do_docker.sh

#!/bin/bash

#

# \$Date: 2021-11-23 21:37:19 +0100 (Di, 23. Nov 2021) $

# \$Revision: 1285 $

# \$Author: alfred $

# \$HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K13_registry.sh $

# \$Id: K13_registry.sh 1285 2021-11-23 20:37:19Z alfred $

#

# Eintragen des Endpoints in die Docker-Registry

#

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

#shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

FILENAME="/var/snap/microk8s/current/args/containerd-template.toml"

sudo sed --in-place '/docker.registry:5000/d' \${FILENAME}

text=' [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.registry:5000"]'

echo "\${text}" | tee -a \${FILENAME}

text=' endpoint = ["http://docker.registry:5000"]'

echo "\${text}" | tee -a \${FILENAME}

EOF

#

chmod 755 ${wd}/do_docker.sh

ansible pc -m shell -a ${id}'/do_docker.sh '

#

# Und jetzt für den Docker selbst

#

cat <<EOF > ${wd}/do_pull.sh

#!/bin/bash

#

# \$Date: 2021-11-23 21:37:19 +0100 (Di, 23. Nov 2021) $

# \$Revision: 1285 $

# \$Author: alfred $

# \$HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K13_registry.sh $

# \$Id: K13_registry.sh 1285 2021-11-23 20:37:19Z alfred $

#

# Konfiguration damit das docker-pull funktioniert

#

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

#shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

sudo mkdir -p /etc/docker

sudo rm -f /etc/docker/daemon.json

tfile=\$(mktemp /tmp/daemon.XXXXXXXXX)

sudo cat <<AllOver > \${tfile}

{

"insecure-registries" : ["docker.registry:5000"]

}

AllOver

sudo cp -f \${tfile} /etc/docker/daemon.json

sudo chmod 666 /etc/docker/daemon.json

#

EOF

#

chmod 755 ${wd}/do_pull.sh

ansible pc -m shell -a ${id}'/do_pull.sh '

##

## Jetzt ist die Docker-Registry online und verfügbar

##

Dieses Skript konfiguriert die Registry als LoadBalancer Service. Damit bekommt die Registry eine fixe IP-Adresse, die von aussen erreichbar ist. Somit können wir den Cluster ansprechen und sind nicht an einen bestimmten Node gebunden. Durch die Verwendung einer Clusterdisk (gesteuert durch Longhorn) ist diese Registry auch ausfallsicher. Der Service sieht dann so aus:

alfred@pc1:~$ k -n container-registry get service

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

registry LoadBalancer 10.152.183.118 192.168.0.213 5000:5000/TCP 7h32m

alfred@pc1:~$

Das Skript trägt weiters die Registry in die Host-Dateien ein:

alfred@pc1:~$ cat /etc/hosts

192.168.0.201 pc1

192.168.0.202 pc2

192.168.0.203 pc3

192.168.0.204 pc4

192.168.0.205 pc5

192.168.0.2 monitoring

192.168.0.2 ntp

127.0.0.1 localhost pc1

# The following lines are desirable for IPv6 capable hosts

::1 ip6-localhost ip6-loopback

fe00::0 ip6-localnet

ff00::0 ip6-mcastprefix

ff02::1 ip6-allnodes

ff02::2 ip6-allrouters

ff02::3 ip6-allhosts

192.168.0.2 monitoring

192.168.0.213 docker.registry

alfred@pc1:~$

Es wird auch der Eintrag für das unsafe Repository gemacht:

alfred@pc1:~$ tail /var/snap/microk8s/current/args/containerd-template.toml

[plugins."io.containerd.grpc.v1.cri".registry]

# 'plugins."io.containerd.grpc.v1.cri".registry.mirrors' are namespace to mirror mapping for all namespaces.

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]

endpoint = ["https://registry-1.docker.io", ]

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]

endpoint = ["http://localhost:5000"]

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.registry:5000"]

endpoint = ["http://docker.registry:5000"]

alfred@pc1:~$

Somit ist das Repository im Cluster verfügbar.

Verwalten des Sourcerepositorys

Es gibt mehrere Befehle um die Daten im Sourcerepository zu beeinflußen.

docker push ${docker_registry}/${image}:latest

Dieser Befehle fügt ein Image in das Repository ein.

curl ${docker_registry}/v2/${image}/tags/list

Mit diesem Befehl kann man die bereits vorhandene tags-Liste von aussen eingesehen werden.

Wenn man aber die Images die bereits im Kubernetes Cluster sind verwalten möchte, dann wird es ein bisschen schwieriger. Dazu habe ich folgende Skripten:

#!/bin/bash

############################################################################################

# $Date: 2021-11-25 21:57:57 +0100 (Do, 25. Nov 2021) $

# $Revision: 1348 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/registry_list.sh $

# $Id: registry_list.sh 1348 2021-11-25 20:57:57Z alfred $

#

# Manipulieren der microk8s.registry

# https://gist.github.com/Kevinrob/4c7f1e5dbf6ce4e94d6ba2bfeff37aeb

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

#shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

docker_registry="docker.registry:5000"

repositories=$(curl -s ${docker_registry}/v2/_catalog)

for repo in $(echo "${repositories}" | jq -r '.repositories[]'); do

echo $repo

tags=$(curl -s "http://${docker_registry}/v2/${repo}/tags/list" | jq -r '.tags[]')

for tag in $tags; do

echo " "$repo:$tag

done

done

#

Dieses Skript zeigt alle Images und deren Tags an, die in der Registry vorhanden sind.

#!/bin/bash

############################################################################################

# $Date: 2021-11-25 21:57:57 +0100 (Do, 25. Nov 2021) $

# $Revision: 1348 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/registry_delete.sh $

# $Id: registry_delete.sh 1348 2021-11-25 20:57:57Z alfred $

#

# Manipulieren der microk8s.registry

# https://gist.github.com/Kevinrob/4c7f1e5dbf6ce4e94d6ba2bfeff37aeb

#

# webdefault:20211123-1301

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

#shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

docker_registry="docker.registry:5000"

# Prüfung ob gesetzt, sonst liefert die Shell hier eine Fehlermeldung

string="${1}"

#echo "${string}"

prefix=":"

suffix=""

xrepo=${string%"$prefix"*}

#echo "${xrepo}"

xtag=${string#"$xrepo$prefix"}

#echo "${xtag}"

xtag=${xtag%"$suffix"*}

#echo "${xtag}"

tags=$(curl -sSL "http://${docker_registry}/v2/${xrepo}/tags/list" | jq -r '.tags[]')

for tag in $tags; do

if [ "${tag} " = "${xtag} " ] ;

then

echo "Lösche "$xrepo:$tag

curl -v -k -X DELETE "http://${docker_registry}/v2/${xrepo}/manifests/$(

curl -k -s -I \

-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \

"http://${docker_registry}/v2/${xrepo}/manifests/${xtag}" \

| awk '$1 == "Docker-Content-Digest:" { print $2 }' \

| tr -d $'\r' \

)"

fi

done

#

Dieses Skript löscht einen angegeben tag, wenn er gefunden wird.

Ein typischer Löschprozess sieht dann so aus:

alfred@bureau:~$ '/home/alfred/svn/trunk/k8s/dev/registry_list.sh'

chrony

chrony:latest

chrony:20211123-1273

chrony:20211123-1275

hello-world

hello-world:20211003

pgmaster

pgmaster:20211125-1327

pgmaster:20211125-1337

pgmaster:20211125-1340

pgmaster:20211125-1339

pgmaster:latest

pgmaster:20211125-1345

pgmaster:20211125-1343

pgmaster:20211125-1334

pgmaster:20211125-1336

pgmaster:20211125-1341

webdefault

webdefault:20211123-1292

webdefault:20211123-1293

webserver

webserver:20211124-1329

webserver:20211124-1321

webserver:20211124-1315

webserver:20211124-1331

webserver:20211124-1305

webserver:20211125-1333

webserver:20211124-1324

webserver:latest

webserver:20211125-1345

webserver:20211124-1306

webserver:20211124-1319

webserver:20211123-1305

webserver:20211124-1328

webserver:20211124-1312

webserver:20211124-1307

webserver:20211124-1327

webserver:20211125-1344

webserver:20211124-1330

webserver:20211124-1314

webserver:20211124-1320

webserver:20211124-1317

webserver:20211124-1332

alfred@bureau:~$ '/home/alfred/svn/trunk/k8s/dev/registry_delete.sh' webdefault:20211123-1293

Lösche webdefault:20211123-1293

* Trying 192.168.0.213:5000...

* Connected to docker.registry (192.168.0.213) port 5000 (#0)

> DELETE /v2/webdefault/manifests/sha256:ede78ca033eba1314667d5c6ced6a279833908d162ffe9103d7d1af42d53b6c7 HTTP/1.1

> Host: docker.registry:5000

> User-Agent: curl/7.74.0

> Accept: */*

>

* Mark bundle as not supporting multiuse

< HTTP/1.1 202 Accepted

< Docker-Distribution-Api-Version: registry/2.0

< X-Content-Type-Options: nosniff

< Date: Thu, 25 Nov 2021 20:55:23 GMT

< Content-Length: 0

<

* Connection #0 to host docker.registry left intact

alfred@bureau:~$ '/home/alfred/svn/trunk/k8s/dev/registry_delete.sh' webdefault:20211123-1293

alfred@bureau:~$

Mit diesen beiden Skripts kann man sich die Images anzeigen lassen, und alte Images löschen.

Zertifikate und LetsEncrypt

Inspiration:

https://suda.pl/5-minute-home-server-with/

https://faun.pub/wildcard-k8s-4998173b16c8

https://collabnix.github.io/kubetools/

https://forum.netcup.de/netcup-intern/technik/11841-let-s-encrypt-wildcard-zertifikate-via-certbot/

Um den Cluster vernünftig betreiben zu können braucht es Zertificate.

Darum habe ich den Certificate-Manager installiert.

#!/bin/bash

############################################################################################

# $Date: 2021-10-21 21:40:29 +0200 (Do, 21. Okt 2021) $

# $Revision: 659 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K5_certmanager.sh $

# $Id: K5_certmanager.sh 659 2021-10-21 19:40:29Z alfred $

#

# cert-manager

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

# Voraussetzung: Scripts in der richtigen Reihenfolge

#

# Definitionen für das Deployment

#

sname=$(basename "$0")

app="mikrok8s/install/${sname}"

pf=\$"Revision: "

sf=" "\$

fr="\$Revision: 659 $"

revision=${fr#*"$pf"}

revision=${revision%"$sf"*}

xd=(`date '+%Y-%m-%d'`)

wd="${HOME}/copy/${app}/${xd}/r${revision}"

id="/opt/cluster/${app}/${xd}/r${revision}"

rm -f -R ${wd}

mkdir -p ${wd}

#

cat <<EOF > ${wd}/install_certmanager.sh

#!/bin/bash

#

# \$Date: 2021-10-21 21:40:29 +0200 (Do, 21. Okt 2021) $

# \$Revision: 659 $

# \$Author: alfred $

# \$HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K5_certmanager.sh $

# \$Id: K5_certmanager.sh 659 2021-10-21 19:40:29Z alfred $

#

# Installation des cert-managers mit helm

#

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

# Voraussetzung: Scripts in der richtigen Reihenfolge

microk8s kubectl create namespace cert-manager

microk8s helm3 repo add jetstack https://charts.jetstack.io

microk8s helm3 repo update

microk8s helm3 install cert-manager jetstack/cert-manager \

--namespace cert-manager --version v1.5.4 \

--set installCRDs=true \

--set ingressShim.defaultIssuerName=letsencrypt-production \

--set ingressShim.defaultIssuerKind=ClusterIssuer \

--set ingressShim.defaultIssuerGroup=cert-manager.io

#

#wget https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.yaml --output-document=${id}/cert-manager.yaml

#wget https://github.com/jetstack/cert-manager/releases/download/v1.5.4/cert-manager.crds.yaml --output-document=${id}/cert-manager.crds.yaml

#microk8s kubectl apply -f ${id}/*.yaml

#

sleep 1m

microk8s kubectl get pods --namespace cert-manager

EOF

chmod 755 ${wd}/install_certmanager.sh

#

ansible pc1 -m shell -a ${id}'/install_certmanager.sh'

#

#!/bin/bash

############################################################################################

# $Date: 2021-11-28 11:05:45 +0100 (So, 28. Nov 2021) $

# $Revision: 1404 $apiVersion: networking.k8s.io/v1

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s/K14_webserver.sh $

# $Id: K14_webserver.sh 1404 2021-11-28 10:05:45Z alfred $

#

# Einspielen der lokalen Konfigurationen - Produktiv ist slainte.

# https://stackoverflow.com/questions/67430592/how-to-setup-letsencrypt-with-kubernetes-microk8s-using-default-ingress

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

#

# Definitionen für das Deployment

#

sname=$(basename "$0")

app="mikrok8s/install/${sname}"

pf=\$"Revision: "

sf=" "\$

fr="\$Revision: 1404 $"

revision=${fr#*"$pf"}

revision=${revision%"$sf"*}

xd=(`date '+%Y-%m-%d'`)

wd="${HOME}/copy/${app}/${xd}/r${revision}"

id="/opt/cluster/${app}/${xd}/r${revision}"

rm -f -R ${wd}

mkdir -p ${wd}

#

cat <<EOF > ${wd}/webserver-depl-svc.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: webserver-depl

namespace: slainte

spec:

selector:

matchLabels:

app: webserver-app

template:

metadata:

labels:

app: webserver-app

spec:

containers:

- name: webserver-app

image: nginx:1.20

---

apiVersion: v1

kind: Service

metadata:

name: webserver-svc

namespace: slainte

spec:

selector:

app: webserver-app

ports:

- port: 80

name: http

targetPort: 80

protocol: TCP

- port: 443

name: https

targetPort: 443

protocol: TCP

EOF

ansible pc1 -m shell -a 'microk8s kubectl apply -f '${id}'/webserver-depl-svc.yaml'

cat <<EOF > ${wd}/letsencrypt-staging.yaml

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-staging

spec:

acme:

#change to your email

email: slainte@slainte.at

server: https://acme-staging-v02.api.letsencrypt.org/directory

privateKeySecretRef:

name: letsencrypt-staging

solvers:

- http01:

ingress:

class: public

EOF

ansible pc1 -m shell -a 'microk8s kubectl apply -f '${id}'/letsencrypt-staging.yaml'

cat <<EOF > ${wd}/letsencrypt-prod.yaml

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-prod

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

#change to your email

email: slainte@slainte.at

privateKeySecretRef:

name: letsencrypt-prod

solvers:

- http01:

ingress:

class: public

EOF

ansible pc1 -m shell -a 'microk8s kubectl apply -f '${id}'/letsencrypt-prod.yaml'

cat <<EOF > ${wd}/ingress-routes-update.yaml

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: webserver-routes

namespace: slainte

annotations:

# Class checken mit kubectl -n ingress describe daemonset.apps/nginx-ingress-microk8s-controller

kubernetes.io/ingress.class: public

# Das ist für das Zertifikat

cert-manager.io/cluster-issuer: "letsencrypt-prod"

# Das ist für das http -> https forwarding

# See https://kubernetes.github.io/ingress-nginx/examples/rewrite/

nginx.ingress.kubernetes.io/rewrite-target: /\$1

nginx.ingress.kubernetes.io/ssl-redirect: "true"

nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

nginx.ingress.kubernetes.io/ssl-temporary-redirect: "false"

nginx.ingress.kubernetes.io/secure-backends: "true"

nginx.ingress.kubernetes.io/ssl-proxy-headers: "X-Forwarded-Proto: https"

nginx.ingress.kubernetes.io/proxy-body-size: 0m

nginx.ingress.kubernetes.io/proxy-buffering: "off"

# nginx.ingress.kubernetes.io/ssl-passthrough: "true"

# https://github.com/nginxinc/kubernetes-ingress/tree/v1.12.0/examples/ssl-services

# nginx.ingress.kubernetes.io/ssl-services: "\${image}-svc"

# nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

spec:

tls:

- hosts:

- k8s.slainte.at

secretName: k8s-slainte-at-tls

rules:

- host: k8s.slainte.at

http:

paths:

- path: /(.*)

pathType: Prefix

backend:

service:

name: webserver-svc

port:

number: 80

defaultBackend:

service:

name: webserver-svc

port:

number: 80

EOF

ansible pc1 -m shell -a 'microk8s kubectl apply -f '${id}'/ingress-routes-update.yaml '

# Service PROD

curl -k -v http://k8s.slainte.at

#erreichbar sein.

#Aber auch mit https

curl -k -v https://k8s.slainte.at

#

## Prüfen des Zertifikates

ansible pc1 -m shell -a 'microk8s kubectl get certificate --all-namespaces'

ansible pc1 -m shell -a 'microk8s kubectl describe certificate --all-namespaces'

ansible pc1 -m shell -a 'microk8s kubectl get certificaterequests.cert-manager.io '

ansible pc1 -m shell -a 'microk8s kubectl describe certificaterequests '

ansible pc1 -m shell -a 'microk8s kubectl get certificatesigningrequests.certificates.k8s.io '

ansible pc1 -m shell -a 'microk8s kubectl get Issuer'

ansible pc1 -m shell -a 'microk8s kubectl get ClusterIssuer'

ansible pc1 -m shell -a 'microk8s kubectl describe ClusterIssuer letsencrypt-prod '

ansible pc1 -m shell -a 'microk8s kubectl get challenges.acme.cert-manager.io '

ansible pc1 -m shell -a 'microk8s kubectl describe challenges.acme.cert-manager.io '

##

exit

Die Zertifikate entstehen dann, wenn sie gebraucht werden. Die Definition ist im Ingress. Derzeit gibt es einen URL pro Namespace, und damit ein Zertifikat.

Die Namespaces werden hier definiert.

#!/bin/bash

############################################################################################

# $Date: 2021-11-23 18:03:25 +0100 (Di, 23. Nov 2021) $

# $Revision: 1272 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/namespace/slainte_env.sh $

# $Id: slainte_env.sh 1272 2021-11-23 17:03:25Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

export secretName="k8s-slainte-at-tls"

export host="k8s.slainte.at"

export namespace_comment="Namespace für die Produktion"

export cluster_issuer="letsencrypt-prod"

export docker_registry="docker.registry:5000"

#

Bzw.

#!/bin/bash

############################################################################################

# $Date: 2021-11-23 18:03:25 +0100 (Di, 23. Nov 2021) $

# $Revision: 1272 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/namespace/default_env.sh $

# $Id: default_env.sh 1272 2021-11-23 17:03:25Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

export secretName="default-k8s-slainte-at-tls"

export host="default.k8s.slainte.at"

export namespace_comment="Namespace zum Testen"

# export cluster_issuer="letsencrypt-staging"

# Auch hier die Prod, wegen HTTP Strict Transport Security (HSTS)

export cluster_issuer="letsencrypt-prod"

export docker_registry="docker.registry:5000"

#

Das sieht dann im Ingress wie folgt aus:

---

# Yaml für ${image}:${tag}

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: ${image}-routes

namespace: ${namespace}

annotations:

kubernetes.io/ingress.class: public

cert-manager.io/cluster-issuer: "${cluster_issuer}"

# https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/

nginx.ingress.kubernetes.io/rewrite-target: /$2

nginx.ingress.kubernetes.io/ssl-redirect: "true"

nginx.ingress.kubernetes.io/ssl-passthrough: "true"

nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

nginx.ingress.kubernetes.io/ssl-temporary-redirect: "false"

nginx.ingress.kubernetes.io/secure-backends: "true"

nginx.ingress.kubernetes.io/ssl-proxy-headers: "X-Forwarded-Proto: https"

nginx.ingress.kubernetes.io/proxy-body-size: 0m

nginx.ingress.kubernetes.io/proxy-buffering: "off"

# https://github.com/nginxinc/kubernetes-ingress/tree/v1.12.0/examples/ssl-services

nginx.ingress.kubernetes.io/ssl-services: "${image}-svc"

nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

spec:

tls:

- hosts:

- ${host}

secretName: ${secretName}

rules:

- host: ${host}

http:

paths: # https://github.com/google/re2/wiki/Syntax, https://www.regular-expressions.info/refcapture.html

- path: /${image}(/|$)(.*)

pathType: Prefix

backend:

service:

name: ${image}-svc

port:

number: 443

defaultBackend:

service:

name: default-svc

port:

number: 80

---

Man könnte aber auch mit Wildcard-Zertifikaten arbeiten. Ist aber in unserem Falle ein wenig übertrieben. Nachdem es ja eh für jeden Namespace und jeden host einen Ingress braucht, kann man dafür auch ein eigenes Zertifikat anlegen lassen.

Entwicklungsumgebung

Inspiration:

https://pimylifeup.com/ubuntu-install-docker/

https://brjapon.medium.com/setting-up-ubuntu-20-04-arm-64-under-raspberry-pi-4-970654d12696

https://microk8s.io/docs/registry-built-in

https://microk8s.io/docs/registry-private

https://github.com/docker-library/hello-world

https://www.freecodecamp.org/news/how-to-remove-images-in-docker/

https://gobyexample.com/hello-world

https://linuxconfig.org/how-to-install-go-on-ubuntu-20-04-focal-fossa-linux

https://forums.docker.com/t/docker-private-registry-how-to-list-all-images/21136/2

https://github.com/fraunhoferfokus/deckschrubber

https://collabnix.github.io/kubetools/

Um das Source-Repository nutzen zu können brauchen wir eine Entwicklungsumgebung. Dafür benutzen wir unseren Entwicklungs-Raspbery PI.

Abbildung 8: Gesamtsystem

Dieser Entwicklungs-Raspberry ist ein Raspberry 4 mit 8GB RAM und einer 120 GB SDRAM Karte. Zusätzlich gibt es noch eine 1TB-USB-Platte für all die Backups.

Auf diesem Rechner ist Docker installiert.

alfred@monitoring:~$ docker version

Client:

Version: 20.10.7

API version: 1.41

Go version: go1.13.8

Git commit: 20.10.7-0ubuntu1~20.04.1

Built: Wed Aug 4 22:53:01 2021

OS/Arch: linux/arm64

Context: default

Experimental: true

Server:

Engine:

Version: 20.10.7

API version: 1.41 (minimum version 1.12)

Go version: go1.13.8

Git commit: 20.10.7-0ubuntu1~20.04.1

Built: Wed Aug 4 19:07:47 2021

OS/Arch: linux/arm64

Experimental: false

containerd:

Version: 1.5.2-0ubuntu1~20.04.2

GitCommit:

runc:

https://github.com/docker-library/hello-world Version: 1.0.0~rc95-0ubuntu1~20.04.2

GitCommit:

docker-init:

Version: 0.19.0

GitCommit:

alfred@monitoring:~$

Es gibt sehr viele Anleitungen, wie man Docker installiert. Ich habe dazu folgendes Skript.

#!/bin/bash

############################################################################################

# $Date: 2021-11-22 18:47:00 +0100 (Mo, 22. Nov 2021) $

# $Revision: 1252 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/portainer/portainer.sh $

# $Id: portainer.sh 1252 2021-11-22 17:47:00Z alfred $

#

# https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04-de

#

# Installieren Docker und Portainer

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

#

sudo apt update

sudo apt install apt-transport-https ca-certificates curl software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"

sudo apt update

apt-cache policy docker-ce

sudo apt install docker-ce

sudo usermod -aG docker ${USER}

#

docker volume create portainer_data

docker run -d -p 8000:8000 -p 9443:9443 --name portainer \

--restart=always \

-v /var/run/docker.sock:/var/run/docker.sock \

-v /portainer_data:/data \

portainer/portainer-ce:latest

docker ps

docker run -d -p 9001:9001 --name portainer_agent \

--restart=always \

-v /var/run/docker.sock:/var/run/docker.sock \

-v /var/lib/docker/volumes:/var/lib/docker/volumes \

portainer/agent:latest

Damit gibt es dann auch den Portainer, der recht praktisch ist, um die lokale Dockerinstallation zu steuern.

Abbildung 9: Portainer

Wir müssen das Repository das sich am Kubernetes Cluster befindet in die Konfiguration am Entwicklungsrechner eintragen.

alfred@monitoring:~/go$ cat /etc/hosts

127.0.0.1 localhost

127.0.0.1 monitoring

127.0.0.1 monitoring.slainte.at

192.168.0.213 docker.registry

192.168.0.201 pc1

192.168.0.202 pc2

192.168.0.203 pc3

192.168.0.204 pc4

192.168.0.205 pc5

alfred@monitoring:~/go$ sudo cat /etc/docker/daemon.json

{

"insecure-registries" : ["docker.registry:5000"]

}

Die Registry ist als Loadbalancer-Service ausfallsicher. Die Storage ist in einem persistant volume, somit funktioniert das auch von überall im Cluster.

Nun können wir versuchen ein hello world zu erzeugen.

package main

import "fmt"

func main() {

fmt.Println("hello world")

}

Dieser Programmteil ist in GO geschrieben. Go wird wie folgt installiert.

alfred@monitoring:~$ sudo apt install golang

alfred@monitoring:~$ go version

go version go1.13.8 linux/arm64

alfred@monitoring:~$

Dann kann man das go Programm testen und builden.

alfred@monitoring:~$ mkdir go

alfred@monitoring:~$ cd go

alfred@monitoring:~/go$ nano hello-world.go

alfred@monitoring:~/go$ go run hello-world.go

hello world

alfred@monitoring:~/go$ go build hello-world.go

alfred@monitoring:~/go$ ls -lisa

total 1936

4688691 4 drwxrwxr-x 2 alfred alfred 4096 Sep 30 11:26 .

1140881 4 drwxr-xr-x 21 alfred alfred 4096 Sep 30 11:25 ..

127267 1924 -rwxrwxr-x 1 alfred alfred 2097605 Sep 30 11:26 hello-world

4688693 4 -rw-rw-r-- 1 alfred alfred 75 Sep 30 11:25 hello-world.go

alfred@monitoring:~/go$ ./hello-world

hello world

alfred@monitoring:~/go$

Jetzt haben wir das go-Programm, und müssen es in einen Docker-Container packen.

alfred@monitoring:~/go$ docker rmi $(docker images -q) # Löscht alle vorhandenen Images im lokalen Repository

Um einen Container zu erzeugen, brauchen wir ein dockerfile.

alfred@monitoring:~/dev/hello-world$ cat dockerfile

# syntax=docker/dockerfile:1

# Alpine is chosen for its small footprint

# compared to Ubuntu

FROM golang:1.16-alpine

WORKDIR /app

LABEL maintainer="microk8s.raspberry@slainte.at"

COPY go.mod ./

COPY *.go ./

RUN go build -o /hallo

EXPOSE 80

CMD ["/hallo"]

alfred@monitoring:~/dev/hello-world$

Nun bauen wir den Container.

alfred@monitoring:~/dev/hello-world$ docker build . -t docker.registry:5000/hello-world:20211003

Sending build context to Docker daemon 9.216kB

Step 1/8 : FROM golang:1.16-alpine

1.16-alpine: Pulling from library/golang

be307f383ecc: Already exists

e31131f141ae: Pull complete

7f3ae2225eeb: Pull complete

27b4cf6759f9: Pull complete

05c56ed0aaf5: Pull complete

Digest: sha256:45412fe3f5016509fc448b83faefc34e6f9e9bcc8ca1db1c54505d5528264e16

Status: Downloaded newer image for golang:1.16-alpine

---> bebfc96a903d

Step 2/8 : WORKDIR /app

---> Running in c81df142320d

Removing intermediate container c81df142320d

---> a81936939cb7

Step 3/8 : LABEL maintainer="microk8s.raspberry@slainte.at"

---> Running in 88c7acfdf90e

Removing intermediate container 88c7acfdf90e

---> b023746a02d8

Step 4/8 : COPY go.mod ./

---> 599501f1547a

Step 5/8 : COPY *.go ./

---> 9f60a66c2ac0

Step 6/8 : RUN go build -o /hallo

---> Running in 03fd6b04a839

Removing intermediate container 03fd6b04a839

---> e62606c61d94

Step 7/8 : EXPOSE 80

---> Running in 2b3562abb146

Removing intermediate container 2b3562abb146

---> 121727752180

Step 8/8 : CMD ["hallo"]

---> Running in cfa9e78598dc

Removing intermediate container cfa9e78598dc

---> 52dfdc427a61

Successfully built 52dfdc427a61

Successfully tagged docker.registry:5000/hello-world:20211003

alfred@monitoring:~/dev/hello-world$

Anzeige im lokalen Repository.

alfred@monitoring:~/go$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

docker.registry:5000/hello-world 20211003 ebe5a2422dca 24 seconds ago 2.1MB

alfred@monitoring:~/go$

Wir können den Inhalt untersuchen, indem wir den Docker in ein Tar-File exportieren.

alfred@monitoring:~/go$ docker ps -lq

fc63f4826cff

alfred@monitoring:~/go$

alfred@monitoring:~/go$ mkdir test

alfred@monitoring:~/go$ docker export fc63f4826cff > ./test/hello-world.tar

alfred@monitoring:~/go$ cd test/

alfred@monitoring:~/go/test$ tar -xf hello-world.tar

alfred@monitoring:~/go/test$ ll

total 4132

drwxrwxr-x 6 alfred alfred 4096 Oct 3 09:37 ./

drwxrwxr-x 4 alfred alfred 4096 Oct 3 09:37 ../

-rwxr-xr-x 1 alfred alfred 0 Oct 3 09:32 .dockerenv*

drwxr-xr-x 4 alfred alfred 4096 Oct 3 09:32 dev/

drwxr-xr-x 2 alfred alfred 4096 Oct 3 09:32 etc/

-rwxrwxr-x 1 alfred alfred 2097605 Sep 30 11:26 hello-world*

-rw-rw-r-- 1 alfred alfred 2105344 Oct 3 09:37 hello-world.tar

drwxr-xr-x 2 alfred alfred 4096 Oct 3 09:32 proc/

drwxr-xr-x 2 alfred alfred 4096 Oct 3 09:32 sys/

alfred@monitoring:~/go/test$

Wir können das Image auch lokal starten.

alfred@monitoring:~/dev/hello-world$ docker run docker.registry:5000/hello-world:20211003

hello world

alfred@monitoring:~/dev/hello-world$

Pushen in das remote-Repository. In dem Falle gibt es einen darunter liegenden den Layer schon.

alfred@monitoring:~/dev/hello-world$ docker push docker.registry:5000/hello-world:20211003

The push refers to repository [docker.registry:5000/hello-world]

6fa65fc3457a: Pushed

af86841dfdf3: Pushed

001055a1bdf7: Pushed

350991c52258: Pushed

93bd567aa306: Pushed

b48145e02449: Pushed

20211003: digest: sha256:cf16e57415939719367e5e17c09d2f17e36af2c1b84f24208f5750ea2c18b485 size: 1570

alfred@monitoring:~/dev/hello-world$

Anzeige aller vorhandenen Images im Remote Repository

alfred@monitoring:~/go$ curl docker.registry:5000/v2/_catalog

{"repositories":["hello-world"]}

alfred@monitoring:~/go$

alfred@monitoring:~/go$ curl docker.registry:5000/v2/hello-world/tags/list

{"name":"hello-world","tags":["20211003","20211001","20210830","20210930"]}

alfred@monitoring:~/go$

alfred@monitoring:~/go$ curl docker.registry:5000/v2/hello-world/manifests/20211003

{

"schemaVersion": 1,

"name": "hello-world",

"tag": "20211003",

"architecture": "arm64",

"fsLayers": [

{

"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"

},

{

"blobSum": "sha256:34b32ef7cc99133a34d56a284820986ce50c2ff9c1864da8557458489d7428a1"

}

],

"history": [

{

"v1Compatibility": "{\"architecture\":\"arm64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello-world\"],\"Image\":\"sha256:c7642e609037b051e03214a85ddc50434de042a6b08c23bcac16dcb92629ee46\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"6d8917fc20a0fcbece354d49781ee260e15cb02df65c35efa5978f583948f9c4\",\"container_config\":{\"Hostname\":\"6d8917fc20a0\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"/hello-world\\\"]\"],\"Image\":\"sha256:c7642e609037b051e03214a85ddc50434de042a6b08c23bcac16dcb92629ee46\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2021-10-03T07:40:42.472956387Z\",\"docker_version\":\"20.10.7\",\"id\":\"031921ba69921f2705920021974bfdc500b6f42530f679aa56481bdbf1543d3e\",\"os\":\"linux\",\"parent\":\"91fdfdcec09b0c57e4f28bd9c1d199796b66e5162c2e0432f8a9dc689952e13c\",\"throwaway\":true,\"variant\":\"v8\"}"

},

{

"v1Compatibility": "{\"id\":\"91fdfdcec09b0c57e4f28bd9c1d199796b66e5162c2e0432f8a9dc689952e13c\",\"created\":\"2021-10-03T07:40:35.230309736Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY file:c46bbf237d2238c6f6241fa3c853f5d4861e4981bd19d09a99fa3f1ef76ed73a in / \"]}}"

}

],

"signatures": [

{

"header": {

"jwk": {

"crv": "P-256",

"kid": "AADV:MDEU:C5BD:UTIA:ME3T:42HB:WJUA:GTV4:DHU6:KPQM:OXBY:RRAS",

"kty": "EC",

"x": "PEw45Fk1zyjGc6iHHYh6_Ydk3mxLC8UE1mvlo_6XY24",

"y": "8e-Ad67SUvJMaYHjlyh05hqv7kcekIm6J-jR8It9unA"

},

"alg": "ES256"

},

"signature": "0iZv8OxENn9Oo_2eTyIQpfxFgBBsIW1z4p0AfkxP-bBxDE-1i5SN76FTwsLdTI9SRHnyY6R0_AoMSB1gWacdeA",

"protected": "eyJmb3JtYXRMZW5ndGgiOjIxMjUsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMS0xMC0wM1QwODoxNjoxM1oifQ"

}

]

alfred@monitoring:~/go$

Somit ist das Image im Repository. Jetzt brauchen wir noch eine Service Definition.

alfred@pc1:/opt/cluster/go$ cat hello-world.yaml

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-world

#namespace: default

spec:

selector:

matchLabels:

app: hello-world

replicas: 1

template:

metadata:

labels:

app: hello-world

annotations:

sidecar.istio.io/inject: "false"

spec:

containers:

- name: hello-world

image: docker.registry:5000/hello-world:20211003

# resource limits

resources:

requests:

memory: "24Mi"

cpu: "500m" # half vcpu

limits:

memory: "64Mi"

cpu: "1000m" # one vcpu

env:

# currently no env vars used for this container

- name: FOO

value: bar

# check for lifetime liveness, restarts if dead

livenessProbe:

exec:

command:

- /hello-world

initialDelaySeconds: 5

periodSeconds: 10

# check for initial readyness

readinessProbe:

exec:

command:

- /hello-world

initialDelaySeconds: 3

periodSeconds: 3

restartPolicy: Always

dnsPolicy: ClusterFirst

---

apiVersion: v1

kind: Service

metadata:

name: hello-world-service

#namespace: default

labels:

app: hello-world

spec:

ports:

# port=available to other containers

- port: 1234

name: hello

# targetPort=exposed from inside container

targetPort: 1234

protocol: TCP

selector:

app: hello-world

---

alfred@pc1:/opt/cluster/go$

Nun installieren wir den Service.

alfred@pc1:/opt/cluster/go$ kubectl apply -f hello-world.yaml

deployment.apps/hello-world created

service/hello-world-service created

alfred@pc1:/opt/cluster/go$

Wir kontrollieren ob der Service richtig gestartet wird.

alfred@pc1:/opt/cluster/go$ kubectl describe -n default pod hello-world-6bb7844865-4j5bw

Name: hello-world-6bb7844865-4j5bw

Namespace: default

Priority: 0

Node: pc5/192.168.0.205

Start Time: Sun, 03 Oct 2021 20:56:03 +0200

Labels: app=hello-world

pod-template-hash=6bb7844865

Annotations: cni.projectcalico.org/podIP: 10.1.80.118/32

cni.projectcalico.org/podIPs: 10.1.80.118/32

sidecar.istio.io/inject: false

Status: Running

IP: 10.1.80.118

IPs:

IP: 10.1.80.118

Controlled By: ReplicaSet/hello-world-6bb7844865

Containers:

hello-world:

Container ID: containerd://e9d5a90c4ac98c57f9f27bdbe88b0bd5535d4e297ecbb103118a963d554615a9

Image: docker.registry:5000/hello-world:20211003

Image ID: docker.registry:5000/hello-world@sha256:1bb9b5564a34689396c097bb410a837d5e074b61caff822cb43921f425b09a50

Port: <none>

Host Port: <none>

State: Waiting

Reason: CrashLoopBackOff

Last State: Terminated

Reason: Completed

Exit Code: 0

Started: Sun, 03 Oct 2021 20:56:08 +0200

Finished: Sun, 03 Oct 2021 20:56:08 +0200

Ready: False

Restart Count: 1

Limits:

cpu: 1

memory: 64Mi

Requests:

cpu: 500m

memory: 24Mi

Liveness: exec [/hello-world] delay=5s timeout=1s period=10s #success=1 #failure=3

Readiness: exec [/hello-world] delay=3s timeout=1s period=3s #success=1 #failure=3

Environment:

FOO: bar

Mounts:

/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vtwl2 (ro)

Conditions:

Type Status

Initialized True

Ready False

ContainersReady False

PodScheduled True

Volumes:

kube-api-access-vtwl2:

Type: Projected (a volume that contains injected data from multiple sources)

TokenExpirationSeconds: 3607

ConfigMapName: kube-root-ca.crt

ConfigMapOptional: <nil>

DownwardAPI: true

QoS Class: Burstable

Node-Selectors: <none>

Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s

node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

Events:

Type Reason Age From Message

---- ------ ---- ---- -------

Normal Scheduled 23s default-scheduler Successfully assigned default/hello-world-6bb7844865-4j5bw to pc5

Warning BackOff 14s (x4 over 19s) kubelet Back-off restarting failed container

Normal Pulled 1s (x3 over 22s) kubelet Container image "docker.registry:5000/hello-world:20211003" already present on machine

Normal Created 1s (x3 over 22s) kubelet Created container hello-world

Normal Started 1s (x3 over 21s) kubelet Started container hello-world

alfred@pc1:/opt/cluster/go$

alfred@pc1:/opt/cluster/go$ kubectl get pod hello-world-6bb7844865-4j5bw

NAME READY STATUS RESTARTS AGE

hello-world-6bb7844865-4j5bw 0/1 CrashLoopBackOff 4 108s

alfred@pc1:/opt/cluster/go$

alfred@pc1:/opt/cluster/go$ kubectl logs hello-world-6bb7844865-4j5bw

hello world

alfred@pc1:/opt/cluster/go$

Das heist, der Service wird ordnungsgemäß installiert, läuft auch einmal und terminiert dann (so ist das Programm). Aus Kubernetes Sicht wird aber über das Deployment versucht den Service wieder herzustellen. Daher wird er permanent restarted. Für das Hello-World Prinzip reicht das. Für einen richtigen Service aber natürlich nicht.

Darum löschen wir den Service auch wieder.

alfred@pc1:/opt/cluster/go$ kubectl delete -f hello-world.yaml

deployment.apps "hello-world" deleted

service "hello-world-service" deleted

alfred@pc1:/opt/cluster/go$

Aber wir haben nun ein funktionierendes Setup. Können auf der Entwicklungsmaschine docker-Container erzeugen, diese in das Repository am Kubernetes Cluster einspielen, und den Service dann auch starten.

Golang IDE

Inspiration:

https://golang.org/doc/articles/wiki/

https://github.com/visualfc/liteide

https://linuxize.com/post/how-to-install-atom-text-editor-on-ubuntu-20-04/

https://www.gophercoders.com/install/atom/configure/

https://snapcraft.io/install/liteide-tpaw/ubuntu

https://elearning.wsldp.com/pcmagazine/install-liteide-on-ubuntu/

Um vernünftig Go Programme entwicklen zu können, braucht man eine vernünftige IDE. Neben IntelliJ (leider nicht in der Community-Edition) gibt es noch LiteIDE sowie den beliebten Editor Atom (wahrscheinlich gibt es noch viele andere). Ich verwenden den Atom-Editor.

Abbildung 10: Atom Editor

Nachdem das Plugin eingerichtet ist, kann man das Projekt bereits editieren.

Damit alles funktioniert muß natürlich go selbst vorhanden sein.

Abbildung 11: Atom Konfiguriert

Man kann aber auch LiteIDE benutzen.

Abbildung 12: LiteIDE

Womit man am liebsten arbeitet, muß jeder für sich selbst entscheiden.

Was auch immer man installiert, meine Rat ist die Applikation nicht über snap zu installieren (wegen des Isolation-Levels), sondern direkt. Die Interaktion zwischen den Snaps (setzen von Variablen usw.) funktioniert sonst unter Umständen nicht wie erwartet.

Am Beispiel von LiteIDE. Siehe auch https://elearning.wsldp.com/pcmagazine/install-liteide-on-ubuntu/

Download der richtigen Datei, entpacken nach /usr/local. Danach wird für den bequement Start eine desktop Datei erzeugt.

[Desktop Entry]

Version=1.0

Name=LiteIDE

Comment="IDE for editing and building projects written in the Go programming language"

GenericName=GoIDE

Exec=/usr/local/liteide/bin/liteide

Terminal=false

Type=Application

Icon=/usr/local/liteide/share/liteide/welcome/images/liteide128.xpm

Categories=Development;

StartupNotify=true

Diese Datei wird dann mit dem Statement

alfred@bureau:~$ cd Schreibtisch/

alfred@bureau:~/Schreibtisch$ ll

insgesamt 480

drwx-----T 2 alfred alfred 4096 Okt 10 09:23 ./

drwx-----T 49 alfred alfred 479232 Okt 10 09:24 ../

-rw-r--r-- 1 alfred alfred 361 Okt 10 09:22 LiteIDE.desktop

alfred@bureau:~/Schreibtisch$ sudo desktop-file-install LiteIDE.desktop

alfred@bureau:~/Schreibtisch$

nach

alfred@bureau:~/Schreibtisch$ ll /usr/share/applications/LiteIDE.desktop

-rw-r--r-- 1 root root 361 Okt 10 09:30 /usr/share/applications/LiteIDE.desktop

alfred@bureau:~/Schreibtisch$

kopiert, und leicht modifiziert. Ab dann kann man im Launcher diese Applikation zu den Favoriten hinzufügen.

Abbildung 13: LiteIDE zu Favoriten hinzufügen

Ab jetzt ist der Start leicht.

Abbildung 14: LiteIDE System Environment

Jetzt muß nur noch das Systemenvironment richtig sein, dann steht dem Entwickeln, testen, Debuggen nichts mehr im Wege.

Lens – the Kubernetes IDE

Inspiration:

https://k8slens.dev/

https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/

https://dev.to/anaisintech/getting-started-with-kubernetes-kubectl-and-microk8s-on-ubuntu-pko

https://microk8s.io/docs/services-and-ports

https://microk8s.io/docs/troubleshooting

Lens ist eine recht vernünftige IDE um mit Kubernetes zu arbeiten. Unter Ubuntu kann das direkt aus dem Software-Repository installiert werden.

Abbildung 15: Lens - Installation unter Ubuntu

Damit das Tool funktionieren kann, brauchen wir auf unserem lokalen Rechner kubectl.

alfred@bureau:~$ snap install kubectl --classic

kubectl version --client

kubectl 1.22.4 aus Canonical✓ installiert

Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.4", GitCommit:"b695d79d4f967c403a96986f1750a35eb75e75f1", GitTreeState:"clean", BuildDate:"2021-11-18T02:34:11Z", GoVersion:"go1.16.10", Compiler:"gc", Platform:"linux/amd64"}

alfred@bureau:~$ kubectl cluster-info

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

The connection to the server localhost:8080 was refused - did you specify the right host or port?

alfred@bureau:~$

Nun fehlt noch die Konfig. Die holen wir uns aus dem existierenden Cluster.

alfred@pc1:~/.kube$ microk8s config > config

alfred@pc1:~/.kube$ cat config

apiVersion: v1

clusters:

- cluster:

certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREekNDQWZlZ0F3SUJBZ0lVSmdyRlRBbmh6SjBXZThxakdFVFErMDFHYWJRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0Z6RVZNQk1HQTFVRUF3d01NVEF1TVRVeUxqRTRNeTR4TUI0WERUSXhNVEV5TXpBM016WXpPRm9YRFRNeApNVEV5TVRBM016WXpPRm93RnpFVk1CTUdBMVVFQXd3TU1UQXVNVFV5TGpFNE15NHhNSUlCSWpBTkJna3Foa2lHCjl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUExYVQ3Y2hkaHUwRzNwc2EvYjdUZHU3UFRoK3l5ek41TmkyRzMKaDBmbUwweUpqOExHL2FGSm9QdW1RRVBNdWYvRFVWUVQrbmFDdjJrdmtjSjFtdlgzdlhBb1hwMHR1S1FLOEFxawp2a1UwNU8rb1pScERJZFQvUUlSSm1Ba3UraHJwWHJscE1TeHRjdUxWZ3hjc0ZpWGU4ZzVxYXVEOGVSMzZudlN1CnJzK3o2RE1rS1lvVzdlYWp4L0loNDBsNkNNY3VVVDNPVnIyTEh0Sm0vM2NaKzZlVEQ3aTNFUjA3TlVmcVk5Y1oKaXcvUzRlSWtUVC9HRjFwZG5mbUtYdnB4NnFKaWVjWDVPanEzM1l5UitBZ3FPdnlBNzVIOWp5SEtqOTdDekdqdgp5ZGJjcmJtYW5SVU1FditlVWVyVDFMeW13SDNFSDk0OUM5VXhkV2pXZTEydVNZaS9YUUlEQVFBQm8xTXdVVEFkCkJnTlZIUTRFRmdRVTZhZjNUSWc3ZjV6N21kYno3NGtCZ2FHVFRMWXdId1lEVlIwakJCZ3dGb0FVNmFmM1RJZzcKZjV6N21kYno3NGtCZ2FHVFRMWXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQwpBUUVBUHppRnhmU29EWjdscGt1TGF0a0VVdy9vUGg0YlIyM2g4bXoyTkpWc0p2NW92bXIvaVA2NXdKa2tKMDVRCnliays0Y0d3TnlFemdQVTBJaFRERnpRVU0vWFRyQlEwZG0zSGFsSHNHVE1iK05Ua3V2REFJQlUzUVhyOWJJYlkKbzVuaUl0SWVRdGR4dVBTbXYwZ2laQ3l6bWpQZTVOcnlRc05ONFQyeFp2UUcrcDhkVnBzWmZhVTd0NERZZlZhbwppTW5KVFJNazdKS3c5M3Y5VDRRcHdsMWthQnlFcjhYUkhWOGxUQUVPa1djOGdlNWx5NlEza1ZGeVAzYUx4djJqCll6K3BxVExncENRQ3JJRlVqcVhpTkdUeUtlQXp1TWlFaWFrclRnODhNZlhaTVdHc0l5NW5GdWt0clpYT0xhL3QKZVo2NEorKzg2SzRrQXRidVdNQUJORjNObEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==

server: https://192.168.0.201:16443

name: microk8s-cluster

contexts:

- context:

cluster: microk8s-cluster

user: admin

name: microk8s

current-context: microk8s

kind: Config

preferences: {}

users:

- name: admin

user:

token: WDZKL1VldzE4aFVNVm03amdmVFR5Z1h2UzNZcTZjeWxNUzVud2ZPcWR5WT0K

alfred@pc1:~/.kube$

Wir kopieren die config auf unseren Rechner in den Ordner ${HOME}/.kube.

alfred@bureau:~/.kube$ ls -lisa

insgesamt 480

1706699 4 drwxr-x--- 2 alfred alfred 4096 Nov 26 11:52 .

1311228 472 drwx-----T 52 alfred alfred 479232 Nov 26 11:52 ..

1705911 4 -rw-rw-r-- 1 alfred alfred 1874 Nov 26 11:51 config

alfred@bureau:~/.kube$

alfred@bureau:~/.kube$ kubectl config get-clusters

NAME

microk8s-cluster

alfred@bureau:~/.kube$

alfred@bureau:~/.kube$ kubectl cluster-info

Kubernetes control plane is running at https://192.168.0.201:16443

CoreDNS is running at https://192.168.0.201:16443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

alfred@bureau:~/.kube$

Nun funktioniert die Kommand-Line-Version. Wir installieren noch die bash-completion (das spart viel Tipparbeit).

alfred@bureau:~$ sudo apt-get install bash-completion

Paketlisten werden gelesen… Fertig

Abhängigkeitsbaum wird aufgebaut… Fertig

Statusinformationen werden eingelesen… Fertig

bash-completion ist schon die neueste Version (1:2.11-2ubuntu1).

bash-completion wurde als manuell installiert festgelegt.

0 aktualisiert, 0 neu installiert, 0 zu entfernen und 0 nicht aktualisiert.

alfred@bureau:~$

alfred@bureau:~$ echo 'source <(kubectl completion bash)' >>~/.bashrc

alfred@bureau:~$ sudo su -l

root@bureau:~# kubectl completion bash >/etc/bash_completion.d/kubectl

root@bureau:~# exit

Abgemeldet

alfred@bureau:~$ echo 'alias k=kubectl' >>~/.bashrc

echo 'complete -F __start_kubectl k' >>~/.bashrc

alfred@bureau:~$

Nun loggen wir uns neu ein. Somit funktioniert der Alias und der Zugriff auf den Cluster von außen.

alfred@bureau:~$ k get all

NAME READY STATUS RESTARTS AGE

pod/webserver-depl-bff4d4675-8v9nz 1/1 Running 2 (179m ago) 18h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 3d3h

service/webserver-svc ClusterIP 10.152.183.121 <none> 443/TCP,80/TCP 38h

NAME READY UP-TO-DATE AVAILABLE AGE

deployment.apps/webserver-depl 1/1 1 1 38h

NAME DESIRED CURRENT READY AGE

replicaset.apps/webserver-depl-cf898f654 0 0 0 38h

replicaset.apps/webserver-depl-98f95758b 0 0 0 38h

replicaset.apps/webserver-depl-646544ddfd 0 0 0 37h

replicaset.apps/webserver-depl-dd8c746d6 0 0 0 37h

replicaset.apps/webserver-depl-86c6d7549f 0 0 0 37h

replicaset.apps/webserver-depl-b485f9695 0 0 0 19h

replicaset.apps/webserver-depl-c8599c9f5 0 0 0 19h

replicaset.apps/webserver-depl-bff4d4675 1 1 1 18h

alfred@bureau:~$

Jetzt können wir den Cluster in der Lens einrichten.

Abbildung 16: Lens - Cluster hinzufügen

Es wird die config aus ${HOME}/.kube/config verwendet.

Abbildung 17: Lens - Cluster Ansicht

Nun ist Lens mit dem Cluster verbunden. Somit kann komfortabel vom eigenen Rechner aus gearbeitet werden.

Alternativ ist natürlich auch das Kubernet Dashboard als Webapplikation vorhanden.

Abbildung 18: Kubernetes Dashboard

Somit kann der Cluster bequem verwaltet und diagnostiziert werden.

Wie sicher das exponieren der API nach aussen ist, weiß ich noch nicht. Im Internet sollten der Cluster durch die Firewall geschützt sein (nur die Ingress-Ports sichtbar).

Generell sollte bei Einsatz im Internet der Firewall große Beachtung zuteil werden. Lokal zu Hause ist die Architektur etwas einfacher.

Abbildung 19: Netzwerk Architektur

Hier ist nur der Ingress nach aussen sichtbar. Alles andere kann von aussen nicht erreicht werden. In Richtung Intranet ist alles erlaubt.

Einfacher WebServer mit Go

Inspiration:

https://golang.org/doc/articles/wiki/

https://www.socketloop.com/tutorials/golang-get-hardware-information-such-as-disk-memory-and-cpu-usage

https://www.freecodecamp.org/news/how-to-remove-images-in-docker/

https://docs.docker.com/engine/reference/commandline/image_pull/

Dieses Kapitel beschreibt einen einfachen WebServer mit Go. Das ist ein richtig funktionierender WebService. Dieser Service wird alle Variablen und die Systemzeit des Containers wiedergeben.

Zuerst brauchen wir ein einfaches Programm.

// Ausgabe eines einfachen HTML-Files und ersetzen von Schlüsselwörtern mit konkreten Informationen

// Inspiration https://golang.org/doc/articles/wiki/

// https://www.socketloop.com/tutorials/golang-get-hardware-information-such-as-disk-memory-and-cpu-usage

//

// alfred@monitoring:~/GetInfo$ go get github.com/shirou/gopsutil/...

package main

import (

"bufio"

"fmt"

"io/ioutil"

"log"

"net/http"

"os"

"runtime"

"strconv"

"strings"

"time"

"github.com/shirou/gopsutil/disk"

"github.com/shirou/gopsutil/host"

"github.com/shirou/gopsutil/mem"

)

type Page struct {

Title string

Body []byte

}

func loadError(xerr string) (xhtml string) {

var xs string

currentTime := time.Now()

xtext, err := ioutil.ReadFile("error.html")

if err != nil {

log.Fatal("Error File kann nicht geöffnet werden " + err.Error())

}

xs = strings.Replace(string(xtext), "%SYSTIME%", currentTime.Format("2006-01-02 15:04:05 Monday"), -1)

xs = strings.Replace(string(xs), "%SYSTEMFEHLER%", xerr, -1)

return xs

}

func loadPage(title string) (*Page, error) {

var xhtml string

var xtmp string

var xenv string

const GB = 1073741824

filename := title + ".html"

log.Println(filename)

xhtml = ""

currentTime := time.Now()

f, err := os.Open(filename)

if err != nil {

log.Println(filename+" kann nicht geöffnet werden: ", err)

xhtml = loadError(err.Error())

} else {

scanner := bufio.NewScanner(f)

for scanner.Scan() {

xtmp = scanner.Text()

if strings.Contains(xtmp, "%SYSTIME%") {

xtmp = strings.Replace(xtmp, "%SYSTIME%", currentTime.Format("2006-01-02 15:04:05 Monday"), -1)

}

if strings.Contains(xtmp, "%OSENVIRONMENT%") {

xenv = ""

for _, pair := range os.Environ() {

variable := strings.Split(pair, "=")

xenv += variable[0] + "=>" + variable[1] + "<br>"

}

xtmp = strings.Replace(xtmp, "%OSENVIRONMENT%", xenv, -1)

}

if strings.Contains(xtmp, "%DISKUSAGE%") {

diskStat, err := disk.Usage("/")

if err != nil {

log.Println(" Plattenbelegung kann nicht gelesen werden: ", err)

}

xenv = "Pfad:" + diskStat.Path +

"<br>FSTYPE:" + diskStat.Fstype +

"<br>Total disk space:" + fmt.Sprintf("%5.1f", float64(diskStat.Total)/GB) +

" GB<br>Free disk space:" + fmt.Sprintf("%5.1f", float64(diskStat.Free)/GB) +

" GB<br>Used disk space:" + fmt.Sprintf("%5.1f", float64(diskStat.Used)/GB) +

" GB<br>Used GB Prozent:" + fmt.Sprintf("%3.1f", diskStat.UsedPercent) +

"<br>Used Inodes:" + strconv.FormatUint(diskStat.InodesUsed, 10) +

"<br>Used Inodes Prozent:" + fmt.Sprintf("%3.1f", diskStat.InodesUsedPercent)

xtmp = strings.Replace(xtmp, "%DISKUSAGE%", xenv, -1)

}

if strings.Contains(xtmp, "%HOSTINFO%") {

// host or machine kernel, uptime, platform Info

hostStat, err := host.Info()

if err != nil {

log.Println(" Hostinformation kann nicht gelesen werden: ", err)

}

xenv = "Hostname: " + hostStat.Hostname +

"<br>OS: " + hostStat.OS +

"<br>Platform: " + hostStat.Platform +

"<br>Host ID(uuid): " + hostStat.HostID +

"<br>Uptime (sec): " + strconv.FormatUint(hostStat.Uptime, 10) +

"<br>Number of processes running: " + strconv.FormatUint(hostStat.Procs, 10)

xtmp = strings.Replace(xtmp, "%HOSTINFO%", xenv, -1)

}

if strings.Contains(xtmp, "%MEMINFO%") {

runtimeOS := runtime.GOOS

vmStat, err := mem.VirtualMemory()

if err != nil {

log.Println(" Memoryinformation kann nicht gelesen werden: ", err)

}

xenv = "OS : " + runtimeOS +

"<br>Total memory: " + fmt.Sprintf("%5.1f", float64(vmStat.Total)/GB) +

" GB<br>Free memory: " + fmt.Sprintf("%5.1f", float64(vmStat.Free)/GB) +

" GB<br>Used memory: " + fmt.Sprintf("%5.1f", float64(vmStat.Used)/GB) +

" GB<br>Percentage used memory: " + strconv.FormatFloat(vmStat.UsedPercent, 'f', 2, 64)

xtmp = strings.Replace(xtmp, "%MEMINFO%", xenv, -1)

}

xhtml += xtmp

}

if err := scanner.Err(); err != nil {

log.Println(filename+" kann nicht gelesen werden: %s\n", err)

xhtml = loadError(err.Error())

}

}

defer f.Close()

return &Page{Title: title, Body: []byte(xhtml)}, nil

}

func viewHandler(w http.ResponseWriter, r *http.Request) {

title := r.URL.Path[len("/view/"):]

p, _ := loadPage(title)

fmt.Fprintf(w, "%s", p.Body)

}

func main() {

log.Println("Main Started")

http.HandleFunc("/view/", viewHandler)

log.Fatal(http.ListenAndServe(":8080", nil))

log.Println("Main End")

}

Um die Abhängigkeiten richtig auflösen zu können, braucht go auch ein module-File. Diese Dateien werden vom SDK LiteIDE selbstständig erzeugt.

alfred@monitoring:~/GetInfo$ cat go.mod

module GetInfo

go 1.16

require (

github.com/StackExchange/wmi v1.2.1 // indirect

github.com/shirou/gopsutil v3.21.9+incompatible // indirect

github.com/tklauser/go-sysconf v0.3.9 // indirect

golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect

)

alfred@monitoring:~/GetInfo$ cat go.sum

github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=

github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=

github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=

github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=

github.com/shirou/gopsutil v3.21.9+incompatible h1:LTLpUnfX81MkHeCtSrwNKZwuW5Id6kCa7/P43NdcNn4=

github.com/shirou/gopsutil v3.21.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=

github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=

github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=

github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=

github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=

golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=

golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

alfred@monitoring:~/GetInfo$

Dieses Programm zeigt nach dem Aufruf von <Link:8080/view/filename> das aufgerufene File <filename> an, und ersetzt gewisse Schlüsselwörter durch konkreten Text. In unserem Beispiel habe ich das File hello.html mit OpenOffice vorbereitet.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

<head>

<meta http-equiv="content-type" content="text/html; charset=utf-8"/>

<title></title>

<meta name="generator" content="LibreOffice 7.1.6.2 (Linux)"/>

<meta name="author" content="Alfred Sabitzer"/>

<meta name="created" content="2021-10-07T16:33:50.696105947"/>

<meta name="changedby" content="Alfred Sabitzer"/>

<meta name="changed" content="2021-10-07T16:36:08.816466992"/>

<style type="text/css">

@page { size: 21cm 29.7cm; margin: 2cm }

p { margin-bottom: 0.25cm; line-height: 115%; background: transparent }

td p { orphans: 0; widows: 0; background: transparent }

</style>

</head>

<body lang="de-AT" link="#000080" vlink="#800000" dir="ltr"><p style="margin-bottom: 0cm; line-height: 100%">

<p style="margin-bottom: 0cm; line-height: 100%"><br/>

</p>

<table width="100%" cellpadding="4" cellspacing="0">

<col width="64*"/>

<col width="64*"/>

<col width="64*"/>

<col width="64*"/>

<tr valign="top">

<td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

OSENVIRONMENT</p>

</td>

<td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

DISKUSAGE</p>

</td>

<td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

HOSTINFO</p>

</td>

<td width="25%" style="border: 1px solid #000000; padding: 0.1cm"><p>

MEMINFO</p>

</td>

</tr>

<tr valign="top">

<td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

%OSENVIRONMENT%

</p>

</td>

<td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

%DISKUSAGE%

</p>

</td>

<td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p>

%HOSTINFO%</p>

</td>

<td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0.1cm"><p>

%MEMINFO%</p>

</td>

</tr>

</table>

<p style="margin-bottom: 0cm; line-height: 100%"><br/>

</p>

<p style="margin-bottom: 0cm; line-height: 100%">Es ist <span style="background: #c0c0c0"><sdfield type=DATETIME sdval="44476,6908896088" sdnum="3079;3079;T. MMMM JJJJ">%SYSTIME%</sdfield></span></p>

</body>

</html>

Nun kompilieren wir das File.

alfred@monitoring:~/GetInfo$ go build GetInfo.go

go: downloading github.com/shirou/gopsutil v3.21.9+incompatible

go: extracting github.com/shirou/gopsutil v3.21.9+incompatible

go: downloading golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac

go: extracting golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac

go: finding github.com/shirou/gopsutil v3.21.9+incompatible

go: finding golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac

alfred@monitoring:~/GetInfo$ ll

total 7716

drwxrwxr-x 2 alfred alfred 4096 Oct 15 20:30 ./

drwxr-xr-x 22 alfred alfred 4096 Oct 11 07:30 ../

-rwxrwxr-x 1 alfred alfred 8009715 Oct 15 20:30 GetInfo*

-rw------- 1 alfred alfred 4473 Oct 10 21:34 GetInfo.go

-rw------- 1 alfred alfred 81 Oct 10 21:48 dockerfile

-rw------- 1 alfred alfred 1434 Oct 10 21:19 error.html

-rw-rw-r-- 1 alfred alfred 0 Oct 11 07:28 getinfo.tar

-rw------- 1 alfred alfred 263 Oct 10 10:04 go.mod

-rw------- 1 alfred alfred 1314 Oct 10 10:04 go.sum

-rw------- 1 alfred alfred 3180 Oct 10 21:32 hello.html

alfred@monitoring:~/GetInfo$ ./GetInfo

Ein kurzer Test auf http://monitoring:8080/view/hello ergibt

Abbildung 20: GetInfo Ausgabe

Das heist das Programm funktioniert. Die Logausgabe sieht aus wie folgt:

alfred@monitoring:~/GetInfo$ ./GetInfo

2021/10/10 21:42:01 Main Started

2021/10/10 21:42:16 hello.html

^C

alfred@monitoring:~/GetInfo$

Jetzt brauchen wir noch ein Docker-File

alfred@monitoring:~/GetInfo$ cat dockerfile

# syntax=docker/dockerfile:1

# Alpine is chosen for its small footprint

# compared to Ubuntu

FROM golang:1.16-alpine

WORKDIR /app

# Download necessary Go modules

COPY go.mod ./

COPY go.sum ./

RUN go mod download

COPY *.go ./

COPY hello.html ./

COPY error.html ./

RUN go build -o /GetInfo

EXPOSE 8080

CMD [ "/GetInfo" ]

alfred@monitoring:~/GetInfo$

um den Container zu bauen.

alfred@monitoring:~/GetInfo$ docker build . -t docker.registry:5000/getinfo:20211015

Sending build context to Docker daemon 16.38kB

Step 1/11 : FROM golang:1.16-alpine

1.16-alpine: Pulling from library/golang

552d1f2373af: Pull complete

eb74239f2a0c: Pull complete

2a8ec004bf6b: Pull complete

12f1fa1a3b20: Pull complete

5c12166b7f9d: Pull complete

Digest: sha256:05df7ff684a2cb06aa207be14a78918cbc3285ed3b965974979e575d59de1c11

Status: Downloaded newer image for golang:1.16-alpine

---> d2f4346f5656

Step 2/11 : WORKDIR /app

---> Running in 9b6c3b098321

Removing intermediate container 9b6c3b098321

---> 738bd59f63db

Step 3/11 : COPY go.mod ./

---> cb6306d5fdf2

Step 4/11 : COPY go.sum ./

---> f7eeef9ebda2

Step 5/11 : RUN go mod download

---> Running in efbe89b22883

Removing intermediate container efbe89b22883

---> cb738e90d4d7

Step 6/11 : COPY *.go ./

---> c5ab2f4e9e5c

Step 7/11 : COPY hello.html ./

---> 8e6738e25799

Step 8/11 : COPY error.html ./

---> 5c9506b2dc3f

Step 9/11 : RUN go build -o /GetInfo.go

---> Running in 3ad675aeb626

Removing intermediate container 3ad675aeb626

---> 0abfe5a9d849

Step 10/11 : EXPOSE 8080

---> Running in 80f728420378

Removing intermediate container 80f728420378

---> b9c5a76c55d2

Step 11/11 : CMD [ "/GetInfo" ]

---> Running in 71d6f27934d2

Removing intermediate container 71d6f27934d2

---> 4fae231152c2

Successfully built 4fae231152c2

Successfully tagged docker.registry:5000/getinfo:20211015

alfred@monitoring:~/GetInfo$

alfred@monitoring:~/GetInfo$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

docker.registry:5000/getinfo 20211015 4fae231152c2 39 seconds ago 317MB

golang 1.16-alpine d2f4346f5656 2 days ago 296MB

alfred@monitoring:~/GetInfo$

Nun starten wir den Container im Vordergrund um den Output zu testen.

alfred@monitoring:~/GetInfo$ docker run --publish 8080:8080 docker.registry:5000/getinfo:20211015

2021/10/15 19:22:13 Main Started

2021/10/15 19:22:40 hello.html

Der Output im Browser ist wie folgt:

Abbildung 21: Docker Test Output

Der Container ist am erwarteten Port erreichbar. Die Anzeige der Werte sind jetzt aus Sicht des Containers, wie man an den Variablen und am Environment sehen kann.

Wir überprüfen die Struktur des gebauten docker-files.

alfred@monitoring:~/GetInfo$ docker save docker.registry:5000/getinfo:20211015 > getinfo.tar

alfred@monitoring:~/GetInfo$ tar -tvf getinfo.tar

-rw-r--r-- 0/0 7059 2021-10-15 21:22 0fca605bf404203fe0e1d41e306687bf70b8cdb4392c41363af0f6712ae28959.json

drwxr-xr-x 0/0 0 2021-10-15 21:22 11ada22ca4865de4368da5b2cdea81c34f3fc62b60e62f00329f839e1e51a431/

-rw-r--r-- 0/0 3 2021-10-15 21:22 11ada22ca4865de4368da5b2cdea81c34f3fc62b60e62f00329f839e1e51a431/VERSION

-rw-r--r-- 0/0 406 2021-10-15 21:22 11ada22ca4865de4368da5b2cdea81c34f3fc62b60e62f00329f839e1e51a431/json

-rw-r--r-- 0/0 5605376 2021-10-15 21:22 11ada22ca4865de4368da5b2cdea81c34f3fc62b60e62f00329f839e1e51a431/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 1a41da37655e1ae130db0ff268801f26989f408ad4e24fee81b18252883fe835/

-rw-r--r-- 0/0 3 2021-10-15 21:22 1a41da37655e1ae130db0ff268801f26989f408ad4e24fee81b18252883fe835/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 1a41da37655e1ae130db0ff268801f26989f408ad4e24fee81b18252883fe835/json

-rw-r--r-- 0/0 2560 2021-10-15 21:22 1a41da37655e1ae130db0ff268801f26989f408ad4e24fee81b18252883fe835/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 278bc14dbdd9bd52e524b98375e76f5b0f975f02167b6a7e8168184e577a8d17/

-rw-r--r-- 0/0 3 2021-10-15 21:22 278bc14dbdd9bd52e524b98375e76f5b0f975f02167b6a7e8168184e577a8d17/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 278bc14dbdd9bd52e524b98375e76f5b0f975f02167b6a7e8168184e577a8d17/json

-rw-r--r-- 0/0 5632 2021-10-15 21:22 278bc14dbdd9bd52e524b98375e76f5b0f975f02167b6a7e8168184e577a8d17/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 37df72a20bd40f12a164a419626ffb20b4554c96983ec36b78024136e65f15c2/

-rw-r--r-- 0/0 3 2021-10-15 21:22 37df72a20bd40f12a164a419626ffb20b4554c96983ec36b78024136e65f15c2/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 37df72a20bd40f12a164a419626ffb20b4554c96983ec36b78024136e65f15c2/json

-rw-r--r-- 0/0 2560 2021-10-15 21:22 37df72a20bd40f12a164a419626ffb20b4554c96983ec36b78024136e65f15c2/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 55a816cb614fc0754c9af49269934861de9251688789bc4a3e9f304ac1059e10/

-rw-r--r-- 0/0 3 2021-10-15 21:22 55a816cb614fc0754c9af49269934861de9251688789bc4a3e9f304ac1059e10/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 55a816cb614fc0754c9af49269934861de9251688789bc4a3e9f304ac1059e10/json

-rw-r--r-- 0/0 3584 2021-10-15 21:22 55a816cb614fc0754c9af49269934861de9251688789bc4a3e9f304ac1059e10/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 5fe6d80a697427a6b315445103e12f8e5e14a6f9f071e119d9da5aa7787dedf0/

-rw-r--r-- 0/0 3 2021-10-15 21:22 5fe6d80a697427a6b315445103e12f8e5e14a6f9f071e119d9da5aa7787dedf0/VERSION

-rw-r--r-- 0/0 1432 2021-10-15 21:22 5fe6d80a697427a6b315445103e12f8e5e14a6f9f071e119d9da5aa7787dedf0/json

-rw-r--r-- 0/0 10244608 2021-10-15 21:22 5fe6d80a697427a6b315445103e12f8e5e14a6f9f071e119d9da5aa7787dedf0/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 678b476f25ed3f1e67caefc50b99dba99e907d2b692e7198f4f7982b1781dd97/

-rw-r--r-- 0/0 3 2021-10-15 21:22 678b476f25ed3f1e67caefc50b99dba99e907d2b692e7198f4f7982b1781dd97/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 678b476f25ed3f1e67caefc50b99dba99e907d2b692e7198f4f7982b1781dd97/json

-rw-r--r-- 0/0 297840128 2021-10-15 21:22 678b476f25ed3f1e67caefc50b99dba99e907d2b692e7198f4f7982b1781dd97/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 916863cb53dae4e13619f82e13ddeded5a2a2f552e5ac0fad747eff6cafe57ea/

-rw-r--r-- 0/0 3 2021-10-15 21:22 916863cb53dae4e13619f82e13ddeded5a2a2f552e5ac0fad747eff6cafe57ea/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 916863cb53dae4e13619f82e13ddeded5a2a2f552e5ac0fad747eff6cafe57ea/json

-rw-r--r-- 0/0 2048 2021-10-15 21:22 916863cb53dae4e13619f82e13ddeded5a2a2f552e5ac0fad747eff6cafe57ea/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 951da7c93bcb5858e3370883aa7ebb5bc7e54ad5a51a9124350c4bf638dfce39/

-rw-r--r-- 0/0 3 2021-10-15 21:22 951da7c93bcb5858e3370883aa7ebb5bc7e54ad5a51a9124350c4bf638dfce39/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 951da7c93bcb5858e3370883aa7ebb5bc7e54ad5a51a9124350c4bf638dfce39/json

-rw-r--r-- 0/0 3584 2021-10-15 21:22 951da7c93bcb5858e3370883aa7ebb5bc7e54ad5a51a9124350c4bf638dfce39/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 c738b6de5b0a35e613204439d94e3142a49ed7c77568b1372a2ac8e44add5431/

-rw-r--r-- 0/0 3 2021-10-15 21:22 c738b6de5b0a35e613204439d94e3142a49ed7c77568b1372a2ac8e44add5431/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 c738b6de5b0a35e613204439d94e3142a49ed7c77568b1372a2ac8e44add5431/json

-rw-r--r-- 0/0 759808 2021-10-15 21:22 c738b6de5b0a35e613204439d94e3142a49ed7c77568b1372a2ac8e44add5431/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 d0cabd374001156bcc1eb54fb096bcb80aa5f94e14ed6c73069b64e31f554deb/

-rw-r--r-- 0/0 3 2021-10-15 21:22 d0cabd374001156bcc1eb54fb096bcb80aa5f94e14ed6c73069b64e31f554deb/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 d0cabd374001156bcc1eb54fb096bcb80aa5f94e14ed6c73069b64e31f554deb/json

-rw-r--r-- 0/0 11713024 2021-10-15 21:22 d0cabd374001156bcc1eb54fb096bcb80aa5f94e14ed6c73069b64e31f554deb/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 e3c9af4387b692800410b9c839fd7964352a56504f884d7975fff36dc077c8c4/

-rw-r--r-- 0/0 3 2021-10-15 21:22 e3c9af4387b692800410b9c839fd7964352a56504f884d7975fff36dc077c8c4/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 e3c9af4387b692800410b9c839fd7964352a56504f884d7975fff36dc077c8c4/json

-rw-r--r-- 0/0 6656 2021-10-15 21:22 e3c9af4387b692800410b9c839fd7964352a56504f884d7975fff36dc077c8c4/layer.tar

drwxr-xr-x 0/0 0 2021-10-15 21:22 eb528dd90d9b2bc0ad36512a3ce210982ca2b2100be249c54335bb4c86b6c520/

-rw-r--r-- 0/0 3 2021-10-15 21:22 eb528dd90d9b2bc0ad36512a3ce210982ca2b2100be249c54335bb4c86b6c520/VERSION

-rw-r--r-- 0/0 482 2021-10-15 21:22 eb528dd90d9b2bc0ad36512a3ce210982ca2b2100be249c54335bb4c86b6c520/json

-rw-r--r-- 0/0 2560 2021-10-15 21:22 eb528dd90d9b2bc0ad36512a3ce210982ca2b2100be249c54335bb4c86b6c520/layer.tar

-rw-r--r-- 0/0 1151 1970-01-01 01:00 manifest.json

-rw-r--r-- 0/0 114 1970-01-01 01:00 repositories

alfred@monitoring:~/GetInfo$ ll *.tar

-rw-rw-r-- 1 alfred alfred 326244864 Oct 15 21:27 getinfo.tar

alfred@monitoring:~/GetInfo$

Wir können aber auch das Dockerfile selbst exportieren.

alfred@monitoring:~/GetInfo$ docker ps -all

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

acdf3516a1ce docker.registry:5000/getinfo:20211015 "/GetInfo" 10 minutes ago Exited (2) 5 minutes ago funny_keller

alfred@monitoring:~/GetInfo$ docker export acdf3516a1ce > getinfo.tar

alfred@monitoring:~/GetInfo$

alfred@monitoring:~/GetInfo$ ll -h *.tar

-rw-rw-r-- 1 alfred alfred 311M Oct 15 21:32 getinfo.tar

alfred@monitoring:~/GetInfo$

alfred@monitoring:~/GetInfo$ tar -tvf getinfo.tar

Mit diesen Befehlen kann man überprüfen was alles im Container drinnenn ist (und das ist eine ganze Menge).

Nun pushen wir den Container in das remote-Repository. Es werden natürlich alle Layer gepushed (in dem Falle gab es die darunterliegenden Layer noch nicht).

alfred@monitoring:~/GetInfo$ docker push docker.registry:5000/getinfo:20211015

The push refers to repository [docker.registry:5000/getinfo]

cae68116d3ec: Pushed

dd68a1e5bd54: Pushed

00e2dc9bf745: Pushed

5a8d0b1f55c0: Pushed

8f352be1ccfe: Pushed

729e17fedb70: Pushed

08ae3e8fdb37: Pushed

6fe1ba639418: Pushed

fa0d2ce7179e: Pushed

092886e29cb5: Pushed

c8d42fc353a6: Pushed

6a91c97933c9: Pushed

ee420dfed78a: Pushed

20211015: digest: sha256:aa82ba777936d4cec354c0b9c5ece353751a96c9717375cfd58d3ed2beea5cf4 size: 3030

alfred@monitoring:~/GetInfo$

Kurze Kontrolle, ob der Container wirklich im Remote Repository gelandet ist.

alfred@monitoring:~/GetInfo$ curl docker.registry:5000/v2/_catalog

{"repositories":["getinfo","hello-world"]}

alfred@monitoring:~/GetInfo$ curl docker.registry:5000/v2/getinfo/tags/list

{"name":"getinfo","tags":["20211015"]}

alfred@monitoring:~/GetInfo$

Abbildung 22: Longhorn Plattengröße

Auch in der Longhorn-Anzeige kann man schön sehen, dass dieses Volume nicht mehr leer ist, und jetzt bereits ca. 1GB Platz braucht.

Nun erzeugen wir ein yaml-File.

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: getinfo

#namespace: default

spec:

selector:

matchLabels:

app: getinfo

replicas: 1

template:

metadata:

labels:

app: getinfo

annotations:

sidecar.istio.io/inject: "false"

spec:

containers:

- name: getinfo

image: docker.registry:5000/getinfo:20211015

# resource limits

resources:

requests:

memory: "24Mi"

cpu: "500m" # half vcpu

limits:

memory: "64Mi"

cpu: "1000m" # one vcpu

env:

# currently no env vars used for this container

- name: FOO

value: bar

# check for lifetime liveness, restarts if dead

livenessProbe:

exec:

command:

- ls

initialDelaySeconds: 5

periodSeconds: 10

# check for initial readyness

readinessProbe:

exec:

command:

- ls

initialDelaySeconds: 3

periodSeconds: 3

# pod bekommt diesselbe Zeitzone wie der darunterliegende Node

volumeMounts:

- name: tz-local

mountPath: /etc/localtime

volumes:

- name: tz-local

hostPath: # abhängig vom darunterliegenden OS. Das hier ist linux.

path: /etc/localtime

restartPolicy: Always

dnsPolicy: ClusterFirst

---

apiVersion: v1

kind: Service

metadata:

name: getinfo-service

#namespace: default

labels:

app: getinfo

spec:

ports:

# port=available to other containers

- port: 8080

name: hello

# targetPort=exposed from inside container

targetPort: 8080

protocol: TCP

selector:

app: getinfo

---

Dieses yaml wenden wir nun im Cluster an.

alfred@pc1:~$ k apply -f getinfo.yaml

deployment.apps/getinfo created

service/getinfo-service created

alfred@pc1:~$ kubectl get all

NAME READY STATUS RESTARTS AGE

pod/webserver-slainte-67494d5d7b-hj99r 1/1 Running 22 67d

pod/getinfo-6c84cff999-mtf5v 1/1 Running 0 89s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 67d

service/webserver-slainte ClusterIP 10.152.183.125 <none> 443/TCP 67d

service/getinfo-service ClusterIP 10.152.183.43 <none> 8080/TCP 6m12s

NAME READY UP-TO-DATE AVAILABLE AGE

deployment.apps/webserver-slainte 1/1 1 1 67d

deployment.apps/getinfo 1/1 1 1 6m12s

NAME DESIRED CURRENT READY AGE

replicaset.apps/webserver-slainte-67494d5d7b 1 1 1 67d

replicaset.apps/getinfo-6c84cff999 1 1 1 89s

replicaset.apps/getinfo-6659cf7655 0 0 0 6m12s

Die Anzeige im Dashboard zeigt uns auch einen gut laufenden Pod.

Abbildung 23: GetInfo Pod im Dashboard

Nun schauen wir, was der Pod zurückliefert:

alfred@pc1:~$ curl http://10.152.183.43:8080/view/hello

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title></title> <meta name="generator" content="LibreOffice 7.1.6.2 (Linux)"/> <meta name="author" content="Alfred Sabitzer"/> <meta name="created" content="2021-10-07T16:33:50.696105947"/> <meta name="changedby" content="Alfred Sabitzer"/> <meta name="changed" content="2021-10-07T16:36:08.816466992"/> <style type="text/css"> @page { size: 21cm 29.7cm; margin: 2cm } p { margin-bottom: 0.25cm; line-height: 115%; background: transparent } td p { orphans: 0; widows: 0; background: transparent } </style></head><body lang="de-AT" link="#000080" vlink="#800000" dir="ltr"><p style="margin-bottom: 0cm; line-height: 100%"><p style="margin-bottom: 0cm; line-height: 100%"><br/></p><table width="100%" cellpadding="4" cellspacing="0"> <col width="64*"/> <col width="64*"/> <col width="64*"/> <col width="64*"/> <tr valign="top"> <td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> OSENVIRONMENT</p> </td> <td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> DISKUSAGE</p> </td> <td width="25%" style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0.1cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> HOSTINFO</p></td> <td width="25%" style="border: 1px solid #000000; padding: 0.1cm"><p> MEMINFO</p> </td> </tr> <tr valign="top"> <td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> PATH=>/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br>HOSTNAME=>getinfo-6c84cff999-mtf5v<br>GOLANG_VERSION=>1.16.9<br>GOPATH=>/go<br>FOO=>bar<br>WEBSERVER_SLAINTE_PORT_443_TCP=>tcp://10.152.183.125:443<br>GETINFO_SERVICE_SERVICE_PORT=>8080<br>GETINFO_SERVICE_PORT=>tcp://10.152.183.43:8080<br>GETINFO_SERVICE_PORT_8080_TCP=>tcp://10.152.183.43:8080<br>GETINFO_SERVICE_PORT_8080_TCP_PROTO=>tcp<br>GETINFO_SERVICE_PORT_8080_TCP_ADDR=>10.152.183.43<br>WEBSERVER_SLAINTE_SERVICE_PORT=>443<br>WEBSERVER_SLAINTE_PORT=>tcp://10.152.183.125:443<br>KUBERNETES_SERVICE_HOST=>10.152.183.1<br>KUBERNETES_PORT=>tcp://10.152.183.1:443<br>KUBERNETES_PORT_443_TCP=>tcp://10.152.183.1:443<br>KUBERNETES_PORT_443_TCP_PORT=>443<br>WEBSERVER_SLAINTE_SERVICE_HOST=>10.152.183.125<br>WEBSERVER_SLAINTE_SERVICE_PORT_HTTPS=>443<br>WEBSERVER_SLAINTE_PORT_443_TCP_PROTO=>tcp<br>KUBERNETES_SERVICE_PORT=>443<br>KUBERNETES_PORT_443_TCP_PROTO=>tcp<br>GETINFO_SERVICE_SERVICE_PORT_HELLO=>8080<br>GETINFO_SERVICE_PORT_8080_TCP_PORT=>8080<br>WEBSERVER_SLAINTE_PORT_443_TCP_PORT=>443<br>WEBSERVER_SLAINTE_PORT_443_TCP_ADDR=>10.152.183.125<br>KUBERNETES_SERVICE_PORT_HTTPS=>443<br>KUBERNETES_PORT_443_TCP_ADDR=>10.152.183.1<br>GETINFO_SERVICE_SERVICE_HOST=>10.152.183.43<br>HOME=>/root<br> </p> </td><td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> Pfad:/<br>FSTYPE:<br>Total disk space: 58.3 GB<br>Free disk space: 46.1 GB<br>Used disk space: 9.7 GB<br>Used GB Prozent:17.4<br>Used Inodes:176202<br>Used Inodes Prozent:4.7 </p> </td> <td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: none; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0cm"><p> Hostname: getinfo-6c84cff999-mtf5v<br>OS: linux<br>Platform: alpine<br>Host ID(uuid): c1e70216-8eeb-4ca0-ac87-cf090dcc2c1f<br>Uptime (sec): 5085<br>Number of processes running: 1</p> </td> <td width="25%" style="border-top: none; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000; padding-top: 0cm; padding-bottom: 0.1cm; padding-left: 0.1cm; padding-right: 0.1cm"><p> OS : linux<br>Total memory: 7.6 GB<br>Free memory: 3.1 GB<br>Used memory: 2.0 GB<br>Percentage used memory: 26.48</p> </td> </tr> </table><p style="margin-bottom: 0cm; line-height: 100%"><br/></p><p style="margin-bottom: 0cm; line-height: 100%">Es ist <span style="background: #c0c0c0"><sdfield type=DATETIME sdval="44476,6908896088" sdnum="3079;3079;T. MMMM JJJJ">2021-10-15 20:00:26 Friday</sdf

alfred@pc1:~$

Sieht ganz gut aus:) Auch der Logoutput ist interessant:

alfred@pc1:~$ kubectl logs getinfo-6c84cff999-mtf5v

2021/10/15 19:58:40 Main Started

2021/10/15 20:00:26 hello.html

alfred@pc1:~$

Um den Output richtig zu sehen, ändere ich zu Testzwecken den Typ des Services auf einen NodePort um den Service von außen erreichen zu können.

alfred@pc1:~$ kubectl edit service getinfo-service

service/getinfo-service edited

alfred@pc1:~$

alfred@pc1:~$ kubectl get service

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 67d

webserver-slainte ClusterIP 10.152.183.125 <none> 443/TCP 67d

getinfo-service NodePort 10.152.183.43 <none> 8080:32304/TCP 19m

alfred@pc1:~$

Der Output aus dem inneren des Pods sieht am Browser auch gut aus.

Abbildung 24: GetInfo Anzeige aus dem Pod

Somit haben wir einen richtigen Service im Kubernetes-Cluster laufen. Wir kontrollieren den Pod noch von innen.

alfred@pc1:~$ kubectl exec getinfo-6c84cff999-mtf5v -it -- /bin/sh

/app # df -h

Filesystem Size Used Available Use% Mounted on

overlay 58.3G 9.8G 46.1G 17% /

tmpfs 64.0M 0 64.0M 0% /dev

tmpfs 3.8G 0 3.8G 0% /sys/fs/cgroup

/dev/mmcblk0p2 58.3G 9.8G 46.1G 17% /etc/localtime

/dev/mmcblk0p2 58.3G 9.8G 46.1G 17% /etc/hosts

/dev/mmcblk0p2 58.3G 9.8G 46.1G 17% /dev/termination-log

/dev/mmcblk0p2 58.3G 9.8G 46.1G 17% /etc/hostname

/dev/mmcblk0p2 58.3G 9.8G 46.1G 17% /etc/resolv.conf

shm 64.0M 0 64.0M 0% /dev/shm

tmpfs 3.8G 12.0K 3.8G 0% /run/secrets/kubernetes.io/serviceaccount

tmpfs 64.0M 0 64.0M 0% /proc/kcore

tmpfs 64.0M 0 64.0M 0% /proc/keys

tmpfs 64.0M 0 64.0M 0% /proc/latency_stats

tmpfs 64.0M 0 64.0M 0% /proc/timer_list

tmpfs 64.0M 0 64.0M 0% /proc/sched_debug

tmpfs 3.8G 0 3.8G 0% /proc/scsi

tmpfs 3.8G 0 3.8G 0% /sys/firmware

/app # date

Sat Oct 16 09:31:11 CEST 2021

/app # top -n 1

Mem: 3902992K used, 4095752K free, 6520K shrd, 180504K buff, 1651716K cached

CPU: 14% usr 11% sys 0% nic 16% idle 52% io 0% irq 4% sirq

Load average: 5.06 3.25 2.38 3/984 1332

PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND

1 0 root S 692m 9% 0 0% /GetInfo

1157 0 root S 1720 0% 3 0% /bin/sh

1332 1157 root R 1652 0% 3 0% top -n 1

/app # exit

alfred@pc1:~$ kubectl top pod getinfo-6c84cff999-mtf5v --containers --use-protocol-buffers

POD NAME CPU(cores) MEMORY(bytes)

getinfo-6c84cff999-mtf5v getinfo 8m 5Mi

alfred@pc1:~$

Sieht ganz gut aus.

Automatisierung der Entwicklungsumgebung

Inspiration:

https://elearning.wsldp.com/pcmagazine/install-liteide-on-ubuntu/ https://www.educative.io/blog/kubernetes-deployments-strategies

https://collabnix.github.io/kubetools/

Um die Entwicklung zu standardisieren und zu beschleunigen, legen wir eine gewisse Struktur an.

Unter dem Home-Directory gibt es ein Verzeichnis für die Templates.

.

├── make.sh

├── make_configmap.sh

├── make_configmap_dir.sh

├── make_docker.sh

├── make_yaml.sh

├── registry_delete.sh

├── registry_list.sh

└── svn.sh

Das svn.sh enthält in meinem Fall den Update der notwendigen Skripten für den Build und Deploy.

alfred@monitoring:~/devtemplates$ cat svn.sh

#!/bin/bash

# Auschecken der Sourcen

svn checkout https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev ./

alfred@monitoring:~/devtemplates$

Das make.sh (kommt dann aus dem svn) enthält die notwendigen Befehle für das Bauen und ausrollen.

#!/bin/bash

############################################################################################

# $Date: 2021-12-18 11:38:15 +0100 (Sa, 18. Dez 2021) $

# $Revision: 1618 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/make.sh $

# $Id: make.sh 1618 2021-12-18 10:38:15Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -s expand_aliases # Benutzen von aliasen im Skript

shopt -o -s nounset #-No Variables without definition

namespace="default"

docker_registry="docker.registry:5000"

#equivalent zu source

. ./env.sh

datum=(`date '+%Y%m%d'`)

revision=(`date '+%H%M%S'`)

#revision=$(svn info | grep "Revision" | awk '{print $2}')

tag="${datum}-${revision}"

echo ${tag} > ${image}.tag # Sicherstellen eines stabilen Image-tags

# Aktualisieren der Sourcen

. ${HOME}/.bash_aliases

doDocker=${BASH_ALIASES[mkd]}

doYaml=${BASH_ALIASES[mky]}

${doDocker}

${doYaml}

#

Diese Datei ruft das Shell-Skript zum Bauen des Dockerfiles und zum Erzeugen der Yamls auf. Es erzeugt auch die "Tag-Datei". Dieser Wert wird von den beiden anderen Skripts verwendet (und garantiert einen stabilen Tag, wenn man nur die Yamls neu bauen möchte).

#!/bin/bash

############################################################################################

# $Date: 2021-12-18 11:38:15 +0100 (Sa, 18. Dez 2021) $

# $Revision: 1618 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/make_docker.sh $

# $Id: make_docker.sh 1618 2021-12-18 10:38:15Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

namespace="default"

docker_registry="docker.registry:5000"

#equivalent zu source

. ./env.sh

datum=(`date '+%Y%m%d'`)

revision=(`date '+%H%M%S'`)

#revision=$(svn info | grep "Revision" | awk '{print $2}')

tag=$(cat ${image}.tag)

# Aktualisieren der Sourcen

svn update

# Docker bauen und in das remote Repo pushen

docker build --no-cache --force-rm . -t ${docker_registry}/${image}:${tag}

docker push ${docker_registry}/${image}:${tag}

docker build --force-rm . -t ${docker_registry}/${image}:latest

docker push ${docker_registry}/${image}:latest

curl ${docker_registry}/v2/${image}/tags/list

#

Diese Datei baut das Docker-Image (einmal mit dem Tag und einmal mit "latest") und pusht das in das Repository.

#!/bin/bash

############################################################################################

# $Date: 2021-12-18 11:38:15 +0100 (Sa, 18. Dez 2021) $

# $Revision: 1618 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/make_yaml.sh $

# $Id: make_yaml.sh 1618 2021-12-18 10:38:15Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

namespace="default"

docker_registry="docker.registry:5000"

#equivalent zu source

. ./env.sh

datum=(`date '+%Y%m%d'`)

revision=(`date '+%H%M%S'`)

#revision=$(svn info | grep "Revision" | awk '{print $2}')

tag=$(cat ${image}.tag)

# Aktualisieren der Sourcen

svn update

# yaml updaten

for val in ${namespace}; do

echo "Namespace: ${val} "

#Namespacespezifisch

. ../namespace/${val}_env.sh

cat ${image}*.yaml > /tmp/${image}.tmp_1

sed 's/\${image}/'"${image}"'/g' /tmp/${image}.tmp_1 > /tmp/${image}.tmp_2

sed 's/\${tag}/'"${tag}"'/g' /tmp/${image}.tmp_2 > /tmp/${image}.tmp_3

sed 's/\${secretName}/'"${secretName}"'/g' /tmp/${image}.tmp_3 > /tmp/${image}.tmp_4

sed 's/\${host}/'"${host}"'/g' /tmp/${image}.tmp_4 > /tmp/${image}.tmp_5

sed 's/\${namespace}/'"${val}"'/g' /tmp/${image}.tmp_5 > /tmp/${image}.tmp_6

sed 's/\${namespace_comment}/'"${namespace_comment}"'/g' /tmp/${image}.tmp_6 > /tmp/${image}.tmp_7

sed 's/\${cluster_issuer}/'"${cluster_issuer}"'/g' /tmp/${image}.tmp_7 > /tmp/${image}.tmp_8

sed 's/\${docker_registry}/'"${docker_registry}"'/g' /tmp/${image}.tmp_8 > /tmp/${image}.yaml

#

# Nun kann das yaml verwendet werden

#

ansible pc1 -m copy -a "src="https://www.litres.ru/tmp/${image}.yaml" dest=./yaml/"${image}_${val}.yaml

# Installieren

ansible pc1 -m shell -a "microk8s kubectl apply -f ./yaml/"${image}_${val}.yaml" --namespace="${val}

done

rm -f /tmp/${image}*.*

#

Dieses Skript baut die yamls (und ersetzt alle Variablen) und deployed das in den Kubernetes-Cluster.

Das make_configmap_dir.sh (kommt dann aus dem svn) hilft beim Erstellen einer configmap basierend auf einem vorhandenen Verzeichnis. Das hilft um Tippfehler, Einrückungsfehler udglm. zu vermeiden.

#!/bin/bash

############################################################################################

# $Date: 2021-12-01 22:31:06 +0100 (Mi, 01. Dez 2021) $

# $Revision: 1457 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/dev/make_configmap_dir.sh $

# $Id: make_configmap_dir.sh 1457 2021-12-01 21:31:06Z alfred $

#

# Erzeugen einer configmap aus einem Verzeichnis

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

#shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

# Überprüfen der Parameter

if [ "${1}" = "" ] ;

then

echo "Usage: $0 Quelle Ziel "

echo "eg: $0 /tmp/myconfigfile /tmp/my_configmap.yaml"

exit -1

fi

if [ "${2}" = "" ] ;

then

echo "Usage: $0 Quelle Ziel "

echo "eg: $0 /tmp/myconfigfile /tmp/my_configmap.yaml"

exit -1

fi

idir=${1}

ofile=${2}

if [ ! -d "${idir}" ]; then # Das ist kein Verzeichnis

echo "Usage: $0 Quelle Ziel "

echo "\"$@\"" "ist kein Verzeichnis"

exit -1

fi

# Schreiben des Ausgabefiles

exec 4> ${ofile}

echo "---">&4

echo "# \${image}-\${tag}" >&4

echo "# generiert mit $0 $1 $2" >&4

echo "apiVersion: v1" >&4

echo "kind: ConfigMap" >&4

echo "metadata:" >&4

string="${ofile}"

#echo "${string}"

prefix="/"

suffix="."

repl=${string%"$prefix"*}

#echo "${repl}"

keyn=${string#"$repl$prefix"}

#echo "${keyn}"

keyn=${keyn%"$suffix"*}

#echo "${keyn}"

keyclean=${keyn//_/$'-'}

outname=${keyclean}

echo " name: ${outname}-cm" >&4

echo " namespace: \${namespace}" >&4

# loop über das Verzeichnis

#echo ${idir}

echo "data: # ${idir}" >&4

FILES="${idir}/*.conf

${idir}/*.ini

${idir}/*.html

${idir}/*.php

${idir}/*.sh

${idir}/*.txt

${idir}/*.json

${idir}/*.webmanifest"

for filename in $FILES ; do

if [ -f "${filename}" ]

then

string="${filename}"

#echo "${string}"

prefix="/"

suffix=""

repl=${string%"$prefix"*}

#echo "${repl}"

keyn=${string#"$repl$prefix"}

#echo "${keyn}"

keyn=${keyn%"$suffix"*}

#echo "${keyn}"

keyclean=${keyn//_/$'-'}

echo " ${keyn}: |" >&4

while read

do

echo " ${REPLY}">&4

done < ${filename}

fi

done

# loop über das Verzeichnis

echo "binaryData: # ${idir}" >&4

FILES="${idir}/*.asc

${idir}/*.rev

${idir}/*.pem

${idir}/*.crt

${idir}/*.key

${idir}/*.png

${idir}/*.jpg

${idir}/authorized_keys

${idir}/*.ico"

for filename in $FILES ; do

if [ -f "${filename}" ]

then

string="${filename}"

#echo "${string}"

prefix="/"

suffix=""

repl=${string%"$prefix"*}

#echo "${repl}"

keyn=${string#"$repl$prefix"}

#echo "${keyn}"

keyn=${keyn%"$suffix"*}

#echo "${keyn}"

keyclean=${keyn//_/$'-'}

cat ${filename} | base64 > /tmp/bd.txt

echo " ${keyn}: |" >&4

while read

do

echo " ${REPLY}">&4

done < /tmp/bd.txt

fi

done

echo "---">&4

exec 4>&-

Damit das überall funktioniert gibt es auch einen Alias in ${home}/.bash_aliases-

#Shortcut um sich tipparbeit zu sparen

alias kubectl='microk8s.kubectl'

alias k='kubectl'

alias kall='microk8s kubectl get all --all-namespaces && microk8s kubectl get ingresses --all-namespaces && microk8s kubectl get endpoints --all-namespaces'

#Dieser alias ist ein Muster, würde alle logs der traefik-pods anzeigen

#Zeigt jetzt die Ingress-Pods

alias klt='(kubectl get pods --all-namespaces) | grep -i nginx-ingress-microk8s-controller | while read a b c; do kubectl logs "$b" -n ingress; done'

alias helm='microk8s.helm3'

alias mk='/home/alfred/devtemplates/make.sh '

alias mkd='/home/alfred/devtemplates/make_docker.sh '

alias mky='/home/alfred/devtemplates/make_yaml.sh '

alias make_configmap='/home/alfred/devtemplates/make_configmap_dir.sh '

Damit das richtig funktioniert, müssen auch die Source-Verzeichnisse eine gewisse Konvention haben. Die Details werden im nächsten Abschnitt anhand des Default-Backend-Serices erklärt.

Kleinster möglicher statischer Webserver

Inspiration:

https://chemidy.medium.com/create-the-smallest-and-secured-golang-docker-image-based-on-scratch-4752223b7324

https://github.com/PierreZ/goStatic

https://hub.docker.com/r/tobilg/mini-webserver

https://devopsdirective.com/posts/2021/04/tiny-container-image/

https://lipanski.com/posts/smallest-docker-image-static-website

https://ashishb.net/tech/docker-101-a-basic-web-server-displaying-hello-world/

Um einen Fallback Backend-Server zu haben (für den Fall dass im Service irgendwo was schief geht) bauen wir uns einen "Under Construction" Service. Dieser Service ist statisch, zeigt ein wenig Information und dient als defaultBackend für alle anderen Services.

Die Projektstruktur ist wie folgt:

.

├── default_ingress.yaml

├── default_latest_container.tar.gz

├── default_svc.yaml

├── default.yaml

├── dockerfile

├── do.sh

├── env.sh

├── html

│ ├── android-chrome-192x192.png

│ ├── android-chrome-512x512.png

│ ├── apple-touch-icon.png

│ ├── favicon-16x16.png

│ ├── favicon-32x32.png

│ ├── favicon.ico

│ ├── healthz

│ ├── index.html

│ ├── site.webmanifest

│ ├── Slainte.jpeg

│ └── website_under_construction.jpg

├── src

│ └── main.go

└── test

└── test.sh

Das env.sh legt die Variablen fest.

#!/bin/bash

############################################################################################

# $Date: 2021-11-27 00:08:17 +0100 (Sa, 27. Nov 2021) $

# $Revision: 1370 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/default/env.sh $

# $Id: env.sh 1370 2021-11-26 23:08:17Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

export image="default"

export namespace="default slainte"

#Namespacespezifisch

. ../namespace/${namespace}_env.sh

#

Dieser Webserver (es ist das Default-Backend für alle) wird sowohl in den Namespace "default" als auch in den Namespace "slainte" ausgerollt.

ARG BUILDER_IMAGE=golang:alpine

############################

# STEP 1 build executable binary

############################

FROM ${BUILDER_IMAGE} as builder

# timezone support

ENV TZ=Europe/Vienna

# Timezone support

RUN apk add git \

tzdata && \

cp /usr/share/zoneinfo/${TZ} /etc/localtime &&\

echo $TZ > /etc/timezone

# Create appuser

ENV USER=appuser

ENV UID=10001

# See https://stackoverflow.com/a/55757473/12429735

RUN adduser \

--disabled-password \

--gecos "" \

--home "/nonexistent" \

--shell "/sbin/nologin" \

--no-create-home \

--uid "${UID}" \

"${USER}"

WORKDIR $GOPATH/src

# Copy the code

COPY src/ /go/src/

# Fetch dependencies.

RUN go mod init main

RUN go mod tidy

RUN go get -d -v

# Build the binary

RUN CGO_ENABLED=0 go build \

-ldflags='-w -s -extldflags "-static"' -a \

-o /go/bin/main .

############################

# STEP 2 build a small image

############################

FROM scratch

#FROM alpine:latest -> Zum testen ist das ganz praktisch

LABEL maintainer="microk8s.raspberry@slainte.at"

LABEL Description="Kleiner statischer Webserver"

# Import from builder.

#COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

COPY --from=builder /etc/localtime /etc/localtime

COPY --from=builder /etc/timezone /etc/timezone

COPY --from=builder /etc/passwd /etc/passwd

COPY --from=builder /etc/group /etc/group

# Copy html

COPY --chown=appuser:appuser html/ /html/

# Copy our static executable

COPY --from=builder /go/bin/main /main

EXPOSE 8080

# Use an unprivileged user.

USER appuser:appuser

WORKDIR /html/

# Run the hello binary.

ENTRYPOINT ["/main"]

Das dockerfile ist ein jetzt ein Multistage-Build. Im ersten Schritt wird das go-Programm compiliert und ein paar notwendig Programme installiert. Im zweiten Schritt werden die notwendigen Dateien aus der Stufe 1 sowie die Web-Dateien kopiert.

#!/bin/bash

############################################################################################

# $Date: 2021-11-26 23:02:04 +0100 (Fr, 26. Nov 2021) $

# $Revision: 1361 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/default/do.sh $

# $Id: do.sh 1361 2021-11-26 22:02:04Z alfred $

#

# Bauen und deployen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

svn update

source env.sh

docker build --no-cache --force-rm . -t ${image}:latest

#docker save ${image}:latest | gzip > ${image}_latest.tar.gz

docker kill $(docker ps | grep -i ${image}:latest | awk '{print $1 }')

docker container rm -f $(docker container ls -a | grep -i ${image}_latest | awk '{print $1 }')

docker run -d --name ${image}_latest -p 8080:8080 ${image}:latest

sleep 10

docker ps

docker export $(docker ps | grep -i ${image}:latest | awk '{print $1 }') | gzip > ${image}_latest_container.tar.gz

echo "docker exec -u 0 -it ${image}_latest /bin/sh"

#docker exec -u 0 -it ${image}_latest /bin/sh

#docker kill $(docker ps | grep -i ${image}:latest | awk '{print $1 }')

#docker container rm $(docker container ls -a | awk '{print $1 }')

#docker run -it --name ${image}_latest ${image}:latest /bin/bash

#

Das do.sh dient zum lokalen Testen mit docker. Hier wird ein Image erzeugt, und man kann testen, wie sich der Container verhält. Dieses Skript exportier den fertigen Container auch in ein tar-File. Das ist recht praktisch um in Ruhe nachzusehen, welche Dateinen wo gelandet sind.

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: ${image}-depl

namespace: ${namespace}

spec:

replicas: 1

selector:

matchLabels:

app: ${image}-app

minReadySeconds: 60

strategy:

type: Recreate

template:

metadata:

labels:

app: ${image}-app

annotations:

sidecar.istio.io/inject: "false"

spec:

# Verhalten, wenn mehrere Replicas notwendig sind

affinity:

podAntiAffinity:

requiredDuringSchedulingIgnoredDuringExecution:

- labelSelector:

matchExpressions:

- key: app

operator: In

values:

- ${image}-app

topologyKey: "kubernetes.io/hostname"

# Container Spec

containers:

- name: ${image}

image: ${docker_registry}/${image}:${tag}

imagePullPolicy: Always

env:

- name: image

value: "${image}"

- name: tag

value: "${tag}"

ports:

- containerPort: 8080

name: http

# check for lifetime liveness, restarts if dead

livenessProbe:

httpGet:

path: /healthz

port: 8080

scheme: HTTP

initialDelaySeconds: 60

timeoutSeconds: 5

periodSeconds: 30

successThreshold: 1

failureThreshold: 3

# check for initial readiness

readinessProbe:

httpGet:

path: /healthz

port: 8080

scheme: HTTP

initialDelaySeconds: 60

timeoutSeconds: 5

periodSeconds: 30

successThreshold: 1

failureThreshold: 3

securityContext:

capabilities:

drop:

- all

add:

- NET_BIND_SERVICE

allowPrivilegeEscalation: false

# Verhalten, beim Beenden

terminationGracePeriodSeconds: 60

restartPolicy: Always

dnsPolicy: ClusterFirst

---

Das default-yaml beschreibt die Verhaltensweise des Services. Nach 60 Sekunden wird geprüft ob der Service bereit und gesund ist (ist in diesem Fall dieselbe Prüfung). Diese Prüfung geschieht alle 30 Sekunden. Weiters werden die Rechte richtig gesetzt. Einerseits hat der User im Container keine Rechte (ist unprivilegiert), so wird nur auch der Pod selbst Rechtemäßig richtig gesetzt (darf nichts, ausser eine Port binden).

---

# Yaml für ${image}:${tag}

---

apiVersion: v1

kind: Service

metadata:

name: ${image}-svc

namespace: ${namespace}

labels:

app: ${image}-app

spec:

ports:

- port: 80

name: http

targetPort: 8080

protocol: TCP

selector:

app: ${image}-app

type: ClusterIP

---

Hier wird der Service beschrieben. Der Service ist nach aussen auf Port 80 erreichbar. Der Pod läuft aber auf Port 8080.

---

# Yaml für ${image}:${tag}

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: ${image}-routes

namespace: ${namespace}

annotations:

kubernetes.io/ingress.class: public

cert-manager.io/cluster-issuer: "${cluster_issuer}"

# https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/

# nginx.ingress.kubernetes.io/rewrite-target: /$2

nginx.ingress.kubernetes.io/ssl-redirect: "true"

nginx.ingress.kubernetes.io/ssl-passthrough: "true"

nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

nginx.ingress.kubernetes.io/ssl-temporary-redirect: "false"

nginx.ingress.kubernetes.io/secure-backends: "true"

nginx.ingress.kubernetes.io/ssl-proxy-headers: "X-Forwarded-Proto: https"

nginx.ingress.kubernetes.io/proxy-body-size: 0m

nginx.ingress.kubernetes.io/proxy-buffering: "off"

# https://github.com/nginxinc/kubernetes-ingress/tree/v1.12.0/examples/ssl-services

# nginx.ingress.kubernetes.io/ssl-services: "default-svc"

# nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

spec:

tls:

- hosts:

- ${host}

secretName: ${secretName}

rules:

- host: ${host}

http:

paths: # https://github.com/google/re2/wiki/Syntax, https://www.regular-expressions.info/refcapture.html

- path: /${image}(/|$)(.*)

pathType: Prefix

backend:

service:

name: ${image}-svc

port:

number: 80

defaultBackend:

service:

name: ${image}-svc

port:

number: 80

---

Hier wird der Ingress beschrieben. Bis zum Ingress-Controller muß der Verkehr gesichert über https gesichert sein. Der Service dahinter kann aber kein https, somit wird das Protokoll am Controller aufgebrochen, und dahinter wird über http kommuniziert. Rewrite führt in der Regel zu Verwirrungen. Siehe auch die Erklärungen in https://kubernetes.github.io/ingress-nginx/examples/rewrite/

Starting in Version 0.22.0, ingress definitions using the annotation nginx.ingress.kubernetes.io/rewrite-target are not backwards compatible with previous versions. In Version 0.22.0 and beyond, any substrings within the request URI that need to be passed to the rewritten path must explicitly be defined in a capture group.

Und nun kommt der Haupteil. Das Go-Programm um eine einfache Webserver-Funktionalität abzubilden.

// Ausgabe eines einfachen HTML-Files

// Inspiration https://golang.org/doc/articles/wiki/

package main

import (

"fmt"

"strings"

"io/ioutil"

"log"

"net/http"

"net/http/httputil"

)

type Page struct {

Title string

Body []byte

}

func loadPage(title string) (*Page, error) {

var xhtml []byte

var err error

title = strings.Replace(title, "default/", "", -1)

title = strings.Replace(title, "default", "", -1)

xhtml, err = ioutil.ReadFile(title)

if err != nil { // wenn was schiefgeht (warum auch immer) wird das index.html gezeigt

log.Println("GET /index.html: " + title )

xhtml, err = ioutil.ReadFile("index.html")

}

//log.Println("RESULT " + title )

return &Page{Title: title, Body: []byte(xhtml)}, err

}

func viewHandler(w http.ResponseWriter, r *http.Request) {

title := r.URL.Path[len("/"):]

//log.Println("URL " + title + "\n")

p, _ := loadPage(title)

fmt.Fprintf(w, "%s", p.Body)

}

func logRequest(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

x, err := httputil.DumpRequest(r, true)

if err != nil {

http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)

return

}

title := r.URL.Path[len("/"):]

title = strings.Replace(title, "default/", "", -1)

title = strings.Replace(title, "default", "", -1)

if title != "healthz" { // Nicht das Log zumüllen.

log.Println(fmt.Sprintf("%q", x))

}

handler.ServeHTTP(w, r)

})

}

func main() {

log.Println("Main Started")

http.HandleFunc("/", viewHandler)

log.Fatal(http.ListenAndServe(":8080", logRequest(http.DefaultServeMux)))

log.Println("Main End")

}

Die Source selbst ist ein einfaches go-Programm. Das Programm zeigt nur die angegebene Datei her. Mehr nicht. Der Aufruf von "/healthz" passiert alle 30 Sekunden. Um das Log nicht vollzumüllen wird dieser Aufruf nicht im Log ausgegeben (alles andere schon).

Wenn ein Aufruf nicht funktioniert (z.b. es gibt die gesuchte Datei nicht), dann wird das "/index.html" hergezeigt. Durch die verschiedenartigen Aufrufe des nginx-Controllers (mal mit dem fixen Teil des Ingress, mal ohne, jenachdem ob man das von aussen aufruft oder ob das ein Nachladen aus dem html ist, zb. das favicon) wird hier hardcoded der default und default/ weggelöscht. Damit ist das Programm nicht mehr wirklich portabel. Vielleicht gibt es in der Zukunft eine bessere Lösung.

Das index.html sieh aus wie folgt:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

Es wird nicht gecached (soweit die Theorie, die Browser und alle Schichten dazwischen halten sich nicht immer daran). Es wird das "website_under_construction.jpg" Bild hergezeigt und ein statischer Text ausgegeben.

Nun testen wir das Ganze am lokalen Rechner.

alfred@bureau:~/svn/trunk/k8s/k8s_app/default$ ./do.sh

+ shopt -o -s nounset

+ svn update

Aktualisiere ».«:

Revision 1377.

+ source env.sh

++ shopt -o -s xtrace

++ shopt -o -s nounset

++ export image=default

++ image=default

++ export 'namespace=default slainte'

++ namespace='default slainte'

++ . ../namespace/default slainte_env.sh

env.sh: Zeile 18: ../namespace/default: Datei oder Verzeichnis nicht gefunden

+ docker build --no-cache --force-rm . -t default:latest

Sending build context to Docker daemon 3.174MB

Step 1/27 : ARG BUILDER_IMAGE=golang:alpine

Step 2/27 : FROM ${BUILDER_IMAGE} as builder

---> 3a38ce03c951

Step 3/27 : ENV TZ=Europe/Vienna

---> Running in 782165bd1a8b

Removing intermediate container 782165bd1a8b

---> 2e2c7a4ea026

Step 4/27 : RUN apk add git tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo $TZ > /etc/timezone

---> Running in 85b32bb185c8

fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz

fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz

(1/7) Installing brotli-libs (1.0.9-r5)

(2/7) Installing nghttp2-libs (1.43.0-r0)

(3/7) Installing libcurl (7.79.1-r0)

(4/7) Installing expat (2.4.1-r0)

(5/7) Installing pcre2 (10.36-r0)

(6/7) Installing git (2.32.0-r0)

(7/7) Installing tzdata (2021e-r0)

Executing busybox-1.33.1-r6.trigger

OK: 22 MiB in 22 packages

Removing intermediate container 85b32bb185c8

---> 3240adcab128

Step 5/27 : ENV USER=appuser

---> Running in 8b8f2c8d3d3b

Removing intermediate container 8b8f2c8d3d3b

---> f2b4866b21ab

Step 6/27 : ENV UID=10001

---> Running in f6feab446106

Removing intermediate container f6feab446106

---> 54a02a4ae4f9

Step 7/27 : RUN adduser --disabled-password --gecos "" --home "/nonexistent" --shell "/sbin/nologin" --no-create-home --uid "${UID}" "${USER}"

---> Running in 05036e634b0c

Removing intermediate container 05036e634b0c

---> e0aa6d53860c

Step 8/27 : WORKDIR $GOPATH/src

---> Running in 962513e1a258

Removing intermediate container 962513e1a258

---> e9c7f169382f

Step 9/27 : COPY src/ /go/src/

---> 383d35179658

Step 10/27 : RUN go mod init main

---> Running in 7de29b1330ae

go: creating new go.mod: module main

go: to add module requirements and sums:

go mod tidy

Removing intermediate container 7de29b1330ae

---> 447d5859e95f

Step 11/27 : RUN go mod tidy

---> Running in ff31b9185dcc

Removing intermediate container ff31b9185dcc

---> d070533a2f6f

Step 12/27 : RUN go get -d -v

---> Running in 1076e9742df7

Removing intermediate container 1076e9742df7

---> cee66da4930a

Step 13/27 : RUN CGO_ENABLED=0 go build -ldflags='-w -s -extldflags "-static"' -a -o /go/bin/main .

---> Running in fda0bfe51009

Removing intermediate container fda0bfe51009

---> 886781406af3

Step 14/27 : FROM scratch

--->

Step 15/27 : LABEL maintainer="microk8s.raspberry@slainte.at"

---> Running in 37be2d43c280

Removing intermediate container 37be2d43c280

---> 74d5145b8a4a

Step 16/27 : LABEL Description="Kleiner statischer Webserver"

---> Running in bea56ecac8d6

Removing intermediate container bea56ecac8d6

---> 8882ec04a7a4

Step 17/27 : COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

---> fc930e8c71cc

Step 18/27 : COPY --from=builder /etc/localtime /etc/localtime

---> 72ab47f2ab4d

Step 19/27 : COPY --from=builder /etc/timezone /etc/timezone

---> 8b6cf086ecc9

Step 20/27 : COPY --from=builder /etc/passwd /etc/passwd

---> a7f34db7d163

Step 21/27 : COPY --from=builder /etc/group /etc/group

---> f5311faa6ef2

Step 22/27 : COPY --chown=appuser:appuser html/ /html/

---> 177ec4c642f2

Step 23/27 : COPY --from=builder /go/bin/main /main

---> 885e4ce994c7

Step 24/27 : EXPOSE 8080

---> Running in b1b0e6258ac8

Removing intermediate container b1b0e6258ac8

---> 5d1baee2add9

Step 25/27 : USER appuser:appuser

---> Running in 3e7a7ed9c791

Removing intermediate container 3e7a7ed9c791

---> 3477baadb6c6

Step 26/27 : WORKDIR /html/

---> Running in fb38b3ed44f9

Removing intermediate container fb38b3ed44f9

---> 99161e3f43d9

Step 27/27 : ENTRYPOINT ["/main"]

---> Running in 60665f656ce7

Removing intermediate container 60665f656ce7

---> f041835dcff7

Successfully built f041835dcff7

Successfully tagged default:latest

++ docker ps

++ grep -i default:latest

++ awk '{print $1 }'

+ docker kill

"docker kill" requires at least 1 argument.

See 'docker kill --help'.

Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]

Kill one or more running containers

++ docker container ls -a

++ awk '{print $1 }'

++ grep -i default_latest

+ docker container rm -f 51a36a4f7ed5

51a36a4f7ed5

+ docker run -d --name default_latest -p 8080:8080 default:latest

ac883b89517387a7a674426ea96b82dd59fcf7e2ce513ef18f21890934627f19

+ sleep 10

+ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

ac883b895173 default:latest "/main" 10 seconds ago Up 10 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp default_latest

041d5fd2b67a portainer/agent:latest "./agent" 5 days ago Up 4 hours 0.0.0.0:9001->9001/tcp, :::9001->9001/tcp portainer_agent

ebb513933fe0 portainer/portainer-ce:latest "/portainer" 5 days ago Up 4 hours 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp, 9000/tcp portainer

+ gzip

++ docker ps

++ grep -i default:latest

++ awk '{print $1 }'

+ docker export ac883b895173

+ echo 'docker exec -u 0 -it default_latest /bin/sh'

docker exec -u 0 -it default_latest /bin/sh

alfred@bureau:~/svn/trunk/k8s/k8s_app/default$

Das Image wird gebaut, und kann im Portainer betrachtet werden.

Abbildung 25: Portainer - Default-Service Container

Der Container startet problemlos.

Abbildung 26: Portainer - Default Service Image Größe

Das Image selbst ist jetzt mit 4,8 MB auch sehr schlank geworden.

Abbildung 27: Portainer - Default Service Image-Layer Größen

Die Zeitzone braucht ca. 2,5KB, was sehr schlank ist.

Die html-Dateien und Bildchen brauchen 528KB.

Die kompilierte go-Datei braucht 4,3MB. Das finde ich recht beachtlich. Denke diesen einfachen Service könnte man mit zb. C einfacher hinbekommen (mehr als GET kann dieser Service ja eh nicht). Wenn mal viel Zeit vorhanden ist, kann man sich das weiter ansehen.

Abbildung 28: Default Webservice

Und wenn man dann diesen Service aufruft, dann sieht das so aus.

Jetzt werden wir den Service am "Entwicklungsrechner" builden, und deployen.

alfred@monitoring:~/dev/default$ mk

+ shopt -o -s nounset

+ namespace=default

+ docker_registry=docker.registry:5000

+ . ./env.sh

++ shopt -o -s xtrace

++ shopt -o -s nounset

++ export image=default

++ image=default

++ export 'namespace=default slainte'

++ namespace='default slainte'

++ . ../namespace/default slainte_env.sh

./env.sh: line 18: ../namespace/default: No such file or directory

+ datum=(`date '+%Y%m%d'`)

++ date +%Y%m%d

++ svn info

++ grep Revision

++ awk '{print $2}'

+ revision=1376

+ tag=20211127-1376

+ svn update

Updating '.':

U do.sh

U src/main.go

Updated to revision 1377.

+ docker build --no-cache . -t docker.registry:5000/default:20211127-1376

Sending build context to Docker daemon 557.1kB

Step 1/27 : ARG BUILDER_IMAGE=golang:alpine

Step 2/27 : FROM ${BUILDER_IMAGE} as builder

---> 975a291cf170

Step 3/27 : ENV TZ=Europe/Vienna

---> Running in 3f12caec82d6

Removing intermediate container 3f12caec82d6

---> d768166f363c

Step 4/27 : RUN apk add git tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo $TZ > /etc/timezone

---> Running in 97dc8787993d

fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/aarch64/APKINDEX.tar.gz

fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/aarch64/APKINDEX.tar.gz

(1/7) Installing brotli-libs (1.0.9-r5)

(2/7) Installing nghttp2-libs (1.43.0-r0)

(3/7) Installing libcurl (7.79.1-r0)

(4/7) Installing expat (2.4.1-r0)

(5/7) Installing pcre2 (10.36-r0)

(6/7) Installing git (2.32.0-r0)

(7/7) Installing tzdata (2021e-r0)

Executing busybox-1.33.1-r6.trigger

OK: 22 MiB in 22 packages

Removing intermediate container 97dc8787993d

---> e373bea9d5ac

Step 5/27 : ENV USER=appuser

---> Running in e09441fbcbf5

Removing intermediate container e09441fbcbf5

---> 782881df7fad

Step 6/27 : ENV UID=10001

---> Running in 51e56a86b293

Removing intermediate container 51e56a86b293

---> a49c317f4d3d

Step 7/27 : RUN adduser --disabled-password --gecos "" --home "/nonexistent" --shell "/sbin/nologin" --no-create-home --uid "${UID}" "${USER}"

---> Running in ecdd977ac68f

Removing intermediate container ecdd977ac68f

---> dbc27ad53f47

Step 8/27 : WORKDIR $GOPATH/src

---> Running in 4192b680ffe3

Removing intermediate container 4192b680ffe3

---> 85b3ccb5bdc9

Step 9/27 : COPY src/ /go/src/

---> 42f076c95522

Step 10/27 : RUN go mod init main

---> Running in c315c3253dd4

go: creating new go.mod: module main

go: to add module requirements and sums:

go mod tidy

Removing intermediate container c315c3253dd4

---> e89ddadaf202

Step 11/27 : RUN go mod tidy

---> Running in 46d037445181

Removing intermediate container 46d037445181

---> ae12bb619e5c

Step 12/27 : RUN go get -d -v

---> Running in 07742fd47a31

Removing intermediate container 07742fd47a31

---> 7a84a944fb38

Step 13/27 : RUN CGO_ENABLED=0 go build -ldflags='-w -s -extldflags "-static"' -a -o /go/bin/main .

---> Running in 5be03523c796

Removing intermediate container 5be03523c796

---> 3b4526214505

Step 14/27 : FROM scratch

--->

Step 15/27 : LABEL maintainer="microk8s.raspberry@slainte.at"

---> Running in b85cc7d56c35

Removing intermediate container b85cc7d56c35

---> 18819bc782dc

Step 16/27 : LABEL Description="Kleiner statischer Webserver"

---> Running in bd9e30ad837d

Removing intermediate container bd9e30ad837d

---> 76989ca004c9

Step 17/27 : COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

---> bf390ed23d43

Step 18/27 : COPY --from=builder /etc/localtime /etc/localtime

---> 12a477258813

Step 19/27 : COPY --from=builder /etc/timezone /etc/timezone

---> fe6ff43916ae

Step 20/27 : COPY --from=builder /etc/passwd /etc/passwd

---> df8e003c5608

Step 21/27 : COPY --from=builder /etc/group /etc/group

---> e137cf1f65b3

Step 22/27 : COPY --chown=appuser:appuser html/ /html/

---> 988dcd53ed26

Step 23/27 : COPY --from=builder /go/bin/main /main

---> 7f2287a4e3e8

Step 24/27 : EXPOSE 8080

---> Running in 7667c76d041b

Removing intermediate container 7667c76d041b

---> 23244506a748

Step 25/27 : USER appuser:appuser

---> Running in 966ba5bdda48

Removing intermediate container 966ba5bdda48

---> 2c46e9cc4ae6

Step 26/27 : WORKDIR /html/

---> Running in d183fe447e3c

Removing intermediate container d183fe447e3c

---> 126d56f90f82

Step 27/27 : ENTRYPOINT ["/main"]

---> Running in 065facb8bf39

Removing intermediate container 065facb8bf39

---> be32ef870cef

Successfully built be32ef870cef

Successfully tagged docker.registry:5000/default:20211127-1376

+ docker push docker.registry:5000/default:20211127-1376

The push refers to repository [docker.registry:5000/default]

dac4a02d94a9: Pushed

b1283fda34d3: Pushed

90937b03a9ab: Pushed

400c6d30c005: Pushed

948ebb21a773: Pushed

9cd5dadbfe87: Pushed

a35817905619: Pushed

20211127-1376: digest: sha256:b740a40b5afb5fd3d5b116f26f0c5e9c8beeed224507a8d89722aa164d47e210 size: 1777

+ docker build . -t docker.registry:5000/default:latest

Sending build context to Docker daemon 557.1kB

Step 1/27 : ARG BUILDER_IMAGE=golang:alpine

Step 2/27 : FROM ${BUILDER_IMAGE} as builder

---> 975a291cf170

Step 3/27 : ENV TZ=Europe/Vienna

---> Using cache

---> d768166f363c

Step 4/27 : RUN apk add git tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo $TZ > /etc/timezone

---> Using cache

---> e373bea9d5ac

Step 5/27 : ENV USER=appuser

---> Using cache

---> 782881df7fad

Step 6/27 : ENV UID=10001

---> Using cache

---> a49c317f4d3d

Step 7/27 : RUN adduser --disabled-password --gecos "" --home "/nonexistent" --shell "/sbin/nologin" --no-create-home --uid "${UID}" "${USER}"

---> Using cache

---> dbc27ad53f47

Step 8/27 : WORKDIR $GOPATH/src

---> Using cache

---> 85b3ccb5bdc9

Step 9/27 : COPY src/ /go/src/

---> Using cache

---> 42f076c95522

Step 10/27 : RUN go mod init main

---> Using cache

---> e89ddadaf202

Step 11/27 : RUN go mod tidy

---> Using cache

---> ae12bb619e5c

Step 12/27 : RUN go get -d -v

---> Using cache

---> 7a84a944fb38

Step 13/27 : RUN CGO_ENABLED=0 go build -ldflags='-w -s -extldflags "-static"' -a -o /go/bin/main .

---> Using cache

---> 3b4526214505

Step 14/27 : FROM scratch

--->

Step 15/27 : LABEL maintainer="microk8s.raspberry@slainte.at"

---> Using cache

---> 18819bc782dc

Step 16/27 : LABEL Description="Kleiner statischer Webserver"

---> Using cache

---> 76989ca004c9

Step 17/27 : COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

---> Using cache

---> bf390ed23d43

Step 18/27 : COPY --from=builder /etc/localtime /etc/localtime

---> Using cache

---> 12a477258813

Step 19/27 : COPY --from=builder /etc/timezone /etc/timezone

---> Using cache

---> fe6ff43916ae

Step 20/27 : COPY --from=builder /etc/passwd /etc/passwd

---> Using cache

---> df8e003c5608

Step 21/27 : COPY --from=builder /etc/group /etc/group

---> Using cache

---> e137cf1f65b3

Step 22/27 : COPY --chown=appuser:appuser html/ /html/

---> Using cache

---> 988dcd53ed26

Step 23/27 : COPY --from=builder /go/bin/main /main

---> Using cache

---> 7f2287a4e3e8

Step 24/27 : EXPOSE 8080

---> Using cache

---> 23244506a748

Step 25/27 : USER appuser:appuser

---> Using cache

---> 2c46e9cc4ae6

Step 26/27 : WORKDIR /html/

---> Using cache

---> 126d56f90f82

Step 27/27 : ENTRYPOINT ["/main"]

---> Using cache

---> be32ef870cef

Successfully built be32ef870cef

Successfully tagged docker.registry:5000/default:latest

+ docker push docker.registry:5000/default:latest

The push refers to repository [docker.registry:5000/default]

dac4a02d94a9: Layer already exists

b1283fda34d3: Layer already exists

90937b03a9ab: Layer already exists

400c6d30c005: Layer already exists

948ebb21a773: Layer already exists

9cd5dadbfe87: Layer already exists

a35817905619: Layer already exists

latest: digest: sha256:b740a40b5afb5fd3d5b116f26f0c5e9c8beeed224507a8d89722aa164d47e210 size: 1777

+ curl docker.registry:5000/v2/default/tags/list

{"name":"default","tags":["20211127-1370","20211126-1363","20211127-1375","20211127-1376","latest","20211127-1368","20211126-1366","20211126-1364","20211126-1367","20211127-1369"]}

+ for val in ${namespace}

+ echo 'Namespace: default '

Namespace: default

+ . ../namespace/default_env.sh

++ shopt -o -s xtrace

++ shopt -o -s nounset

++ export secretName=default-k8s-slainte-at-tls

++ secretName=default-k8s-slainte-at-tls

++ export host=default.k8s.slainte.at

++ host=default.k8s.slainte.at

++ export 'namespace_comment=Namespace zum Testen'

++ namespace_comment='Namespace zum Testen'

++ export cluster_issuer=letsencrypt-prod

++ cluster_issuer=letsencrypt-prod

++ export docker_registry=docker.registry:5000

++ docker_registry=docker.registry:5000

+ cat default.yaml default_ingress.yaml default_svc.yaml

+ sed 's/\${image}/default/g' /tmp/default.tmp_1

+ sed 's/\${tag}/20211127-1376/g' /tmp/default.tmp_2

+ sed 's/\${secretName}/default-k8s-slainte-at-tls/g' /tmp/default.tmp_3

+ sed 's/\${host}/default.k8s.slainte.at/g' /tmp/default.tmp_4

+ sed 's/\${namespace}/default/g' /tmp/default.tmp_5

+ sed 's/\${namespace_comment}/Namespace zum Testen/g' /tmp/default.tmp_6

+ sed 's/\${cluster_issuer}/letsencrypt-prod/g' /tmp/default.tmp_7

+ sed 's/\${docker_registry}/docker.registry:5000/g' /tmp/default.tmp_8

+ ansible pc1 -m copy -a 'src=/tmp/default.yaml dest=./yaml/default_default.yaml'

pc1 | CHANGED => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": true,

"checksum": "67bfe3d0797cc90b73b336144dc06d636a2c8632",

"dest": "./yaml/default_default.yaml",

"gid": 1002,

"group": "alfred",

"md5sum": "1b31fdd037dad6074a99a1b31d729003",

"mode": "0664",

"owner": "alfred",

"size": 4025,

"src": "/home/alfred/.ansible/tmp/ansible-tmp-1638031158.5600948-144393498642769/source",

"state": "file",

"uid": 1002

}

+ ansible pc1 -m shell -a 'microk8s kubectl apply -f ./yaml/default_default.yaml --namespace=default'

pc1 | CHANGED | rc=0 >>

deployment.apps/default-depl configured

ingress.networking.k8s.io/default-routes unchanged

service/default-svc unchanged

+ for val in ${namespace}

+ echo 'Namespace: slainte '

Namespace: slainte

+ . ../namespace/slainte_env.sh

++ shopt -o -s xtrace

++ shopt -o -s nounset

++ export secretName=k8s-slainte-at-tls

++ secretName=k8s-slainte-at-tls

++ export host=k8s.slainte.at

++ host=k8s.slainte.at

++ export 'namespace_comment=Namespace für die Produktion'

++ namespace_comment='Namespace für die Produktion'

++ export cluster_issuer=letsencrypt-prod

++ cluster_issuer=letsencrypt-prod

++ export docker_registry=docker.registry:5000

++ docker_registry=docker.registry:5000

+ cat default.yaml default_ingress.yaml default_svc.yaml

+ sed 's/\${image}/default/g' /tmp/default.tmp_1

+ sed 's/\${tag}/20211127-1376/g' /tmp/default.tmp_2

+ sed 's/\${secretName}/k8s-slainte-at-tls/g' /tmp/default.tmp_3

+ sed 's/\${host}/k8s.slainte.at/g' /tmp/default.tmp_4

+ sed 's/\${namespace}/slainte/g' /tmp/default.tmp_5

+ sed 's/\${namespace_comment}/Namespace für die Produktion/g' /tmp/default.tmp_6

+ sed 's/\${cluster_issuer}/letsencrypt-prod/g' /tmp/default.tmp_7

+ sed 's/\${docker_registry}/docker.registry:5000/g' /tmp/default.tmp_8

+ ansible pc1 -m copy -a 'src=/tmp/default.yaml dest=./yaml/default_slainte.yaml'

pc1 | CHANGED => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": true,

"checksum": "0e3a43d058a2c730b8b5fbd7db7f23ac3d3fdadf",

"dest": "./yaml/default_slainte.yaml",

"gid": 1002,

"group": "alfred",

"md5sum": "06fe20022546778e75e70d83160c3698",

"mode": "0664",

"owner": "alfred",

"size": 4001,

"src": "/home/alfred/.ansible/tmp/ansible-tmp-1638031173.35852-146586287804062/source",

"state": "file",

"uid": 1002

}

+ ansible pc1 -m shell -a 'microk8s kubectl apply -f ./yaml/default_slainte.yaml --namespace=slainte'

pc1 | CHANGED | rc=0 >>

deployment.apps/default-depl configured

ingress.networking.k8s.io/default-routes unchanged

service/default-svc unchanged

+ rm -f /tmp/default.tmp_1 /tmp/default.tmp_2 /tmp/default.tmp_3 /tmp/default.tmp_4 /tmp/default.tmp_5 /tmp/default.tmp_6 /tmp/default.tmp_7 /tmp/default.tmp_8 /tmp/default.yaml

alfred@monitoring:~/dev/default$

Hier wird der Container gebuilded, in die Registry gepusht (einmal mit einem klaren Tag Datum+svn-Revision, und eimal mit dem tag "latest"). Dann wird pro Namespace (in dem Fall default und Slainte) ein separates "grosses" yaml erzeugt, dass am pc1 abgelegt wird. Dieses yaml wird dann eingespielt.

alfred@bureau:~$ k get --all-namespaces deployment | grep -i default

default default-depl 1/1 1 1 3h27m

slainte default-depl 1/1 1 1 17h

alfred@bureau:~$ k get --all-namespaces service | grep -i default-svc

default default-svc ClusterIP 10.152.183.43 <none> 80/TCP 3h27m

slainte default-svc ClusterIP 10.152.183.55 <none> 80/TCP 17h

alfred@bureau:~$ k get --all-namespaces ingress | grep -i default

slainte default-routes <none> k8s.slainte.at 127.0.0.1 80, 443 17h

default default-routes <none> default.k8s.slainte.at 127.0.0.1 80, 443 3h27m

alfred@bureau:~$ k get --all-namespaces pod | grep -i default

default default-depl-66587f4464-b49cx 1/1 Running 0 6m

slainte default-depl-66587f4464-5lxt2 1/1 Running 0 5m49s

alfred@bureau:~$

Alles da und laufend. Nun wenden wir das Testscript an.

#!/bin/bash

############################################################################################

# $Date: 2021-11-27 14:28:59 +0100 (Sa, 27. Nov 2021) $

# $Revision: 1374 $

# $Author: alfred $

# $HeadURL: https://monitoring.slainte.at/svn/slainte/trunk/k8s/k8s_app/default/test/test.sh $

# $Id: test.sh 1374 2021-11-27 13:28:59Z alfred $

#

# test aller möglichen Verbindungen

#

############################################################################################

#shopt -o -s errexit #—Terminates the shell script if a command returns an error code.

shopt -o -s xtrace #—Displays each command before it’s executed.

shopt -o -s nounset #-No Variables without definition

#

# Slainte

#

curl -k -v http://k8s.slainte.at/default # liefert 308 - Permanent verzogen auf https://

curl -k -v https://k8s.slainte.at/default # liefert 200 - Zeigt die index.html an

curl -k -v https://k8s.slainte.at/default/ # liefert 200 - Zeigt die index.html an

curl -k -v https://k8s.slainte.at/default/index.html # liefert 200 - Zeigt die index.html an

curl -k -v https://k8s.slainte.at/default/healthz # liefert 200 - Zeigt die index.html an

curl -k -v https://k8s.slainte.at/default/unbekannt # liefert 200 - Zeigt die index.html an

#

# default

#

curl -k -v http://default.k8s.slainte.at/default # liefert 308 - Permanent verzogen auf https://

curl -k -v https://default.k8s.slainte.at/default # liefert 200 - Zeigt die index.html an

curl -k -v https://default.k8s.slainte.at/default/ # liefert 200 - Zeigt die index.html an

curl -k -v https://default.k8s.slainte.at/default/index.html # liefert 200 - Zeigt die index.html an

curl -k -v https://default.k8s.slainte.at/default/healthz # liefert 200 - Zeigt die index.html an

curl -k -v https://default.k8s.slainte.at/default/unbekannt # liefert 200 - Zeigt die index.html an

#

Das Ergebnis sieht dann so aus:

alfred@bureau:~/svn/trunk/k8s/k8s_app/default$ '/home/alfred/svn/trunk/k8s/k8s_app/default/test/test.sh'

+ shopt -o -s nounset

+ curl -k -v http://k8s.slainte.at/default

* Trying 87.243.186.29:80...

* Connected to k8s.slainte.at (87.243.186.29) port 80 (#0)

> GET /default HTTP/1.1

> Host: k8s.slainte.at

> User-Agent: curl/7.74.0

> Accept: */*

>

* Mark bundle as not supporting multiuse

< HTTP/1.1 308 Permanent Redirect

< Date: Sat, 27 Nov 2021 16:47:01 GMT

< Content-Type: text/html

< Content-Length: 164

< Connection: keep-alive

< Location: https://k8s.slainte.at/default

<

<html>

<head><title>308 Permanent Redirect</title></head>

<body>

<center><h1>308 Permanent Redirect</h1></center>

<hr><center>nginx</center>

</body>

</html>

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v https://k8s.slainte.at/default

* Trying 87.243.186.29:443...

* Connected to k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=k8s.slainte.at

* start date: Nov 23 09:14:40 2021 GMT

* expire date: Feb 21 09:14:39 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x5631000885c0)

> GET /default HTTP/2

> Host: k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:01 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v https://k8s.slainte.at/default/

* Trying 87.243.186.29:443...

* Connected to k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=k8s.slainte.at

* start date: Nov 23 09:14:40 2021 GMT

* expire date: Feb 21 09:14:39 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x5654a1e305c0)

> GET /default/ HTTP/2

> Host: k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:01 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v https://k8s.slainte.at/default/index.html

* Trying 87.243.186.29:443...

* Connected to k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=k8s.slainte.at

* start date: Nov 23 09:14:40 2021 GMT

* expire date: Feb 21 09:14:39 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x557b8aec95c0)

> GET /default/index.html HTTP/2

> Host: k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:01 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v https://k8s.slainte.at/default/healthz

* Trying 87.243.186.29:443...

* Connected to k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=k8s.slainte.at

* start date: Nov 23 09:14:40 2021 GMT

* expire date: Feb 21 09:14:39 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x5587d59f75c0)

> GET /default/healthz HTTP/2

> Host: k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:02 GMT

< content-type: text/plain; charset=utf-8

< content-length: 3

< strict-transport-security: max-age=15724800; includeSubDomains

<

OK

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v https://k8s.slainte.at/default/unbekannt

* Trying 87.243.186.29:443...

* Connected to k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=k8s.slainte.at

* start date: Nov 23 09:14:40 2021 GMT

* expire date: Feb 21 09:14:39 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x560d6f6625c0)

> GET /default/unbekannt HTTP/2

> Host: k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:02 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

* Connection #0 to host k8s.slainte.at left intact

+ curl -k -v http://default.k8s.slainte.at/default

* Trying 87.243.186.29:80...

* Connected to default.k8s.slainte.at (87.243.186.29) port 80 (#0)

> GET /default HTTP/1.1

> Host: default.k8s.slainte.at

> User-Agent: curl/7.74.0

> Accept: */*

>

* Mark bundle as not supporting multiuse

< HTTP/1.1 308 Permanent Redirect

< Date: Sat, 27 Nov 2021 16:47:02 GMT

< Content-Type: text/html

< Content-Length: 164

< Connection: keep-alive

< Location: https://default.k8s.slainte.at/default

<

<html>

<head><title>308 Permanent Redirect</title></head>

<body>

<center><h1>308 Permanent Redirect</h1></center>

<hr><center>nginx</center>

</body>

</html>

* Connection #0 to host default.k8s.slainte.at left intact

+ curl -k -v https://default.k8s.slainte.at/default

* Trying 87.243.186.29:443...

* Connected to default.k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=default.k8s.slainte.at

* start date: Nov 23 20:32:48 2021 GMT

* expire date: Feb 21 20:32:47 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x55c6b9a945c0)

> GET /default HTTP/2

> Host: default.k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:02 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

</center>

</body>

</html>

* Connection #0 to host default.k8s.slainte.at left intact

+ curl -k -v https://default.k8s.slainte.at/default/

* Trying 87.243.186.29:443...

* Connected to default.k8s.slainte.at (87.243.186.29) port 443 (#0)

* ALPN, offering h2

* ALPN, offering http/1.1

* successfully set certificate verify locations:

* CAfile: /etc/ssl/certs/ca-certificates.crt

* CApath: /etc/ssl/certs

* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

* TLSv1.3 (IN), TLS handshake, Certificate (11):

* TLSv1.3 (IN), TLS handshake, CERT verify (15):

* TLSv1.3 (IN), TLS handshake, Finished (20):

* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

* TLSv1.3 (OUT), TLS handshake, Finished (20):

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

* ALPN, server accepted to use h2

* Server certificate:

* subject: CN=default.k8s.slainte.at

* start date: Nov 23 20:32:48 2021 GMT

* expire date: Feb 21 20:32:47 2022 GMT

* issuer: C=US; O=Let's Encrypt; CN=R3

* SSL certificate verify ok.

* Using HTTP2, server supports multi-use

* Connection state changed (HTTP/2 confirmed)

* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0

* Using Stream ID: 1 (easy handle 0x5630bc8605c0)

> GET /default/ HTTP/2

> Host: default.k8s.slainte.at

> user-agent: curl/7.74.0

> accept: */*

>

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* old SSL session ID is stale, removing

* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!

< HTTP/2 200

< date: Sat, 27 Nov 2021 16:47:02 GMT

< content-type: text/html; charset=utf-8

< content-length: 881

< strict-transport-security: max-age=15724800; includeSubDomains

<

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/transitional.dtd">

<html>

<head>

<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html; charset=utf-8">

<meta HTTP-EQUIV=CACHE_CONTROL CONTENT="no-cache, no-store, must-revalidate">

<meta HTTP-EQUIV=PRAGMA CONTENT="no-cache">

<meta HTTP-EQUIV=MAX-AGE CONTENT="3">

<meta HTTP-EQUIV=EXPIRES CONTENT="0">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">

<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="manifest" href="/site.webmanifest">

<title>K8's Default-Seite</title>

</head>

<body>

<center>

<img src="https://www.litres.ru/pub/t/67486969.json/website_under_construction.jpg" alt="">

<p><br/><p>

<p>Bitte verständigen Sie den Siteadmin<br/><p>

Ücretsiz ön izlemeyi tamamladınız.

₺221,68

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
515 s. 126 illüstrasyon
ISBN:
9783742770134
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi:

Benzer kitaplar