Читать книгу: «K8s Applications mit MicroK8S auf Raspberry PI»
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>
Бесплатный фрагмент закончился.
