Implementierung eines Algorithmen-Trainingssystems basierend auf k8s (Architekturidee + Implementierungsplan)

Hintergrund des Projekts

​ Um die Anforderungen der xx University an Projektmanagement und -kontrolle, Personalkoordination, Fortschrittskontrolle, Aufgabenzuweisung, Ressourcenzuweisung, Datenanalyse und Leistungsmanagement im Prozess der wissenschaftlichen Forschung, Lehre und Ausbildung zu erfüllen, bietet sie eine integrierte Projektkoordination und Big-Data-Sammlung für den praktischen Unterricht. , Daten-Crowdsourcing-Service, Datenbereinigung und -verwaltung, Datenanalyseplattform und andere häufig verwendete Werkbänke, ist es notwendig, eine Reihe von Systemmanagement, Projektmanagement, Kursmanagement, Algorithmusentwicklung, Daten-Crowdsourcing aufzubauen, Literaturrecherche, Genehmigungsmanagement und akademischer Zirkel als integrierte Plattform für Lehre, wissenschaftliche Forschung und praktische Ausbildung.

Algorithmus-Trainingssystem

Einführung

Das Algorithmustraining ist eine der Kernfunktionen dieses Systems. Es bietet Benutzern eine Deep-Learning-Algorithmus-Trainingsplattform, die eine Vielzahl gängiger Datensätze enthält, und unterstützt die Codeprogrammierung und die grafische Programmierung. Der Code unterstützt drei Sprachen: Python, Scala , und r. Programmierung Vollständige Modellierung durch Ziehen und Ablegen von Komponenten und Auswählen von Parametern ohne Schreiben von Code zum Starten des Algorithmustrainings Diese Programmiermethode reduziert die Schwelle für Entwickler erheblich und verbessert die Entwicklungseffizienz.

Funktionale Anforderungen

  1. Algorithmus-Trainingsaufgaben erstellen
  2. Code-Online-Schreiben (unterstützt R, Scala, Python-Sprache)
  3. Algorithmusmodell visuell erstellen
  4. Stellen Sie sicher, dass das Algorithmusmodell konform ist
  5. Die grafische Ansicht des Algorithmusmodells kann in eine Codeansicht umgewandelt werden
  6. Algorithmus-Trainingsaufgaben online ausführen
  7. Kann den Ausführungsstatus des Codes und Ausführungsprotokolle überwachen
  8. Elastic Computing Power Scheduling
    • Algorithmische Aufgaben können angeordnet werden, wie z. B. Ausführungsreihenfolge, maximale Anzahl von Durchläufen usw.
    • Kann Serverressourcen automatisch planen, um eine laufende Umgebung für Algorithmusaufgaben bereitzustellen
    • Jede Algorithmusaufgabe kann die maximalen Ressourcen verwenden und die maximale Laufzeit ist steuerbar
    • Kann die maximalen Ressourcen begrenzen, die durch die Aufgabenausführung verbraucht werden
    • Nachdem der Algorithmus ausgeführt wurde, können Rechenressourcen automatisch zurückgefordert werden

Nicht-funktionale Anforderungen

  1. Sicherheit: Die Betriebsressourcen und Systemberechtigungen von Benutzerprogrammen müssen begrenzt werden, dh die Programmlaufzeit, der Speicher, die CPU und die Anzahl der Threads des Benutzers sind begrenzt und können das System nicht beschädigen.

Verantwortung

Die folgende Abbildung stellt ein einfaches Diagramm der Ausführungslogik dar. Nachdem der Benutzer den Code übermittelt hat, wird eine unabhängige Maschine im Servercluster erstellt und der Code auf der Maschine ausgeführt. 0

Kurz gesagt, meine Verantwortung besteht darin, Serverressourcen elastisch zu planen, um eine Umgebung für die Aufgabenausführung bereitzustellen, und die Aufgabenausführung zu arrangieren.

Bild-20221013164651872

Gestaltungsideen

Dieses System ähnelt dem oj-System (Online Judgement). Die Ausführungslogik besteht darin, Code für den Benutzer zu übermitteln. Der Client sendet den Benutzercode über http an den Server, und der Server führt den Code auf der Bewertungsmaschine aus und gibt den Lauf zurück Ergebnis. Der vom Benutzer übermittelte Code ist nicht unbedingt sicher.Er kann endlose Prozesse erstellen oder Dateien verbrauchen, um die Ressourcen des Evaluierungscomputers zu verbrauchen, oder eine Verbindung zu einem entfernten Dienst herstellen, wodurch eine Hintertür für den Angreifer bereitgestellt wird. Um die Sicherheit des Servers zu gewährleisten, müssen wir die von Benutzerprogrammen und Systemaufrufen verwendeten Ressourcen begrenzen.

Ressourcenbeschränkungen

Das Einschränken der Ressourcen, die ein Programm ausführen kann, bezieht sich auf das Einschränken von Ressourcen wie Arbeitsspeicher, Laufzeit, Anzahl der Prozesse und Threads. Verwenden Sie im Allgemeinen setrlimit () zum Abschließen.

Die Funktion setrlimit() ist eine Funktion von C. Informationen zur spezifischen Verwendung finden Sie unter https://blog.csdn.net/u012206617/article/details/89286635

Limit für Systemaufrufe

Alle systembezogenen Operationen des Programms wie Ein- und Ausgabe, Erstellen von Prozessen, Abrufen von Systeminformationen usw. erfordern Systemaufrufe (System Call). Durch das Einschränken von Systemaufrufen können einige gefährliche Verhaltensweisen des Programms eingeschränkt werden, z. B. das Abrufen der Systemverzeichnisstruktur oder das Abrufen von Systemberechtigungen. Wenn ein Programm, das Systemaufrufe nicht einschränkt, zusammen mit einem Server oder anderen Programmen ausgeführt wird, kann dies die Sicherheit des Systems und den Betrieb anderer Programme beeinträchtigen.

Derzeit sind die zwei Schemata, die üblicherweise verwendet werden , um Systemaufrufe einzuschränken, ptrace() und seccomp . Das Prinzip des ersteren besteht darin, das Hauptprogramm jedes Mal zu benachrichtigen, wenn das Zielprogramm versucht, einen Systemaufruf durchzuführen, wenn festgestellt wird, dass es sich um ein gefährliches System handelt aufrufen, wird das Programm rechtzeitig beendet. ptrace() generiert zwei Interrupts für jeden Systemaufruf (einen vor dem Eintritt in den Systemaufruf und einen nach der Rückkehr des Systemaufrufs), was die Effizienz beeinträchtigt.Im Vergleich dazu ist seccmap möglicherweise die bessere Wahl.

seccomp (vollständiger Name securecomputing mode) ist ein Sicherheitsmechanismus, der vom Linux-Kernel unterstützt wird. Im Linux-System wird eine große Anzahl von Systemaufrufen (Systemaufrufen) direkt Benutzermodusprogrammen ausgesetzt. Es werden jedoch nicht alle Systemaufrufe benötigt, und unsicherer Code, der Systemaufrufe missbraucht, kann eine Sicherheitsbedrohung für das System darstellen. Durch seccomp beschränken wir das Programm auf die Verwendung bestimmter Systemaufrufe, die die Gefährdung des Systems verringern und gleichzeitig das Programm in einen "sicheren" Zustand versetzen können.

ptrace()Die verwendeten Bewertungssysteme sind: HustOJ , UOJ , die seccampverwendeten sind: QDUOJ , TJudger .

Docker-basierte Sandbox

Die Verwendung von Codierung zur Begrenzung der Ressourcen und Systemaufrufe des laufenden Programms erfordert eine komplexe logische Verarbeitung, und es gibt immer noch große Sicherheitsrisiken, wenn Benutzerprogramme und Webserver zusammen ausgeführt werden. Wir können eine andere Denkweise in Betracht ziehen, um die Systemsicherheit zu gewährleisten. Isolieren Sie das Zielprogramm von die Systemumgebung, um eine Sandbox (SandBox)-Umgebung zu bilden.

Was ist eine Sandbox?

Sandbox bezieht sich auf ein virtuelles Systemprogramm, die von ihm bereitgestellte Umgebung ist unabhängig von jedem laufenden Programm und wirkt sich nicht auf das vorhandene System aus. Tatsächlich ist die Java Virtual Machine jvm der verwendete Sandbox-Mechanismus.

Docker ist eine Open-Source-Anwendungs-Container-Engine, die es Entwicklern ermöglicht, ihre Anwendungen und Abhängigkeiten in einen leichten, portablen Container zu packen. Die Container nutzen den Sandbox-Mechanismus vollständig ohne Schnittstellen zwischen ihnen.

Gedankengang

Wir müssen nur jedes Mal einen Docker-Container erstellen, wenn wir das Benutzerprogramm ausführen, die Eingabedaten und das Benutzerprogramm übergeben, das Programm im Container ausführen lassen, einen Überwachungsthread erstellen, den Ausführungsstatus des Programms überwachen und die Ausgabe in einem bestimmten Das Verzeichnis kann mit der Außenwelt gemountet werden .

Die Mirroring-Technologie von Docker kann entsprechende Betriebsumgebungen für verschiedene Sprachen bereitstellen.

Für Sprachen, die eine laufende Umgebung benötigen (wie Python und andere Skriptsprachen), können wir das Bibliotheksverzeichnis des Systems auf den Container abbilden.Was die Sicherheit betrifft, muss dies im Allgemeinen nicht berücksichtigt werden, da Benutzerprogramme auch als ausgeführt werden normale Benutzer im Container Das Verzeichnis hat keine Schreibrechte und es gibt keine sensiblen Informationen wie Konfigurationsdateien, die im Bibliotheksverzeichnis gelesen werden können.

Die von Docker bereitgestellte Netzwerkfunktion kann auch genutzt werden, um das Zielprogramm im Container zu vernetzen.

Für Ressourcenlimits können Sie die von Docker bereitgestellten Ressourcenlimits verwenden

Vorteile und Nachteile

Vorteil:

  • Sie müssen keinen eigenen Code schreiben, um die Ressourcenbegrenzungsfunktion abzuschließen, Sie können direkt die von Docker bereitgestellte Ressourcenbegrenzung verwenden

  • Die Container sind für eine höhere Sicherheit voneinander isoliert

  • Systemaufrufe müssen nicht eingeschränkt werden

Mangel:

  • Die Containererstellung erfordert einen gewissen Overhead, und jede Ausführung wurde getestet, um etwa eine zusätzliche Sekunde an Zeit in Anspruch zu nehmen.

Da das Training des Deep-Learning-Algorithmus selbst viel Zeit in Anspruch nimmt, ist der Zeitaufwand für die Containererstellung bei diesem System im Grunde vernachlässigbar

Bild-20221013171745497

Probleme zu berücksichtigen

  1. Der Server verwendet einen Cluster, wie soll der erstellte Docker verwaltet werden?

  2. Wie können Docker-Ressourcen zurückgefordert werden, nachdem das Benutzerprogramm ausgeführt wurde?

  3. Wie stellt Docker in einer Clusterumgebung die erforderlichen Ressourcen bereit (die Maschine, auf der sich Docker befindet, befindet sich möglicherweise nicht auf derselben Maschine wie die Ressourcenmaschine).

  4. Wie überwacht man den Programmausführungsstatus in Echtzeit? (fehlgeschlagen, erfolgreich, läuft)

Anordnen von Docker-Containern basierend auf k8s

Tatsächlich besteht der Kern der oben genannten Probleme darin, das Problem der maschinenübergreifenden Verwaltung von Containern zu lösen , d Quellversion von Googles umfangreichem Containerverwaltungssystem borg. , das Funktionen wie Anwendungsbereitstellung, Wartung und Erweiterungsmechanismen bereitstellt.

Einführung in Kubernetes

Kubernetes ist eine vollständige Unterstützungsplattform für verteilte Systeme, die mehrschichtigen Sicherheitsschutz, Zugriffsmechanismus, mehrinstanzenfähige Anwendungsunterstützung, transparente Dienstregistrierung, Diensterkennung, integrierten Lastenausgleich, leistungsstarke Fehlererkennung und Selbstheilungsmechanismen sowie Service-Rolling unterstützt Upgrade und Online-Erweiterung, skalierbarer automatischer Planungsmechanismus für Ressourcen, Ressourcenkontingentverwaltungsfunktionen mit mehreren Granularitäten, umfassende Verwaltungstools, einschließlich Entwicklung, Tests, Bereitstellung, Betriebs- und Wartungsüberwachung, komplette verteilte Systementwicklung und Supportplattform aus einer Hand.

k8s verwendet pod als kleinste Planungseinheit, um Container anzuordnen , und Container werden in Pods gekapselt . Ein Pod kann aus einem oder mehreren Containern bestehen, die den gleichen Lebenszyklus haben und gemeinsam auf Node als Ganzes orchestriert werden. Sie teilen sich Umgebungen, Speichervolumen und IP-Bereich.

Wir erstellen Pods im Allgemeinen nicht direkt, sondern lassen den k8s-Controller Pods erstellen und verwalten. **Im Controller können Sie die Bereitstellungsmethode des Pods definieren, wie viele Kopien, auf welchem ​​Node er ausgeführt werden muss und so weiter. Unterschiedliche Controller haben unterschiedliche Eigenschaften und eignen sich für unterschiedliche Geschäftsumgebungen. Zu den gängigen Controllern gehören **Deployment, DaemonSet, Job, CronJob, StatefulSet und andere Controller. ** Im Folgenden sind die anwendbaren Szenarien mehrerer Controller aufgeführt.

  1. Bereitstellung: geeignet für die zustandslose Bereitstellung von Diensten

  2. StatefulSet: geeignet für die Bereitstellung von zustandsbehafteten Diensten

  3. DaemonSet: Nach der Bereitstellung werden alle Knotenknoten bereitgestellt, wie z. B. einige typische Anwendungsszenarien:

    Führen Sie beispielsweise den Clusterspeicher-Daemon auf jedem Knoten aus

    Führen Sie den Protokollerfassungs-Daemon auf jedem Knoten aus

  4. Job: Führen Sie eine Aufgabe einmal oder mehrmals aus

  5. CronJob: Regelmäßige oder geplante Ausführungsaufgaben

k8s stellt Ressourcenverwaltungs- und Planungsmethoden bereit. Sie müssen nur Parameter in der Vorlagendatei festlegen, um die Rechenressourcen des Pods, externe Referenzressourcen und Ressourcenobjekte zu begrenzen.

Wenn Sie mehr über k8s erfahren möchten, können Sie die offiziellen Dokumente lesen , die sehr vollständig sind

Umsetzungsideen

Basierend auf der obigen Einführung können wir eine allgemeine Implementierungsidee bilden. Wir müssen nur jedes Mal einen Pod erstellen, wenn wir ein Benutzerprogramm ausführen, Ressourcen dem Pod zuordnen und den Programmcode des Benutzers im Pod ausführen. Wir müssen nicht über Container-Orchestrierung nachdenken.

Wie oben erwähnt, wird die Erstellung von Pods im Allgemeinen vom Controller übernommen.Nach dem Verständnis der Eigenschaften der fünf häufig verwendeten Controller ist der Job-Controller zweifellos der Controller, der unsere Geschäftsanforderungen am besten erfüllt. Stellen wir uns den Job Controller im Detail vor:

Ein Job-Controller kann drei Arten von Aufgaben ausführen:

  • Einmalige Aufgaben : Normalerweise wird nur ein Pod gestartet (es sei denn, der Pod schlägt fehl und der Pod-Erstellungsfehler wird weiterhin neu gestartet). Sobald der Pod erfolgreich beendet wurde, ist der Job abgeschlossen.
  • Serielle Aufgabe : Führen Sie eine Aufgabe mehrmals hintereinander aus. Wenn die vorherige Aufgabe abgeschlossen ist, wird die nächste Aufgabe ausgeführt, bis alle Aufgaben ausgeführt sind.
  • Parallele Aufgabe : Führen Sie mehrere Aufgaben gleichzeitig gleichzeitig aus.

Hinweis: Die serielle und parallele Ausführung mehrerer Aufgaben bezieht sich hier auf die mehrfache Ausführung derselben Aufgabe.

Je nach Geschäftsanforderungen ist es am besten, einmalige Aufgaben zu verwenden. Erstellen Sie jedes Mal, wenn der Benutzer das Programm ausführt, einen Job-Controller, der einmalige Aufgaben ausführt, und lassen Sie den Pod das Benutzerprogramm ausführen. Wenn das Programm abgeschlossen ist, Der Pod wird beendet und der Job abgeschlossen. Sie können die Parameter auch so einstellen spec.ttlSecondsAfterFinished, dass der Job gelöscht wird, nachdem eine gewisse Zeit gewartet wurde, nachdem die Aufgabenausführung abgeschlossen ist. (Im Allgemeinen müssen Sie etwas Zeit reservieren, zum Beispiel müssen Sie das Ausführungsprotokoll überprüfen oder so).

Bild-20221013170406401

Ressourcenbeschränkungen

In Bezug auf die Ressourcenbeschränkung stellt die Verwendung der von Docker bereitgestellten Ressourcenbeschränkungsmethode, wie oben erwähnt, auch Ressourcenverwaltungs- und Planungsmethoden in k8s dar. Sie müssen nur Parameter in der Vorlagendatei festlegen, um Pod-Computing-Ressourcen, externe Referenzressourcen und Ressourcenobjekte zu begrenzen. Die Anwendungsfälle sind wie folgt:

resources: #资源限制和请求的设置
 limits: #资源限制的设置
 	cpu: String #CPU的限制,单位为CPU内核数,将用于docker run --cpu-quota 参数;
 	#也可以使用小数,例如0.1,它等价于表达式100m(表示100milicore)
	memory: String #内存限制,单位可以为MiB/GiB/MB/GB,将用于docker run --memory参数,
 requests: #资源请求的设置
 	cpu: String #CPU请求,容器刚启动时的可用CPU数量,将用于docker run --cpu shares参数
 	memory: String #内存请求,容器刚启动时的可用内存数量

Aber an dieser Stelle gibt es noch zwei ungelöste Probleme:

  1. Der Ressourcenserver und der k8s-Clusterserver sind getrennt So greifen Sie auf die für den Programmbetrieb erforderlichen Ressourcen (z. B. Benutzercode usw.) im Pod zu
  2. So überwachen Sie den Status der Codeausführung

Lösen Sie das Problem der Datenspeicherung und -freigabe zwischen Containern

Lassen Sie mich zuerst auf die erste Frage eingehen: In Docker verwenden wir Mounts, um Hostdateien Containern zuzuordnen, während k8s seine eigene Abstraktion des Speichervolumens (Volume) definiert und die von ihnen bereitgestellten Datenspeicherfunktionen sehr leistungsfähig sind. Daten können nicht nur durch Konfiguration in Pods eingespeist werden, sondern Daten können auch zwischen Containern innerhalb von Pods geteilt werden. Für Pods verschiedener Maschinen kann die gemeinsame Nutzung von Daten durch die Definition von Speichervolumes realisiert werden.

Speichervolumen in k8s werden hauptsächlich in 4 Kategorien unterteilt

  • Lokales Speichervolumen: Wird hauptsächlich für die gemeinsame Nutzung von Daten zwischen Containern im Pod oder für die Speicherung und gemeinsame Nutzung von Daten zwischen Pod und Node verwendet
  • Netzwerkspeichervolumen: Wird hauptsächlich zum Speichern und Teilen von Daten zwischen mehreren Pods oder mehreren Knoten verwendet
  • Persistentes Speichervolumen: Basierend auf dem Netzwerkspeichervolumen müssen sich Benutzer nicht um das vom Speichervolumen verwendete Speichersystem kümmern, sondern müssen nur definieren, wie viele Ressourcen verbraucht werden müssen
  • Konfigurationsspeichervolumen: Wird hauptsächlich verwendet, um Konfigurationsinformationen in jeden Pod einzufügen

Das von diesem System ausgewählte Netzwerkspeichervolume nfs basiert auf folgenden Gründen:

  1. Es gibt mehrere Computer, auf denen der Anbieter ausgeführt wird, d. h. es sind mehrere Knoten erforderlich, um den Datenspeicher gemeinsam zu nutzen.
  2. Die Konfiguration der Speicherklasse ist zu kompliziert, und die Verwendung von Netzwerkspeichervolumes kann die Anforderungen erfüllen.
  3. Der serverlose Betrieb und die Wartung des Unternehmens, der Aufbau und die Nutzung des k8s-Clusters werden alle von einer Person durchgeführt.Persistenter Speicher dient nur dazu, die Benutzer des Speichersystems von den Anbietern zu entkoppeln, sodass eine Verwendung nicht erforderlich ist.

Netzwerkspeichervolumen

Hier muss das Konzept des Netzwerkspeichervolumens erwähnt werden: Das Netzwerkspeichervolumen soll das Problem der Datenspeicherung und -freigabe zwischen mehreren Pods oder mehreren Nodes lösen. k8s unterstützt Produkte und Netzwerkspeicherlösungen vieler Cloud-Anbieter, wie z. B. NFS / iSCSI /GlusterFS/RDB/flocker usw.

Dieses System verwendet das Netzwerkdateisystem NFS (Network File System) , das es Computern im Netzwerk ermöglicht, Ressourcen über das TCP/IP-Netzwerk gemeinsam zu nutzen . Über NFS können lokale NFS-Clientanwendungen Dateien auf dem NFS-Server direkt lesen und schreiben, genau wie beim Zugriff auf lokale Dateien .

Wir müssen nur den Ressourcenserver als Server und den k8s-Clusterserver als Client verwenden und die Technologie des Netzwerkspeichervolumens verwenden, damit Pods in k8s die Verzeichnisse und Dateien des Ressourcenservers gemeinsam nutzen können. NFS kann die Sicherheit exponierter Verzeichnisse einschränken durch Konfigurationsdateien, wie z. B. Beschränkung des Zugriffs auf Hosts, Lese- und Schreibberechtigungen usw.

Bild-20221013150115011

Überwachen Sie den Status der Programmausführung

Der erste ist, wie man den Zustand des Benutzerprogramms beurteilt. Die Ausführung des Benutzerprogramms kann grob in vier Zustände unterteilt werden: Warteschlange, Ausführung, Fehler und Abschluss. Wir haben bereits erwähnt, dass ein Überwachungs-Thread gestartet wird, um den Zustand zu überwachen das Programm im Container

Verschiedene Stadien der Pod-Lebensdauer entsprechen einem Phasenwert , und wir können den Laufstatus des Benutzerprogramms anhand dieser Phasenwerte beurteilen.

  • Ausstehend: Der Pod wurde vom k8s-System akzeptiert, aber ein oder mehrere Container-Images wurden noch nicht erstellt. Zum Beispiel führen die Rechenzeit, die vor der Planung verbraucht wird, und die Zeit, die für das Herunterladen des Abbilds über das Netzwerk verbraucht wird, dazu, dass diese Vorbereitungen dazu führen, dass das Container-Abbild nicht erstellt wird
  • Wird ausgeführt: Der Pod wurde an den Knoten gebunden und alle Container wurden erstellt. Mindestens ein Container wird noch ausgeführt oder gestartet oder neu gestartet
  • Erfolgreich: Alle Container im Pod wurden erfolgreich beendet und mindestens ein Container wies einen fehlgeschlagenen Beendigungsstatus auf. Das heißt, der Container wurde mit einem Nicht-Null-Status beendet oder wurde vom System beendet
  • Fehlgeschlagen: Alle Container im Pod wurden beendet und mindestens ein Container wies einen fehlgeschlagenen Beendigungsstatus auf. Das heißt, der Container wurde entweder mit einem Nicht-Null-Status beendet oder vom System beendet
  • Unbekannt: Aus irgendeinem Grund kann der Status des Pods nicht abgerufen werden. Dies wird normalerweise durch einen Kommunikationsfehler auf dem Host verursacht, auf dem sich der Pod befindet.

Hier gibt es ein Problem. Dies ist der Status des Pods. Wie können wir feststellen, dass nach dem erfolgreichen Pod der Code, der im Pod-Container ausgeführt wird, ebenfalls erfolgreich ist? Das heißt, wie entsprechen die Erfolgs- und Fehlerstatus des Pods dem Status von Tatsächlich gibt es in einem Wort "Der Container wird entweder mit einem Nicht-Null-Status beendet oder vom System beendet" , was bedeutet, dass der Fehlerstatus nur angezeigt wird, wenn der Container mit einem Nicht-Null-Status beendet wird Status, außer dass das System den Container direkt beendet und bewirkt, dass der Container beendet wird. Dann müssen wir nur den Laufstatus des Benutzerprogramms innerhalb des Containers überwachen und, wenn es nicht läuft, das Programm mit einem Status ungleich Null beenden. Beispielsweise der folgende Shell-Befehl

python run.py
# 判断命令是否执行成功
if [ $? -ne 0 ];then
    echo "================执行失败==============================="
    exit 0;
fi

Da die einmalige Ausführung eines Jobs einen Pod und einen Container startet, müssen wir das Skript nur ausführen, wenn der Container startet.Der Befehlsparameter und der Argumentparameter des Pods in k8s können jeweils den Startbefehl und die Startparameterliste des festlegen Diese beiden Parameter können den gewünschten Effekt erzielen.

Hinweis: Die Befehls- und Argumenteinstellungen überschreiben jeweils den im ursprünglichen Docker-Image definierten EntryPoint und CMD. Bitte beachten Sie bei der Verwendung die folgenden Regeln:

  • Wenn in der Vorlage kein Befehl oder keine Argumente bereitgestellt werden, werden die im Docker-Image definierten Standardwerte zur Ausführung verwendet.

  • Wenn der Befehl in der Vorlage bereitgestellt wird, aber keine Argumente bereitgestellt werden, wird nur der bereitgestellte Befehl verwendet. Sowohl der standardmäßige EntryPoint als auch der im Docker-Image definierte Standardbefehl werden ignoriert.

  • Wenn nur Argumente bereitgestellt werden, wird der im Docker-Image definierte Standard-EntryPoint in Kombination mit den bereitgestellten Argumenten ausgeführt.

  • Wenn sowohl der Befehl als auch die Argumente bereitgestellt werden, werden der im Docker-Image definierte standardmäßige Einstiegspunkt und Befehl ignoriert. Der bereitgestellte Befehl und die Argumente werden zur Ausführung kombiniert.

Obwohl der Status des Programms anhand des Status des Pods beurteilt werden kann, kann der Status des Pods derzeit nicht in Echtzeit überwacht werden.Zu diesem Zeitpunkt gibt es drei Umsetzungspläne für die Forschung:

  1. Periodischer Abfrage-Pod-Status
  2. Verwenden Sie den k8s-Lebenszyklus-Callback
  3. Verwenden Sie zur Überwachung die k8s-Uhr
Pod-Status regelmäßig abfragen

Die Idee ist sehr einfach. Überprüfe den Status des entsprechenden Jobs für eine gewisse Zeit, aber die Nachteile sind auch groß. Erstens wird es eine Verzögerung geben. Zweitens verbraucht das Polling viel k8s-Server und Webserver. Wenn es welche gibt Zu viele Jobs, es wird zweifellos die Serverlast stark erhöhen, daher ist es nicht ratsam.

Verwenden Sie den k8s-Lebenszyklus-Callback

k8s verfügt über zwei Lebenszyklusereignisse, das PostStart-Ereignis und das PreStop-Ereignis, die Rückrufe ausführen, kurz bevor der Container erfolgreich erstellt wird und bevor der Container endet.

  • PostStart: Unmittelbar nachdem der Container erfolgreich erstellt wurde, wird ein Ereignis ausgelöst und der Callback ausgeführt . Wenn die Operation im Rückruf fehlschlägt, wird der Container beendet und es wird bestimmt, ob der Container gemäß der Neustartrichtlinie des Containers neu gestartet werden soll.

  • PreStop: **Bevor der Container endet, wird ein Event ausgelöst und ein Callback ausgeführt. ** Unabhängig vom Ergebnis der Callback-Ausführung wird der Container beendet.

Es gibt zwei Möglichkeiten, den Callback zu implementieren, eine ist Exec, die andere ist HttpGet,

  • Der Exec-Callback führt einen bestimmten Befehl oder Vorgang aus. Wenn der von Exec ausgeführte Befehl im stdout-Ergebnis OK ist, bedeutet dies, dass die Ausführung erfolgreich war; andernfalls wird er als abnormal betrachtet und das Kubelet erzwingt einen Neustart des Containers.
  • Der HttpGet-Callback führt eine bestimmte HttpGet-Anfrage aus und beurteilt anhand des zurückgegebenen HTTP-Statuscodes, ob die Anfrage erfolgreich ausgeführt wurde.

Wir können den Callback-Mechanismus verwenden, um eine http-Anforderung zu initiieren, wenn der Container erfolgreich erstellt wurde, und den Server benachrichtigen, den Status auf „running“ zu ändern

Initiieren Sie eine HTTP-Anforderung, bevor der Container endet, übergeben Sie den Programmausführungsstatus (Fehler oder Erfolg) an den Server und benachrichtigen Sie dann den Server, den Status zu ändern.

Die folgende Abbildung ist das Lebenszyklus-Ereignisdiagramm des Pods

Bild-20221013202125728

Frage

Obwohl diese Lösung machbar ist, zeigt sich im eigentlichen Implementierungsprozess, dass der Status der Programmausführung nicht direkt über die Get-Anfrage zurückgesendet werden kann und der Server nur benachrichtigt werden kann, um den Status des Pods abzufragen. Aber zu diesem Zeitpunkt hat der Pod noch nicht angehalten und läuft noch, sodass wir nicht wissen können, ob das Programm erfolgreich läuft. Aber später dachten wir daran, die asynchrone Verwendung von watch zu aktivieren, um den Status des Containers zu überwachen, wenn der Pod stoppt, damit der nachfolgende Status des Pods überwacht werden kann.

Verwenden Sie watch, um den Pod-Status zu überwachen

In k8s können die Statusänderungen von Pods oder anderen Komponenten kontinuierlich über die Watch-Oberfläche wie die Pod-Liste erkannt werden.Wenn sich der Pod-Status ändert, wird dies ausgegeben

kubectl get pod -w   或者--watch

Die allgemeine Idee ist, den von k8s bereitgestellten javaClient zu verwenden, um die Uhr aufzurufen, um den Status des Pods zu überwachen (beim Erstellen eines Jobs wird das Label verwendet, um den Job und den Pod eindeutig zu identifizieren). Prozess, javaClient verwendet es beim Aufrufen der k8s-Uhr-API. Es ist OkHttpClient. Gemäß dem offiziellen Beispiel wird OkHttpClient10s die Verbindung trennen. Wir können die Timeout-Periode von OkHttpClient durch Konfiguration festlegen. Die Timeout-Periode sollte nicht zu lang eingestellt werden. Wenn die Verbindung besteht nicht rechtzeitig geschlossen wird, verschwendet der k8s apiserver viele Verbindungen und der Druck wird hoch sein, aber wenn es zu kurz ist, kann die Verbindung geschlossen werden, bevor das Programm die Ausführung beendet.

Hier kommt also eine weitere Frage: Wie kann man die Timeout-Periode von OkHttpClient vernünftig einstellen und sicherstellen, dass die Verbindung nicht vor dem Ende des Programms geschlossen wird?

Frage

Die anfängliche Lösung für dieses Problem besteht darin, die Timeout-Periode von **OkHttpClient etwas länger als das Zeitlimit des Benutzerprogramms zu setzen, **weil die Ausführungs-Timeout des Benutzerprogramms den Pod definitiv zerstört, also spielt es keine Rolle, selbst wenn die Verbindung ist geschlossen. Dieses System ist jedoch ein Algorithmus-Trainingssystem. ** Die Ausführungszeit ist im Allgemeinen lang, und die Zeitbegrenzung des Benutzerprogramms ist relativ breit. ** Wenn eine solche Verbindung für eine lange Zeit aufrechterhalten wird, ist sie in der Tat eine Verschwendung Ressourcen.

Lebenszyklus-Callback + Überwachung des kontinuierlichen Listener-Status

Aber wenn wir es mit der vorherigen Strategie kombinieren und die Pod-Überwachung vor dem Ende des Pods starten (zu diesem Zeitpunkt wurde das Programm ausgeführt) , kann die Verbindungszeit stark reduziert werden. Also haben wir uns letztendlich für die Strategie entschieden, Lebenszyklus-Callback + Watch-Überwachung zu kombinieren.

Bild-20221014102725472

Bisher wurden die Design- und Implementierungsideen der Sandbox-Umgebung für die Ausführung von Benutzerprogrammen erläutert, aber es gibt noch einige Details, die wir lösen müssen.

  1. Wie kann man die Anzahl der Jobs für einen Job kontrollieren?
  2. Wie kann dem Benutzer das Ausführungsergebnis des Benutzerprogramms angezeigt werden?

Steuern Sie die Anzahl der Jobs für einen Job

Zusätzlich zur Begrenzung der Ressourcen eines einzelnen Benutzerprogramms müssen wir auch die Anzahl der Programme begrenzen, die insgesamt ausgeführt werden, da sonst viele Benutzer, die Programme zusammen ausführen, immer noch die Serverressourcen füllen.

Gedankengang

Die einfachste Möglichkeit, die Anzahl der Jobs für einen Job zu begrenzen, besteht darin, die Anzahl der Jobs abzufragen, die gerade zur Laufzeit laufen, und einen Job zu erstellen, wenn sie kleiner als max ist.

Bild-20221014110704604

Was ist zu tun, wenn die Anzahl der Jobs die Höchstgrenze überschreitet? Es ist definitiv nicht gut, dem Benutzer direkt mitzuteilen, dass der Vorgang fehlgeschlagen ist. Es ist besser, einen Container zu haben, der die Anforderung des Benutzers speichern und die Ausführungsanforderung des Benutzers eingeben kann den Warteschlangenstatus und warten Sie, bis andere Programme abgeschlossen sind. , und lassen Sie die Ausführung fortsetzen. Diese Logik macht es uns leicht, uns die Datenstruktur der Warteschlange vorzustellen, und die Verwendung von Nachrichtenwarteschlangen-Middleware kann dieses Problem zweifellos gut lösen.

Der konkrete Ablauf ist:

  • Der Benutzer sendet eine Anforderung an den Server, um das Programm auszuführen, und speichert die Anforderung direkt in der Verbraucherwarteschlange

  • Nach Erhalt der Anfrage geht der Consumer zunächst zum k8s-Server, um die Anzahl der Jobs abzufragen

  • Wenn die Menge verbraucht ist, ohne das Limit zu überschreiten, senden Sie eine Anfrage an den k8s-Server, um einen Job zu erstellen

  • Wenn es größer oder gleich dem Grenzwert ist, wird die Nachricht am Anfang der Warteschlange erneut gespeichert und nach einer bestimmten Zeit verarbeitet

Bild-20221014140533923

Obwohl die obigen Schritte die Anforderungen erfüllen können, gibt es noch Teile, die optimiert werden können.Wir haben oben erwähnt, dass wir bei der Beurteilung der Anzahl der Jobs immer zum k8s-Server gehen, um die Anzahl der Jobs abzufragen.Wenn die Anzahl der Jobs die limit, die Meldung Die Warteschlange wird sich weiterhin wiederholen und jedes Mal die Anzahl der Jobs in k8s abfragen, was zweifellos den Druck auf den k8s-Server erhöhen wird.Tatsächlich können wir die Anzahl der laufenden Jobs manuell pflegen, anstatt zu gehen zu k8s jedes Mal Erkundigen. Wir müssen nur die grundlegende Datenstrukturzeichenfolge von redis verwenden, um die maximale Grenze des Jobs zwischenzuspeichern , jedes Mal, wenn ein Job gestartet wird, eins subtrahieren und eins hinzufügen, wenn der Job zerstört wird.

Allerdings muss hier beachtet werden, dass es sich um eine atomare Operation vom Reduzieren des Jobs um eins bis zum Initiieren einer Anforderung zum Erstellen eines Jobs handeln muss, sodass Sie hier eine verteilte Sperre hinzufügen können Problem des Daten-Rollbacks. Wie bereits erwähnt, wird der Job automatisch zerstört, daher müssen wir den Pod überwachen, um die Anzahl der Jobs aufrechtzuerhalten, wenn der Pod fehlschlägt oder abgeschlossen wird.

So informieren Sie den Benutzer über das Ausführungsergebnis

Wir haben oben erwähnt, dass die Ausgabe des **Benutzerprogramms in einem bestimmten Verzeichnis** gespeichert wird, aber wie kann man dem Benutzer die Ausgabe des Programms mitteilen?

Dazu gibt es zwei Möglichkeiten:

  1. Verwenden Sie Websocket, um eine lange Verbindung herzustellen, Protokolldateien in regelmäßigen Abständen zu lesen und Protokolle in Echtzeit auszugeben.
  2. Lassen Sie den Client den Protokollinhalt direkt anfordern, und Sie können das neueste Protokoll jedes Mal sehen, wenn Sie es anfordern.

Da die Programme dieses Systems im Allgemeinen zeitaufwändig sind, erfordert der Aufbau einer Langzeitverbindung viele Ressourcen, um die Verbindung aufrechtzuerhalten.Nach bisherigen Entwicklungserfahrungen kann der Aufbau einer Langzeitverbindung aufgrund von Netzwerkschwankungenoder dergleichen leicht getrennt werden Benutzeroperationen und die Entwicklungsschritte sind umständlich.In Anbetracht dessen, dass das Programmprotokoll keine Echtzeitanforderungen hat, wird diezweite Lösung angenommen.

Mini-Objektspeicher

Das Speichersystem für Protokolle und Codedateien wurde ebenfalls in gewissem Umfang entworfen.

Dieses System verfügt über einen speziellen Ressourcenserver, der das verteilte Objektspeichersystem verwendet, das vom Open-Source-Objektspeicherprojekt Minio erstellt wurde. Das Hauptziel von minio design ist eine Standardlösung für Private Cloud Object Storage. Es wird hauptsächlich verwendet, um riesige Bilder, Videos, Dokumente usw. zu speichern. Es eignet sich sehr gut zum Speichern von unstrukturierten Daten mit großer Kapazität wie Bildern, Videos, Protokolldateien, Sicherungsdaten und Container-/Virtual Machine-Images.

Informationen zum spezifischen Erstellungsprozess finden Sie im Blog https://blog.csdn.net/R1011/article/details/124399434

Wir haben oben auch erwähnt, dass wir nfs verwenden, um den k8s-Clusterserver und den Ressourcenserver miteinander zu verbinden und das Verzeichnis des Ressourcenservers mit dem k8s-Cluster zu teilen. Wir laden die Codedatei des Benutzers in das Ressourcenverzeichnis von minio hoch und teilen sie mit dem k8s-Cluster, damit das Code-Verzeichnis des Benutzers zur Laufzeit über das Datenvolume abgebildet wird. Gleichzeitig benötigen wir ein Datenvolume, um beim Start ein bestimmtes Benutzer-Log-Verzeichnis abzubilden. Das Protokoll wird in dieses Verzeichnis ausgegeben, wenn das Programm läuft, damit das Protokoll mit dem Ressourcenserver synchronisiert werden kann. Wenn der Client das Programmprotokoll abfragt, muss er nur über minio direkt auf die Protokolldatei zugreifen.

Zusammenfassen

Abschließend verwenden wir ein Bild, um die Architektur dieses Algorithmussystems zusammenzufassen.

Bild-20221014154441257

Supongo que te gusta

Origin blog.csdn.net/qq_45473439/article/details/127326736
Recomendado
Clasificación