
Aloha, Leute! Ich heiĂe Oleg Anastasyev, ich arbeite in Odnoklassniki im Plattform-Team. Neben mir gibt es in Odnoklassniki eine Menge Hardware. Wir haben vier Rechenzentren mit etwa 500 Racks und mehr als 8.000 Servern. Irgendwann haben wir verstanden, dass die EinfĂŒhrung eines neuen Verwaltungssystems es uns ermöglichen wird, die Technik effizienter zu betreiben, die Verwaltung von ZugĂ€ngen zu erleichtern, die (Neu)Verteilung von Rechenressourcen zu automatisieren, die EinfĂŒhrung neuer Dienste zu beschleunigen und schneller auf groĂangelegte AusfĂ€lle zu reagieren.
Was ist daraus geworden?
Neben mir und der Menge an Hardware gibt es auch Menschen, die mit dieser Hardware arbeiten: Ingenieure, die direkt in den Rechenzentren sind; Netzwerktechniker, die die Netzwerkinfrastruktur einrichten; Administratoren oder SRE, die die Ausfallsicherheit der Infrastruktur gewĂ€hrleisten; und Entwicklerteams, von denen jedes fĂŒr einen Teil der Funktionen des Portals verantwortlich ist. Die von ihnen entwickelte Software funktioniert ungefĂ€hr so:

Benutzeranfragen kommen sowohl an die Fronten des Hauptportals , als auch an andere, zum Beispiel an die Fronten der Musik-API. Diese rufen zur Verarbeitung der GeschĂ€ftslogik den Anwendungsserver auf, der bei der Bearbeitung der Anfrage die notwendigen spezialisierten Mikrodienste aufruft â one-graph (Graf sozialer Netzwerke), user-cache (Cache von Benutzerprofilen) usw.
Jeder dieser Dienste ist auf zahlreichen Maschinen bereitgestellt, und jeder von ihnen hat verantwortliche Entwickler, die fĂŒr das Funktionieren der Module, deren Betrieb und technologische Entwicklung zustĂ€ndig sind. All diese Dienste werden auf physischen Servern gestartet, und bis vor kurzem haben wir genau eine Aufgabe pro Server gestartet, d.h. er war fĂŒr eine bestimmte Aufgabe spezialisiert.
Warum ist das so? Dieser Ansatz hatte mehrere Vorteile:
- Es erleichtert die Massenverwaltung. Angenommen, eine Aufgabe erfordert bestimmte Bibliotheken oder Konfigurationen. Dann wird der Server genau einer bestimmten Gruppe zugewiesen, eine Policy fĂŒr diese Gruppe wird beschrieben (oder sie ist bereits beschrieben) und diese Konfiguration wird zentral und automatisiert auf alle Server dieser Gruppe verteilt.
- Es vereinfacht die Diagnostik. Angenommen, Sie beobachten eine erhöhte Auslastung der CPU und erkennen, dass diese nur von dem Prozess verursacht worden sein kann, der auf dieser Hardware lÀuft. Die Suche nach dem Schuldigen endet sehr schnell.
- Es vereinfacht Ăberwachung. Wenn mit dem Server etwas nicht stimmt, informiert der Monitor darĂŒber, und Sie wissen genau, wer schuld ist.
Ein Dienst, der aus mehreren Replikaten besteht, erhĂ€lt mehreren Server zugewiesen â je einen fĂŒr jede. Dann wird die Rechenressource fĂŒr den Dienst sehr einfach zugewiesen: so viele Server der Dienst hat, so viel Ressourcen kann er maximal verbrauchen. "Einfach" bedeutet hier nicht, dass es leicht zu verwenden ist, sondern dass die Zuteilung der Ressourcen manuell erfolgt.
Ein solcher Ansatz erlaubte es uns auch, spezialisierte Hardwarekonfigurationen fĂŒr Aufgaben zu erstellen, die auf diesem Server ausgefĂŒhrt werden. Wenn die Aufgabe groĂe Datenmengen speichert, verwenden wir einen 4U-Server mit einem Chassis fĂŒr 38 Festplatten. Wenn die Aufgabe rein rechenintensiv ist, können wir einen gĂŒnstigeren 1U-Server kaufen. Das ist aus Sicht der Rechenressourcen effizient. Zudem ermöglicht uns dieser Ansatz, viermal weniger Maschinen bei einer Auslastung zu verwenden, die mit einem uns freundlichen sozialen Netzwerk vergleichbar ist.
Eine solche Effizienz der Nutzung der Rechenressourcen sollte auch wirtschaftliche Effizienz gewĂ€hrleisten, wenn man davon ausgeht, dass das teuerste die Server sind. Lange Zeit war die Hardware am teuersten, und wir haben viel Aufwand betrieben, um die Kosten der Hardware zu senken, indem wir Algorithmen zur GewĂ€hrleistung der Ausfallsicherheit entwickelten, um die Anforderungen an die ZuverlĂ€ssigkeit der Ausstattung zu reduzieren. Und heute sind wir an einem Punkt angelangt, an dem der Preis des Servers nicht mehr entscheidend ist. Wenn man nicht die neueste Exotik betrachtet, hat die konkrete Konfiguration der Server im Rack keine Bedeutung. Jetzt sind wir mit einem anderen Problem konfrontiert â den Kosten fĂŒr den Platz, den der Server im Rechenzentrum einnimmt, d.h. den Platz im Rack.
Sich darĂŒber im Klaren zu werden, haben wir beschlossen zu berechnen, wie effizient wir die Racks nutzen.
Wir haben den Preis des leistungsstĂ€rksten Servers aus wirtschaftlicher Sicht ermittelt, berechnet, wie viele solcher Server wir in unsere Racks stellen können, wie viele Aufgaben wir basierend auf dem alten Modell "ein Server = eine Aufgabe" darauf ausfĂŒhren könnten und inwieweit diese Aufgaben die Hardware auslasten wĂŒrden. Wir haben das berechnet und waren erschĂŒttert. Es stellte sich heraus, dass die Effizienz der Rack-Nutzung bei uns bei etwa 11 % liegt. Die Schlussfolgerung ist offensichtlich: Wir mĂŒssen die Effizienz der Nutzung von Rechenzentren steigern. Die Lösung scheint einfach: Auf einem Server mehrere Aufgaben gleichzeitig ausfĂŒhren. Doch hier beginnen die Schwierigkeiten.
Die Massenkonfiguration wird erheblich komplexer - jetzt ist es unmöglich, einem Server eine bestimmte Gruppe zuzuweisen. Denn jetzt können auf einem Server mehrere Aufgaben verschiedener Teams ausgefĂŒhrt werden. DarĂŒber hinaus kann die Konfiguration fĂŒr verschiedene Anwendungen konfliktreich sein. Auch die Diagnose wird komplizierter: Wenn Sie einen erhöhten Prozessor- oder Festplattenverbrauch auf einem Server sehen, wissen Sie nicht, welches der Aufgaben Probleme verursacht.
Das Wichtigste ist jedoch, dass es keine Isolation zwischen den Aufgaben gibt, die auf demselben Rechner ausgefĂŒhrt werden. Beispielsweise zeigt das Diagramm der durchschnittlichen Antwortzeiten einer Serveranwendung vor und nachdem auf demselben Server eine zusĂ€tzliche, nicht mit der ersten verbundene Anwendung gestartet wurde - die Antwortzeit der Hauptaufgabe hat sich erheblich erhöht.

Offensichtlich mĂŒssen Aufgaben entweder in Containern oder in virtuellen Maschinen ausgefĂŒhrt werden. Da praktisch alle unsere Aufgaben unter einer einzigen OS (Linux) laufen oder entsprechend angepasst sind, ist es nicht erforderlich, viele verschiedene Betriebssysteme zu unterstĂŒtzen. Entsprechend ist Virtualisierung nicht notwendig, da sie aufgrund der zusĂ€tzlichen Betriebskosten weniger effizient ist als Containerisierung.
Als Umsetzung von Containern zur AusfĂŒhrung von Aufgaben direkt auf Docker-Servern ist das eine gute Option: Die Images von Dateisystemen lösen die Probleme mit konfliktierenden Konfigurationen gut. Die Möglichkeit, dass Images aus mehreren Schichten bestehen, ermöglicht es uns, das benötigte Datenvolumen fĂŒr ihre Bereitstellung in der Infrastruktur erheblich zu reduzieren, indem wir gemeinsame Teile in separate Basisschichten auslagern. Damit werden die Basisschichten (und die gröĂten) recht schnell in der gesamten Infrastruktur zwischengespeichert, und fĂŒr die Bereitstellung verschiedener Anwendungstypen und Versionen mĂŒssen nur noch kleine Schichten ĂŒbertragen werden.
Zudem bieten das bereitgestellte Registry und die Tagging-Funktion von Docker uns fertige Primitiven fĂŒr Versionierung und Code-Bereitstellung in der Produktion.
Docker stellt, wie jede andere Ă€hnliche Technologie, von Haus aus ein gewisses MaĂ an Isolation der Container bereit. Zum Beispiel wird jedem Container ein Limit fĂŒr die Nutzung des Arbeitsspeichers der Maschine zugewiesen, das er nicht ĂŒberschreiten kann. AuĂerdem können die Container hinsichtlich der CPU-Nutzung isoliert werden. FĂŒr uns war die Standardisolation jedoch nicht ausreichend. Aber dazu mehr weiter unten.
Die direkte AusfĂŒhrung von Containern auf Servern ist nur ein Teil der Probleme. Ein anderer Teil betrifft die Platzierung der Container auf den Servern. Man muss verstehen, welcher Container auf welchen Server gesetzt werden kann. Diese Aufgabe ist nicht so einfach, da die Container möglichst eng auf den Servern platziert werden mĂŒssen, ohne die Geschwindigkeit ihrer AusfĂŒhrung zu verringern. Eine solche Platzierung kann auch hinsichtlich der Ausfallsicherheit kompliziert sein. Oft möchten wir die Replikate desselben Dienstes in verschiedenen Racks oder sogar in verschiedenen Hallen des Rechenzentrums platzieren, damit wir bei einem Ausfall eines Racks oder einer Halle nicht alle Replikate des Dienstes gleichzeitig verlieren.
Container manuell zu verteilen, ist keine Option, wenn man 8000 Server und 8000 bis 16000 Container hat.
Zudem wollten wir den Entwicklern mehr Autonomie bei der Ressourcenzuteilung geben, sodass sie ihre Dienste selbststÀndig in der Produktion bereitstellen können, ohne Hilfe eines Administrators. Dabei wollten wir die Kontrolle behalten, damit ein weniger wichtiger Dienst nicht alle Ressourcen unserer Rechenzentren verbraucht.
Offensichtlich ist eine Verwaltungs-Ebene erforderlich, die dies automatisch ĂŒbernimmt.
Hier sind wir bei einem einfachen und verstÀndlichen Bild angekommen, das alle Architekten lieben: drei KÀstchen.

one-cloud Masters â ein ausfallsicherer Cluster, der fĂŒr die Orchestrierung der Cloud verantwortlich ist. Der Entwickler sendet ein Manifest an den Master, das alle notwendigen Informationen zur Bereitstellung des Dienstes enthĂ€lt. Der Master gibt auf dieser Grundlage Anweisungen an die ausgewĂ€hlten Minions (Maschinen, die zum Starten von Containern vorgesehen sind). Auf den Minions befindet sich unser Agent, der den Befehl erhĂ€lt, bereits seine Befehle an Docker ĂŒbergibt, und Docker konfiguriert den Linux-Kernel zum Start des entsprechenden Containers. Neben der AusfĂŒhrung von Befehlen berichtet der Agent kontinuierlich an den Master ĂŒber StatusĂ€nderungen sowohl des Minion-GerĂ€ts als auch der darauf gestarteten Container.
Ressourcenzuteilung
Jetzt lassen Sie uns die komplexere Aufgabe der Ressourcenzuteilung fĂŒr mehrere Minions betrachten.
Rechenressource in one-cloud â das ist:
- Die Rechenleistung des Prozessors, die von einer bestimmten Aufgabe benötigt wird.
- Der verfĂŒgbare Speicher fĂŒr die Aufgabe.
- Netzwerkverkehr. Jeder der Minions hat ein spezifisches Netzwerkinterface mit begrenzter Bandbreite, daher können Aufgaben nicht ohne BerĂŒcksichtigung des ĂŒbertragenden Datenvolumens im Netzwerk verteilt werden.
- Festplatten. Neben dem offensichtlichen Platz fĂŒr die Daten der Aufgabe weisen wir auch den Festplattentyp zu: HDD oder SSD. Festplatten können eine bestimmte Anzahl von Anfragen pro Sekunde bedienen â IOPS. Daher weisen wir fĂŒr Aufgaben, die mehr IOPS generieren als eine einzelne Festplatte bedienen kann, auch "Spindeln" zu â d. h. Speichereinheiten, die ausschlieĂlich fĂŒr die Aufgabe reserviert werden mĂŒssen.
FĂŒr einen bestimmten Dienst, beispielsweise user-cache, können wir die benötigten Ressourcen auf folgende Weise festhalten: 400 CPU-Kerne, 2,5 TB Speicher, 50 Gbit/s Netzwerkverkehr in beide Richtungen, 6 TB Speicher auf HDD, verteilt auf 100 Spindeln. Oder in einer fĂŒr uns vertrauteren Form so:
alloc:
cpu: 400
mem: 2500
lan_in: 50g
lan_out: 50g
hdd:100x6TDie Ressourcen des Dienstes user-cache verbrauchen nur einen Teil aller verfĂŒgbaren Ressourcen in der Produktionsinfrastruktur. Daher möchten wir sicherstellen, dass user-cache nicht mehr Ressourcen verbraucht, als ihm zugewiesen wurden, egal ob durch einen Operatorfehler oder nicht. Das heiĂt, wir mĂŒssen die Ressourcen limitieren. Aber woran könnten wir das Kontingent festmachen?
Lassen Sie uns zu unserem stark vereinfachten Schema der Interaktion von Komponenten zurĂŒckkehren und es mit mehr Details neu zeichnen â so:

Was ins Auge fÀllt:
- Web-Frontend und Musik verwenden isolierte Cluster desselben Anwendungsservers.
- Es lassen sich logische Schichten unterscheiden, zu denen diese Cluster gehören: Frontends, Caches, Speicher- und Datenverwaltungsschicht.
- Das Frontend ist heterogen, es besteht aus verschiedenen funktionalen Teilsystemen.
- Caches können ebenfalls nach dem Teilsystem verteilt werden, dessen Daten sie cachen.
Lassen Sie uns das Bild noch einmal neu zeichnen:

Oh! Wir sehen eine Hierarchie! Das bedeutet, dass wir Ressourcen in gröĂeren Einheiten verteilen können: einen verantwortlichen Entwickler fĂŒr einen Knoten dieser Hierarchie zuweisen, der zur funktionalen Teilsystem (wie âmusicâ im Bild) gehört, und zu demselben Hierarchielevel ein Kontingent binden. Eine solche Hierarchie ermöglicht es uns auch, die Dienste flexibler zur besseren Verwaltung zu organisieren. Zum Beispiel teilen wir alle Webdienste, da dies eine sehr groĂe Gruppierung von Servern ist, in mehrere kleinere Gruppen auf, die im Bild als group1, group2 angezeigt werden.
Indem wir ĂŒberflĂŒssige Linien entfernen, können wir jeden Knoten unseres Bildes in einer flacheren Form notieren: group1.web.front, api.music.front, user-cache.cache.
So kommen wir zum Konzept der âhierarchischen Warteschlangeâ. Diese hat einen Namen wie âgroup1.web.frontâ. Ihr wird ein Kontingent fĂŒr Ressourcen und Nutzerrechte zugewiesen. Eine Person aus DevOps erhĂ€lt das Recht, einen Dienst in die Warteschlange zu senden, und dieser Mitarbeiter kann etwas in der Warteschlange starten, wĂ€hrend jemand aus OpsDev â administrative Rechte erhĂ€lt und jetzt die Warteschlange verwalten kann, Personen zuweisen, diesen Personen Rechte geben usw. Dienste, die in dieser Warteschlange gestartet werden, werden im Rahmen des Kontingents der Warteschlange ausgefĂŒhrt. Wenn das Rechenkontingent der Warteschlange nicht ausreicht, um alle Dienste gleichzeitig auszufĂŒhren, werden sie nacheinander ausgefĂŒhrt und bilden somit die Warteschlange.
Lassen Sie uns die Dienste genauer betrachten. Ein Dienst hat einen vollstĂ€ndigen Namen, der immer den Namen der Warteschlange enthĂ€lt. Der Dienst des Web-Frontends wird dann den Namen haben ok-web.group1.web.front. Der Dienst des Anwendungsservers, auf den er zugreift, wird den Namen erhalten ok-app.group1.web.front. Jeder Dienst hat ein Manifest, in dem alle erforderlichen Informationen fĂŒr die Bereitstellung auf bestimmten Maschinen angegeben sind: Wie viele Ressourcen diese Aufgabe benötigt, welche Konfiguration dafĂŒr erforderlich ist, wie viele Repliken vorhanden sein mĂŒssen, Eigenschaften zur Fehlerbehandlung dieses Dienstes. Nach der Bereitstellung des Dienstes auf den Maschinen erscheinen seine Instanzen. Diese werden ebenfalls eindeutig benannt - mit der Instanznummer und dem Namen des Dienstes: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, âŠ
Das ist sehr praktisch: Wenn wir nur den Namen des laufenden Containers betrachten, können wir sofort viel herausfinden.
Lassen Sie uns nun nÀher kennenlernen, was diese Instanzen eigentlich tun: die Aufgaben.
Isolationsklassen von Aufgaben
Alle Aufgaben in OK (und wahrscheinlich ĂŒberall) können in Gruppen eingeteilt werden:
- Aufgaben mit kurzer Latenz - prod. FĂŒr solche Aufgaben und Dienste ist die Antwortlatenz (latency) sehr wichtig, wie schnell jede Anfrage vom System verarbeitet wird. Beispiele fĂŒr Aufgaben sind: Web-Frontends, Caches, Anwendungsserver, OLTP-Speicher usw.
- Berechnung Aufgaben - batch. Hier ist die Geschwindigkeit der Verarbeitung jeder einzelnen Anfrage unwichtig. FĂŒr sie ist wichtig, wie viele Berechnungen insgesamt in einem bestimmten (groĂen) Zeitraum diese Aufgabe durchfĂŒhrt (throughput). Dazu gehören alle Aufgaben in MapReduce, Hadoop, maschinelles Lernen, Statistik.
- Hintergrundaufgaben - idle. FĂŒr solche Aufgaben sind weder Latenz noch Durchsatz besonders wichtig. Dazu gehören verschiedene Tests, Migrationen, Neuberechnungen, Datenkonvertierungen von einem Format in ein anderes. Einerseits Ă€hneln sie den Berechnungsaufgaben, andererseits ist es uns nicht so wichtig, wie schnell sie abgeschlossen werden.
Schauen wir uns an, wie solche Aufgaben Ressourcen, zum Beispiel die CPU, verbrauchen.
Aufgaben mit kurzer Latenz. Ein solches Aufgabemuster wird folgendermaĂen aussehen:

Eine Anfrage vom Benutzer wird bearbeitet, die Aufgabe nutzt alle verfĂŒgbaren CPU-Kerne, verarbeitet die Anfrage, gibt eine Antwort zurĂŒck, wartet auf die nĂ€chste Anfrage und bleibt stehen. Eine neue Anfrage kommt - wieder wird alles gewĂ€hlt, was verfĂŒgbar ist, berechnet und wir warten auf die nĂ€chste.
Um eine minimale Latenz fĂŒr eine solche Aufgabe zu garantieren, mĂŒssen wir die maximal verbrauchten Ressourcen reservieren und die erforderliche Anzahl von Kernen auf dem Minion (der Maschine, die die Aufgabe ausfĂŒhrt) reservieren. Dann ergibt sich die Reservierungsformel fĂŒr unsere Aufgabe wie folgt:
alloc: cpu = 4 (max)Und wenn wir einen Minion-Server mit 16 Kernen haben, können darauf genau vier solcher Aufgaben platziert werden. Besonders hervorzuheben ist, dass der durchschnittliche CPU-Verbrauch dieser Aufgaben oft sehr niedrig ist â was offensichtlich ist, da ein erheblicher Teil der Zeit die Aufgabe auf eine Anfrage wartet und nichts tut.
Berechnungsaufgaben. Ihr Muster wird etwas anders sein:

Der durchschnittliche Ressourcenverbrauch der CPU bei solchen Aufgaben ist ziemlich hoch. Oft möchten wir, dass die Berechnungsaufgabe innerhalb einer bestimmten Zeit ausgefĂŒhrt wird, daher mĂŒssen wir die minimale Anzahl an Prozessoren reservieren, die sie benötigt, damit die gesamte Berechnung in einer akzeptablen Zeit abgeschlossen werden kann. Ihre Reservierungsformel wird so aussehen:
alloc: cpu = [1,*)âBitte platziere es auf dem Minion, wo es mindestens einen freien Kern gibt, und alles Weitere wird genutzt.â
Hier ist die Effizienz der Nutzung bereits weitaus besser als bei Aufgaben mit kurzer Verzögerung. Doch der Gewinn wird viel gröĂer, wenn man beide Aufgabentypen auf demselben Minion-Server kombiniert und dessen Ressourcen dynamisch verteilt. Wenn die Aufgabe mit kurzer Verzögerung CPU benötigt, erhĂ€lt sie sie sofort, und wenn die Ressourcen nicht mehr benötigt werden, werden sie der Berechnungsaufgabe zugeteilt, d. h. so:

Aber wie macht man das?
ZunĂ€chst klĂ€ren wir die Sache mit prod und seinem alloc: cpu = 4. Wir mĂŒssen vier Kerne reservieren. In Docker run gibt es zwei Möglichkeiten, dies zu tun:
- Durch die Option
--cpuset=1-4, d. h. der Aufgabe vier bestimmte Kerne auf dem Server zuweisen. - Verwenden
--cpuquota=400_000 --cpuperiod=100_000, eine CPU-Zeitquote zuweisen, d. h. angeben, dass die Aufgabe alle 100 ms Echtzeit nicht mehr als 400 ms CPU-Zeit verbrauchen darf. Es handelt sich um dieselben vier Kerne.
Aber welche dieser Methoden ist geeignet?
Das cpuset sieht ziemlich attraktiv aus. Die Aufgabe hat vier dedizierte Kerne, was bedeutet, dass die CPU-Caches höchst effizient arbeiten werden. Das hat jedoch auch eine Kehrseite: Wir mĂŒssten die Verteilung der Berechnungen auf die weniger ausgelasteten Kerne der Maschine selbst ĂŒbernehmen, anstatt dies durch das Betriebssystem erledigen zu lassen, was eine ziemlich anspruchsvolle Aufgabe ist, insbesondere wenn wir versuchen, Batch-Aufgaben auf einer solchen Maschine zu platzieren. Tests haben gezeigt, dass hier die Quoten-Option besser geeignet ist: So hat das Betriebssystem mehr Freiheit bei der Auswahl des Kerns fĂŒr die AusfĂŒhrung der Aufgabe im aktuellen Moment und die Prozessorzeit wird effizienter verteilt.
Lass uns anschauen, wie man in Docker eine Reservierung nach Mindestanzahl an Kernen erstellt. Eine Quote fĂŒr Batch-Aufgaben ist bereits nicht mehr anwendbar, da das Festlegen eines Maximums nicht notwendig ist; es reicht aus, lediglich ein Minimum zu garantieren. Hier eignet sich die Option gut, docker run --cpushares.
Wir haben uns darauf geeinigt, dass wenn ein Batch ein Minimum von einem Kern garantiert, wir angeben, --cpushares=1024, und wenn mindestens zwei Kerne benötigt werden, dann geben wir an, --cpushares=2048. CPU-Anteile beeinflussen nicht die Verteilung der Prozessorzeit, solange diese ausreicht. Wenn also der Prod momentan nicht alle seine vier Kerne nutzt, gibt es nichts, was die Batch-Aufgaben einschrĂ€nkt, und sie können die zusĂ€tzliche Prozessorzeit verwenden. In einer Situation mit ProzessorengpĂ€ssen, wenn der Prod alle vier Kerne verbraucht hat und auf die Quote stöĂt, wird die verbleibende Prozessorzeit proportional zu den cpushares verteilt, d.h. in einer Situation mit drei freien Kernen erhĂ€lt die Aufgabe mit 1024 cpushares einen Kern, und die anderen beiden - die Aufgabe mit 2048 cpushares.
Aber die Verwendung von Quoten und Anteilen reicht nicht aus. Wir mĂŒssen sicherstellen, dass eine Aufgabe mit kurzer Verzögerung PrioritĂ€t gegenĂŒber einer Batch-Aufgabe bei der Verteilung der Prozessorzeit erhĂ€lt. Ohne eine solche Priorisierung wĂŒrde die Batch-Aufgabe die gesamte Prozessorzeit in Anspruch nehmen, wenn sie fĂŒr den Prod erforderlich ist. Bei Docker run gibt es keine Optionen zur Priorisierung von Containern, aber CPU-Scheduler-Richtlinien in Linux kommen zur Hilfe. Mehr dazu kann man lesen, , und im Rahmen dieses Artikels werden wir sie kurz durchgehen:
- SCHED_OTHER
StandardmĂ€Ăig erhalten alle normalen Benutzerprozesse auf einer Linux-Maschine. - SCHED_BATCH
FĂŒr ressourcenintensive Prozesse vorgesehen. Wenn eine Aufgabe im Prozessor platziert wird, wird eine sogenannte Aktivierungsstrafe eingefĂŒhrt: Eine solche Aufgabe hat eine geringere Wahrscheinlichkeit, Prozessorressourcen zu erhalten, wenn dieser gerade von einer Aufgabe mit SCHED_OTHER verwendet wird. - SCHED_IDLE
Hintergrundprozess mit sehr niedriger PrioritĂ€t, sogar niedriger als nice â19. Wir verwenden unsere Open-Source-Bibliothek , um die erforderliche Politik beim Starten des Containers ĂŒber den Befehl festzulegen
one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )Aber selbst wenn Sie nicht in Java programmieren, kann das Gleiche mit dem Befehl chrt getan werden:
chrt -i 0 $pidLassen Sie uns alle unsere Isolationsstufen in einer Tabelle zur Veranschaulichung zusammenfassen:
Isolationsklasse
Beispiel alloc
Docker Run Optionen
sched_setscheduler chrt*
Prod
cpu = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER
Batch
Cpu = [1, * )
--cpushares=1024
SCHED_BATCH
Idle
Cpu= [2, *)
--cpushares=2048
SCHED_IDLE
*Wenn Sie chrt aus dem Container heraus durchfĂŒhren, könnte die Berechtigung sys_nice erforderlich sein, da Docker diese Berechtigung standardmĂ€Ăig beim Starten des Containers entzieht.
Aber Aufgaben benötigen nicht nur Prozessorressourcen, sondern auch Verkehr, der die Verzögerung der Netzwerkaufgabe noch mehr beeinflusst als eine falsche Verteilung der Prozessorressourcen. Daher möchten wir natĂŒrlich dasselbe Bild auch fĂŒr den Verkehr erhalten. Das heiĂt, wenn eine prod-Aufgabe irgendwelche Pakete ins Netzwerk sendet, quotieren wir die maximale Geschwindigkeit (Formel alloc: lan=[*,500mbps) ), mit der die Prod das tun kann. FĂŒr Batch garantieren wir jedoch nur eine minimale Bandbreite, beschrĂ€nken jedoch nicht die maximale (Formel alloc: lan=[10Mbps,*) ) Dabei muss der Verkehr von Prod Vorrang vor Batch-Aufgaben haben.
Hier hat Docker keine primitiven Elemente, die wir verwenden könnten. Aber uns kommt zur Hilfe. Wir konnten das gewĂŒnschte Ergebnis mit der Disziplin erreichen. Damit teilen wir zwei Klassen von Verkehr auf: hochpriorisierter Prod und niedrigpriorisierter Batch/Idle. Das Ergebnis der Konfiguration fĂŒr den ausgehenden Verkehr sieht so aus:
Hier ist 1:0 â âRoot qdiscâ der Disziplin hsfc; 1:1 â untergeordnete Klasse hsfc mit einer Gesamtbandbreitenlimit von 8 Gbit/s, unter die alle untergeordneten Klassen aller Container fallen; 1:2 â untergeordnete hsfc-Klasse, die fĂŒr alle batch und idle Aufgaben mit einem âdynamischenâ Limit gilt, auf das wir weiter unten eingehen. Die anderen untergeordneten hsfc-Klassen sind exklusive Klassen fĂŒr derzeit aktive prod-Container mit Limits, die ihren Manifesten entsprechen â 450 und 400 Mbit/s. Jeder hsfc-Klasse ist eine qdisc-Warteschlange fq oder fq_codel zugewiesen, abhĂ€ngig von der Kernel-Version von Linux, um Paketverluste bei Verkehrsspitzen zu vermeiden.
In der Regel dienen die Disziplinen tc nur der Priorisierung des ausgehenden Verkehrs. Aber wir möchten auch den eingehenden Verkehr priorisieren â denn irgendeine batch-Aufgabe kann mĂŒhelos den gesamten eingehenden Kanal belegen, indem sie beispielsweise ein groĂes Paket eingehender Daten fĂŒr Map&Reduce erhĂ€lt. DafĂŒr verwenden wir das Modul , das ein virtuelles Interface ifbX fĂŒr jede Netzwerkschnittstelle erstellt und den eingehenden Verkehr von der Schnittstelle zum ausgehenden auf ifbX umleitet. AnschlieĂend arbeiten fĂŒr ifbX dieselben Disziplinen zur Kontrolle des ausgehenden Verkehrs, fĂŒr den die hsfc-Konfiguration sehr Ă€hnlich sein wird:
In unseren Experimenten haben wir festgestellt, dass hsfc die besten Ergebnisse liefert, wenn die Klasse 1:2 des nicht priorisierten batch/idle Verkehrs auf den Minion-Maschinen auf nicht mehr als einen bestimmten freien Durchsatz begrenzt wird. Andernfalls beeinflusst der nicht priorisierte Verkehr die Latenz der prod-Aufgaben zu stark. Der aktuelle Wert des freien Durchsatzes wird von miniond jede Sekunde bestimmt, indem der durchschnittliche Verkehr von allen prod-Aufgaben dieses Minions gemessen
und von der Bandbreite der Netzwerkschnittstelle
mit einem kleinen Puffer abgezogen wird, d. h.

Die Bandbreiten werden unabhĂ€ngig fĂŒr den eingehenden und ausgehenden Verkehr bestimmt. Und gemÀà den neuen Werten konfiguriert miniond das Limit der nicht priorisierten Klasse 1:2 neu.
So haben wir alle drei Isolationsklassen implementiert: prod, batch und idle. Diese Klassen haben einen signifikanten Einfluss auf die AusfĂŒhrungsmerkmale der Aufgaben. Daher haben wir beschlossen, dieses Merkmal an die Spitze der Hierarchie zu setzen, damit bei einem Blick auf den Namen der hierarchischen Warteschlange sofort klar ist, womit wir es zu tun haben:

All unsere Bekannten web und music Fronten werden dann in der Hierarchie unter prod platziert. Zum Beispiel lassen Sie uns den Dienst Music Catalog, der regelmĂ€Ăig einen Katalog von Tracks aus der Sammlung von hochgeladenen mp3-Dateien in "Odnoklassniki" erstellt. Ein Beispiel fĂŒr einen Idle-Service könnte sein music transformer, der die LautstĂ€rke der Musik normalisiert.
Nachdem wir die ĂŒberflĂŒssigen Linien wieder entfernt haben, können wir die Namen unserer Dienste flacher gestalten, indem wir die Klassifizierung der Aufgabe am Ende des vollstĂ€ndigen Dienstnamens anfĂŒgen: web.front.prod, catalog.music.batch, transformer.music.idle.
Und jetzt, wenn wir uns den Servicenamen ansehen, verstehen wir nicht nur, welche Funktion er erfĂŒllt, sondern auch seine Klassifizierung, was bedeutet, dass wir seine KritikalitĂ€t usw. erkennen.
Alles ist wunderbar, aber es gibt eine bittere Wahrheit. Es ist unmöglich, Aufgaben, die auf einer Maschine laufen, vollstÀndig zu isolieren.
Was wir erreicht haben: Wenn die Batch-Aufgabe intensiv nur CPU-Ressourcen verbraucht, dann erledigt der integrierte CPU-Planer in Linux seine Aufgabe sehr gut, und es gibt praktisch keinen Einfluss auf die Prod-Aufgabe. Wenn diese Batch-Aufgabe jedoch aktiv mit dem Speicher arbeitet, zeigt sich der gegenseitige Einfluss bereits. Das geschieht, weil die Prozessor-Caches der Prod-Aufgabe "ausgesogen" werden â letztlich nehmen die Cache-Fehler zu, und der Prozessor verarbeitet die Prod-Aufgabe langsamer. Solch eine Batch-Aufgabe kann die Latenz unseres typischen Prod-Containers um 10 % erhöhen.
Die Isolierung des Verkehrs ist noch schwieriger, da moderne Netzwerkkarten ĂŒber eine interne Warteschlange fĂŒr Pakete verfĂŒgen. Wenn ein Paket von der Batch-Aufgabe zuerst eingereicht wird, wird es auch zuerst ĂŒber das Kabel ĂŒbertragen, und da kann man nichts machen.
AuĂerdem ist es uns bisher nur gelungen, die Aufgabe der Priorisierung von TCP-Verkehr zu lösen: FĂŒr UDP funktioniert der Ansatz mit HSFC nicht. Und selbst im Fall von TCP-Verkehr, wenn die Batch-Aufgabe viel Verkehr generiert, erhöht sich auch hier die Latenz der Prod-Aufgabe um etwa 10 %.
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 Ausfallszenarien und NotfĂ€lle genauer betrachten. Lassen Sie uns mit einem einfachen Szenario beginnen â dem Ausfall eines Containers.
Ein Container kann auf verschiedene Weise ausfallen. Dies kann ein Experiment, ein Fehler oder ein Problem im Manifest sein, wodurch die Prod-Aufgabe mehr Ressourcen verbraucht als im Manifest angegeben. Wir hatten einen Fall, in dem ein Entwickler einen komplexen Algorithmus implementierte, ihn mehrmals ĂŒberarbeitete, sich dabei selbst verwirrte und schlieĂlich so kompliziert wurde, dass die Aufgabe ziemlich untrivial in eine Endlosschleife geriet. Da die Prod-Aufgabe jedoch eine höhere PrioritĂ€t hat als alle anderen auf denselben Minions, begann sie, alle verfĂŒgbaren CPU-Ressourcen zu verbrauchen. In dieser Situation half die Isolation, genauer gesagt, das Kontingent fĂŒr die CPU-Zeit. Wenn einer Aufgabe ein Kontingent zugewiesen ist, verbraucht sie nicht mehr. Daher bemerkten die Batch- und anderen Prod-Aufgaben, die auf demselben Rechner liefen, nichts.
Das zweite mögliche Problem ist der Absturz des Containers. Hier helfen uns die Neustart-Richtlinien, die jeder kennt; Docker kommt damit sehr gut zurecht. Praktisch alle Prod-Aufgaben haben die Neustart-Richtlinie "always". Manchmal verwenden wir "on_failure" fĂŒr Batch-Aufgaben oder zur Fehlerbehebung von Prod-Containern.
Was kann man tun, wenn ein ganzer Minion nicht verfĂŒgbar ist?
Offensichtlich kann man den Container auf einem anderen Rechner starten. Das Interessanteste dabei ist, was mit der IP-Adresse (den IP-Adressen), die dem Container zugewiesen sind, passiert.
Wir können Containern die gleichen IP-Adressen zuweisen wie den Maschinen-Minions, auf denen diese Container gestartet werden. Dann Ă€ndert sich die IP-Adresse des Containers, wenn er auf einem anderen Rechner gestartet wird, und alle Clients mĂŒssen verstehen, dass der Container umgezogen ist; sie mĂŒssen nun die andere Adresse ansteuern, was einen separaten Service fĂŒr die Service Discovery erfordert.
Service Discovery ist praktisch. Auf dem Markt gibt es viele Lösungen mit unterschiedlichem Grad an Ausfallsicherheit zur Organisation eines Service-Registers. Oft wird in solchen Lösungen auch die Logik eines Lastenausgleichers implementiert, sowie die Speicherung zusÀtzlicher Konfigurationen in Form eines KV-Speichers usw.
Wir möchten jedoch auf die Notwendigkeit verzichten, ein separates Register einzufĂŒhren, da das die EinfĂŒhrung eines kritischen Systems bedeuten wĂŒrde, das von allen Diensten in der Produktion genutzt wird. Das bedeutet, dass es einen potenziellen Ausfallpunkt darstellt, und es mĂŒssen sehr ausfallsichere Lösungen gewĂ€hlt oder entwickelt werden, was offensichtlich sehr kompliziert, zeitaufwendig und teuer ist.
Und ein weiterer groĂer Nachteil: Damit unsere alte Infrastruktur mit der neuen funktioniert, mĂŒsste man alle Aufgaben vollstĂ€ndig umschreiben, um ein Service Discovery-System zu verwenden. Es gibt SEHR viel Arbeit, und manchmal ist es bis zur Unmöglichkeit, wenn es um niedrigstufige GerĂ€te geht, die auf Betriebssystemkern- oder Hardware-Ebene arbeiten. Die Implementierung dieser FunktionalitĂ€t mithilfe etablierter LösungsansĂ€tze, wie zum Beispiel , wĂŒrde an vielen Stellen eine zusĂ€tzliche Last bedeuten, an anderen Stellen â eine KomplexitĂ€t der Wartung und zusĂ€tzliche Ausfallszenarien. Da wir die KomplexitĂ€t nicht erhöhen wollten, entschieden wir uns, die Nutzung von Service Discovery optional zu machen.
In one-cloud folgt die IP der Instanz, d.h. jede Aufgabeninstanz hat ihre eigene IP-Adresse. Diese Adresse ist "statisch": sie wird jeder Instanz zum Zeitpunkt der ersten Bereitstellung des Dienstes in der Cloud zugewiesen. Wenn der Dienst wÀhrend seiner Lebensdauer eine unterschiedliche Anzahl von Instanzen hatte, wird im Ergebnis so viele IP-Adressen zugewiesen, wie es maximal Instanzen gab.
Diese Adressen Ă€ndern sich anschlieĂend nicht: Sie werden einmal zugewiesen und existieren wĂ€hrend der gesamten Lebensdauer des Dienstes in der Produktion weiter. Die IP-Adressen folgen den Containern im Netzwerk. Wenn ein Container auf einen anderen Knoten verschoben wird, wird die Adresse ebenfalls mit ihm ĂŒbertragen.
So kommt es selten vor, dass der Dienstnamen mit seiner Liste von IP-Adressen abgeglichen wird. Wenn man sich noch einmal die Instanznamen des Dienstes betrachtet, die wir zu Beginn des Artikels erwĂ€hnt haben (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, âŠ), dann werden wir feststellen, dass sie FQDN Ă€hneln, die im DNS verwendet werden. Das ist auch der Fall, denn zur Anzeige der Instanznamen von Diensten in ihren IP-Adressen verwenden wir das DNS-Protokoll. Dabei gibt dieses DNS alle reservierten IP-Adressen aller Container zurĂŒck â sowohl der laufenden als auch der gestoppten (angenommen, es werden drei Replikate verwendet, und uns stehen fĂŒnf Adressen zur VerfĂŒgung â alle fĂŒnf werden zurĂŒckgegeben). Die Clients, die diese Informationen erhalten, versuchen, eine Verbindung zu allen fĂŒnf Replikaten herzustellen â und bestimmen auf diese Weise, welche davon aktiv sind. Diese Methode zur Feststellung der VerfĂŒgbarkeit ist deutlich zuverlĂ€ssiger, da hier weder DNS noch Service Discovery beteiligt sind, was bedeutet, dass es keine schwer lösbaren Probleme hinsichtlich der AktualitĂ€t der Informationen und der Fehlertoleranz dieser Systeme gibt. DarĂŒber hinaus können wir in kritischen Diensten, von denen der gesamte Portalbetrieb abhĂ€ngt, auf DNS ganz verzichten und die IP-Adressen einfach in die Konfiguration eintragen.
Die Umsetzung eines solchen IP-Transfers fĂŒr Container kann nicht trivial sein â und wir werden an einem nĂ€chsten Beispiel darauf eingehen, wie das funktioniert:

Angenommen, der one-cloud Master befiehlt dem Minion M1, zu starten 1.ok-web.group1.web.front.prod mit der Adresse 1.1.1.1. Auf dem Minion lĂ€uft , das diese Adresse an spezielle Server ankĂŒndigt . Letztere haben eine BGP-Session mit dem NetzwerkgerĂ€t, in das die Route der Adresse 1.1.1.1 auf M1 ĂŒbertragen wird. M1 routet die Pakete mit den Mitteln von Linux ins Containerinnere. Es gibt drei route reflectoren, da dies ein sehr kritischer 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, die Verbindung zwischen dem one-cloud Master und dem Minion M1 ist abgebrochen. Der one-cloud Master wird nun davon ausgehen, dass M1 vollstĂ€ndig ausgefallen ist. Das heiĂt, er wird dem Minion M2 den Befehl geben, zu starten web.group1.web.front.prod Mit der gleichen Adresse 1.1.1.1. Jetzt haben wir zwei Konflikt-Routen im Netz fĂŒr 1.1.1.1: auf M1 und auf M2. Um solche Konflikte zu lösen, verwenden wir den Multi Exit Discriminator, der in der BGP-AnkĂŒndigung angegeben wird. Dies ist eine Zahl, die das Gewicht der angekĂŒndigten Route anzeigt. Die Route mit dem niedrigeren MED-Wert wird aus den Konflikten ausgewĂ€hlt. Der Master von one-cloud unterstĂŒtzt MED als integralen Bestandteil der IP-Adressen von Containern. Zuerst wird die Adresse mit einem relativ hohen MED = 1.000.000 ausgegeben. Wenn es jedoch zu einem Notfall-Transfer des Containers kommt, verringert der Master das MED, und M2 erhĂ€lt den Befehl, die Adresse 1.1.1.1 mit MED = 999.999 anzukĂŒndigen. Der Instance, die auf M1 lĂ€uft, bleibt dabei offline, und ihr weiteres Schicksal interessiert uns kaum, bis die Verbindung zum Master wiederhergestellt ist, wenn sie dann wie ein alter Duplicat gestoppt wird.
NotfÀlle
Alle Systeme zur Verwaltung von Rechenzentren bewĂ€ltigen kleinere AusfĂ€lle in der Regel akzeptabel. Der Ausfall eines Containers ist praktisch ĂŒberall die Norm.
Schauen wir uns an, wie wir mit einem Notfall umgehen, zum Beispiel mit einem Stromausfall in einem oder mehreren RĂ€umen des Rechenzentrums.
Was bedeutet ein Notfall fĂŒr das System zur Verwaltung des Rechenzentrums? Zuerst einmal ist es ein massiver gleichzeitiger Ausfall vieler Maschinen, und das Management-System muss gleichzeitig viele Container migrieren. Aber bei einem sehr groĂflĂ€chigen Notfall kann es vorkommen, dass nicht alle Aufgaben auf anderen Minions umverteilt werden können, da die Ressourcen-KapazitĂ€t des Rechenzentrums unter 100 % Auslastung fĂ€llt.
Oft gehen NotfÀlle mit einem Ausfall auch der Management-Ebene einher. Dies kann durch einen Ausfall der Hardware verursacht werden, hÀufiger jedoch, weil NotfÀlle nicht getestet werden und die Management-Ebene selbst unter der erhöhten Last zusammenbricht.
Was kann man in all dem tun?
Massive Migrationen bedeuten, dass in der Infrastruktur eine groĂe Anzahl von Aktionen, Migrationen und Platzierungen stattfindet. Jede Migration kann einige Zeit in Anspruch nehmen, die notwendig ist, um Container-Images zu den Minions zu liefern und zu entpacken, Container zu starten und zu initialisieren usw. Daher ist es wĂŒnschenswert, dass wichtigere Aufgaben vor weniger wichtigen gestartet werden.
Lassen Sie uns erneut die uns vertraute Hierarchie der Dienste betrachten und versuchen zu entscheiden, welche Aufgaben wir zuerst starten möchten.

NatĂŒrlich sind dies die Prozesse, die direkt an der Bearbeitung von Benutzeranfragen beteiligt sind, d. h. prod. Wir geben dies an mit PrioritĂ€t der Platzierung â einer Zahl, die einer Warteschlange zugewiesen werden kann. Wenn eine Warteschlange eine höhere PrioritĂ€t hat, werden ihre Dienste zuerst platziert.
In prod weisen wir höhere PrioritĂ€ten zu, 0; in batch etwas niedrigere, 100; in idle noch niedrigere, 200. Die PrioritĂ€ten werden hierarchisch angewendet. Alle Aufgaben auf einer tieferen Hierarchieebene haben die entsprechende PrioritĂ€t. Wenn wir wollen, dass innerhalb von prod die Caches vor den Frontends gestartet werden, weisen wir den Caches eine PrioritĂ€t von 0 und den Frontunterwarteschlangen eine PrioritĂ€t von 1 zu. Wenn wir beispielsweise möchten, dass das Hauptportal aus den Frontends zuerst gestartet wird und der Musik-Frontend danach, können wir letzterem eine niedrigere PrioritĂ€t zuweisen â 10.
Ein weiteres Problem ist der Mangel an Ressourcen. Wir haben also eine groĂe Anzahl von GerĂ€ten verloren, ganze SĂ€le im Rechenzentrum, und wir haben so viele Dienste gestartet, dass es jetzt fĂŒr alle nicht genug Ressourcen gibt. Wir mĂŒssen entscheiden, welche Aufgaben wir opfern, damit die wichtigsten kritischen Dienste weiterhin funktionieren.

Im Gegensatz zur PrioritĂ€t der Platzierung können wir nicht einfach alle batch-Aufgaben opfern, einige davon sind wichtig fĂŒr den Betrieb des Portals. Daher haben wir separat PrioritĂ€t der VerdrĂ€ngung aufgaben ĐČŃЎДл especĂal. Bei der Platzierung kann eine Aufgabe mit höherer PrioritĂ€t eine Aufgabe mit niedrigerer PrioritĂ€t verdrĂ€ngen, d. h. anhalten, wenn es keine freien Minions mehr gibt. In diesem Fall wird die Aufgabe mit niedrigerer PrioritĂ€t wahrscheinlich unplatziert bleiben, d. h. es gibt keinen geeigneten Minion mit ausreichend freien Ressourcen mehr.
In unserer Hierarchie ist es sehr einfach, eine solche VerdrĂ€ngungsprioritĂ€t anzugeben, dass prod- und batch-Aufgaben idle-Aufgaben verdrĂ€ngen oder anhalten, aber sich gegenseitig nicht, indem wir fĂŒr idle eine PrioritĂ€t von 200 angeben. Genau wie im Fall der PlatzierungsprioritĂ€t können wir unsere Hierarchie nutzen, um komplexere Regeln zu beschreiben. Zum Beispiel geben wir an, dass wir auf die Musikfunktion verzichten, wenn uns die Ressourcen fĂŒr das Haupt-Webportal fehlen, indem wir fĂŒr die entsprechenden Knoten eine niedrigere PrioritĂ€t festlegen: 10.
VorfÀlle im Rechenzentrum
Warum kann das gesamte Rechenzentrum ausfallen? Naturkatastrophen. Es gab einen interessanten Beitrag darĂŒber, wie . Die Elemente kann man als Obdachlose betrachten, die einmal im Kanal die Optik abgebrannt haben, und das Rechenzentrum komplett den Kontakt zu den anderen Standorten verloren hat. Ein Grund fĂŒr den Ausfall kann auch der menschliche Faktor sein: Ein Operator könnte einen Befehl erteilen, der dazu fĂŒhrt, dass das gesamte Rechenzentrum ausfĂ€llt. Dies kann durch einen groĂen Bug geschehen. Insgesamt fallen Rechenzentren â das ist keine Seltenheit. Bei uns passiert das alle paar Monate einmal.
Und das machen wir, damit niemand #ĐŸĐșжОĐČĐž auf Twitter postet.
Die erste Strategie ist die Isolation. Jede Instanz von one-cloud ist isoliert und kann Maschinen nur eines Rechenzentrums verwalten. Das heiĂt, der Verlust der Cloud aufgrund von Bugs oder falschen Befehlen des Operators bedeutet den Verlust nur eines Rechenzentrums. Darauf sind wir vorbereitet: Es gibt eine Reservierungspolitik, bei der die Replikate der Anwendungen und Daten in allen Rechenzentren platziert werden. Wir nutzen ausfallsichere Datenbanken und testen regelmĂ€Ăig AusfĂ€lle.
Da wir heute ĂŒber vier Rechenzentren verfĂŒgen, gibt es auch vier separate, vollstĂ€ndig isolierte Instanzen von one-cloud.
Dieser Ansatz schĂŒtzt nicht nur vor physischen AusfĂ€llen, sondern kann auch vor Fehlern des Operators schĂŒtzen.
Was kann man noch mit dem menschlichen Faktor machen? Wenn ein Operator der Cloud einen seltsamen oder potenziell gefĂ€hrlichen Befehl erteilt, könnte plötzlich verlangt werden, eine kleine Aufgabe zu lösen, um zu ĂŒberprĂŒfen, wie gut er ĂŒberlegt hat. Zum Beispiel, wenn es sich um einen massiven Stopp vieler Replikate handelt oder einfach um einen seltsamen Befehl â die Anzahl der Replikate zu reduzieren oder den Namen des Images zu Ă€ndern und nicht nur die Versionsnummer im neuen Manifest.
Ergebnisse
Einzigartige Merkmale von one-cloud:
- Eine hierarchische und ĂŒbersichtliche Benennungsskema fĂŒr Dienste und Container,, die es ermöglicht, sehr schnell zu erkennen, um welche Aufgabe es sich handelt, zu welchem Zweck sie dient, wie sie funktioniert und wer dafĂŒr verantwortlich ist.
- Wir verwenden unsere Techniken zur Kombination von Prod- und Batch-Aufgaben auf Minions, um die Effizienz der Maschinen gemeinsam zu nutzen, zu erhöhen. Statt cpuset verwenden wir CPU-Quoten, Shares, CPU-Planerpolitiken und Linux QoS.
- Es gelang uns nicht, die Container, die auf einer Maschine arbeiten, vollstÀndig zu isolieren, aber ihr gegenseitiger Einfluss bleibt auf 20 % beschrÀnkt.
- Die Organisation der Dienste in eine Hierarchie hilft bei der automatischen Beseitigung von NotfÀllen mithilfe von Platzierungs- und VerdrÀngungsprioritÀten..
FAQ
Warum haben wir keine fertige Lösung ĂŒbernommen?
- Verschiedene Klassen von Aufgabenisolierungen erfordern unterschiedliche Logik beim Deployment auf Minions. WĂ€hrend Prod-Aufgaben einfach durch Ressourcensicherung bereitgestellt werden können, mĂŒssen Batch- und Idle-Aufgaben unter BerĂŒcksichtigung der tatsĂ€chlichen Ressourcenauslastung auf den Maschinen-Minions platziert werden.
- Die Notwendigkeit, solche Ressourcen, die von Aufgaben verbraucht werden, zu berĂŒcksichtigen, wie:
- Netzwerkbandbreite;
- Arten und âSpindelnâ von Festplatten.
- Die Notwendigkeit, PrioritÀten der Dienste bei der Behebung von Störungen festzulegen, sowie die Rechte und Quoten von Teams auf Ressourcen, was durch hierarchische Warteschlangen in one-cloud gelöst wird.
- Die Notwendigkeit, menschliche Bezeichnungen fĂŒr Container zu haben, um die Reaktionszeiten auf Störungen und VorfĂ€lle zu verkĂŒrzen.
- Die Unmöglichkeit, Service Discovery gleichzeitig flĂ€chendeckend zu implementieren; die Notwendigkeit, lange Zeit mit Aufgaben zu koexistieren, die auf physischen Hosts platziert sind â dies wird durch âstatischeâ IP-Adressen, die Container folgen, gelöst, und infolgedessen die Notwendigkeit einer einzigartigen Integration in groĂe Netzwerkstrukturen.
All diese Funktionen wĂŒrden erhebliche Umgestaltungen bestehender Lösungen erfordern, und nach einer AbschĂ€tzung des Arbeitsaufwands haben wir festgestellt, dass wir unsere eigene Lösung mit ungefĂ€hr dem gleichen Aufwand entwickeln können. Doch unsere Lösung wird erheblich einfacher zu betreiben und weiterzuentwickeln sein â sie enthĂ€lt keine unnötigen Abstraktionen, die unnötige FunktionalitĂ€ten unterstĂŒtzen.
An diejenigen, die die letzten Zeilen lesen â danke fĂŒr Ihre Geduld und Aufmerksamkeit!
Quelle: habr.com
