Gebäudeoptimierte Container für Kubernetes

Einführung

Containerbilder sind das primäre Verpackungsformat für die Definition von Anwendungen innerhalb von Kubernetes. Als Grundlage für Pods und andere Objekte spielen Bilder eine wichtige Rolle bei der Nutzung der Kubernetes-Funktionen, um Anwendungen auf der Plattform effizient auszuführen. Gut gestaltete Bilder sind sicher, leistungsstark und fokussiert. Sie können auf Konfigurationsdaten oder Anweisungen von Kubernetes reagieren und Endpunkte implementieren, die das Orchestrierungssystem verwendet, um den internen Anwendungsstatus zu verstehen.

In diesem Artikel stellen wir einige Strategien zur Erstellung hochwertiger Bilder vor und erläutern einige allgemeine Ziele, die Ihnen bei der Entscheidungsfindung beim Containerisieren von Anwendungen helfen sollen. Wir werden uns auf das Erstellen von Images konzentrieren, die auf Kubernetes ausgeführt werden sollen, aber viele der Vorschläge gelten gleichermaßen für das Ausführen von Containern auf anderen Orchestrierungsplattformen oder in anderen Kontexten.

Merkmale effizienter Behälterbilder

Bevor wir uns mit konkreten Maßnahmen zum Erstellen von Container-Images befassen, werden wir uns mit den Eigenschaften eines guten Container-Images befassen. Was sollten Ihre Ziele beim Entwerfen neuer Bilder sein? Welche Eigenschaften und welches Verhalten sind am wichtigsten?

Einige Eigenschaften, die angestrebt werden sollten, sind:

Ein einziger, genau definierter Zweck

Behälterbilder sollten einen einzelnen diskreten Fokus haben. Stellen Sie sich Container-Images nicht als virtuelle Maschinen vor, bei denen es sinnvoll sein kann, zusammengehörige Funktionen zu verpacken. Behandeln Sie Ihre Container-Images stattdessen wie Unix-Dienstprogramme, und konzentrieren Sie sich strikt darauf, eine kleine Sache gut zu machen. Anwendungen können außerhalb des Containerbereichs koordiniert werden, um komplexe Funktionen zu erstellen.

Generisches Design mit der Möglichkeit, die Konfiguration zur Laufzeit einzufügen

Behälterbilder sollten nach Möglichkeit unter Berücksichtigung der Wiederverwendung entworfen werden. Die Möglichkeit, die Konfiguration zur Laufzeit anzupassen, ist beispielsweise häufig erforderlich, um grundlegende Anforderungen wie das Testen Ihrer Images vor der Bereitstellung in der Produktion zu erfüllen. Kleine, allgemeine Bilder können in verschiedenen Konfigurationen kombiniert werden, um das Verhalten zu ändern, ohne neue Bilder zu erstellen.

Kleine Bildgröße

Kleinere Bilder haben in Clusterumgebungen wie Kubernetes eine Reihe von Vorteilen. Sie werden schnell auf neue Knoten heruntergeladen und verfügen häufig über einen kleineren Satz installierter Pakete, wodurch die Sicherheit verbessert werden kann. Reduzierte Container-Images vereinfachen das Debuggen von Problemen, indem der Softwareaufwand minimiert wird.

Extern verwalteter Zustand

Container in Clusterumgebungen haben einen sehr unbeständigen Lebenszyklus, einschließlich geplanter und ungeplanter Abschaltungen aufgrund von Ressourcenknappheit, Skalierung oder Knotenausfällen. Um die Konsistenz aufrechtzuerhalten, die Wiederherstellung und Verfügbarkeit Ihrer Dienste zu unterstützen und Datenverluste zu vermeiden, ist es wichtig, dass Sie den Anwendungsstatus an einem stabilen Ort außerhalb des Containers speichern.

Einfach zu verstehen

Es ist wichtig zu versuchen, die Containerbilder so einfach und verständlich wie möglich zu halten. Bei der Fehlerbehebung können Sie eine schnellere Lösung erzielen, indem Sie das Problem einfach durch Anzeigen der Container-Image-Konfiguration oder Testen des Containerverhaltens ermitteln. Wenn Sie sich Containerbilder als Verpackungsformat für Ihre Anwendung anstelle einer Maschinenkonfiguration vorstellen, können Sie die richtige Balance finden.

Befolgen Sie die Best Practices für containerisierte Software

Bilder sollten darauf abzielen, innerhalb des Containermodells zu arbeiten, anstatt dagegen zu wirken. Vermeiden Sie die Implementierung herkömmlicher Systemadministrationsmethoden wie das Einbeziehen vollständiger Init-Systeme und das Daemonisieren von Anwendungen. Melden Sie sich standardmäßig ab, damit Kubernetes die Daten Administratoren zur Verfügung stellen kann, anstatt einen internen Protokollierungsdämon zu verwenden. Jedes dieser Verfahren unterscheidet sich von den Best Practices für vollständige Betriebssysteme.

Nutzen Sie die Funktionen von Kubernetes voll aus

Neben der Konformität mit dem Containermodell ist es wichtig, die von Kubernetes bereitgestellten Umgebungen und Tools zu verstehen und mit ihnen in Einklang zu bringen. Das Bereitstellen von Endpunkten für Verfügbarkeits- und Bereitschaftsprüfungen oder das Anpassen des Betriebs basierend auf Änderungen in der Konfiguration oder Umgebung kann beispielsweise dazu beitragen, dass Ihre Anwendungen die dynamische Bereitstellungsumgebung von Kubernetes zu ihrem Vorteil nutzen.

Nachdem wir einige der Eigenschaften festgelegt haben, die hochfunktionale Containerbilder definieren, können wir uns eingehender mit Strategien befassen, mit denen Sie diese Ziele erreichen können.

Minimale, gemeinsam genutzte Basisschichten wiederverwenden

Wir können damit beginnen, die Ressourcen zu untersuchen, aus denen Container-Images erstellt werden: Basis-Images. Jedes Container-Image wird entweder aus einemparent image, einem als Ausgangspunkt verwendeten Bild, oder aus der abstraktenscratch-Ebene, einer leeren Bildebene ohne Dateisystem, erstellt. Abase image ist ein Container-Image, das als Grundlage für zukünftige Images dient, indem es das grundlegende Betriebssystem definiert und die Kernfunktionalität bereitstellt. Bilder bestehen aus einer oder mehreren übereinander angeordneten Bildebenen, um ein endgültiges Bild zu erstellen.

Wenn Sie direkt vonscratch aus arbeiten, sind keine Standarddienstprogramme oder Dateisysteme verfügbar. Dies bedeutet, dass Sie nur auf äußerst eingeschränkte Funktionen zugreifen können. Während Bilder, die direkt ausscratch erstellt wurden, sehr rational und minimal sein können, besteht ihr Hauptzweck darin, Basisbilder zu definieren. In der Regel möchten Sie Ihre Container-Images auf einem übergeordneten Image erstellen, das eine Basisumgebung für die Ausführung Ihrer Anwendungen erstellt, damit Sie nicht für jedes Image ein vollständiges System erstellen müssen.

Es gibt zwar Basis-Images für eine Vielzahl von Linux-Distributionen, Sie sollten jedoch überlegen, welche Systeme Sie auswählen. Jede neue Maschine muss das übergeordnete Bild und alle zusätzlichen Ebenen, die Sie hinzugefügt haben, herunterladen. Bei großen Bildern kann dies eine erhebliche Menge an Bandbreite in Anspruch nehmen und die Startzeit Ihrer Container beim ersten Start spürbar verlängern. Es gibt keine Möglichkeit, ein Image, das als übergeordnetes Element im Container-Erstellungsprozess verwendet wird, zu reduzieren. Es ist daher eine gute Idee, mit einem minimalen übergeordneten Element zu beginnen.

In funktionsreichen Umgebungen wie Ubuntu kann Ihre Anwendung in einer Ihnen vertrauten Umgebung ausgeführt werden. Es sind jedoch einige Nachteile zu berücksichtigen. Ubuntu-Images (und ähnliche konventionelle Distributions-Images) sind in der Regel relativ groß (über 100 MB), was bedeutet, dass alle daraus erstellten Container-Images diese Gewichtung übernehmen.

Alpine Linux ist eine beliebte Alternative für Basis-Images, da es viele Funktionen erfolgreich in ein sehr kleines Basis-Image (~ 5 MB) packt. Es enthält einen Paketmanager mit umfangreichen Repositorys und die meisten Standarddienstprogramme, die Sie von einer minimalen Linux-Umgebung erwarten würden.

Beim Entwerfen Ihrer Anwendungen empfiehlt es sich, für jedes Image dasselbe übergeordnete Element zu verwenden. Wenn Ihre Bilder einen übergeordneten Layer freigeben, wird der übergeordnete Layer von Computern, auf denen Ihre Container ausgeführt werden, nur einmal heruntergeladen. Danach müssen sie nur noch die Ebenen herunterladen, die sich zwischen Ihren Bildern unterscheiden. Dies bedeutet, dass es eine gute Idee sein kann, ein gemeinsames übergeordnetes Bild zu erstellen, von dem Sie erben möchten, wenn Sie über gemeinsame Funktionen oder Merkmale verfügen, die Sie in jedes Bild einbetten möchten. Bilder, die eine gemeinsame Herkunft haben, minimieren die Menge zusätzlicher Daten, die Sie auf neuen Servern herunterladen müssen.

Containerebenen verwalten

Nachdem Sie ein übergeordnetes Image ausgewählt haben, können Sie Ihr Container-Image definieren, indem Sie zusätzliche Software hinzufügen, Dateien kopieren, Ports verfügbar machen und auszuführende Prozesse auswählen. Bestimmte Anweisungen in der Bildkonfigurationsdatei (aDockerfile, wenn Sie Docker verwenden) fügen Ihrem Bild zusätzliche Ebenen hinzu.

Aus vielen der gleichen Gründe, die im vorherigen Abschnitt erwähnt wurden, ist es wichtig zu berücksichtigen, wie Sie Ihren Bildern Ebenen hinzufügen, da Größe, Vererbung und Laufzeitkomplexität daraus resultieren. Um zu vermeiden, dass große, unhandliche Bilder erstellt werden, ist es wichtig zu verstehen, wie Containerebenen interagieren, wie die Build-Engine Ebenen zwischenspeichert und wie subtile Unterschiede in ähnlichen Anweisungen große Auswirkungen auf die von Ihnen erstellten Bilder haben können.

Grundlegendes zu Bildebenen und zum Erstellen von Caches

Docker erstellt jedes Mal eine neue Bildebene, wenn eine AnweisungRUN,COPY oderADD ausgeführt wird. Wenn Sie das Image erneut erstellen, überprüft die Build-Engine jede Anweisung, um festzustellen, ob für den Vorgang eine Image-Ebene zwischengespeichert wurde. Wenn im Cache eine Übereinstimmung gefunden wird, wird die vorhandene Bildebene verwendet, anstatt die Anweisung erneut auszuführen und die Ebene neu zu erstellen.

Dieser Prozess kann die Erstellungszeiten erheblich verkürzen, es ist jedoch wichtig, den Mechanismus zu verstehen, der zur Vermeidung potenzieller Probleme verwendet wird. Bei Anweisungen zum Kopieren von Dateien wieCOPY undADD vergleicht Docker die Prüfsummen der Dateien, um festzustellen, ob der Vorgang erneut ausgeführt werden muss. Für die Anweisungen vonRUNprüft Docker, ob eine vorhandene Bildebene für diese bestimmte Befehlszeichenfolge zwischengespeichert ist.

Obwohl dies möglicherweise nicht sofort offensichtlich ist, kann dieses Verhalten zu unerwarteten Ergebnissen führen, wenn Sie nicht vorsichtig sind. Ein häufiges Beispiel hierfür ist das Aktualisieren des lokalen Paketindex und das Installieren von Paketen in zwei separaten Schritten. Wir werden Ubuntu für dieses Beispiel verwenden, aber die Grundvoraussetzung gilt auch für Basis-Images für andere Distributionen:

Beispiel für die Paketinstallation Dockerfile

FROM ubuntu:18.04
RUN apt -y update
RUN apt -y install nginx
. . .

Hier wird der lokale Paketindex in einer Anweisung vonRUN(apt -y update) aktualisiert und Nginx in einer anderen Operation installiert. Dies funktioniert problemlos, wenn es zum ersten Mal verwendet wird. Wenn die Docker-Datei jedoch später aktualisiert wird, um ein zusätzliches Paket zu installieren, kann es zu Problemen kommen:

Beispiel für die Paketinstallation Dockerfile

FROM ubuntu:18.04
RUN apt -y update
RUN apt -y install nginx php-fpm
. . .

Wir haben dem Installationsbefehl, der mit der zweiten Anweisung ausgeführt wird, ein zweites Paket hinzugefügt. Wenn seit dem vorherigen Image-Build eine erhebliche Zeit vergangen ist, schlägt der neue Build möglicherweise fehl. Dies liegt daran, dass in der Anweisung zur Aktualisierung des Paketindex (RUN apt -y update)not geändert wurde, sodass Docker die dieser Anweisung zugeordnete Bildebene wiederverwendet. Da wir einen alten Paketindex verwenden, befindet sich die Version desphp-fpm-Pakets, die wir in unseren lokalen Datensätzen haben, möglicherweise nicht mehr in den Repositorys, was zu einem Fehler führt, wenn der zweite Befehl ausgeführt wird.

Um dieses Szenario zu vermeiden, müssen Sie alle Schritte, die voneinander abhängig sind, in einer einzelnenRUN-Anweisung zusammenfassen, damit Docker bei einer Änderung alle erforderlichen Befehle erneut ausführt:

Beispiel für die Paketinstallation Dockerfile

FROM ubuntu:18.04
RUN apt -y update && apt -y install nginx php-fpm
. . .

Die Anweisung aktualisiert jetzt den lokalen Paketcache, sobald sich die Paketliste ändert.

Reduzierung der Bildebenengröße durch Optimierung der RUN-Anweisungen

Das vorige Beispiel zeigt, wie das Caching-Verhalten von Docker die Erwartungen untergraben kann. Es gibt jedoch noch einige andere Dinge zu beachten, wie die Anweisungen vonRUNmit dem Layer-System von Docker interagieren. Wie bereits erwähnt, schreibt Docker am Ende jeder Anweisung vonRUNdie Änderungen als zusätzliche Bildebene fest. Um die Kontrolle über den Umfang der erzeugten Bildebenen auszuüben, können Sie unnötige Dateien in der endgültigen Umgebung bereinigen, indem Sie die Artefakte berücksichtigen, die durch die von Ihnen ausgeführten Befehle eingeführt werden.

Im Allgemeinen bietet das Verketten von Befehlen zu einem einzelnenRUN-Befehl eine große Kontrolle über die zu schreibende Ebene. Für jeden Befehl können Sie den Status der Ebene (apt -y update) einrichten, den Kernbefehl (apt install -y nginx php-fpm) ausführen und unnötige Artefakte entfernen, um die Umgebung zu bereinigen, bevor sie festgeschrieben wird. Beispielsweise verketten viele Docker-Dateienrm -rf /var/lib/apt/lists/* bis zum Ende vonapt Befehlen und entfernen die heruntergeladenen Paketindizes, um die endgültige Layergröße zu verringern:

Beispiel für die Paketinstallation Dockerfile

FROM ubuntu:18.04
RUN apt -y update && apt -y install nginx php-fpm && rm -rf /var/lib/apt/lists/*
. . .

Um die Größe der von Ihnen erstellten Bildebenen weiter zu verringern, kann es hilfreich sein, andere unbeabsichtigte Nebenwirkungen der von Ihnen ausgeführten Befehle zu begrenzen. Beispielsweise installiertapt zusätzlich zu den explizit deklarierten Paketen standardmäßig auch "empfohlene" Pakete. Sie können--no-install-recommends in Ihreapt-Befehle aufnehmen, um dieses Verhalten zu entfernen. Sie müssen möglicherweise experimentieren, um herauszufinden, ob Sie sich auf eine der in den empfohlenen Paketen enthaltenen Funktionen verlassen können.

Wir haben in diesem Abschnitt als Beispiel Paketverwaltungsbefehle verwendet, aber diese Prinzipien gelten auch für andere Szenarien. Die allgemeine Idee besteht darin, die erforderlichen Bedingungen zu erstellen, den Befehl "Minimum Viable" auszuführen und dann alle unnötigen Artefakte in einem einzigenRUN-Befehl zu bereinigen, um den Overhead der zu produzierenden Ebene zu verringern.

Verwenden von mehrstufigen Builds

Multi-stage builds wurden in Docker 17.05 eingeführt, sodass Entwickler die endgültigen Laufzeitbilder, die sie erstellen, genauer steuern können. Mit mehrstufigen Builds können Sie Ihre Docker-Datei in mehrere Abschnitte unterteilen, die unterschiedliche Stufen darstellen, wobei jeder eineFROM-Anweisung enthält, um separate übergeordnete Bilder anzugeben.

In früheren Abschnitten wurden Bilder definiert, mit denen Sie Ihre Anwendung erstellen und Assets vorbereiten können. Diese enthalten häufig Build-Tools und Entwicklungsdateien, die zum Erstellen der Anwendung benötigt werden, zum Ausführen jedoch nicht erforderlich sind. Jede in der Datei definierte nachfolgende Stufe hat Zugriff auf Artefakte, die von vorherigen Stufen erzeugt wurden.

Die letzte AnweisungFROMdefiniert das Image, das zum Ausführen der Anwendung verwendet wird. In der Regel handelt es sich hierbei um ein reduziertes Image, das nur die erforderlichen Laufzeitanforderungen installiert und dann die Anwendungsartefakte kopiert, die in früheren Phasen erstellt wurden.

Mit diesem System müssen Sie sich weniger Gedanken über die Optimierung der Anweisungen vonRUNin den Erstellungsphasen machen, da diese Containerebenen im endgültigen Laufzeitabbild nicht vorhanden sind. Sie sollten weiterhin darauf achten, wie Anweisungen mit dem Ebenen-Caching in den Erstellungsphasen interagieren, aber Ihre Bemühungen können eher auf die Minimierung der Erstellungszeit als auf die endgültige Bildgröße gerichtet sein. Die Beachtung der Anweisungen in der letzten Phase ist immer noch wichtig für die Reduzierung der Bildgröße. Durch die Trennung der verschiedenen Phasen Ihres Containerbaus ist es jedoch einfacher, optimierte Bilder zu erhalten, ohne die Komplexität von Dockerfiles zu beeinträchtigen.

Gültigkeitsbereich auf Container- und Pod-Ebene

Während die Entscheidungen, die Sie in Bezug auf Anweisungen zum Erstellen von Containern treffen, wichtig sind, wirken sich umfassendere Entscheidungen zur Containerisierung Ihrer Services häufig direkter auf Ihren Erfolg aus. In diesem Abschnitt erfahren Sie, wie Sie Ihre Anwendungen am besten von einer konventionellen Umgebung auf eine Containerplattform umstellen können.

Containerisierung nach Funktion

Im Allgemeinen empfiehlt es sich, jedes Stück unabhängiger Funktionalität in ein separates Container-Image zu packen.

Dies unterscheidet sich von gängigen Strategien in Umgebungen mit virtuellen Maschinen, in denen Anwendungen häufig in einem Image zusammengefasst werden, um die Größe zu verringern und die zum Ausführen der VM erforderlichen Ressourcen zu minimieren. Da Container einfache Abstraktionen sind, die nicht den gesamten Betriebssystemstapel virtualisieren, ist dieser Kompromiss für Kubernetes weniger überzeugend. Während eine virtuelle Webstack-Maschine einen Nginx-Webserver mit einem Gunicorn-Anwendungsserver auf einer einzelnen Maschine bündelt, um eine Django-Anwendung zu bedienen, können diese in Kubernetes in separate Container aufgeteilt werden.

Das Entwerfen von Containern, die eine einzelne Funktionalität für Ihre Dienste implementieren, bietet eine Reihe von Vorteilen. Jeder Container kann unabhängig entwickelt werden, wenn Standardschnittstellen zwischen Diensten eingerichtet sind. Beispielsweise könnte der Nginx-Container möglicherweise als Proxy für eine Reihe verschiedener Backends verwendet werden oder als Load Balancer, wenn eine andere Konfiguration angegeben wird.

Nach der Bereitstellung kann jedes Container-Image unabhängig skaliert werden, um unterschiedliche Ressourcen- und Lasteinschränkungen zu berücksichtigen. Durch die Aufteilung Ihrer Anwendungen in mehrere Container-Images gewinnen Sie Flexibilität bei der Entwicklung, Organisation und Bereitstellung.

Kombinieren von Containerbildern in Pods

In Kubernetes sindpods die kleinste Einheit, die direkt von der Steuerebene verwaltet werden kann. Pods bestehen aus einem oder mehreren Containern sowie zusätzlichen Konfigurationsdaten, um der Plattform mitzuteilen, wie diese Komponenten ausgeführt werden sollen. Die Container in einem Pod werden immer auf demselben Arbeitsknoten im Cluster geplant, und das System startet fehlgeschlagene Container automatisch neu. Die Pod-Abstraktion ist sehr nützlich, führt jedoch eine weitere Entscheidungsebene ein, wie die Komponenten Ihrer Anwendungen gebündelt werden sollen.

Wie Containerbilder werden auch Pods weniger flexibel, wenn zu viele Funktionen in einer Einheit zusammengefasst werden. Pods selbst können mithilfe anderer Abstraktionen skaliert werden, die Container können jedoch nicht unabhängig voneinander verwaltet oder skaliert werden. Um das vorige Beispiel weiter zu verwenden, sollten die separaten Nginx- und Gunicorn-Container wahrscheinlich nicht zu einem einzigen Pod gebündelt werden, damit sie separat gesteuert und bereitgestellt werden können.

Es gibt jedoch Szenarien, in denen es sinnvoll ist, funktional unterschiedliche Container zu einer Einheit zusammenzufassen. Im Allgemeinen können diese als Situationen kategorisiert werden, in denen ein zusätzlicher Container die Kernfunktionalität des Hauptcontainers unterstützt oder verbessert oder dessen Anpassung an die Bereitstellungsumgebung erleichtert. Einige gängige Muster sind:

  • Sidecar: Der sekundäre Container erweitert die Kernfunktionalität des Hauptcontainers, indem er eine unterstützende Dienstprogrammrolle übernimmt. Beispielsweise kann der Sidecar-Container Protokolle weiterleiten oder das Dateisystem aktualisieren, wenn sich ein Remote-Repository ändert. Der Primärcontainer bleibt auf seine Kernverantwortung konzentriert, wird jedoch durch die Funktionen des Beiwagens erweitert.

  • Ambassador: Ein Botschaftercontainer ist dafür verantwortlich, (häufig komplexe) externe Ressourcen zu entdecken und mit ihnen zu verbinden. Der Primärcontainer kann über die interne Pod-Umgebung an bekannten Schnittstellen mit einem Botschaftercontainer verbunden werden. Der Botschafter abstrahiert die Back-End-Ressourcen und den Proxy-Verkehr zwischen dem primären Container und dem Ressourcenpool.

  • Adaptor: Ein Adaptercontainer ist für die Normalisierung der Schnittstellen, Daten und Protokolle der Primärcontainer verantwortlich, um sie an die von anderen Komponenten erwarteten Eigenschaften anzupassen. Der primäre Container kann mit nativen Formaten betrieben werden, und der Adaptercontainer übersetzt und normalisiert die Daten, um mit der Außenwelt zu kommunizieren.

Wie Sie vielleicht bemerkt haben, unterstützt jedes dieser Muster die Strategie, standardmäßige, generische primäre Container-Images zu erstellen, die dann in verschiedenen Kontexten und Konfigurationen bereitgestellt werden können. Die sekundären Container helfen dabei, die Lücke zwischen dem primären Container und der verwendeten spezifischen Bereitstellungsumgebung zu schließen. Einige Beiwagencontainer können auch wiederverwendet werden, um mehrere Primärcontainer an die gleichen Umgebungsbedingungen anzupassen. Diese Muster profitieren von dem durch die Pod-Abstraktion bereitgestellten gemeinsamen Dateisystem- und Netzwerk-Namespace und ermöglichen dennoch die unabhängige Entwicklung und flexible Bereitstellung standardisierter Container.

Entwerfen für die Laufzeitkonfiguration

Es besteht eine gewisse Spannung zwischen dem Wunsch, standardisierte, wiederverwendbare Komponenten zu erstellen, und den Anforderungen, die an die Anpassung von Anwendungen an ihre Laufzeitumgebung gestellt werden. Die Laufzeitkonfiguration ist eine der besten Methoden, um die Lücke zwischen diesen Bedenken zu schließen. Komponenten sind sowohl allgemein als auch flexibel aufgebaut, und das erforderliche Verhalten wird zur Laufzeit durch Bereitstellung zusätzlicher Konfigurationsinformationen für die Software beschrieben. Dieser Standardansatz funktioniert sowohl für Container als auch für Anwendungen.

Um mit Blick auf die Laufzeitkonfiguration zu arbeiten, müssen Sie sowohl bei der Anwendungsentwicklung als auch bei der Containerisierung vorausschauend vorgehen. Anwendungen sollten so konzipiert sein, dass sie Werte aus Befehlszeilenparametern, Konfigurationsdateien oder Umgebungsvariablen lesen, wenn sie gestartet oder neu gestartet werden. Diese Konfigurationsanalyse- und -injektionslogik muss vor der Containerisierung im Code implementiert werden.

Beim Schreiben einer Docker-Datei muss der Container auch unter Berücksichtigung der Laufzeitkonfiguration entworfen werden. Container verfügen über eine Reihe von Mechanismen zur Bereitstellung von Daten zur Laufzeit. Benutzer können Dateien oder Verzeichnisse vom Host als Volumes im Container bereitstellen, um die dateibasierte Konfiguration zu ermöglichen. Ebenso können Umgebungsvariablen beim Start des Containers in die interne Container-Laufzeit übergeben werden. Die Dockerfile-AnweisungenCMD undENTRYPOINT können auch so definiert werden, dass Laufzeitkonfigurationsinformationen als Befehlsparameter übergeben werden können.

Da Kubernetes übergeordnete Objekte wie Pods bearbeitet, anstatt Container direkt zu verwalten, stehen Mechanismen zur Verfügung, mit denen die Konfiguration definiert und zur Laufzeit in die Containerumgebung eingefügt werden kann. Mit KubernetesConfigMaps undSecrets können Sie Konfigurationsdaten separat definieren und die Werte dann zur Laufzeit als Umgebungsvariablen oder Dateien in die Containerumgebung projizieren. ConfigMaps sind Allzweckobjekte zum Speichern von Konfigurationsdaten, die je nach Umgebung, Testphase usw. variieren können. Secrets bieten eine ähnliche Oberfläche, wurden jedoch speziell für vertrauliche Daten wie Kontokennwörter oder API-Anmeldeinformationen entwickelt.

Wenn Sie die in jeder Abstraktionsebene verfügbaren Laufzeitkonfigurationsoptionen verstehen und richtig verwenden, können Sie flexible Komponenten erstellen, die ihre Hinweise auf von der Umgebung bereitgestellte Werte stützen. Auf diese Weise können dieselben Container-Images in sehr unterschiedlichen Szenarien wiederverwendet werden, was den Entwicklungsaufwand verringert, indem die Anwendungsflexibilität verbessert wird.

Implementieren von Prozessmanagement mit Containern

Beim Übergang zu container-basierten Umgebungen verlagern Benutzer häufig zunächst vorhandene Workloads mit wenigen oder keinen Änderungen auf das neue System. Sie packen Anwendungen in Container, indem sie die Tools, die sie bereits verwenden, in die neue Abstraktion einschließen. Während es hilfreich ist, Ihre üblichen Muster zu verwenden, um migrierte Anwendungen zum Laufen zu bringen, kann das Löschen früherer Implementierungen in Containern manchmal zu einem ineffektiven Design führen.

Behandeln von Containern wie Anwendungen, nicht wie Dienste

Probleme treten häufig auf, wenn Entwickler signifikante Dienstverwaltungsfunktionen in Containern implementieren. Beispielsweise kann das Ausführen von systemd-Diensten im Container oder das Dämonisieren von Webservern als bewährte Methode in einer normalen Computerumgebung angesehen werden, sie widersprechen jedoch häufig den Annahmen, die dem Containermodell inhärent sind.

Hosts verwalten Container-Lebenszyklusereignisse, indem sie Signale an den Prozess senden, der als PID (Prozess-ID) 1 im Container fungiert. PID 1 ist der erste Prozess, der gestartet wird. Dies wäre das Init-System in herkömmlichen Computerumgebungen. Da der Host jedoch nur PID 1 verwalten kann, kann es vorkommen, dass ein herkömmliches Init-System zum Verwalten von Prozessen im Container nicht zur Steuerung der primären Anwendung verwendet werden kann. Der Host kann das interne Init-System starten, stoppen oder beenden, die primäre Anwendung jedoch nicht direkt verwalten. Die Signale geben manchmal das beabsichtigte Verhalten an die laufende Anwendung weiter. Dies erhöht jedoch die Komplexität und ist nicht immer erforderlich.

In den meisten Fällen ist es besser, die Ausführungsumgebung im Container zu vereinfachen, sodass PID 1 die primäre Anwendung im Vordergrund ausführt. In Fällen, in denen mehrere Prozesse ausgeführt werden müssen, ist PID 1 für die Verwaltung des Lebenszyklus nachfolgender Prozesse verantwortlich. Bestimmte Anwendungen, wie Apache, erledigen dies von Haus aus, indem sie Worker, die Verbindungen verwalten, erzeugen und verwalten. Für andere Anwendungen kann in einigen Fällen ein Wrapper-Skript oder ein sehr einfaches Init-System wiedumb-init oder das enthaltenetini-Init-System verwendet werden. Unabhängig von der von Ihnen gewählten Implementierung sollte der als PID 1 im Container ausgeführte Prozess angemessen auf die von Kubernetes gesendetenTERM-Signale reagieren, um sich wie erwartet zu verhalten.

Verwalten der Containerintegrität in Kubernetes

Die Bereitstellungen und Dienste von Kubernetes bieten ein Lebenszyklusmanagement für lang laufende Prozesse und einen zuverlässigen, dauerhaften Zugriff auf Anwendungen, selbst wenn zugrunde liegende Container neu gestartet werden müssen oder die Implementierungen sich ändern. Indem Sie die Verantwortung für die Überwachung und Aufrechterhaltung des Servicestatus außerhalb des Containers übernehmen, können Sie die Tools der Plattform für die Verwaltung fehlerfreier Workloads nutzen.

Damit Kubernetes Container ordnungsgemäß verwalten kann, muss es wissen, ob die in Containern ausgeführten Anwendungen fehlerfrei sind und Arbeit leisten können. Um dies zu ermöglichen, können Container Aktivitätsprüfungen implementieren: Netzwerkendpunkte oder Befehle, mit denen der Anwendungszustand gemeldet werden kann. Kubernetes überprüft regelmäßig die definierten Verfügbarkeitsprüfungen, um festzustellen, ob der Container wie erwartet funktioniert. Wenn der Container nicht ordnungsgemäß reagiert, startet Kubernetes den Container neu, um die Funktionalität wiederherzustellen.

Kubernetes bietet auch Bereitschaftssonden an, ein ähnliches Konstrukt. Anstatt anzuzeigen, ob die Anwendung in einem Container fehlerfrei ist, bestimmen Bereitschaftsprüfungen, ob die Anwendung für den Empfang von Datenverkehr bereit ist. Dies kann hilfreich sein, wenn eine containerisierte Anwendung über eine Initialisierungsroutine verfügt, die abgeschlossen werden muss, bevor Verbindungen empfangen werden können. Kubernetes verwendet Bereitschaftsprüfungen, um zu bestimmen, ob ein Pod zu einem Service hinzugefügt oder aus diesem entfernt werden soll.

Das Definieren von Endpunkten für diese beiden Sondentypen kann Kubernetes dabei helfen, Ihre Container effizient zu verwalten, und kann verhindern, dass Probleme mit dem Containerlebenszyklus die Serviceverfügbarkeit beeinträchtigen. Die Mechanismen zur Beantwortung dieser Art von Integritätsanforderungen müssen in die Anwendung selbst integriert und in der Docker-Image-Konfiguration verfügbar gemacht werden.

Fazit

In diesem Handbuch haben wir einige wichtige Überlegungen behandelt, die zu berücksichtigen sind, wenn
containerisierte Anwendungen in Kubernetes ausführt. Um es noch einmal zu wiederholen, einige der Vorschläge von
, die wir besprochen haben, waren:

  • Verwenden Sie minimale, gemeinsam nutzbare übergeordnete Bilder, um Bilder mit minimaler Aufblähung zu erstellen und die Startzeit zu reduzieren

  • Verwenden Sie mehrstufige Builds, um die Containerbuild- und Laufzeitumgebung zu trennen

  • Kombinieren Sie die Dockerfile-Anweisungen, um saubere Bildebenen zu erstellen und Fehler beim Zwischenspeichern von Bildern zu vermeiden

  • Containerisierung durch Isolieren diskreter Funktionen, um flexible Skalierung und Verwaltung zu ermöglichen

  • Entwerfen Sie Pods, um eine einzige, fokussierte Verantwortung zu übernehmen

  • Bündeln Sie Hilfscontainer, um die Funktionalität des Hauptcontainers zu verbessern oder ihn an die Implementierungsumgebung anzupassen

  • Erstellen Sie Anwendungen und Container, um auf die Laufzeitkonfiguration zu reagieren und eine größere Flexibilität bei der Bereitstellung zu ermöglichen

  • Führen Sie Anwendungen als primäre Prozesse in Containern aus, damit Kubernetes Lebenszyklusereignisse verwalten kann

  • Entwickeln Sie Integritäts- und Verfügbarkeitsendpunkte in der Anwendung oder im Container, damit Kubernetes die Integrität des Containers überwachen kann

Während des gesamten Entwicklungs- und Implementierungsprozesses müssen Sie Entscheidungen treffen, die sich auf die Robustheit und Effektivität Ihres Service auswirken können. Wenn Sie die Unterschiede zwischen containerisierten Anwendungen und herkömmlichen Anwendungen kennen und wissen, wie diese in einer verwalteten Clusterumgebung funktionieren, können Sie einige häufige Probleme vermeiden und alle Funktionen von Kubernetes nutzen.