pixel
Select Page

NETWAYS Blog

DockerCon 2022: Wie geht Containersecurity?

Buzzwords wie Software Supply Chain, Container Security Scanning oder Software Bill of Materials (SBOM) sind in den vergangenen zwei Jahren vermehrt in aller Munde, nicht zuletzt aufgrund des anhaltenden Trends zur Containerisierung vormals monolithischer Anwendungen und deren Betrieb als sog. Microservices. Allerdings kann nach wie vor nicht jeder, der auf die ein oder andere Weise mit Docker oder Containern im Allgemeinen zu tun hat, etwas mit diesen Begriffen anfangen. Aus diesem Grund habe ich den Fokus meines virtuellen Besuchs der diesjährigen DockerCon auf genau diesen Themebereich – Containersecurity – gelegt und möglichst viele Best Practices für Dich zusammengefasst.

 

Die Ausgangslage

Die großen Sicherheitslücken der vergangenen zwei Jahre, sei es der Solarwinds-Breach oder die Log4J/Log4Shell-Exploits haben einmal mehr schmerzlich bewusst gemacht: In fast jedem Softwareprodukt, egal ob proprietär oder Open Source, befinden sich zahlreiche mehr oder weniger gut gepflegte Abhängigkeiten verschiedenster Maintainer – in vielen Applikationen stecken bis zu 80% Open Source Code, die es “auf dem Schirm” zu behalten gilt. Dies gilt natürlich auch für Container(-images), die letzten Endes nichts anderes tun, als die Applikation zu bündeln und (weitestgehend) losgelöst vom ausführenden Hostsystem ausführbar zu machen.

Dennoch gelten Container paradoxerweise oft als besonders sicher, sei es aufgrund der von außen wahrgenommenen “Kapselung” oder der geringen Größe – soviel potentiell vulnerable oder bösartige Software kann da doch gar nicht drinstecken, oder? Mögen diese Wahrnehmungen in der Theorie und im Bestfall auch stimmen, sieht die Praxis in den allermeisten Fällen anders aus, wie die ein oder andere Keynote im Rahmen der DockerCon gezeigt hat.

Laut SysDig, einem Spezialisten für Cloudsecurity, laufen 58% der in den einschlägigen Containerregistries (DockerHub, Google, Github, etc.) verfügbaren Containerimages als root, anstatt einen geeigneteren, unprivilegierten Nutzer zu verwenden. Außerdem beinhalten selbst die offiziellen, von den Registries kuratierten Containerimages beliebter Frameworks oder Distributionen dutzende detektierbare Schwachstellen.

Offensichtlich gibt es also zum Einen ein falsches Gefühl von Sicherheit innerhalb der Nutzergemeinschaft von Containern, zum Anderen aber schlichtweg keine Blaupause oder “OneFitsAll”-Lösung, die für beliebige Container(-images) absolute Sicherheit verspricht. Nur eine Sache ist klar: “Hinten” anzufangen, ist nicht rentabel – Container einfach zu deployen und dann im Betrieb nach Sicherheitslücken zu suchen, ist teuer, erzeugt vermeidbaren zusätzlichen Aufwand und nimmt Flexibilität. Die Absicherung der Container muss “nach links” geschoben werden, möglichst an den Beginn der Containerisierung. Doch wo ansetzen?

 

Die Möglichkeiten

Um den Ursprung für Schwachstellen in Container(-images) und damit einhergehend mögliche Ansatzpunkte zur Absicherung zu identifizieren, muss man die “Lebensphasen” eines Container(-images) verstehen. Diese lassen sich kurz und bündig wie folgt darstellen:

 

  • Bau des Images (lokal oder via CI/CD)
  • Distribution des Images – Push in eine Registry (Self-hosted oder bei einem Anbieter), Pull von Usern/Programmen
  • Deployment des Images (Openshift, (Managed) Kubernetes, etc.) und Betrieb des Containers

 

An dieser Stelle wird hoffentlich klar, warum ich permanent “Container(-image)” schreibe – abhängig von der betrachteten Lebensphase haben wir es beim Thema Containersicherheit entweder mit einem erstellten Image oder mit einem laufenden Container, quasi einer Instanziierung dieses Images zu tun. Mit dieser Aufschlüsselung in verschiedene Abschnitte können wir uns nun mögliche Schritte zur Absicherung unserer Container anschauen.

Containerimages werden entweder lokal von Entwickler:innen, DevOps-Engineers, etc. auf Grundlage eines sog. Dockerfiles gebaut – alternativ lässt sich dieser Vorgang aber auch von CI/CD Pipelines umsetzen, wenn man den Dockerfile und andere benötigten Ressourcen bspw. in GitLab eincheckt. Die meisten Möglichkeiten zur Absicherung des späteren Container(-images) ergeben sich bereits in diesem ersten Schritt – der Quellcode der zu containerisierenden Applikation liegt vor, ebenso die Definition des Containers selbst, und auch möglicherweise vulnerable oder bösartige Abhängigkeiten wurden noch nicht in das spätere Containerimage eingebettet.

Ist das Containerimage lokal oder in einer Pipeline gebaut worden, muss man es zur späteren Nutzung in Produktion in eine Containerregistry übertragen. Diese kann entweder selbst gehosted werden, als private, aber gemanagte Instanz in der Cloud laufen oder öffentlich von jedem beliebigen Nutzer einsehbar sein. Auch bei diesem Vorgang gibt es Möglichkeiten, die Supply Chain zwischen Entwickler und Endnutzer abzusichern.

Auch wenn im Dockerfile gewisse Best Practices befolgt werden, kann der Container diese in vielen Fällen beim Deployment überschreiben – das letzte Wort haben hierbei immer Kubernetes, Openshift, Docker Desktop und Konsorten. Aus diesem Grund müssen einige der Überlegungen, die in der Build-Phase des Containerimages stattgefunden haben, auch hier noch einmal herangezogen, betrachtet und evaluiert werden, um die für die konkrete Nutzung besten Einstellungen und Kompromisse zu finden.

Ist der Container erst einmal deployed, gibt es nicht mehr viele Möglichkeiten, ihn “von innen” weiter abzusichern. Dennoch kann und sollte man sich fortlaufend Gedanken bspw. um die Erreichbarkeit innerhalb des Clusters, des Netzwerkes, der Cloud etc. machen, Regeln nachziehen wo nötig und natürlich auch Update- und Backupstrategien im Hinterkopf behalten. Am Ende des Tages merkt man spätestens jetzt: Nach dem Deployment eines Containers seine Sicherheit zu überprüfen und zu garantieren, ist nicht sinnvoll – wir brauchen einen Left Shift.

 

Die Umsetzung

Buildphase

Nach viel Theorie und Konjunktiv können wir uns jetzt konkrete Umsetzungsmöglichkeiten der besprochenen Ansätze anschauen. Beginnen werden wir “ganz links” beim Bau der Containerimages. Vieles lässt sich hier über verschiedene Direktiven im genutzten Dockerfile festlegen – Docker listet alle möglichen Direktiven sowie sinnvolle Best Practices und Caveats in der Docker Dokumentation auf. Für uns besonders interessant sind die Direktiven ADD, COPY und USER. Wie eingangs erwähnt, nutzen mehr als die Hälfte aller Containerimages per Default den root Nutzer innerhalb des Containers, obwohl das in vielen Fällen gar nicht notwendig wäre – auf einem klassischen Server läuft ein Apache2 Webserver schließlich auch als User apache, warum sollte/müsste das innerhalb eines Containers bspw. auf Debian-Basis anders sein?

Die anderen beiden erwähnten Direktiven beziehen sich auf die Abwägung, ob man gewisse Verzeichnisse und Dateien aus seiner Entwicklungsumgebung denn tatsächlich im endgültigen Container braucht – oder ob man die Applikation direkt lokal baut, und lediglich die fertige Binary via COPY in das Containerimage überträgt. In diesem Fall muss man natürlich darauf achten, dass etwaige zur Laufzeit benötigte Tools (ich meine dich, curl) und Bibliotheken sich auch im endgültigen Containerimage befinden. Alternativ kann man innerhalb eines Dockerfiles eine build und eine run Umgebung schaffen – auf diese Weise kann man die Applikation innerhalb des Containers bauen und im Anschluss lediglich die benötigten binären Artefakte und andere benötigten Ressourcen in das Lauzeitimage kopieren. Für diese Vorgehensweise würde es sich anbieten, das Entwicklungsrepository via ADD in die build Umgebung des Containerimages zu übertragen.

Diese Vorgehensweise bringt uns direkt zur nächsten “beliebten” Unsicherheit in Container(-images): Lokal hinterlegte Credentials, Entwicklertokens, Cloudzugänge etc. Es gibt vermutlich keine Art von geheimen Daten, die nicht bereits versehentlich in Containerimages “vergessen” wurde. Das kann schnell passieren – ein Entwickler nutzt ein Shellscript mit Zugangsdaten, um von seiner Entwicklungsumgebung in ein Testcluster zu deployen, eine .yaml-Datei, um Daten aus einer Entwicklerdatenbank zu lesen etc. Im “schlimmsten Fall” überträgt er wissentlich eine Datei mit Zugangsdaten, die die containerisierte Applikation später in Produktion nutzen soll, oder hinterlegt sensible Daten im Containerimage als Umgebungsvariable mittels ENV.

Zur Bewältigung dieser Problematik gibt es die Möglichkeit, ähnlich wie eine .gitignore Datei für Git-Repositories eine .dockerignore Datei für den Buildvorgang eines Containerimages zu hinterlegen. Alle in dieser Datei aufgeführten Dateien und Verzeichnisse werden vom Dockerdaemon bei der Verarbeitung des Build-Contexts, von ADD und von COPY Direktiven ignoriert und finden sich somit zu keinem Zeitpunkt im Containerimage wieder. Dringend benötigte Umgebungsvariablen zur Konfiguration der containerisierten Applikation können auch zum Zeitpunkt des Deployments noch übergeben werden, bspw. mittels des Parameters -e via Docker-CLI oder dem Einlesen von Secrets als Umgebungsvariablen in Kubernetes.

Grundlegende Maßnahmen wie die Nutzung eines passenden Base-Images in der FROM Direktive (es muss nicht immer debian:buster oder ubuntu:20.04 sein), das Vermeiden der Öffnung unbenötigter Ports via EXPOSE sowie der Nutzung sog. Kannibalen-Tags (latest zeigt nach jedem Imageupdate auf eine neue Version) sollten darüber hinaus natürlich immer befolgt und beachtet werden.

Distributionsphase

Haben wir nun lokal oder in der Pipeline ein in sich möglichst sicheres Containerimage gebaut, muss es auf die eine oder andere Art und Weise seinen Weg in eine Containerregistry finden, um von anderen Nutzern heruntergeladen und deployed werden zu können. Für diese Vorgänge werden einem von den meisten Containerregistries Werkzeuge an die Hand gegeben, mit deren Hilfe wir den Prozess des Up-/Downloads von Containerimages absichern können.

Letzten Endes sind Containerregistries nichts anderes als APIs mit der Fähigkeit, Nutzer zu authentifizieren und zu authorisieren, Containerimages in Empfang zu nehmen, zu speichern und deren Versionierung im Blick zu behalten. Wie immer, wenn man über das Internet mit einer API spricht, gilt: HTTPS ist Pflicht! Darüber hinaus bieten Registries verschiedene Möglichkeiten, zusätzliche Maßnahmen gegen die Verbreitung unsicherer Images oder die Manipulation vorhandener Containerimages zu treffen. So können Imageregistries oftmals alle verwalteten Containerimages auf bekannte Schwachstellen scannen und den Download solcher Images durch Endnutzer untersagen. Auch die digitale Signierung von verwalteten Containerimages ist oftmals möglich, z.B. mittels sigstore und cosign.

Deploymentphase

Wird unser Containerimage nun im Rahmen eines Kubernetes-Deployments oder Docker-CLI-Befehls aus der Registry gepulled und deployed, haben wir einmal mehr die Möglichkeit, Sicherheit zu forcieren: Wie bereits erwähnt, können wir zum Einen die Voreinstellungen in Hinblick auf Umgebungsvariablen, User- und Gruppenkontext uvm. überschreiben, zum Anderen bietet natürlich auch die Absicherung der ausführenden Infrastruktur selbst die Möglichkeit, dass Deployment so sicher wie möglich zu gestalten.

Hierzu zählen z.B. die Nutzung sog. rootless Container, die von einem Container Runtime Interface (CRI) wie Docker oder containerd ausgeführt werden, die selbst nicht im root Kontext laufen. Auch die Nutzung restriktiver Netzwerk- und Firewallpolicies kann dabei helfen, die Risiken durch möglicherweise vulnerable oder bösartige Container zu minimieren. Konfiguration und Forcierung dieser Maßnahmen innerhalb eines Clusters können schnell zur Sisyphusarbeit werden – hier kann ein gemanagtes Kubernetes-Cluster von Vorteil sein, bspw. Managed Kubernetes von NETWAYS Web Services. Darüber hinaus sollte man eine nachhaltige Update-Strategie für seine Containerimages verfolgen: Es gilt, einen guten Kompromiss zwischen regelmäßigen Updates zu finden, aber nicht sofort jede neue Version (und deren evtl. neu eingeführte Schwachstellen) in Produktion zu deployen.

 

Das Fazit

Container(-images) sicher zu erstellen, zu verwalten und zu deployen ist ein langer Weg voller Stolpersteine. Wie so oft beim Thema Sicherheit wird man die 100% aller Voraussicht nach nicht erreichen – Nichtexistenz von Sicherheitslücken lässt sich nun einmal nicht beweisen. Dennoch habe ich Dich hoffentlich für das Thema und die teils falschen, gängigen Annahmen und Praktiken sensibilisieren können und nebenbei einige Best Practices an die Hand gegeben, die das Risiko durch vulnerable Container deutlich verringern.

Solltest Du nun mehr über den Vorgang des Imagebaus, den Betrieb von Containern in Kubernetes oder Containerisierung im Allgemeinen erfahren wollen, schau Dir doch einmal unser Kubernetes Schulungsangebot von NETWAYS an. In diesem eintägigen Workshop vermittle ich Dir praxisnah und einsteigerfreundlich alles, was Du für die ersten eigenen Schritte mit Docker und Kubernetes wissen musst. Ich freue mich auf Dich!

Daniel Bodky
Daniel Bodky
Consultant

Daniel kam nach Abschluss seines Studiums im Oktober 2021 zu NETWAYS und berät nun Kunden zu den Themen Icinga2 und Kubernetes. Nebenher schreibt er in seiner Freizeit kleinere Tools für verschiedenste Einsatzgebiete, nimmt öfters mal ein Buch in die Hand oder widmet sich seinem viel zu großen Berg Lego. In der wärmeren Jahreszeit findet man ihn außerdem oft auf dem Fahrrad oder beim Wandern.

Ansible – How to create reusable tasks

Ansible is known for its simplicity, lightweight footprint and flexibility to configure nearly any device in your infrastructure. Therefore it’s used in large scale environments shared between teams or departments. Often tasks could be used in multiple playbooks to combine update routines, setting downtimes at an API or update data at the central asset management.

To use external tasks in Ansible we use the include_task module. This module dynamically includes the tasks from the given file. When used in a specific plays we would assign play specific variables to avoid confusion. For example:


vim tasks/get_ldap_user.yml

- name: get user from ldap
  register: users
  community.general.ldap_search:
    bind_pw: "{{ myplay_ad_bind_pw }}"
    bind_dn: "{{ myplay_ad_bind_dn }}"
    server_uri: "{{ myplay_ad_server }}"
    dn: "{{ myplay_ad_user_dn }}"
    filter: "(&(ObjectClass=user)(objectCategory=person)(mail={{ myplay_usermail }}))"
    scope: children
    attrs:
      - cn
      - mail
      - memberOf
      - distinguishedName

If this task should be used in another playbook to reduce the amount of code or is used again with other conditions or values. Therefore the variables need to be overwritten or if it is another playbook the variables are named wrong.

The solve this problem change the variables to unused generic variables. And assign your own variables in the include_task statement.


vim tasks/get_ldap_user.yml

- name: get user from ldap
  register: users
  community.general.ldap_search:
    bind_pw: "{{ _ad_bind_pw }}"
    bind_dn: "{{ _ad_bind_dn }}"
    server_uri: "{{ _ad_server }}"
    dn: "{{ _ad_user_dn }}"
    filter: "(&(ObjectClass=user)(objectCategory=person)(mail={{ _ad_usermail }}))"
    scope: children
    attrs:
      - cn
      - mail
      - memberOf
      - distinguishedName

The include_task vars parameter provides own variables to the tasks.


vim plays/user_management.yml
[...]
- name: check if user exists in ldap
  include_tasks:
    file: tasks/get_ldap_user.yml
  vars: 
    _ad_bind_pw: "{{ play_ad_pw }}"
    _ad_bind_dn: "{{ play_ad_user }}"
    _ad_server: "{{ play_ad_server }}"
    _ad_user_dn: "OU=users,DC=example,DC=de"
    _ad_usermail: "{{ play_usermail }}"

This can be easily combined with loops, to enhance the reusability of your tasks even more! Checkout this blogpost about looping multiple tasks. Ansible – Loop over multiple tasks

Check out our Blog for more awesome posts and if you need help with Ansible send us a message or sign up for one of our trainings!

Thilo Wening
Thilo Wening
Senior Consultant

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.

Riesige Datenmengen transportieren – ein Ding der Unmöglichkeit?

Zugegeben, vor dem konkreten Problem, eine fast 2 TB große Datei von einem System zu transportieren, auf das man nur über mehrere Hops Zugriff hat, steht man nicht jeden Tag. Aber wenn doch, dann bietet eine ganze Sammlung an Tools eine gemeinsame Lösung an.

Mit “mehrere Hops” ist gemeint, dass es nicht möglich ist, mit den üblichen Bordmitteln wie scp oder rsync Daten zu kopieren. Ein Beispiel könnte ein Citrix-Zugang mit PuTTy sein, der zwar Shell-Zugriff erlaubt, aber kein Kopieren. Im konkreten Fall “durften” die Daten natürlich kopiert werden, es war schlicht nur technisch nicht “möglich”. Der Host, auf dem die Daten lagen, hatte Zugriff auf Websites im Internet durfte aber keine anderen Protokolle “nach draussen” benutzen.

Zerhacken und Zerteilen um riesige Datenmengen transportieren zu können

Zwar ist es heutzutage eigentlich problemlos möglich, auch riesige Datenmengen von A nach B zu kopieren. Da wir hier aber von einem nicht unerheblichen Zeitraum sprechen, den die Kopie braucht und ein kurzer Abbruch wertvolle Zeit gekostet hätte, war der Ansatz erstmal folgender:

  • Die Daten so klein komprimieren wie es sinnvoll ist (hier mit bzip2)
  • Die Daten klein hacken (mit split)
  • Die einzelnen Stücke dann hochladen
  • Beim Empfänger zusammensetzen und entpacken

Das lässt sich ganz gut per Shell realisieren.

nohup time tar cjvf testdb.tar.bz ../backups/full/ &
split -b5000000000 testdb.tar.bz

Die Datenbank bestand dabei aus einigen sehr kleinen Dateien und einigen, die etliche GB und teilweise sogar über 1 TB groß waren. Es zahlte sich also nicht aus, die Dateien einzeln zu kopieren.

  • nohup lässt den Befehl weiterlaufen, auch wenn die Verbindung abbricht und schreibt den Output nach nohup.out
  • time gibt ab, wie lang das Komprimieren braucht. Weil ich einfach ein neugieriger Mensch bin
  • tar mit -j komprimiert die Dateien mit bzip2 statt gzip was in vielen Fällen zwar länger dauert aber deutlich kleinere Dateien hervorbringt
  • split hackt eiskalt die Dateien in kleine Teile. In diesem Fall jeweils 5 GB groß. Dabei hat der Befehl noch einige Optionen um einem das Leben leichter zu machen. Es zahlt sich aus, da etwas mehr nachzustöbern als ich das damals gemacht hab

Somit hat man dann eine große Anzahl “ausreichend kleiner” Dateien. Mit tail -f nohup.out kann man beobachten, was sich gerade tut. Das geht auch, wenn disconnected wurde. Alternativ kann man auch screen oder tmux nehmen. Da diese Tools aber Probleme mit manchen remote Verbindungen machen und nicht immer zur Verfügung stehen, bleib’ ich persönlich lieber bei nohup.

Übrigens sollte man nicht unbedingt an die maximale Obergrenze des Uploads gehen. Beim ersten Versuch hab’ ich das getan und die Daten wurden teilweise von NextCloud verworfen. Warum, hab’ ich nicht mehr versucht herauszufinden, weil es bei der Methode eigentlich egal ist, ob man wenige große oder sehr viele sehr kleine Dateien nimmt. 5 GB haben aber gut funktioniert.

Der Transporter

Zugegeben, ich hatte den Vorteil, eine NextCloud Instanz nutzen zu können, die noch dazu genug Platz bot. Der Trick funktioniert bis hier her aber natürlich auch mit jedem anderen Übertragungsweg. Auch wenn man die Dateien ggf. kleiner hacken muss.

Für den weiteren Schritt baut man sich ein kleines Script.

#!/bin/bash
for i in $(ls x*)
do
  curl -T $i -u transportuser:$MYPASSWORD https://nextcloud.example.com/remote.php/webdav/testdb/$i
done

Auch dazu ein paar Erklärungen.

  • ls x* gibt alle Dateien aus, die split erstellt hat. Ohne weitere Optionen startet der Name aller zerlegten Dateien mit x
  • In der NextCloud wird im Home von User transportuser der Ordner testdb angelegt
  • curl nutzt WebDAV um die einzelnen Schnippsel hochzuladen
  • Der User transportuser wurde dafür extra angelegt und der Ordner testdb den eigentlichen Empfängern freigegeben. Das erleichtert das Management und vor allem das Passworthandling
  • Das Passwort kommt im nächsten Schritt

Tatsächlich riesige Datenmengen transportieren

Das Script hat noch eine Einschränkung. Da es auf einem “fremden” System liegt, möchte man darin natürlich nicht das Passwort eines NextCloud Users hinterlegen. Wir haben einige Möglichkeiten ausprobiert und dabei auch bedacht, dass man es evtl. aus der Shell-History oder der Prozessliste auslesen könnte. Das beste, das wir bisher gefunden haben ist eine Umgebungsvariable, die nicht in der History landet.

 export MYPASSWORD=mysupersecret
nohup time ./thetransporter.sh

Und wieder Erläuterungen.

  • Die erste Zeile ist um eine Stelle eingerückt. Das verhindert (üblicherweise!) dass sie in die Shell History aufgenommen wird. Bevor man sich darauf verlässt, sollte man das unbedingt testen!
  • Die zweite Zeile ruft schlicht das Script von oben auf, wo die Umgebungsvariable genutzt wird

Andere Lösungen, wie eine interaktive Angabe beissen sich sich mit nohup.

Ganz unabhängig davon, ob das Passwort hier sicher genug war oder nicht, schadet ein Wechsel des Passworts direkt im Anschluss sicher nicht. Hat man sich einen eigenen User für den Transport angelegt, kann man ihn auch einfach wieder entfernen.

Die Auflösung

Hat man die Daten so in die NextCloud gepushed, kann man sie einfach mit dem NextCloud Client auf ein eigenes Gerät synchronisieren lassen. Tip: Man kann im Client angeben, welche Ordner synchronisiert werden sollen und welche nicht, falls man mehrere Geräte angeschlossen hat.

Um die Daten wieder zusammenzubauen reicht ein schlichtes cat.

cat x* > testdb.tar.bz2
tar xvf testdb.tar.bz2

Und was? Ja, Erläuterungen.

  • split benennt die Dateien so, dass die alphabetische Sortierung von cat sie wieder richtig zusammenbaut
  • tar ist inzwischen so schlau, dass man die Kompressionsmethode nicht mehr angeben muss. Schadet zwar nicht, sieht aber irgendwie cooler aus so

Besonders charmant finde ich daran, dass es den Tools völlig wurscht ist, was in den Dateien drin ist. Ob das Klartext, binaries oder was auch immer sind. Sie tun ja nichts mit dem Inhalt, zerhacken sie nur und stückeln sie zusammen.

Wer uns gern an solchen Lösungen kiefeln und werkeln sehen möchte, schliesst am besten gleich einen Support-Vertrag bei uns ab.

Thomas Widhalm
Thomas Widhalm
Lead Senior Systems Engineer

Pronomina: er/ihm. Anrede: "Hey, Du" oder wenn's ganz förmlich sein muss "Herr". Thomas war Systemadministrator an einer österreichischen Universität und da besonders für Linux und Unix zuständig. Seit 2013 ist er bei der NETWAYS. Zuerst als Consultant, jetzt als Leiter vom Operations Team der NETWAYS Professional Services, das unter anderem zuständig ist für Support und Betriebsunterstützung. Nebenbei hat er sich noch auf alles mögliche rund um den Elastic Stack spezialisiert, schreibt und hält Schulungen und...

Kubernetes: Mehrere Cluster mit kubectl

Während man mit Kubernetes arbeitet oder entwickelt, wird man in den seltensten Fällen alles auf einem Cluster machen. Ob man ein lokales Minikube verwendet, oder Zugriff auf verschiedene Cluster für Produktion und Testing hat, muss man die Daten ja verwalten, und auswählen in welchem Kontext man gerade arbeitet.

Struktur der Konfiguration

Die Konfiguration für kubectl und andere Kubernetes Clients findet man normalerweise in $HOME/.kube/config. Ich möchte hier ein paar Beispiele zeigen, wie man hier neue Cluster hinzufügen kann. Der Inhalt sieht im einfachsten Fall so aus. Hier sind die Daten bzw. Secrets abgekürzt.

apiVersion: v1
kind: Config
current-context: my-cluster
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://my-cluster:6443
  name: my-cluster
contexts:
- context:
    cluster: my-cluster
    user: admin
  name: my-cluster
users:
- name: admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

Hier sind nun folgende Bereiche wichtig:

  • clusters definiert die bekannten Cluster mit API Endpunkt (URL) und dem passenden CA Zertifikat
  • users definiert Zugangsdaten und Authentifizierungsmechanismen, meistens ein Client Zertifikat, Auth-Tokens oder SSO Daten
  • contexts verbindet Cluster und Authentifizierung, und erlaubt auch das setzen des Namespaces
  • current-context zeigt an welcher Context gerade benutzt wird

Die aktuell verwendete Konfiguration kann man sich bequem anzeigen lassen, und dabei werden dann auch die Detaildaten zensiert – wie oben.

kubectl config view

Andere Konfigurations Dateien

Viele Kubernetes Anbieter stellen direkt die fertige Konfigurationsdatei zur Verfügung, die speichert man entweder direkt unter $HOME/.kube/config oder spezifiziert diese explizit.

kubectl --kubeconfig /tmp/my-config config view

export KUBECONFIG=/tmp/my-config
kubectl config view

Dabei wird nun diese Datei alleine geladen und ist direkt benutzbar. KUBECONFIG gilt dann für alle weiteren Befehle in der Shell auch.

Verbinden der Konfiguration

Um mehrere Dateien zusammenzuführen kann man nun auch mehrere Dateien laden, und so zusammenführen.

cp ~/.kube/config ~/.kube/config.bak
KUBECONFIG=~/.kube/config.bak:/tmp/my-config kubectl config view --raw >~/.kube/config

Bitte unbedingt die Datei vor dem speichern prüfen, doppelte Namen können zu Fehlern führen und der Context wird dann neu gesetzt.

Für kleinere Korrekturen kann man dann auch problemlos die Datei nachträglich von Hand editieren.

Tools und Shell Erweiterungen

Ich nutze gerne die Tools kubectx und kubens um schnell zwischen verschiedenen Kontexten umschalten zu können, bzw. zu sehen mit was ich gerade arbeite. Dazu gibt es auch die praktische Auto-Vervollständigung für die Shell.

Welchen Kontext bzw. Namespace ich gerade nutze, zeigt mir außerdem mein Bash Prompt auch an. Hier am Beispiel von powerline-shell mit meinen Modifikationen.

Es existieren auch einfachere Möglichkeiten, wie die kube-ps1 von jonmosco auf GitHub.

Mehr Infos

Wir bereiten gerade unsere neue Kubernetes Quick Start Schulung vor um den Teilnehmern einen schnellen Einstieg in die Benutzung von Kubernetes zu bringen. Wenn du Interesse hast mehr mit uns in Kubernetes einzusteigen, schau doch mal nach den Terminen.

Das Headerbild stammt von Loik Marras via unsplash.

Von Fackeln, elektrischen Schafen und Datenpunkten

Hallo und Willkommen im Jahr 2021! sheep

Damit sind wir offiziell 2 Jahre nach dem originalen Zeitablauf von Blade Runner welcher 2019 spielt.
Hmm, nirgends sind Nexus 6 Modelle die Rumlaufen und von elektrischen Schafen träumen.
(Auch keine Flugautos) *seufz*. Egal !!

Kommen wir zu dem eigentlichen Thema diese Woche.
Ich würde euch gern im neuen Jahr kurz erklären wie ihr mit einfachen Bordmitteln & wenig Aufwand aus den den meisten APIs Prometheus Daten exportiert ohne Zusatzsoftware zu installieren.

Explainer!! Was macht man mit den Fackeln https://prometheus.io/ ist eine Software welche mit ihrer eigenen Time Series Database auch Graphen Zeichnen kann und einen ziemlichen Erfolg hat, https://prometheus.io/docs/prometheus/latest/storage/ wenn man allerdings aus seiner Allerweltsapplikation Daten nach Prometheus exportieren will ist man leicht Aufgeschmissen. Viele Anbieter verkaufen teuer Plugins welche dies für Software tun oder es gibt halt keine Schnittstelle.

Ich versuche in meinem Beispiel aufzuzeigen wie man sich selbst einen Prometheus Output baut und ihn mit dem Prometheus node_exporter zu der Haupt Prometheus Instanz bringt.

Was brauchen wir an Software dafür machen wir einen kleinen Einkaufszettel 🙂

Die Zutaten:
1x Software welche eine API zur verfügung stellt – als Beispiel nehmen wir Icinga 2 kann aber eine Software eurer Wahl sein
1x Prometheus node_exporter (muss man leider zusätzlich installieren)
1x crontab – bringt hoffentlich eure Linux Distro von Haus aus mit
1x curl – hoffentlich auch schon von “Werk” ab mit an Bord
1x awk – same here auch hoffentlich schon mit an Bord
1x jq – Okay .. ich geb zu das muss man nachinstallieren (ich versuche mich gerade unter dem Schreibtisch zu verstecken)
https://stedolan.github.io/jq/download/

Nun wo wir die Zutaten kennen kommen wir zu dem eigentlichen Rezept.
*/1 * * * * curl -k -s -u 'user:password' https://localhost:5665/v1/status | jq '.results[1].status.active_host_checks_1min' | awk {'print "node_icinga2_active_checks1m""{" "active=" "\"" "/checks1m" "\"" "}" " "$1'} > /var/lib/node_exporter/textfile_collector/active_checks.prom.$$ && mv /var/lib/node_exporter/textfile_collector/active_checks.prom.$$ /var/lib/node_exporter/textfile_collector/active_checks.prom

Was habe Ich da in der Zeile über dieser eigentlich verbrochen?
Dröseln wir das doch auf, Schritt für Schritt.

Step 1:
*/1 * * * * curl -k -s -u 'user:password' https://localhost:5665/v1/status
Ich gehe im Crontab jede Minute her und greife mir aus der lokalen icinga 2 Instanz per Api den Status per JSON.

Step 2:
| jq '.results[1].status.active_host_checks_1min'
Dann gebe ich den JSON Output an jq weiter. Dies lass ich auf einen Wert aus dem JSON parsen. Idealerweise ein Zahlenwert welchen einen schönen Datenpunkt abgibt.

Step 3:
| awk {'print "node_icinga2_active_checks1m""{" "active=" "\"" "/checks1m" "\"" "}" " "$1'}
Dies reicht leider für Prometheus nicht aus wir müssen das etwas aufbereiten das Prometheus das auch als Datenquelle akzeptiert. Der Inhalt eines .prom Files muss in folgenderweise Formatiert sein. Hier kommt awk für die Aufbereitung ins Spiel.

Was nach dem awk Befehl rauskommt sieht formatiert ungefähr so aus node_icinga2_active_checks1m{active="/checks1m"} 607.

Ich hab mal aus Beschreibungsgründen den vorderen Teil node_icinga2_active_checks1m genannt. Könnte aber auch node_raspberrypi15_active_tempsensor30s sein. Danach folgt {active="/checks1m"} 607 welches nochmal auf den Key=Value + Wert Part hinweist. Ich bin aber ehrlich hier schwimme ich selbst etwas. Wenn mir jemand hier Aufschlüsseln kann was hier “Notwendig” ist wäre ich sehr Dankbar.

Step 4:
> /var/lib/node_exporter/textfile_collector/active_checks.prom.$$ && mv /var/lib/node_exporter/textfile_collector/active_checks.prom.$$ /var/lib/node_exporter/textfile_collector/active_checks.prom
Danach wird der von awk aufbereitete String in eine active_checks.prom Datei geschrieben.

Damit haben wir ein valides Prometheus File welches wir dem node_exporter (Prometheus) übergeben können.
Was hat es nun mit /var/lib/node_exporter/textfile_collector/active_checks.prom auf sich .. Der oben in der Zutatenliste erwähnte Prometheus node_exporter ist im Grunde die Datenschleuder welche konfiguriert, in einem selbst definierten Intervall (scrape_intervall) die active_checks.prom Datei in Richtung Prometheus Hauptinstanz wirft. Die dann wiederum die Werte in ihre Time Series Database einträgt und den Graphen zeichnet.

Da der Prometheus node_exporter selbst unterschiedliche Möglichkeiten hat Daten entgegenzunehmen, wir aber in "textform" Daten ihm liefern legen wir die .prom Datei unter dem Pfad /var/lib/node_exporter/textfile_collector/ ab.

Damit der node_exporter weiß wann er in welchem Intervall die von uns Aufbereiteten Daten an das Haupt Prometheus übergeben soll.
Muss Konfig ihm sagen wann (kurzer Abriss der /etc/prometheus/prometheus.yml)
...
## Add Node Exporter
- job_name: 'icinga2'
   scrape_interval: 60s
   static_configs:
      - targets: ['x.x.x.x:9100'] <= Adresse der Haupt Prometheus Instanz welche in dem Screenshot unten uns dann einen Graphen zeichnet.

Im Prometheus (Hauptinstanz) haben wir dann so einen Graphen wie im Screenshot unten, dies kann man auch noch schöner in Grafana zeichnen lassen sprich wenn man in seiner Software ein Grafana Fenster eingebunden hat kann man diese Graphen da hinein verlinken.

Ich hoffe ich konnte euch etwas den Einstieg in das neue Jahr verschönern mit meinem Versuch euch einen Prometheus Datenexport nahe zubringen und ich freue mich über euer Feedback.
Servus bis zum nächsten Mal !

David

David Okon
David Okon
Senior Systems Engineer

Weltenbummler David hat aus Berlin fast den direkten Weg zu uns nach Nürnberg genommen. Bevor er hier anheuerte, gab es einen kleinen Schlenker nach Irland, England, Frankreich und in die Niederlande. Alles nur, damit er sein Know How als IHK Geprüfter DOSenöffner so sehr vertiefen konnte, dass er vom Apple Consultant den Sprung in unser Professional Services-Team wagen konnte. Er ist stolzer Papa eines Sohnemanns und bei uns mit der Mission unterwegs, unsere Kunden zu...