One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Aloha, Leute! Mein Name ist Oleg Anastasyev, ich arbeite bei Odnoklassniki im Platform-Team. Und außer mir funktioniert in Odnoklassniki noch jede Menge Hardware. Wir verfügen über vier Rechenzentren mit etwa 500 Racks mit mehr als 8 Servern. An einem bestimmten Punkt wurde uns klar, dass die Einführung eines neuen Managementsystems es uns ermöglichen würde, Geräte effizienter zu laden, die Zugriffsverwaltung zu erleichtern, die (Neu-)Verteilung von Computerressourcen zu automatisieren, die Einführung neuer Dienste zu beschleunigen und Reaktionen zu beschleunigen zu Großunfällen.

Was ist dabei herausgekommen?

Neben mir und einer Menge Hardware gibt es auch Leute, die mit dieser Hardware arbeiten: Ingenieure, die direkt in Rechenzentren sitzen; Netzwerker, die Netzwerksoftware einrichten; Administratoren oder SREs, die für die Ausfallsicherheit der Infrastruktur sorgen; und Entwicklungsteams, jedes von ihnen ist für einen Teil der Funktionen des Portals verantwortlich. Die von ihnen erstellte Software funktioniert in etwa so:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Benutzeranfragen werden sowohl an den Fronten des Hauptportals entgegengenommen www.ok.ruund auf anderen, zum Beispiel im Bereich der Musik-API. Um die Geschäftslogik zu verarbeiten, rufen sie den Anwendungsserver auf, der bei der Verarbeitung der Anfrage die erforderlichen spezialisierten Microservices aufruft – One-Graph (Graph sozialer Verbindungen), User-Cache (Cache von Benutzerprofilen) usw.

Jeder dieser Dienste wird auf vielen Maschinen bereitgestellt und jeder von ihnen verfügt über verantwortliche Entwickler, die für die Funktionsweise der Module, ihren Betrieb und ihre technologische Entwicklung verantwortlich sind. Alle diese Dienste laufen auf Hardware-Servern, und bis vor Kurzem haben wir pro Server genau einen Task gestartet, der also auf eine bestimmte Aufgabe spezialisiert war.

Warum so? Dieser Ansatz hatte mehrere Vorteile:

  • Erleichtert Massenmanagement. Nehmen wir an, eine Aufgabe erfordert einige Bibliotheken und einige Einstellungen. Und dann wird der Server genau einer bestimmten Gruppe zugeordnet, die cfengine-Richtlinie für diese Gruppe beschrieben (oder bereits beschrieben) und diese Konfiguration zentral und automatisch auf alle Server dieser Gruppe ausgerollt.
  • Vereinfacht Diagnostik. Nehmen wir an, Sie schauen sich die erhöhte Belastung des Zentralprozessors an und stellen fest, dass diese Belastung nur durch die Aufgabe erzeugt werden kann, die auf diesem Hardware-Prozessor läuft. Die Suche nach einem Schuldigen endet sehr schnell.
  • Vereinfacht Überwachung. Wenn mit dem Server etwas nicht stimmt, meldet der Monitor dies und Sie wissen genau, wer dafür verantwortlich ist.

Einem Dienst, der aus mehreren Replikaten besteht, werden mehrere Server zugewiesen – jeweils einer. Dann wird die Rechenressource für den Dienst ganz einfach zugewiesen: die Anzahl der Server, über die der Dienst verfügt, die maximale Menge an Ressourcen, die er verbrauchen kann. „Einfach“ bedeutet hier nicht, dass es einfach zu bedienen ist, sondern in dem Sinne, dass die Ressourcenzuweisung manuell erfolgt.

Dieser Ansatz hat es uns auch ermöglicht spezielle Eisenkonfigurationen für eine Aufgabe, die auf diesem Server ausgeführt wird. Wenn die Aufgabe große Datenmengen speichert, verwenden wir einen 4U-Server mit einem Gehäuse mit 38 Festplatten. Wenn die Aufgabe rein rechnerisch ist, können wir einen günstigeren 1U-Server kaufen. Dies ist recheneffizient. Dieser Ansatz ermöglicht es uns unter anderem, viermal weniger Maschinen mit einer Auslastung zu verwenden, die mit der eines freundlichen sozialen Netzwerks vergleichbar ist.

Eine solche Effizienz bei der Nutzung von Rechenressourcen sollte auch die Wirtschaftlichkeit gewährleisten, wenn wir davon ausgehen, dass Server das Teuerste sind. Lange Zeit war Hardware am teuersten, und wir haben große Anstrengungen unternommen, um den Hardwarepreis zu senken, indem wir Fehlertoleranzalgorithmen entwickelten, um die Anforderungen an die Hardware-Zuverlässigkeit zu reduzieren. Und heute sind wir an einem Punkt angelangt, an dem der Preis des Servers nicht mehr entscheidend ist. Wenn Sie nicht an die neuesten Exoten denken, spielt die konkrete Konfiguration der Server im Rack keine Rolle. Jetzt haben wir ein weiteres Problem – den Preis des Platzes, den der Server im Rechenzentrum einnimmt, also den Platz im Rack.

Als wir erkannten, dass dies der Fall war, beschlossen wir zu berechnen, wie effektiv wir die Racks nutzen.
Wir haben den Preis des leistungsstärksten Servers von den wirtschaftlich vertretbaren Servern genommen, berechnet, wie viele solcher Server wir in Racks unterbringen könnten, wie viele Aufgaben wir auf ihnen nach dem alten Modell „ein Server = eine Aufgabe“ ausführen würden und wie viele davon Aufgaben könnten die Ausrüstung nutzen. Sie zählten und vergossen Tränen. Es stellte sich heraus, dass unsere Effizienz bei der Nutzung von Racks etwa 11 % beträgt. Die Schlussfolgerung liegt auf der Hand: Wir müssen die Effizienz der Nutzung von Rechenzentren steigern. Die Lösung scheint offensichtlich: Sie müssen mehrere Aufgaben gleichzeitig auf einem Server ausführen. Aber hier beginnen die Schwierigkeiten.

Die Massenkonfiguration wird deutlich komplizierter – es ist nun nicht mehr möglich, eine einzelne Gruppe einem Server zuzuordnen. Schließlich können jetzt mehrere Aufgaben unterschiedlicher Befehle auf einem Server gestartet werden. Darüber hinaus kann die Konfiguration für verschiedene Anwendungen widersprüchlich sein. Auch die Diagnose wird komplizierter: Wenn Sie einen erhöhten CPU- oder Festplattenverbrauch auf einem Server feststellen, wissen Sie nicht, welche Aufgabe Probleme verursacht.

Aber das Wichtigste ist, dass es keine Isolation zwischen Aufgaben gibt, die auf derselben Maschine ausgeführt werden. Hier ist zum Beispiel ein Diagramm der durchschnittlichen Antwortzeit einer Serveraufgabe vor und nach dem Start einer anderen Computeranwendung auf demselben Server, die in keiner Weise mit der ersten zusammenhängt – die Antwortzeit der Hauptaufgabe hat sich erheblich erhöht.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Offensichtlich müssen Sie Aufgaben entweder in Containern oder in virtuellen Maschinen ausführen. Da fast alle unsere Aufgaben unter einem Betriebssystem (Linux) laufen oder darauf angepasst sind, müssen wir nicht viele verschiedene Betriebssysteme unterstützen. Dementsprechend ist eine Virtualisierung nicht erforderlich; aufgrund des zusätzlichen Overheads ist sie weniger effizient als die Containerisierung.

Als Implementierung von Containern zum Ausführen von Aufgaben direkt auf Servern ist Docker ein guter Kandidat: Dateisystem-Images lösen Probleme mit widersprüchlichen Konfigurationen gut. Die Tatsache, dass Bilder aus mehreren Schichten bestehen können, ermöglicht es uns, die für ihre Bereitstellung in der Infrastruktur erforderliche Datenmenge erheblich zu reduzieren, indem wir gemeinsame Teile in separate Basisschichten aufteilen. Dann werden die grundlegenden (und umfangreichsten) Schichten ziemlich schnell in der gesamten Infrastruktur zwischengespeichert, und um viele verschiedene Arten von Anwendungen und Versionen bereitzustellen, müssen nur kleine Schichten übertragen werden.

Darüber hinaus bieten uns eine vorgefertigte Registrierung und Image-Tagging in Docker vorgefertigte Grundelemente für die Versionierung und Bereitstellung von Code für die Produktion.

Docker bietet uns wie jede andere ähnliche Technologie sofort ein gewisses Maß an Container-Isolation. Beispiel: Speicherisolation – jedem Container wird eine Grenze für die Nutzung des Maschinenspeichers zugewiesen, über die hinaus kein Verbrauch mehr erfolgt. Sie können Container auch basierend auf der CPU-Auslastung isolieren. Für uns reichte die Standarddämmung jedoch nicht aus. Aber mehr dazu weiter unten.

Das direkte Ausführen von Containern auf Servern ist nur ein Teil des Problems. Der andere Teil bezieht sich auf das Hosten von Containern auf Servern. Sie müssen verstehen, welcher Container auf welchem ​​Server platziert werden kann. Dies ist keine so einfache Aufgabe, da Container so dicht wie möglich auf Servern platziert werden müssen, ohne deren Geschwindigkeit zu verringern. Eine solche Platzierung kann auch unter dem Gesichtspunkt der Fehlertoleranz schwierig sein. Oft möchten wir Replikate desselben Dienstes in verschiedenen Racks oder sogar in verschiedenen Räumen des Rechenzentrums platzieren, damit wir bei einem Ausfall eines Racks oder Raums nicht sofort alle Replikate des Dienstes verlieren.

Die manuelle Verteilung von Containern ist keine Option, wenn Sie 8 Server und 8 bis 16 Container haben.

Darüber hinaus wollten wir Entwicklern mehr Unabhängigkeit bei der Ressourcenzuteilung geben, damit sie ihre Dienste selbst in der Produktion hosten können, ohne die Hilfe eines Administrators. Gleichzeitig wollten wir die Kontrolle behalten, damit nicht einige kleinere Dienste alle Ressourcen unserer Rechenzentren verbrauchen.

Natürlich brauchen wir eine Kontrollschicht, die dies automatisch erledigt.

So kamen wir zu einem einfachen und verständlichen Bild, das alle Architekten lieben: drei Quadrate.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

One-Cloud Masters ist ein Failover-Cluster, der für die Cloud-Orchestrierung verantwortlich ist. Der Entwickler sendet ein Manifest an den Master, das alle zum Hosten des Dienstes erforderlichen Informationen enthält. Auf dieser Grundlage erteilt der Master Befehle an ausgewählte Minions (Maschinen, die zum Ausführen von Containern bestimmt sind). Die Minions verfügen über unseren Agenten, der den Befehl empfängt, seine Befehle an Docker ausgibt und Docker den Linux-Kernel so konfiguriert, dass er den entsprechenden Container startet. Zusätzlich zur Ausführung von Befehlen berichtet der Agent dem Master kontinuierlich über Zustandsänderungen sowohl der Minion-Maschine als auch der darauf laufenden Container.

Ressourcenzuweisung

Schauen wir uns nun das Problem der komplexeren Ressourcenzuweisung für viele Schergen an.

Eine Rechenressource in einer Cloud ist:

  • Die Menge an Prozessorleistung, die von einer bestimmten Aufgabe verbraucht wird.
  • Die für die Aufgabe verfügbare Speichermenge.
  • Netzwerktraffic. Jeder der Minions verfügt über eine spezifische Netzwerkschnittstelle mit begrenzter Bandbreite, sodass es unmöglich ist, Aufgaben zu verteilen, ohne die Datenmenge zu berücksichtigen, die sie über das Netzwerk übertragen.
  • Festplatten. Zusätzlich zum Speicherplatz für diese Aufgaben weisen wir natürlich auch den Festplattentyp zu: HDD oder SSD. Festplatten können eine begrenzte Anzahl von Anfragen pro Sekunde verarbeiten – IOPS. Daher weisen wir für Aufgaben, die mehr IOPS erzeugen, als eine einzelne Festplatte bewältigen kann, auch „Spindeln“ zu – also Festplattengeräte, die ausschließlich für die Aufgabe reserviert werden müssen.

Dann können wir für einige Dienste, zum Beispiel für den Benutzer-Cache, die verbrauchten Ressourcen auf diese Weise erfassen: 400 Prozessorkerne, 2,5 TB Speicher, 50 Gbit/s Datenverkehr in beide Richtungen, 6 TB Festplattenspeicher auf 100 Spindeln. Oder in einer bekannteren Form wie dieser:

alloc:
    cpu: 400
    mem: 2500
    lan_in: 50g
    lan_out: 50g
    hdd:100x6T

Benutzer-Cache-Dienstressourcen verbrauchen nur einen Teil aller verfügbaren Ressourcen in der Produktionsinfrastruktur. Daher möchte ich sicherstellen, dass der Benutzercache nicht plötzlich, sei es durch einen Bedienerfehler oder nicht, mehr Ressourcen verbraucht, als ihm zugewiesen sind. Das heißt, wir müssen die Ressourcen begrenzen. Aber woran könnten wir die Quote knüpfen?

Kehren wir zu unserem stark vereinfachten Diagramm des Zusammenspiels von Komponenten zurück und zeichnen es mit mehr Details neu – etwa so:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Was fällt Ihnen ins Auge:

  • Das Web-Frontend und die Musik nutzen isolierte Cluster desselben Anwendungsservers.
  • Wir können die logischen Schichten unterscheiden, zu denen diese Cluster gehören: Fronten, Caches, Datenspeicher und Verwaltungsschicht.
  • Das Frontend ist heterogen, es besteht aus unterschiedlichen funktionalen Subsystemen.
  • Caches können auch über das Subsystem verteilt sein, dessen Daten sie zwischenspeichern.

Lassen Sie uns das Bild noch einmal neu zeichnen:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Bah! Ja, wir sehen eine Hierarchie! Dies bedeutet, dass Sie Ressourcen in größeren Blöcken verteilen können: Weisen Sie einen verantwortlichen Entwickler einem Knoten dieser Hierarchie zu, der dem funktionalen Subsystem entspricht (wie „Musik“ im Bild), und weisen Sie derselben Ebene der Hierarchie eine Quote zu. Diese Hierarchie ermöglicht es uns auch, Dienste flexibler zu organisieren und so die Verwaltung zu vereinfachen. Zum Beispiel teilen wir das gesamte Web, da es sich um eine sehr große Gruppe von Servern handelt, in mehrere kleinere Gruppen auf, im Bild als Gruppe1, Gruppe2 dargestellt.

Indem wir die zusätzlichen Zeilen entfernen, können wir jeden Knoten unseres Bildes in einer flacheren Form schreiben: group1.web.front, api.music.front, user-cache.cache.

So kommen wir zum Konzept der „hierarchischen Warteschlange“. Es hat einen Namen wie „group1.web.front“. Ihm wird ein Kontingent für Ressourcen und Benutzerrechte zugewiesen. Wir geben der Person von DevOps die Rechte, einen Dienst an die Warteschlange zu senden, und ein solcher Mitarbeiter kann etwas in der Warteschlange starten, und die Person von OpsDev wird Administratorrechte haben, und jetzt kann sie die Warteschlange verwalten, dort Personen zuweisen, Geben Sie diesen Personen Rechte usw. Dienste, die in dieser Warteschlange ausgeführt werden, werden innerhalb des Kontingents der Warteschlange ausgeführt. Reicht das Rechenkontingent der Warteschlange nicht aus, um alle Dienste gleichzeitig auszuführen, werden diese nacheinander ausgeführt und bilden so die Warteschlange selbst.

Schauen wir uns die Leistungen genauer an. Ein Dienst hat einen vollständig qualifizierten Namen, der immer den Namen der Warteschlange enthält. Dann hat der vordere Webdienst den Namen ok-web.group1.web.front. Und der Anwendungsserverdienst, auf den es zugreift, wird aufgerufen ok-app.group1.web.front. Jeder Dienst verfügt über ein Manifest, das alle notwendigen Informationen für die Platzierung auf bestimmten Maschinen angibt: wie viele Ressourcen diese Aufgabe verbraucht, welche Konfiguration dafür erforderlich ist, wie viele Replikate vorhanden sein sollten, Eigenschaften für die Behandlung von Fehlern dieses Dienstes. Und nachdem der Dienst direkt auf den Maschinen platziert wurde, erscheinen seine Instanzen. Sie werden auch eindeutig benannt – als Instanznummer und Dienstname: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

Das ist sehr praktisch: Wenn wir uns nur den Namen des laufenden Containers ansehen, können wir sofort viel herausfinden.

Schauen wir uns nun genauer an, was diese Instanzen tatsächlich ausführen: Aufgaben.

Aufgabenisolationsklassen

Alle Aufgaben in OK (und wahrscheinlich überall) können in Gruppen unterteilt werden:

  • Aufgaben mit kurzer Latenz – prod. Für solche Aufgaben und Dienste ist die Antwortverzögerung (Latenz) sehr wichtig, also wie schnell jede der Anfragen vom System verarbeitet wird. Beispiele für Aufgaben: Webfronts, Caches, Anwendungsserver, OLTP-Speicher usw.
  • Rechenprobleme – Batch. Dabei kommt es nicht auf die Bearbeitungsgeschwindigkeit der einzelnen Anfrage an. Für sie ist es wichtig, wie viele Berechnungen diese Aufgabe in einem bestimmten (langen) Zeitraum (Durchsatz) durchführt. Dabei handelt es sich um alle Aufgaben von MapReduce, Hadoop, maschinellem Lernen und Statistik.
  • Hintergrundaufgaben – inaktiv. Für solche Aufgaben sind weder Latenz noch Durchsatz sehr wichtig. Dazu gehören verschiedene Tests, Migrationen, Neuberechnungen und die Konvertierung von Daten von einem Format in ein anderes. Einerseits ähneln sie kalkulierten, andererseits spielt es für uns keine Rolle, wie schnell sie erledigt sind.

Sehen wir uns an, wie solche Aufgaben Ressourcen verbrauchen, beispielsweise den Zentralprozessor.

Aufgaben mit kurzer Verzögerung. Eine solche Aufgabe weist ein ähnliches CPU-Verbrauchsmuster auf:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Eine Anfrage des Benutzers geht zur Bearbeitung ein, die Aufgabe beginnt, alle verfügbaren CPU-Kerne zu nutzen, verarbeitet sie, gibt eine Antwort zurück, wartet auf die nächste Anfrage und stoppt. Die nächste Anfrage kam – wieder haben wir alles ausgewählt, was da war, berechnet und warten auf die nächste.

Um die minimale Latenz für eine solche Aufgabe zu gewährleisten, müssen wir die maximal verbrauchten Ressourcen nutzen und die erforderliche Anzahl von Kernen auf dem Minion (der Maschine, die die Aufgabe ausführt) reservieren. Dann lautet die Reservierungsformel für unser Problem wie folgt:

alloc: cpu = 4 (max)

und wenn wir eine Minion-Maschine mit 16 Kernen haben, dann können genau vier solcher Aufgaben darauf platziert werden. Wir stellen insbesondere fest, dass die durchschnittliche Prozessorauslastung solcher Aufgaben oft sehr gering ist – was offensichtlich ist, da die Aufgabe einen erheblichen Teil der Zeit auf eine Anfrage wartet und nichts tut.

Rechenaufgaben. Ihr Muster wird etwas anders sein:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Der durchschnittliche CPU-Ressourcenverbrauch für solche Aufgaben ist recht hoch. Oftmals möchten wir, dass eine Berechnungsaufgabe in einer bestimmten Zeit abgeschlossen werden kann. Daher müssen wir die erforderliche Mindestanzahl an Prozessoren reservieren, damit die gesamte Berechnung in einer akzeptablen Zeit abgeschlossen werden kann. Die Reservierungsformel sieht folgendermaßen aus:

alloc: cpu = [1,*)

„Bitte platzieren Sie es auf einem Diener, wo es mindestens einen freien Kern gibt, und dann wird es alles verschlingen, so viele wie es sind.“

Hier ist die Effizienz der Nutzung bereits deutlich besser als bei Aufgaben mit kurzer Verzögerung. Der Gewinn wird jedoch viel größer sein, wenn Sie beide Arten von Aufgaben auf einer Minion-Maschine kombinieren und deren Ressourcen unterwegs verteilen. Wenn eine Aufgabe mit kurzer Verzögerung einen Prozessor benötigt, erhält er diesen sofort, und wenn die Ressourcen nicht mehr benötigt werden, werden sie an die Rechenaufgabe übergeben, also etwa so:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Aber wie geht das?

Schauen wir uns zunächst prod und seine Zuordnung an: cpu = 4. Wir müssen vier Kerne reservieren. Im Docker-Betrieb kann dies auf zwei Arten erfolgen:

  • Nutzung der Option --cpuset=1-4, d. h. der Aufgabe vier bestimmte Kerne auf der Maschine zuweisen.
  • Zu verwenden --cpuquota=400_000 --cpuperiod=100_000Weisen Sie ein Kontingent für die Prozessorzeit zu, d. h. geben Sie an, dass die Aufgabe alle 100 ms Echtzeit nicht mehr als 400 ms Prozessorzeit verbraucht. Man erhält die gleichen vier Kerne.

Doch welche dieser Methoden ist geeignet?

CPU-Set sieht ziemlich attraktiv aus. Die Aufgabe verfügt über vier dedizierte Kerne, was bedeutet, dass die Prozessor-Caches so effizient wie möglich arbeiten. Dies hat auch eine Kehrseite: Wir müssten die Aufgabe übernehmen, Berechnungen auf die entlasteten Kerne der Maschine statt auf das Betriebssystem zu verteilen, und das ist keine triviale Aufgabe, insbesondere wenn wir versuchen, Batch-Aufgaben auf einer solchen zu platzieren Maschine. Tests haben gezeigt, dass die Option mit einem Kontingent hier besser geeignet ist: Dadurch hat das Betriebssystem mehr Freiheit bei der Auswahl des Kerns, der die Aufgabe im aktuellen Moment ausführt, und die Prozessorzeit wird effizienter verteilt.

Lassen Sie uns herausfinden, wie Sie in Docker Reservierungen basierend auf der Mindestanzahl von Kernen vornehmen. Das Kontingent für Batch-Aufgaben ist nicht mehr anwendbar, da keine Begrenzung des Maximums erforderlich ist, sondern lediglich das Minimum garantiert werden muss. Und hier passt die Option gut docker run --cpushares.

Wir haben vereinbart, dass wir dies angeben, wenn für eine Charge eine Garantie für mindestens einen Kern erforderlich ist --cpushares=1024, und wenn es mindestens zwei Kerne gibt, dann geben wir an --cpushares=2048. CPU-Anteile beeinträchtigen die Verteilung der Prozessorzeit in keiner Weise, sofern genügend davon vorhanden ist. Wenn das Produkt also derzeit nicht alle vier Kerne nutzt, gibt es keine Einschränkung für Batch-Aufgaben und sie können zusätzliche Prozessorzeit beanspruchen. Wenn jedoch in einer Situation, in der es an Prozessoren mangelt, das Produkt alle vier seiner Kerne verbraucht und sein Kontingent erreicht hat, wird die verbleibende Prozessorzeit proportional zu den CPU-Anteilen aufgeteilt, d. h. in einer Situation mit drei freien Kernen ist es einer werden einer Aufgabe mit 1024 CPU-Anteilen zugewiesen, und die restlichen zwei werden einer Aufgabe mit 2048 CPU-Anteilen zugewiesen.

Doch die Nutzung von Quoten und Aktien reicht nicht aus. Wir müssen sicherstellen, dass eine Aufgabe mit einer kurzen Verzögerung bei der Zuweisung von Prozessorzeit Vorrang vor einer Batch-Aufgabe erhält. Ohne eine solche Priorisierung nimmt die Batch-Aufgabe die gesamte Prozessorzeit in dem Moment in Anspruch, in dem sie vom Produkt benötigt wird. Es gibt keine Container-Priorisierungsoptionen in Docker Run, aber Linux-CPU-Scheduler-Richtlinien sind nützlich. Sie können darüber im Detail lesen hier, und im Rahmen dieses Artikels gehen wir kurz auf sie ein:

  • SCHED_OTHER
    Standardmäßig empfangen alle normalen Benutzerprozesse auf einem Linux-Computer.
  • SCHED_BATCH
    Entwickelt für ressourcenintensive Prozesse. Beim Platzieren einer Aufgabe auf einem Prozessor wird eine sogenannte Aktivierungsstrafe eingeführt: Es ist weniger wahrscheinlich, dass eine solche Aufgabe Prozessorressourcen erhält, wenn sie derzeit von einer Aufgabe mit SCHED_OTHER verwendet wird
  • SCHED_IDLE
    Ein Hintergrundprozess mit einer sehr niedrigen Priorität, sogar niedriger als nett -19. Wir nutzen unsere Open-Source-Bibliothek eins-nio, um beim Starten des Containers per Aufruf die notwendige Richtlinie festzulegen

one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )

Aber auch wenn Sie nicht in Java programmieren, können Sie das Gleiche mit dem Befehl chrt tun:

chrt -i 0 $pid

Fassen wir der Übersichtlichkeit halber alle unsere Isolationsstufen in einer Tabelle zusammen:

Isolationsklasse
Alloc-Beispiel
Docker-Ausführungsoptionen
sched_setscheduler chrt*

Prod
CPU = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

Stapel
CPU = [1, *)
--cpushares=1024
SCHED_BATCH

Leerlauf
CPU= [2, *)
--cpushares=2048
SCHED_IDLE

*Wenn Sie chrt aus einem Container heraus ausführen, benötigen Sie möglicherweise die Funktion sys_nice, da Docker diese Funktion standardmäßig beim Starten des Containers entfernt.

Doch Aufgaben verbrauchen nicht nur den Prozessor, sondern auch den Datenverkehr, was sich noch stärker auf die Latenz einer Netzwerkaufgabe auswirkt als die falsche Zuweisung von Prozessorressourcen. Deshalb wollen wir natürlich auch beim Verkehr genau das gleiche Bild bekommen. Das heißt, wenn eine Produktaufgabe einige Pakete an das Netzwerk sendet, begrenzen wir die maximale Geschwindigkeit (Formel Zuweisung: lan=[*,500 MBit/s) ), mit welchem ​​Produkt dies möglich ist. Und für Batch garantieren wir nur den Mindestdurchsatz, begrenzen jedoch nicht den Höchstdurchsatz (Formel Zuweisung: lan=[10Mbps,*) ) In diesem Fall sollte der Produktverkehr Vorrang vor Batch-Aufgaben haben.
Hier verfügt Docker über keine Grundelemente, die wir verwenden können. Aber es kommt uns zu Hilfe Linux-Verkehrskontrolle. Mit Hilfe der Disziplin konnten wir das gewünschte Ergebnis erzielen Hierarchische faire Servicekurve. Mit seiner Hilfe unterscheiden wir zwei Verkehrsklassen: Prod mit hoher Priorität und Batch/Idle mit niedriger Priorität. Daher sieht die Konfiguration für ausgehenden Datenverkehr wie folgt aus:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

hier ist 1:0 die „root qdisc“ der HSFC-Disziplin; 1:1 – HSFC-Unterklasse mit einer Gesamtbandbreitenbegrenzung von 8 Gbit/s, unter der die Unterklassen aller Container platziert werden; 1:2 – Die untergeordnete Klasse hsfc ist allen Batch- und Leerlaufaufgaben gemeinsam und verfügt über eine „dynamische“ Beschränkung, die weiter unten erläutert wird. Die verbleibenden untergeordneten hsfc-Klassen sind dedizierte Klassen für aktuell ausgeführte Produktcontainer mit Grenzwerten, die ihren Manifesten entsprechen – 450 und 400 Mbit/s. Jeder hsfc-Klasse wird je nach Linux-Kernel-Version eine qdisc-Warteschlange fq oder fq_codel zugewiesen, um Paketverluste bei Verkehrsspitzen zu vermeiden.

Typischerweise dienen TC-Disziplinen dazu, nur ausgehenden Datenverkehr zu priorisieren. Aber wir möchten auch den eingehenden Datenverkehr priorisieren – schließlich kann eine Batch-Aufgabe problemlos den gesamten eingehenden Kanal auswählen und beispielsweise eine große Menge an Eingabedaten für Map&Reduce empfangen. Hierzu verwenden wir das Modul wennb, das für jede Netzwerkschnittstelle eine virtuelle ifbX-Schnittstelle erstellt und eingehenden Datenverkehr von der Schnittstelle auf ausgehenden Datenverkehr auf ifbX umleitet. Darüber hinaus arbeiten für ifbX dieselben Disziplinen an der Steuerung des ausgehenden Datenverkehrs, für den die hsfc-Konfiguration sehr ähnlich sein wird:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Während der Experimente haben wir herausgefunden, dass hsfc die besten Ergebnisse zeigt, wenn die 1:2-Klasse des Batch-/Leerlaufverkehrs ohne Priorität auf Minion-Maschinen auf nicht mehr als eine bestimmte freie Spur beschränkt ist. Andernfalls hat nicht prioritärer Datenverkehr einen zu großen Einfluss auf die Latenz von Produktaufgaben. miniond ermittelt jede Sekunde die aktuelle Menge an freier Bandbreite und misst dabei den durchschnittlichen Verkehrsverbrauch aller Produktaufgaben eines bestimmten Minions One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki und Subtrahieren von der Bandbreite der Netzwerkschnittstelle One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki mit einem kleinen Rand, d.h.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Bänder werden unabhängig für eingehenden und ausgehenden Datenverkehr definiert. Und entsprechend den neuen Werten konfiguriert miniond das Nicht-Prioritätsklassenlimit 1:2 neu.

Daher haben wir alle drei Isolationsklassen implementiert: Prod, Batch und Idle. Diese Klassen haben großen Einfluss auf die Leistungsmerkmale von Aufgaben. Deshalb haben wir uns entschieden, dieses Attribut an der Spitze der Hierarchie zu platzieren, damit beim Blick auf den Namen der hierarchischen Warteschlange sofort klar ist, womit wir es zu tun haben:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Alle unsere Freunde Netz и Musik Die Fronten werden dann in der Hierarchie unter Prod platziert. Platzieren wir beispielsweise den Dienst unter „Batch“. Musikkatalog, das regelmäßig einen Katalog von Titeln aus einer Reihe von auf Odnoklassniki hochgeladenen MP3-Dateien zusammenstellt. Ein Beispiel für einen Dienst im Leerlauf wäre Musiktransformator, wodurch die Musiklautstärke normalisiert wird.

Nachdem die zusätzlichen Zeilen wieder entfernt wurden, können wir unsere Dienstnamen flacher schreiben, indem wir die Task-Isolationsklasse am Ende des vollständigen Dienstnamens hinzufügen: web.front.prod, Katalog.Musik.Batch, transformator.musik.idle.

Wenn wir uns nun den Namen des Dienstes ansehen, verstehen wir nicht nur, welche Funktion er ausführt, sondern auch seine Isolationsklasse, also seine Kritikalität usw.

Alles ist großartig, aber es gibt eine bittere Wahrheit. Es ist unmöglich, Aufgaben, die auf einer Maschine ausgeführt werden, vollständig zu isolieren.

Was wir erreicht haben: Wenn die Charge intensiv verbraucht wird nur CPU-Ressourcen, dann erledigt der integrierte Linux-CPU-Scheduler seine Arbeit sehr gut und es gibt praktisch keine Auswirkungen auf die Produktionsaufgabe. Wenn diese Batch-Aufgabe jedoch beginnt, aktiv mit dem Speicher zu arbeiten, zeigt sich bereits eine gegenseitige Beeinflussung. Dies liegt daran, dass die Produktionsaufgabe aus den Speichercaches des Prozessors „ausgewaschen“ wird. Dies führt dazu, dass Cache-Fehler zunehmen und der Prozessor die Produktionsaufgabe langsamer verarbeitet. Eine solche Batch-Aufgabe kann die Latenz unseres typischen Produktcontainers um 10 % erhöhen.

Die Isolierung des Datenverkehrs ist noch schwieriger, da moderne Netzwerkkarten über eine interne Paketwarteschlange verfügen. Wenn das Paket aus der Batch-Aufgabe zuerst dort ankommt, wird es als erstes über das Kabel übertragen, und es kann nichts dagegen unternommen werden.

Darüber hinaus ist es uns bisher nur gelungen, das Problem der Priorisierung des TCP-Verkehrs zu lösen: Der hsfc-Ansatz funktioniert für UDP nicht. Und selbst im Fall von TCP-Verkehr führt dies zu einer etwa 10 %igen Erhöhung der Verzögerung der Produktionsaufgabe, wenn die Batch-Aufgabe viel Verkehr erzeugt.

Fehlertoleranz

Eines der Ziele bei der Entwicklung von One-Cloud war die Verbesserung der Fehlertoleranz von Odnoklassniki. Daher möchte ich im Folgenden mögliche Ausfall- und Unfallszenarien genauer betrachten. Beginnen wir mit einem einfachen Szenario – einem Containerausfall.

Der Container selbst kann auf verschiedene Weise ausfallen. Dabei kann es sich um eine Art Experiment, Bug oder Fehler im Manifest handeln, wodurch die Prod-Aufgabe beginnt, mehr Ressourcen zu verbrauchen, als im Manifest angegeben. Wir hatten einen Fall: Ein Entwickler implementierte einen komplexen Algorithmus, überarbeitete ihn viele Male, dachte zu viel nach und geriet so durcheinander, dass sich das Problem letztendlich auf eine nicht triviale Art und Weise in einer Schleife drehte. Und da die Prod-Aufgabe eine höhere Priorität hat als alle anderen auf denselben Minions, begann sie, alle verfügbaren Prozessorressourcen zu verbrauchen. In dieser Situation rettete die Isolation bzw. das CPU-Zeitkontingent den Tag. Wenn einer Aufgabe ein Kontingent zugewiesen wird, verbraucht die Aufgabe nicht mehr. Daher bemerkten Batch- und andere Produktionsaufgaben, die auf demselben Computer ausgeführt wurden, nichts.

Das zweite mögliche Problem ist das Herunterfallen des Behälters. Und hier retten uns Neustart-Richtlinien, jeder kennt sie, Docker selbst leistet hervorragende Arbeit. Für fast alle Produktionsaufgaben gilt die Richtlinie „Immer neu starten“. Manchmal verwenden wir on_failure für Batch-Aufgaben oder zum Debuggen von Produktcontainern.

Was können Sie tun, wenn ein ganzer Diener nicht verfügbar ist?

Führen Sie den Container natürlich auf einem anderen Computer aus. Der interessante Teil hier ist, was mit der/den IP-Adresse(n) passiert, die dem Container zugewiesen sind.

Wir können Containern dieselben IP-Adressen zuweisen wie den Minion-Maschinen, auf denen diese Container ausgeführt werden. Wenn der Container dann auf einem anderen Computer gestartet wird, ändert sich seine IP-Adresse und alle Clients müssen verstehen, dass der Container verschoben wurde, und müssen nun zu einer anderen Adresse wechseln, was einen separaten Service Discovery-Dienst erfordert.

Service Discovery ist praktisch. Auf dem Markt gibt es viele Lösungen mit unterschiedlicher Fehlertoleranz für die Organisation einer Service-Registrierung. Häufig implementieren solche Lösungen eine Load-Balancer-Logik, speichern zusätzliche Konfigurationen in Form von KV-Speicher usw.
Wir möchten jedoch die Notwendigkeit vermeiden, eine separate Registrierung zu implementieren, da dies die Einführung eines kritischen Systems bedeuten würde, das von allen Diensten in der Produktion verwendet wird. Dies bedeutet, dass dies ein potenzieller Fehlerpunkt ist und Sie eine sehr fehlertolerante Lösung auswählen oder entwickeln müssen, was natürlich sehr schwierig, zeitaufwändig und teuer ist.

Und noch ein großer Nachteil: Damit unsere alte Infrastruktur mit der neuen funktioniert, müssten wir absolut alle Aufgaben neu schreiben, um eine Art Service Discovery-System zu verwenden. Es gibt eine Menge Arbeit und an manchen Stellen ist es fast unmöglich, wenn es um Low-Level-Geräte geht, die auf der Betriebssystem-Kernel-Ebene oder direkt mit der Hardware arbeiten. Implementierung dieser Funktionalität unter Verwendung etablierter Lösungsmuster, wie z Beiwagen würde an manchen Stellen eine zusätzliche Belastung bedeuten, an anderen eine Erschwerung des Betriebs und zusätzliche Ausfallszenarien. Da wir die Dinge nicht verkomplizieren wollten, haben wir uns entschieden, die Nutzung von Service Discovery optional zu machen.

In einer Cloud folgt die IP dem Container, d. h. jede Aufgabeninstanz hat ihre eigene IP-Adresse. Diese Adresse ist „statisch“: Sie wird jeder Instanz zugewiesen, wenn der Dienst zum ersten Mal an die Cloud gesendet wird. Hatte ein Dienst im Laufe seines Lebens eine unterschiedliche Anzahl an Instanzen, so werden ihm am Ende so viele IP-Adressen zugewiesen, wie es maximale Instanzen gab.

Anschließend ändern sich diese Adressen nicht: Sie werden einmal vergeben und bleiben während der gesamten Lebensdauer des Dienstes in der Produktion bestehen. IP-Adressen folgen Containern im gesamten Netzwerk. Wenn der Container an einen anderen Minion übertragen wird, folgt ihm die Adresse.

Daher ändert sich die Zuordnung eines Dienstnamens zu seiner Liste von IP-Adressen nur sehr selten. Wenn Sie sich noch einmal die Namen der Dienstinstanzen ansehen, die wir am Anfang des Artikels erwähnt haben (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), werden wir feststellen, dass sie den im DNS verwendeten FQDNs ähneln. Richtig, um die Namen von Dienstinstanzen ihren IP-Adressen zuzuordnen, verwenden wir das DNS-Protokoll. Darüber hinaus gibt dieser DNS alle reservierten IP-Adressen aller Container zurück – sowohl laufende als auch gestoppte (angenommen, es werden drei Replikate verwendet und wir haben dort fünf Adressen reserviert – alle fünf werden zurückgegeben). Clients, die diese Informationen erhalten haben, werden versuchen, eine Verbindung mit allen fünf Replikaten herzustellen – und so festzustellen, welche funktionieren. Diese Möglichkeit zur Bestimmung der Verfügbarkeit ist wesentlich zuverlässiger, da sie weder DNS noch Service Discovery beinhaltet, was bedeutet, dass keine schwierigen Probleme zu lösen sind, um die Relevanz der Informationen und die Fehlertoleranz dieser Systeme sicherzustellen. Darüber hinaus können wir bei kritischen Diensten, von denen der Betrieb des gesamten Portals abhängt, überhaupt kein DNS verwenden, sondern einfach IP-Adressen in die Konfiguration eingeben.

Die Implementierung einer solchen IP-Übertragung hinter Containern kann nicht trivial sein – und wir sehen uns anhand des folgenden Beispiels an, wie es funktioniert:

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Nehmen wir an, der One-Cloud-Master gibt Minion M1 den Befehl zum Ausführen 1.ok-web.group1.web.front.prod mit Adresse 1.1.1.1. Funktioniert bei einem Diener Vogel, wodurch diese Adresse speziellen Servern bekannt gegeben wird Routenreflektor. Letztere haben eine BGP-Sitzung mit der Netzwerk-Hardware, in die die Route der Adresse 1.1.1.1 auf M1 übersetzt wird. M1 leitet Pakete innerhalb des Containers unter Linux weiter. Es gibt drei Route-Reflector-Server, da dies ein sehr wichtiger Teil der One-Cloud-Infrastruktur ist – ohne sie wird das Netzwerk in der One-Cloud nicht funktionieren. Wir platzieren sie in verschiedenen Racks, wenn möglich in verschiedenen Räumen des Rechenzentrums, um die Wahrscheinlichkeit eines gleichzeitigen Ausfalls aller drei zu verringern.

Nehmen wir nun an, dass die Verbindung zwischen dem One-Cloud-Master und dem M1-Minion verloren geht. Der One-Cloud-Master geht nun davon aus, dass M1 komplett ausgefallen ist. Das heißt, es gibt dem M2-Minion den Befehl zum Starten web.group1.web.front.prod mit der gleichen Adresse 1.1.1.1. Jetzt haben wir zwei widersprüchliche Routen im Netzwerk für 1.1.1.1: auf M1 und auf M2. Um solche Konflikte zu lösen, nutzen wir den Multi Exit Discriminator, der in der BGP-Ankündigung spezifiziert ist. Dies ist eine Zahl, die das Gewicht der angekündigten Route angibt. Unter den widersprüchlichen Routen wird die Route mit dem niedrigeren MED-Wert ausgewählt. Der One-Cloud-Master unterstützt MED als integralen Bestandteil von Container-IP-Adressen. Erstmals wird die Adresse mit einem ausreichend großen MED = 1 geschrieben. Im Falle eines solchen Notfall-Containertransfers reduziert der Master den MED und M000 erhält bereits den Befehl, die Adresse 000 mit MED = anzukündigen 2. Die auf M1.1.1.1 laufende Instanz bleibt in diesem Fall bestehen, es besteht keine Verbindung, und sein weiteres Schicksal interessiert uns wenig, bis die Verbindung mit dem Master wiederhergestellt ist und er wie ein alter Take gestoppt wird.

Unfälle

Alle Rechenzentrumsverwaltungssysteme behandeln kleinere Ausfälle immer akzeptabel. Containerüberlauf ist fast überall die Norm.

Schauen wir uns an, wie wir mit einem Notfall umgehen, beispielsweise einem Stromausfall in einem oder mehreren Räumen eines Rechenzentrums.

Was bedeutet ein Unfall für ein Rechenzentrums-Managementsystem? Erstens handelt es sich um einen massiven einmaligen Ausfall vieler Maschinen, und das Steuerungssystem muss viele Container gleichzeitig migrieren. Wenn die Katastrophe jedoch sehr groß ist, kann es vorkommen, dass nicht alle Aufgaben anderen Minions zugewiesen werden können, da die Ressourcenkapazität des Rechenzentrums unter 100 % der Auslastung sinkt.

Unfälle gehen oft mit einem Versagen der Kontrollschicht einher. Dies kann aufgrund des Ausfalls seiner Ausrüstung passieren, häufiger jedoch aufgrund der Tatsache, dass Unfälle nicht getestet werden und die Kontrollschicht selbst aufgrund der erhöhten Belastung zusammenbricht.

Was können Sie dagegen tun?

Massenmigrationen bedeuten, dass in der Infrastruktur eine große Anzahl an Aktivitäten, Migrationen und Bereitstellungen stattfindet. Jede der Migrationen kann einige Zeit in Anspruch nehmen, um Container-Images an Minions zu übermitteln und zu entpacken, Container zu starten und zu initialisieren usw. Daher ist es wünschenswert, dass wichtigere Aufgaben vor weniger wichtigen gestartet werden.

Schauen wir uns noch einmal die Hierarchie der uns bekannten Dienste an und versuchen wir zu entscheiden, welche Aufgaben wir zuerst ausführen möchten.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Dies sind natürlich die Prozesse, die direkt an der Bearbeitung von Benutzeranfragen beteiligt sind, also Prod. Wir kennzeichnen dies mit Platzierungspriorität — eine Nummer, die der Warteschlange zugewiesen werden kann. Wenn eine Warteschlange eine höhere Priorität hat, werden ihre Dienste zuerst platziert.

Auf prod weisen wir höhere Prioritäten zu, 0; bei Charge - etwas niedriger, 100; im Leerlauf – noch niedriger, 200. Prioritäten werden hierarchisch angewendet. Alle Aufgaben unten in der Hierarchie haben eine entsprechende Priorität. Wenn wir möchten, dass Caches innerhalb von Prod vor Frontends gestartet werden, weisen wir Cache = 0 und vorderen Unterwarteschlangen = 1 Prioritäten zu. Wenn wir beispielsweise möchten, dass das Hauptportal zuerst von den Frontends gestartet wird und nur die Musikfront Dann können wir letzterem eine niedrigere Priorität zuweisen – 10.

Das nächste Problem ist der Mangel an Ressourcen. Eine große Menge an Geräten, ganze Hallen des Rechenzentrums, fielen aus, und wir haben so viele Dienste neu gestartet, dass jetzt nicht mehr genügend Ressourcen für alle vorhanden sind. Sie müssen entscheiden, welche Aufgaben Sie opfern möchten, um die wichtigsten kritischen Dienste am Laufen zu halten.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Im Gegensatz zur Platzierungspriorität können wir nicht alle Batch-Aufgaben wahllos opfern; einige davon sind wichtig für den Betrieb des Portals. Daher haben wir dies gesondert hervorgehoben Vorkaufspriorität Aufgaben. Wenn eine Aufgabe mit höherer Priorität platziert wird, kann sie einer Aufgabe mit niedrigerer Priorität zuvorkommen, d. h. sie stoppen, wenn keine freien Minions mehr vorhanden sind. In diesem Fall wird eine Aufgabe mit niedriger Priorität voraussichtlich unbesetzt bleiben, d. h. es wird keinen passenden Minion mehr dafür geben, der über genügend freie Ressourcen verfügt.

In unserer Hierarchie ist es sehr einfach, eine Vorrangpriorität anzugeben, sodass Produktions- und Batch-Aufgaben inaktive Aufgaben vorziehen oder stoppen, sich gegenseitig jedoch nicht, indem Sie eine Priorität für inaktive Aufgaben von 200 angeben. Genau wie im Fall der Platzierungspriorität haben wir können unsere Hierarchie nutzen, um komplexere Regeln zu beschreiben. Geben wir beispielsweise an, dass wir die Musikfunktion opfern, wenn wir nicht über genügend Ressourcen für das Hauptwebportal verfügen, indem wir die Priorität für die entsprechenden Knoten niedriger setzen: 10.

Ganze DC-Unfälle

Warum könnte das gesamte Rechenzentrum ausfallen? Element. War ein guter Beitrag Der Hurrikan beeinträchtigte die Arbeit des Rechenzentrums. Die Elemente können als Obdachlose betrachtet werden, die einst die Optik im Verteiler durchgebrannt haben und das Rechenzentrum den Kontakt zu anderen Standorten vollständig verloren hat. Die Fehlerursache kann auch ein menschlicher Faktor sein: Der Bediener gibt einen solchen Befehl, dass das gesamte Rechenzentrum zusammenbricht. Dies könnte aufgrund eines großen Fehlers passieren. Im Allgemeinen sind Zusammenbrüche von Rechenzentren keine Seltenheit. Das passiert uns alle paar Monate einmal.

Und das tun wir, um zu verhindern, dass irgendjemand #alive twittert.

Die erste Strategie ist Isolation. Jede One-Cloud-Instanz ist isoliert und kann Maschinen in nur einem Rechenzentrum verwalten. Das heißt, der Verlust einer Cloud aufgrund von Fehlern oder falschen Bedienerbefehlen ist der Verlust nur eines Rechenzentrums. Darauf sind wir vorbereitet: Wir verfügen über ein Redundanzkonzept, bei dem sich Replikate der Anwendung und Daten in allen Rechenzentren befinden. Wir verwenden fehlertolerante Datenbanken und testen regelmäßig auf Fehler.
Seit heute haben wir vier Rechenzentren, das heißt vier separate, völlig isolierte Instanzen einer Cloud.

Dieser Ansatz schützt nicht nur vor physischem Versagen, sondern kann auch vor Bedienfehlern schützen.

Was kann man sonst noch mit dem Faktor Mensch machen? Wenn ein Bediener der Cloud einen seltsamen oder möglicherweise gefährlichen Befehl gibt, wird er möglicherweise plötzlich gebeten, ein kleines Problem zu lösen, um zu sehen, wie gut er gedacht hat. Wenn es sich beispielsweise um eine Art Massenstopp vieler Replikate oder nur um einen seltsamen Befehl handelt – Reduzieren Sie die Anzahl der Replikate oder ändern Sie den Namen des Bildes und nicht nur die Versionsnummer im neuen Manifest.

One-Cloud – Betriebssystem auf Rechenzentrumsebene in Odnoklassniki

Ergebnisse

Besonderheiten von One-Cloud:

  • Hierarchisches und visuelles Benennungsschema für Dienste und Container, wodurch Sie sehr schnell herausfinden können, um welche Aufgabe es sich handelt, womit sie zusammenhängt, wie sie funktioniert und wer dafür verantwortlich ist.
  • Wir wenden unsere an Technik der Kombination von Produkt- und Batch-Aufgaben auf Minions, um die Effizienz der Maschinenfreigabe zu verbessern. Anstelle von cpuset verwenden wir CPU-Kontingente, Freigaben, CPU-Scheduler-Richtlinien und Linux QoS.
  • Es war nicht möglich, die auf derselben Maschine laufenden Container vollständig zu isolieren, aber ihre gegenseitige Beeinflussung bleibt innerhalb von 20 %.
  • Die Organisation von Diensten in einer Hierarchie hilft bei der automatischen Notfallwiederherstellung Platzierungs- und Vorkaufsprioritäten.

FAQ

Warum haben wir nicht eine fertige Lösung gewählt?

  • Verschiedene Klassen der Aufgabenisolation erfordern unterschiedliche Logik, wenn sie auf Minions angewendet werden. Wenn Produktionsaufgaben durch einfaches Reservieren von Ressourcen platziert werden können, müssen Batch- und Leerlaufaufgaben platziert werden, um die tatsächliche Ressourcennutzung auf Minion-Maschinen zu verfolgen.
  • Die Notwendigkeit, die von Aufgaben verbrauchten Ressourcen zu berücksichtigen, wie zum Beispiel:
    • Netzwerk Bandbreite;
    • Arten und „Spindeln“ von Festplatten.
  • Die Notwendigkeit, die Prioritäten von Diensten während der Notfallreaktion, die Rechte und Kontingente von Befehlen für Ressourcen anzugeben, wird durch hierarchische Warteschlangen in einer Cloud gelöst.
  • Die Notwendigkeit einer menschlichen Benennung von Containern, um die Reaktionszeit bei Unfällen und Zwischenfällen zu verkürzen
  • Die Unmöglichkeit einer einmaligen umfassenden Implementierung von Service Discovery; die Notwendigkeit einer langen Koexistenz mit Aufgaben, die auf Hardware-Hosts gehostet werden – etwas, das durch „statische“ IP-Adressen nach Containern gelöst wird, und infolgedessen die Notwendigkeit einer einzigartigen Integration in eine große Netzwerkinfrastruktur.

Alle diese Funktionen würden erhebliche Änderungen bestehender Lösungen erfordern, um sie an uns anzupassen, und nachdem wir den Arbeitsaufwand abgeschätzt hatten, stellten wir fest, dass wir unsere eigene Lösung mit ungefähr den gleichen Arbeitskosten entwickeln könnten. Aber Ihre Lösung wird viel einfacher zu bedienen und zu entwickeln sein – sie enthält keine unnötigen Abstraktionen, die Funktionen unterstützen, die wir nicht benötigen.

Allen, die die letzten Zeilen gelesen haben, vielen Dank für Ihre Geduld und Aufmerksamkeit!

Source: habr.com

Kommentar hinzufügen