Teile und Herrsche - das ist in so vielen Aspekten immer wieder wahr.
Nach Jahrmillionen in denen man die Ressourcen eines Computers maximal auslastete, weil die sind ja teuer, begann man - als die Preise fielen, die Aufgaben zu verteilen, ein Server für eine Aufgabe. Dann erkannte man erstaunllicherweise, dass viele der Rechner nicht mal im Ansatz ausgelastet werden. Verschwendung. Dann fing man an, die Dienste in virtuelle Maschinen auf der Hardware zu konsolidieren. Dadurch waren die Rechner gut ausgelastet. Aber die Admins kamen kaum hinterher - jede VM ist ihr eigenes Betriebssystem mit einem Zoo von Diensten und Paketen, damit die VM das tut, wofür sie gedacht ist. Und jedes OS verbrennt auch Ressourcen für OS Aktionen, braucht Sicherheitsupdates und so weiter. Dann ging es los mit Paravirtualisierung und es dauerte dann nicht mehr lange und Docker war geboren.
Docker ist keine Virtualisierung. Auch wenn es manchmal so aussieht, aber es paravirtualisiert, genauer es nutzt die Möglichekten der CPU und des Betriebssystems, die Prozesse von einander zu trennen. Einzig die für einen Prozess notwendigen Ressourcen (Dateien) werden dupliziert und nicht mehr das ganze Betriebssystem. Ich vergleiche das immer gerne mit dem Chroot, was man schon länger kennt.
Das sind schon viele Vorteile, der eigentliche Vorteil ist aber - man verschmutzt mit einem Dockercontainer nicht sein Basissystem. Die Konfigurationsarbeiten für den Betrieb eines Dienstes bleibt im Docker - genauer im Dockercontainer. Wirft man das weg, ist alles spezifische weg. Das hat den Vorteil, dass man schnell was ausprobieren kann, das hat aber auch den Vorteil bei Fehlern schnell zurück zum Grundsystem zu kommen.
Die normale Nutzung von Docker basiert darauf, dass man mit dem Befehl Docker diverse unabhängige Befehle absendet und über Parametrisierung der einzelnen Befehle dann eine Infrastruktur aufbaut. Zum Beispiel braucht es für ein abgeschottetes System aus zwei Diensten Datenbank und Webserver einen Befehl um ein Netzwerk zu erzeugen, einen um den Dienst Datenbank zu starten und mit dem Netzwerk zu verknüpfen und einen um das gleiche mit dem Webserver zu tun. Das ist ziemlich viel Handarbeit und man fängt schnell an, dass zu scripten.
Und hier beginnt die Arbeit von docker-compose
. Dieses Tool bietet die Möglichkeit in einer YAML Datei eine Infrastruktur zu definieren.
Hier mal ein Beispiel:
version: '2.1'
services:
postgres:
image: postgres
volumes:
- ./postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
keycloak:
image: jboss/keycloak
command: -b 0.0.0.0 -Djboss.bind.address.private=127.0.0.1
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_DATABASE: keycloak
DB_USER: keycloak
DB_SCHEMA: public
DB_PASSWORD: password
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: pasword
ports:
- "2600:8443"
depends_on:
- postgres
networks:
default:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: "172.26.0.0/24"
- subnet: "fc00:2600::/96"
Das ist ein einfaches Beispiel und es sieht komplizierter aus, als es ist. Die erste Zeile enthält eine Versionsnummer, die für docker-compose relevant ist. Hier ist 2.1 eine gute Wahl. Es gibt auch neuere Versionen, die bringen aber nur etwas in Zusammenhang mit docker-swarm, welches wir nicht nutzen. Dann gibt es drei Blöcke (in unserem einfachen Beispiel sind nur zwei zu sehen) - services, networks, volumes.
Im Bereich services
definieren wir, die jeweiligen Docker-Container, die zusammen einen “Service” abbilden. Theoretisch kann man hier eine ganze Infrastruktur abbilden - allerdings begrenz docker-compose sich auf einen Host und verteilt die Container nicht in einem Kubernetes Cluster. Jedes Element darunter hat einen frei zu wählenden Namen und darunter gibt es dann einige elementare Einträge wie image
oder build
um anzugeben, welcher Container zu verwenden ist, welche Ports freigegeben werden bestimmt man mit ports
. Der Rest ist optional. Zum Beispiel environment
definiert Umgebungsspezifische Variablen. Man kann diese auch aus einer Datei auslesen. Man kann für bestimmte Pfade im Container auch volumes
deklarieren, um sie außerhalb des Containers zu lagern. Hier verwende ich einen relativen Pfad - was in Docker eigentlich nicht erlaubt ist, aber mit docker-compose ab seiner Definitionsdatei funktioniert. Man kann auch Volumes definieren, die anders liegen. Dafür gibt es ja die Volumes Definition (die wir hier nicht verwenden).
Weiterhin kann man Netzwerke mit networks
definieren - ich mache das gerne explizit, weil man so mehr Kontrolle hat und für IPv6 ist das sogar zwingend notwendig, weil Docker bzgl. IPv6 leider kein gutes Vorbild in der Umsetzung ist. Es wird gesagt, IPv6 geht - man muss es aktivieren (via enable_ipv6), aber es wählt dann nicht selbständig ein Segment aus dem Host-Adressraum, es macht kein NAT (und arbeitet damit bei IPv6 gänzlich anders als IPv4). Viel schlimmer ist aber, dass offene Ports von Containern in IPv6 für alle offen sind, egal ob man das will oder nicht. Bei IPv4 wird die ports
deklaration also beachtet - bei IPv6 nicht. Ich verwende daher gerne ein Hilfspaket von robertkl/ipv6nat.
Zum Thema IPv6 NAT kann man viel erzählen und diskutieren. Aber so lange man keine dynamische Kontrolle über die Ports hat, die Docker aufmacht, ist das Thema bei einem direkt ans Internet gehängten Server obsolet. Da muss was “vor”.
Bedingt durch die Unabhängigkeit der einzelnen Services voneinander und der Tatsache, dass jedes seine eigene Infrastruktur bekommt, ergibt sich in einer gemeinsam zu nutzenden Ressource schnell ein Problem. Bei Docker ist das das Netzwerk. Jeder Service bekommt sein eigenes lokales internes Netzsegment und das landet alles auf einem Interface. Zum Teil schränkt Docker die Nutzung erheblich ein. Zum Teil ignoriert es bestehende Regeln und liefert keine Lösung. Dann muss die Firewall ran.
Wir haben auf einem Server A einen Service etabliert, und wollen nun nur von einem Server B darauf zugreifen. Alle anderen sollen den Service nicht sehen. Hängt der Server im Internet, kann man das nur mit lokalen Firewallregeln auf Server A direkt lösen.
Docker erstellt selber ein kleines Regelwerk in den IPTables Tabellen NAT und Filter. Dabei geht es Docker primär darum, die Pakete vom externen Netzwerkinterface auf das interne Netz des jeweiligen Service zu bekommen.
Die NAT Regeln sorgen dafür, dass Anfragen vom externen Interface auf dem externen Port auf das interne Interface des Docker Containers mit seinem internen Port weitergereicht werden. Hier sollte man nicht viel dran ändern. Bei den Filtern kann man aber sowohl INPUT als auch eine Docker-spezifische Chain DOCKER-USER befüllen. Wichtig dabei ist - wenn, dann nur diese beide Chains flushn und alles andere so lassen.
Was ich mache ist - die INPUT Chain wie üblich konfigurieren - alles was rein darf, darf rein, den Rest drop’n. Zu beachten gilt, dass die Forward Chain für NAT zuerst angesprochen wird und INPUT deshalb nur für Nicht-Docker Dienste relevant ist. Die für Docker relevanten Services finden sich in DOCKER-USER Anwendung und müssen dort getrennt behandelt werden. Bei der DOCKER-USER Chain (die in FORWARD angesiedelt ist), muss man vorsichtig sein mit dem DROP oder REJECT. Hier sollte man die Regeln nur auf das externe Interface anwenden, damit die Kommunikation zwischen Containern (die über interne Bridges und docker0 laufen) nicht gestört werden.
In letzter Zeit kam das Thema Cloud häufiger in meiner Umgebung vor, als Virus oder Bit.
Wenn man dann erstmal anfängt, darüber nachzudenken, was das Wort bedeutet, kommt man schnell auf die Frage, wie ist Cloud eigentlich definiert. Und da fängt es schnell an, wolkig zu werden. Es gibt zwar Definitionen, aber die sind schwammig und bedeuten alles und nichts. Die einen kennen eine Private Cloud und meinen damit zumeist eine Umgebung mit Hosts und VMs. Die andere Seite spricht gerne auch mal von “Cloud-nativ” und meint damit Serverless-Systeme, bei denen jeder Request einen Script ausführt und dessen Ausführungszeit haargenau abgerechnet wird. Was eine Cloud ist - ist also beliebig wolkig zu sehen.
Ich bin nicht wirklich glücklich damit, zumal mich das Marketingdeutsch gewaltig stört. Mein Auto hat ‘ne Batterie. Ist es deshalb nicht auch ein E-Auto?…
Taucht man etwas tiefer in die Welt der Cloud-afinen Menschen ein, beginnt sich der Wald etwas zu lichten. In der Realität muss man nämlich konkreter werden und da geht es dann schnell um die Themen, die wir auch einfach beim richtigen Namen nennen können. Nur sind die nicht so griffig. Ziel ist es, die Services maximal von einander zu trennen. Beim Thema Host+VMs handelt es sich um Virtualisierung. Hier kann man relativ einfach pro VM einen Service definieren. Der Vorteil ist. Der Service hat seine gesamte Landschaft, die ihm behagt, um sich herum gestrickt. Der Nachteil ist, dass der gesamte Overhead eines kompletten Betriebssystem jedesmal mitkommt. So wird aus einem simplen Java-Prozess gleich ein Thema um Kernel Updates, Firewalls und ich weiß nicht was.
Da der Overhead für große Umgebungen irgendwann unwirtschaftlich war und die Linuxwelt schon lange eine Alternative anbot, entwickelte sich mit der Zeit das Thema Paravirtualisierung zu einer besseren Lösung. Als Docker Inc. dann noch mit verhältnismäßig simplen Tools für CLI um die Ecke kam, war das Thema Cloud langsam geboren und benannt. Paravirtualisierung trennt die Services durch Prozessisolation. Ein Programm A kann in seiner Isolationsschicht nicht auf Programm B zugreifen, wenn dieser sich (leienhaft ausgedrückt) nicht im gleichen Context befindet. Was also eine CPU bei der Virtualisierung mit VMs macht, kann auch der Linux-Kernel auf Prozessebene. Er trennt die Programme hart voneinander. Durch den Einsatz von Images (eigentlich TAR-Balls) kann jeder Service in einen Container verpackt werden und liefert sein Ökosystem mit (also alle Libs, die ein Programm so während der Laufzeit braucht). Das OS wird aber nicht wirklich gestartet. Nur das Programm. Nun ist <a href="Docker ansich schon schön - aber auch hier gilt wieder das Problem - je mehr Services man betreibt, desto unübersichtlicher wird es.
Also fingen einige Leute an, ein System um Docker zu stricken. Sie nutzen die Ausführungsmöglichkeiten und die Tools um eine Management Umgebung drumherum zu bauen und fertig war Kubernetes.
Gefühlt ist die Installation von Kubernetes einfach… wenn man weiß, wie. In der Realität gibt es diverse Haken und Ösen. Benutzt man Docker als Unterbau, ist das Leben relativ einfach, weil sich das Ökosystem Kubernetes um Docker aufbaut. Da Docker aber langsam stirbt und die diversen Probleme ziemlich nerven, würde man gerne auf was anderes setzen. Mein erster Gedanke war Podman
. Leider braucht Kubernetes einen Dienst, gegen den er sich verbinden kann und da kam Podman anfangs nicht in Frage. Deshalb hab ich die Alternative Basis CRI-O
eingesetzt. Letztlich wrappen Docker und Podman CRI-O. Da Docker nun auch schon langsam sinkt, bedient man sich dem daraus geschaffenen Industriestandard containerd
.
Ich hab zwei Wege ausprobiert, um Kubernetes zu installieren.
Der Ansible Script, den ich gebaut habe, tut eigentlich nicht viel mehr als folgendes:
Auf enem dem Master-Node wird dann mit kubeadm init ..
der Cluster gestartet und das Netzwerk vorbereitet. Die einzelnen Nodes joinen dann den Cluster mit kubeadmin join
und einem Token.
Dieses Basissetup ist dann erstmal ein Cluster. Es gibt eine Reihe von Services, die dem kube-system hinzugefügt werden sollten.
Ich gebe zu, Kubernetes alleine ist im produktiven Einsatz schwierig zu handhaben. Der Umgang mit den Yaml-Configs, die man “apply”‘n muss, ist gewöhnungsbedürftig. Deshalb kamen später auch Managementtools, wie OpenShift auf, um einem Admin das Leben in produktiven Umgebungen zu erleichtern.
Da ich sowieso überall und nirgends Ansible verwende, liefert eine Suche im Netz relativ schnell kubespray
ans Tageslicht. Kubespray ist ein spezielles Repository mit einem eigenen Ökosystem für Kubernetes. Es richtet nicht nur Kubernetes an sich ein, es bereitet die Systeme auch entsprechend vor. Leider lässt es sich schlecht in eigene Ansible Repositories integrieren. Das ist aber auch so ziemlich der einzige Nachteil.
Folgende Schritte sind zu erledigen.
cp -r inventory/sample inventory/mycluster
kube_nodes
, drei etcd
Nodes und ein oder zwei kube_control_plane
setcd_deployment_type
auf host
stellen (statt docker)container_manager
auf containerd
stellenAnschließend dann das Playbook cluster.yml
ausführen. Es wird ein etcd installiert. Dann wird der ausgewählte container manager ausgeführt (in unserem Fall containerd). Dann wird Kubernetes konfiguriert
Anschließend kann man das Kubernetes Dashboard installieren.
Die Konfigdatei mit curl https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml > kubernetes-dashboard-deployment.yml
herunterladen. Die Datei enthält diverse fürs Dashboard notwendige Deklarationen. Eine davon ist der Service kubernetes-dashboard
. An diesem stellt man den spec-type Nodeport ein.
...
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
ports:
- port: 443
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
type: NodePort
...
Der Sinn dahinter ist, dass damit das Dashboard auf allen Nodes im Kubernetes auf einen Port erreichbar ist und keinen Ingress braucht. Die Datei dann mit kubectl apply -f kubernetes-dashboard-deployment.yml
einfügen.
Anschließend muss ein Admin-Account angelegt werden. Das ist etwas seltsam nur mit Tokens.
# acoby-admin.yml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: acoby-admin
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: acoby-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: acoby-admin
namespace: kube-system
Das auch einbinden mit kubectl apply -f acoby-admin.yml
. Dann das Token extrahieren.
SA_NAME="acoby-admin"
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep ${SA_NAME} | awk '{print $1}')
Denn relevanten Ports auf den Nodes findet man mit
kubectl get service -n kubernetes-dashboard
Dann brauchen wir einen Ingress Controller. Den gibt es hier
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.43.0/deploy/static/provider/baremetal/deploy.yaml
Ein Beispiel Service auf Basis von HTTPBin konfiguriert man wie folgt.
kubectl apply -f https://github.com/istio/istio/raw/master/samples/httpbin/httpbin.yaml
Damit ist der Service im Kubernetes ausgerollt. Um ihn über den Ingress Controller erreichbar zu machen, muss folgende Definition eingefügt werden.
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpbin-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: httpbin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: httpbin
port:
number: 8000
Relevant ist hier der Host und sein Name. Über die Domain httpbin.example.com
werden alle Anfragen an den Ingress an den Service httpbin auf Port 8000 weitergeleitet.
Hier ein paar hilfreiche Kommandos
kubectl get ing
kubectl get svc -n ingress-nginx
Der letzte Befehl zeigt uns den Quellport für unseren Kubernetes Ingress. Dieser ist von außen erreichbar.
Am Ende noch ein Beispiel für NFS. Die Einbindung für persistente Volumes ist trivial.
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
capacity:
storage: 200Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
nfs:
path: /mnt/md1/shares/kubernetes
server: 192.168.10.2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
labels:
demo: nfs-pvc
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Gi
Dabei wird das NFS Share /mnt/md1/shares/kubernetes
vom NFS Server 192.168.10.2
mit 200GB als PersistentVolume und Claim dem Cluster bekannt gemacht.
Hier eine Liste nützlicher Befehle
kubectl cluster-info
Zeigt einem den Status des Kubernetes Clusters an. Wichtig finde ich den Befehl, weil er den Einstiegspunkt (Master) aufzeigt.
kubectl completion bash > /etc/bash_completion.d/kubectl
Erzeugt die Bash-Completion und erstellt eine entsprechende Datei für Bash (gibts auch für zsh). Danach einmal neu einloggen und das Leben mit kubectl
ist deutlich einfacher.
kubectl get services --all-namespaces
Die Services in Kubernetes sind in Namespaces gruppiert. Mit diesem Befehl kann man sich alle Namespaces und die darin definierten Services anzeigen lassen. Sehr wichtig für den ersten Überblick.
kubectl get pods --all-namespaces
Mindestens genauso interessant ist die Liste aller Pods. In jedem Namespace, zu jedem Service gibt es eine Reihe von Pods. Jeder Pod enthält einen oder mehreren Container (also Docker-Prozess).
kubectl get secrets
Liefert eine Liste aller Tokens, die in dem Cluster im Namespace default konfiguriert sind. Kann man mit –all-namespace auch auf alle Namespaces anwenden.
kubectl get secrets default-token-....
Liefert dem Default-Token. Eigentlich das gleiche, wie get secret. Mit folgendem Befehl kann man sich aber das Zertifikat ziehen, was man zB. braucht, um Gitlab zu füttern.
kubectl get secret default-token-... -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
Liefert das Zertifkat des Defaulttokens. Das wird zB. für Gitlab gebraucht.
kubectl apply -f patch.yaml
Mit dem Apply Parameter kann man den Kubernetes Cluster konfigurieren. Das ist ein hochgradig wichtiger Befehl, der so richtig nervig ist. Denn die Daten im YAML Format sind alles andere als hübsch und schlecht beschrieben. Beispiele folgen weiter unten.
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
Damit bekommt man eine Token für den externen Zugriff aufs System. Der innere Kubectl Befehl listet alle Tokens auf, grepped den User bzw. Tokenname, den man will und der zweite äußere Befehl zeigt mit describe secret die Daten darin an.
kubectl logs --namespace gitlab-managed-apps install-helm
Damit kann man sich die Logs eines Pods anschauen.
kubectl exec -it --namespace default shell-demo /bin/bash
Mit dem Befehl kann man in einen Pod einsteigen und einen Befehl ausführen. Das -it verursacht, dass der Befehl nicht gleich wieder beendet wird (also wirklich ein TTY öffnet). Ist sehr ähnlich zu docker/podman exec.
heml repo add stable https://kubernetes-charts.storage.googleapis.com
helm repo update
helm repo remove stable
Helm ist eine Art Meta-CLI-Befehl. Damit kann man in den Cluster einige vorgefertigte Pakete installieren. Ziel ist die Vereinfachung der Installation und des Managements. Ist wie ein Paketmanager zu betrachten. Die Pakete liegen als Charts vor. Meiner Meinung nach ein verwirrender Begriff.
Hier finden sich einige nützliche und interessante Links an, die sich im Laufe der Recherche angesammelt haben.
Mal genauer reinschauen:
bootcamp2.yaml
apiVersion: v1 kind: Service metadata: name: bootcamp-service spec: type: ClusterIP selector: app: bootcamp ports:
04 helm repo list 07 helm repo update 20 kubectl get pods 64 kubectl get pods -o wide 22 kubectl get nodes 30 kubectl get deployments 56 kubectl get services 57 kubectl get svc 26 kubectl proxy 44 kubectl describe pods 33 kubectl apply -f bootcamp.yml 42 kubectl exec kubernetes-bootcamp-fcc5bfb48-48pvr - sh 52 curl http://localhost:8001/api 53 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr 54 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr/proxy 55 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr/proxy/ 65 more /run/flannel/subnet.env
120 kubectl get services my-nginx 121 kubectl delete services my-nginx 122 kubectl delete services hellok8s-service 123 kubectl delete services kubernetes 124 pico test.yaml 125 kubectl delete deployments.apps hellok8s-deployment 126 kubectl create -f test.yaml 127 kubectl get all 128 kubectl get all 129 kubectl get all 130 kubectl port-forward service/hellok8s-service 8080:8080 131 more test.yaml 132 kubectl get all 133 kubectl port-forward service/bootcamp-service 8080:8080 134 ll 135 ls -al 136 curl https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml 137 curl https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml 138 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml 139 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml 140 kubectl create secret generic -n metallb-system memberlist –from-literal=secretkey="$(openssl rand -base64 128)" 141 kubectl get all -n metallb-system 142 kubectl get all -n metallb-system 143 pico metallbconfig.yaml 144 kubectl apply -f metallbconfig.yaml 145 ls -al 146 more bootcamp2.yml 147 history
Als am Anfang der IPv4 Welt der 4 Milliarden Addressen umfassende Zahlenraum definiert wurde, war nicht klar, welche Auswirkungen diese Entscheidung hatte.
Das heute fast jeder Mensch ein Handy hat, welches eine Adresse braucht, um sich im Internet bewegen zu können, war da noch nicht abzusehen. Man hat sich lange damit beholfen, via NAT separate Adressräume zu bilden. Aber mittlerweile hat man als Admin beim Aufbau von VPNs so häufig mit Adressraumkollisionen zu tun, dass man schon über weitere Maßnahmen wie VLAN nachdenken muss. Das es speziell im ostasiatischen Raum langsam eng wird, führte zu erhöhtem Druck, sich diesem Thema anzunehmen.
Deshalb hat man sich IPv6 ausgedacht. Der Adressraum wirkt im ersten Moment riesig. Eine Adresse stammt aus dem Bereich 2^128. Das ist eine enorm große Menge.
Leider verschwendet man diese Adressen. Bei einigen Hostern kriegt man pro virtueller Maschine einen 64er Block. Das sind 17 Trillionen Adressen. Wofür man die in einer einzelnen VM gebrauchen könnte, kann ich beileibe nicht sagen. Mir fallen keine Szenarien ein. Selbst wenn man den IPv4 Adressraum mit seinen 4 Milliarden Adressen darin spiegeln will, kann man das auch wieder 4 Milliarden mal tun. Wenn man zB. also in der VM mit Docker Prozessvirtualisierung betreibt, könnte man Milliarden von Adressen benutzen - ich denke, die CPU und der RAM wird früher aufgeben.
Nun, mal abgesehen von den Absurditäten mit den riesigen Zahlen muss man sich auch einige Themen genauer anschauen. IPv6 wirkt zunächst ziemlich gleich zu IPv4. Mal abgesehen davon, dass die Adressen anders geschrieben werden, gibt es eine große Gemeinheit. Man hat sich entschieden kein NAT mehr zuzulassen. Da kann man verschiedener Meinung sein, aber spätestens wenn es um Datenschutz oder Absicherung von inneren und externen Systemen geht, kommt man da schnell an Punkte, an denen man bisher einfach anders gedacht hat. Eine zentrale Instanz für das Routing (NAT) braucht es eigentlich nicht mehr und so mit gerät auch das Thema Firewall, was häufig mit Routing kombiniert wird, schnell in Bedrängnis.
Hier finden sich einige nützliche und interessante Links an, die sich im Laufe der Recherche angesammelt haben.
Docker hat das mit der IPv6 Unterstützung ziemlich versaut, weil man dort einfach sagt, installier es, konfigurier es und gut ist - leider wird bei IPv6 aber auch das Portforwarding ignoriert und jeder Port der in einem Container offen ist, wird nach außen offen gemacht, egal ob gewollt oder nicht. Und das ist natürlich Mist, daher die Krücke mit einem Image, dass beim Docker-Daemon prüft, wenn neue Services hochkommen und dann die Routen und Firewallregeln auch auf IPv6 anwendet.
Interessant ist dabei evtl auch, dass man das nutzen kann im Zusammenhang mit Acoby, dass dort gesagt wird, von welchem Loadbalancer aus die Ports offen sind (statt 0.0.0.0)
GlusterFS ist ein einfaches und nützliches verteiltes Dateisystem.
Hat man Dateien, die auf mehreren Servern synchron gehalten werden sollen, ist Gluster ideal. Man kann mit Gluster eine Reihe von unterschiedlichen Redundanz-Szenarien abbilden. Zum Beispiel kann man RAID-ähnliche Spiegelungen (0,1,5,10) aufbauen, in dem man bei der Anlage der Volumes die Zahl der Kopien definiert. Auf diese Weise lassen sich die Platten mehrerer Server verknüpfen und grosse Datenmengen ablegen.
Ein Wort der Warnung vorweg. Egal wie man es anstellt, die synchrone Verteilung von Daten verbraucht immer auch Netzwerkresourcen und ist von dessen Kapazität direkt abhängig. selbst für wenige Datenänderungen (ein schreibender Nutzer) kann ein Server mit einem 1Gbs Interface zu wenig sein. Die Server müssen darüber nicht nur den Dienst anbieten, sondern auch den Datenabgleich zwischen allen Knoten. Ausserdem ist Gluster für Dateien da, das Verteilen von Datenbankdateien oder Sockets ist kritisch und kann bei starker Netzauslastung für Fehler sorgen. Wir konnten das bei besagtem Setup nach nur wenigen Tagen Betrieb reproduzieren. Gluster muss nicht, aber sollte eine separate Netzwerkkarte für sich alleine haben und die sollte man gut im Auge behalten.
Was in dem Setup aber gut geht, sind reine Filesysteme mit grossen Readanteil, wie zB HTDoc-Verzeichnisse. Da Gluster auf Pfaden aufsetzt und damit keine eigenen Partitionen braucht, ist das Anlegen relativ einfach auf auf Maschinen mit nur einer Platte (VM) möglich.
Gluster ist als Paket bei Debian dabei und daher ist eine Installation denkbar einfach.
apt-get install glusterfs-server
service glusterd start
Damit ist der Server installiert und der Service bereit zur Nutzung. Zu beachten ist natürlich, dass das auf allen Nodes zu erfolgen hat.
Damit die Nodes sich gegenseitig kennt, beginnt man auf einem der Nodes ein “Probe” auf die anderen.
server1:$/> gluster peer probe server2.fqdn
Dadurch wird nicht nur eine Art Ping ausgeführt. Die Server tauschen auch im Zweifel Schlüssel aus und kennen sich von nun an. Man kann auch einen Server wieder aus dem Cluster werfen.
Die Konfiguration geschieht in zwei Phasen. Zunächst brauchen wir Volumes, die sich alle Nodes teilen. Die Teilung erfolgt auf drei Methoden - alles wird gespiegelt auf allen gleich, ein Teil wird gespiegelt, so dass alle Dateien mindestens x mal vorhanden sind oder es wird gestretcht - also aller Speicher wird genutzt, die Dateien liegen aber nur einmal vor und werden nur von der einen Quelle gelesen. Man kann das sehr gut mit den RAID Leveln vergleichen, wobei letzterer die geringste Ausfallsicherheit hat, erster maximal sicher ist.
Nun kommt es darauf an, wie man die Volumes konfiguriert und Gluster führt hier einen Begriff ein, der Brick heißt. Ich stelle mir vor, dass Brick ein Teil des Gluster-Clusters ist. Alle Steine zusammen ergeben das Volume. Was ich nicht verstehe, warum man bei Gluster derart unterscheidet zwischen dem Node und dem Brick und die Beispiele es einem Anfänger hier auch unnötig kompliziert machen. Letzlich ist es nur ein Pfad.
Man gibt also für ein Volume pro Node an, wo die Dateien für das Volume abgelegt sind. Ich tue das pro Volume und pro Node immer am gleichen Ort pro Server. Also legen wir auf allen Server ein Verzeichnispfad an.
mkdir -p /srv/gfs/gv0
In diesem Verzeichnis liegen zukünftig alle Daten unseres Volumes gv0. Dann können wir das Volume anlegen.
gluster volume create gv0 replica 2 server1.fqdn:/srv/gfs/gv0 server2.fqdn:/srv/gfs/gv0 force
In dem Verzeichnis wird ein .glusterfs Verzeichnis angelegt, in dem Gluster alle seine Verwaltungsdaten ablegt. Der Rest wird verteilt und kann so auch benutzt werden. Ich empfehle aber eher einen Mountpoint anzulegen und das GlusterFS zu mounten und richtig daraufzu schreiben.
Dafür braucht es einen Client. Auch den gibt es natürlich bei Debian als Paket.
apt-get install glusterfs-client
mount -t glusterfs ac-sh0003.acoby.de:gv2 /mnt/
Will man darüber hinaus Gluster für die Volumes von Docker verwenden, empfehle ich folgendes:
docker plugin install --alias gfs trajano/glusterfs-volume-plugin --grant-all-permissions --disable
docker plugin set gfs SERVERS=server1.fqdn,server2.fqdn
docker plugin enable gfs
Dann kann man sehr simple Docker anweisen, die Volumes mit dem PlugIn GFS verteilt abzulegen.
volumes:
htdocs:
driver: gfs:latest
name: "gv0/htdocs"
Will man die Performance optimieren, gibt es eine Vielzahl an Parametern, die man setzen kann.
gluster volume set gv0 nfs.disable on
gluster volume set gv0 performance.cache-max-file-size 128MB
gluster volume set gv0 performance.cache-size 256MB
gluster volume set gv0 performance.flush-behind on
Und hiermit kann man den Zustand des Systems einsehen.
gluster volume info
gluster volume status
gluster volume heal gv0 info summary
Hier finden sich einige nützliche und interessante Links an, die sich im Laufe der Recherche angesammelt haben.