CONAN CPUs
« | 24 Jun 2023 | »Buildsysteme wie CMake,
XMake oder CONAN
sorgen unter anderem auch dafür, dass die Builds so schnell wie möglich
durchlaufen.
Und auf unseren heutigen Multikern-CPUs bedeutet das, dass sie so viele
CPU Kerne wie möglich parallel einsetzen.
Problematisch wird es nur, wenn das eben “zu viel” ist.
Neulich in der Firma …
Plötzlich schlägt eine Vielzahl an Builds fehl. Der MSVC meldet
Internal compiler errors
und der GCC schreibt:
Out of memory: Kill process (cc1plus)
auf die Konsole.
Die Meldung kannte ich, sie deutete darauf hin, dass ein Compilerprozess vom System terminiert wurde und dahinter steckt oft fehlender RAM (bzw. fehlender virtueller Speicher im Allgemeinen).
Bei der Meldung des Problems an die Buildserver Admins kam zurück, dass wir zu viele Kerne parallel nutzen und das herunter limitieren sollen.
Und das hat mir dann die Woche versaut …
Rechenformeln
Für mich zählte die alte Formeln: RAM = CPU-cores * 2GB
(Das absolute Minimum wäre RAM = CPU-cores * 1GB
)
Wird sie eingehalten, kann jeder Kern einen Compiler-Prozess erhalten und
parallel daran werkeln.
Beim Raspberry PI 2 mit 4 Kernen und 1 GB wird die Regel verletzt, weshalb mir dort öfter der Build abbricht. Vor allem wenn Monster wie BOOST oder SQLite gebaut werden sollen.
Von einem offiziellen Buildserver in der Cloud erwarte ich allerdings, dass diese einfachen Regeln eingehalten werden, denn unser benutztes CONAN ermittelt die Anzahl der CPUs und startet dann eben genau so viele Prozesse wie möglich.
Nun wurde uns aber mitgeteilt, dass im Hintergrund Kubernetes für die Ressourcenverwaltung eingesetzt wird und dieses unsere Dockersitzungen verwaltet. Hier wird dann im virtualisierten Container die reale Anzahl an Kernen des Hosts ausgegeben, nur der nutzbare RAM wurde auf 16 GB reduziert. Das bedeutet laut Formel, dass man nur 8 Kerne und damit 8 Compilerjobs parallel nutzen sollte, CONAN ermittelte jedoch 32 Kerne und überforderte somit das System.
Workarounds als Lösung
Nachdem mir mitgeteilt wurde, dass es nicht möglich sei die aufgezählten CPU-Kerne im virtualisierten OS herunterzusetzen, blieb mir nur der Ausweg eine Methode zu finden, wie man CONAN dazu bringt weniger parallele Jobs zu starten.
Zum Glück wurde mit CONAN_CPU_COUNT
bereits eine Umgebungsvariable
eingeführt, mit der man exakt definieren kann, wie viele CPUs und damit
parallele Jobs ausgeführt werden sollen.
Alternativ kann man auch in conanfile.py
beeinflussen, damit nicht
automatisch ein -j N
an CMake geschickt wird, und anschließend lässt sich
ein zusätzlicher Kommandozeilenparameter an CMake
senden, der die
gewünschte Anzahl an Jobs erzeugt.
Das sieht dann etwa so aus:
Hartkodierte Limits sind falsch!
Jetzt kann man zwar mit CONAN_CPU_COUNT=8
das Limit hart setzen, womit der
Build auf genau diesem Server läuft. Soll der Build aber auch auf anderen
Servern laufen, kommt es dort sofort zu Problemen, wenn dieser mit weniger
CPUs und RAM ausgestattet ist. Und genau das passierte auch bei uns.
Am Ende wurde eine neue Variable auf den Buildserven etabliert, die die
maximal nutzbare Anzahl an CPUs definiert. Die Idee war, dass wir diese
Variable dann im CI-Script an CONAN_CPU_COUNT
zuweisen.
Auch dieser Schritt führte wieder ein Problem ein, nämlich dass
CONAN den Dienst verweigerte, wenn diese externe Variable nicht definiert
war. Dann wurde nämlich CONAN_CPU_COUNT
auf einen Leerstring gesetzt,
was zum Abbruch wegen einer Fehlkonfiguration führte.
Und der wahre Hintergrund war:
Tatsächlich war das Problem auf die Umstellung von CGROUPS v1 auf CGROUPS v2
zurückzuführen, die auf den Kubernetes Servern durchgeführt wurde.
Und wir nutzen die CONAN Version 1.50, welche nur CGROUPS v1 unterstützt.
Das Geheimnis liegt in cpu.py
, welches bis v1.50 die beiden Pfade
/sys/fs/cgroup/cpu/cpu.cfs_quota_us
und
/sys/fs/cgroup/cpu/cpu.cfs_period_us
auslas und das CPU-Limit daraus
errechnete, was nur unter CGROUPS v1 funktioniert.
Neuere Versionen von CONAN testen zuerst die CGROUPS v2 Pfade
/sys/fs/cgroup/cgroup.controllers
und
/sys/fs/cgroup/cpu.max
bevor sie auf die beiden V1 Pfade zurückgreifen.
Tja … nur leider lässt sich das Build-Image erst wieder mit dem nächsten größeren Software-Release auf CONAN 1.51+ upgraden.
Somit bleibt leider vorerst nur der hässliche Workaround mit
CONAN_CPU_COUNT
.
Fazit
Ich kotz’ gleich im Strahl …
Buildsysteme sollen ihre Umgebung korrekt erkennen können und sich darauf best-möglich einrichten. Und Hostsysteme sollen ihren Containern von außen nur die Ressourcen anzeigen, die sie nutzen dürfen.
Ich verstehe nicht, warum mit CGROUPS v2 die Kompatibilität der sysfs
Pfade
gebrochen wird, und ich verstehe auch nicht, warum Docker überhaupt erst die
reale CPU-Anzahl an Prozesse im Container zurückliefert.
In jedem Fall hat diese unnötige Panne unzählige Stunden gekostet und wird in Zukunft noch weitere Zeit einnehmen.
Ich für meinen Teil habe daraus allerdings gelernt, dass Docker ganz
unschöne Seiteneffekte produzieren kann, denn auch meine Codes würden nicht
im sysfs
nach Dateien suchen, wenn sie die CPU-Anzahl wissen wollen.
Und ganz nebenbei:
Auf meinem Azure Buildserver läuft eine VM mit einer CPU und 512 MB RAM. Eigentlich würden darauf alle Builds fehlschlagen, doch ich habe dort den Swapspace aktiviert. Und schon führt die Verletzung der obigen Formel nicht mehr zum Crash, es dauert nur länger, wenn die Platte rattert.
Soll heißen: Man kann die CPU/RAM/Job Überlastung auch noch anders abfangen.