Reboots

Ein Bug im libvirt-Paket von CentOS 6 hat zur Folge, dass wir in den nächsten Nächten leider 24 unserer Server rebooten müssen. Betroffen sind diese Server: alphard, avior, diphda, markab, mirfac, regulus, eltanin, enif, arcturus, alioth, alkaid, dubhe, peacock, sabic, suhail, bellatrix, acrux, betelgeuse, alnilam, alpheca, shaula, elnath, gienah und schedar. (Mist, schon wieder Appetit auf Käse.)

Hier sollen kurz die Gründe erläutert werden und bei der Gelegenheit mal ein genereller Überblick über das gegeben werden, was wir im Punkto Reboots in nächster Zeit vorhaben, denn wir planen Reboots in Zukunft deutlich häufiger durchzuführen als bisher und das ganze besser zu Kommunizieren.

unser Server-Setup

Die Server bei Uberspace sind mittlerweile allesamt virtuelle Maschinen. Früher haben wir noch Xen zur Virtualisierung eingesetzt, seit ein paar Jahren steigen wir aber auf KVM um und mittlerweile laufen alle Uberspace Server als Gäste auf KVM-Wirten. Einer der Gründe für die Virtualisierung ist die Abstraktion von der Hardware, das vereinfacht das Setup der Gäste erheblich und macht es leichter sie auf andere Hardware zu migrieren falls das nötig wird, weil im Gast bei so einer Migration keine neuen Treiber benötigt werden. Mit Migration ist hierbei nicht nur der Umzug auf neue Hardware gemeint, wenn wir alte Server ausmustern und die Gäste auf neuere umziehen, die mehr Leistung bringen, energieeffizienter sind, weiterhin Support vom Hersteller bekommen und meist mehr Gäste stemmen können als ihre Vorgänger, sondern auch die Migration des Gastes von einem seiner beiden Wirte auf den anderen im laufenden Betrieb. Das ist eigentlich ein Failover-Feature, wir nutzen das aber auch und deutlich häufiger dazu die Wirte unter den Gästen updaten und rebooten zu können.

Ein KVM-Wirt hat in diesem Setup meist eine gerade Anzahl von Gästen und zwei Partner, die meist über und unter ihm im Rack stehen und mit denen er jeweils über eine simple Crossover-Ethernet-Verbindung verbunden ist. Mit jedem Partner teilt ein Server eine Hälfte seiner Gäste. Die Gäste liegen auf verteilt-replizierten Block Devices (warum ich um den heißen Brei herum rede) auf je zwei Wirten und jeder Wirt kennt die Configs seiner Gäste, egal ob diese gerade auf ihm selbst oder einem seiner Partner laufen. Im seltenen Fall dass einer der Wirte ausfällt oder -- viel häufiger -- dass wir einen der Wirte rebooten wollen, müssen die beiden Partner dann je eine Hälfte der Gäste übernehmen und kurzzeitig mehr Last stemmen, worauf sie aber ausgelegt sind. Das Ganze machen wir nicht mit drei Servern, sondern mit ein paar Dutzend Server die wie in einer Kette aneinander gereiht sind. Bis auf die Kettenendglieder hat jeder Server zwei Partner.

Im Regelfäll können wir die Gäste im laufenden Betrieb von einem Wirt auf den anderen migrieren. Die beiden Wirte synchronisieren dafür über einen Zeitraum von mehreren Minuten den RAM-Inhalt des Gastes zwischen sich und sobald sie dabei annähernd 100% erreicht haben wird der Gast auf dem Wirt auf dem er bisher lief für ein paar Sekundenbruchteile eingefroren, der restliche RAM-Inhalt und nun auch der Zustand des Prozessors auf den Partner übertragen und dann wird der Gast auf dem neuen Wirt wieder aufgetaut. Wer derweil auf dem Gast eingeloggt ist oder sonstwie mit ihm interagiert, bekommt davon nichts mit, wenn überhaupt ruckelt es ganz kurz in der Verbindung, was aber nicht von andern Rucklern wie z.B. im Netzwerk zu unterscheiden ist.

Wir hatten uns diese Möglichkeit der Live-Migration früher lange selbst verbaut, da wir für bestimmte Dinge wie z.B. Swap oder /tmp aus Performance-Gründen keine verteilt-replizierten Block Devices nutzen wollten. Mittlerweile haben wir das aber alles abgebaut und haben jetzt durch die Bank weg virtuelle Maschinen die in dieser Art und Weise live-migriert werden können. (Dachten wir jedenfalls.) Wir haben das Erreichen dieses Ziels damals direkt mit einem koordinierten Durch-Rebooten aller KVM-Wirte gefeiert, die sich über frische Treiber, neue Kernel-Versionen und Firmware-Updates für Mainboards und RAID-Controller freuten. Alles in allem war das ein wichtiger Meilenstein für uns bei der Weiterentwicklung unseres Server-Setups von den bescheidenen Anfängen hin zu einem skalierbaren und leicht zu wartenden Setup.

die CPUs virtueller Maschinen

Wenn wir neue virtuelle Maschinen aufsetzen tun wir das mit mit dem Tool virt-install und nutzen dabei einen Parameter der dafür sorgt, dass der neue Gast vom KVM bzw. Qemu eine CPU emuliert bekommt, die von den Fähigkeiten her weitgehend der des Wirtes entspricht: --cpu host. Andernfalls würde den Gästen eine deutlich primitivere CPU vorgemacht und sie würden viele neuere Features der CPU nicht nutzen können. Dabei ist ein bißchen Vorsicht geboten, wenn wir neue KVM-Wirte anschaffen müssen wir darauf acht geben ob diese mit einer neueren CPU-Generation ausgeliefert werden und falls dem so ist müssen wir neue Gäste auf dem älteren ihrer beiden Wirte anlegen, damit sie eine CPU emuliert bekommen die auf beiden Wirten emuliert werden kann. Es ist kein großes Problem das in den Arbeitsablauf zu integrieren, es muss nur eben gemacht werden.

Irgendwann im letzten Jahr tauchte bei CentOS / RHEL 6 im Zusammenhang mit einem neuen CPU-Feature ein Bug auf. Das neue CPU-Feature betraf eine der (lahmeren) Methoden mit der Prozesse auf einer CPU das Verstreichen von Zeit in Relation zu den Taktzyklen der CPU messen können, im Grunde nur ein simpler Zähler namens Time Stamp Counter. Die meisten Programmier-Anleitungen raten von dieser Methode ab, aber einige Programme nutzen sie und so hat Intel dieser Funktion vor einer Weile ein Upgrade verpasst das als CPU-Feature den Namen invtsc für "Invariant Time Stamp Counter" bekam. Details erklärt die EN-hanced Wikipedia.

Dieses neue CPU-Feature ist vermutlich ganz nett (ich hab keine Ahnung), es hat aber einen Nachteil: Wenn diese Funktion gegenüber virtuellen Maschinen exponiert wird, dann wird es unmöglich diese durch eine Live-Migration umzuziehen (oder einzufrieren), da dabei der Zustand des Invariant Time Stamp Counter nicht übertragen werden kann und dies zu Problemen mit Programmen führen würde, die auf diesen Zähler schauen. Im Changelog von Qemu findet sich ein Eintrag dazu der nahelegt, dass Qemu von Hause aus das Richtige tut und diese Funktion nicht an Gäste durchreicht, außer wenn explizit dazu aufgefordert. (Das sähe dann wohl so aus: qemu-kvm -cpu host,migratable=no,+invtsc.) -- Wunderbar, bei uns käme niemals jemand auf die Idee so einen Parameter mitzugeben.

Bug 1138222

Warum erzähle ich das alles? Nun, weil als ich gestern ein virtuelle Maschine von einem Wirt auf den anderen live-migrieren wollte Folgendes passierte:

[root@tatiana ~]# virsh migrate --verbose --live --abort-on-error --timeout 600 acrux qemu+ssh://jill/system 
error: Requested operation is not valid: domain has CPU feature: invtsc  

(tatiana und jill sind die beiden KVM-Wirte, benannt nach Tatiana Romanova und Jill Masterton. Fans werden die Referenz verstehen.)

Meine erste Reaktion bei so einem Fehler ist ein mildes, eher neugieriges WTF und Stirnrunzeln gefolgt von einer Recherche mit der Suchmaschine des geringsten Misstrauens. Das förderte dann diesen Bugreport zu Tage. Offenbar war bei CentOS / RHEL 6 der Patch versäumt worden, der verhindert dass Gäste die invtsc-Funktion durchgereicht bekommen, außer wenn dies explizit verlangt wird. Der Fehler seitens libvirt bzw. virt-install ist, dass virt-install --cpu host eine XML-Config für den Gast generiert in der invtsc an den Gast durchgereicht wird, was nicht so sein sollte.

Kann ja mal passieren, betrifft bestimmt nur zwei, drei Gäste, CentOS und RHEL sind Enterprise-Distributionen und da wird sowas doch sicherlich schnell bemerkt und behoben… eine schnelle Suche über all unsere KVM-Wirte förderte zu Tage, dass satte 24 Gäste mit dieser CPU-Funktion laufen und somit derzeit nicht live-migriert werden können. -- An der Stelle hatte ich einen nicht ganz so kleinen Wutanfall und musste erstmal Klaus-Kinski-Videos gucken. Aber leider hilft in solchen Situationen kein sudo und kein Geschrei, also weiter im Text.

(Es gab auch einen Workaround für den Bug bei dem libvirt quasi für die invtsc-Funktion geblendet wird, so dass libvirt dieses CPU-Feature nicht mehr erkennt und nicht mehr an Gäste durchreicht. Das ist sicherlich ein Weg um zu verhindern, dass Gäste so gestartet werden, aber es hilft wenig bei Gästen die schon so gestartet wurden und genau das war über den Zeitraum von etwa einem Jahr bei unseren neuen Uberspace Servern ohne unser Wissen passiert, bevor der Bug endlich gefixt wurde.)

Ein simpler Neustart der Gäste wie bei einem Kernel-Update hilft leider nicht, die Gäste müssen auf den Wirten einmal richtig beendet und neu angelegt werden, damit die Änderung in der KVM-Config übernommen wird.

(Nebenbei habe ich direkt noch einen Icinga-Check gebaut der solche Gäste findet und dann warnt. Das war recht einfach, da bei KVM Gäste in der Prozessliste des Wirtes auftauchen und dort wenn dann das Argument invtsc ebenfalls auftaucht, z.B. so:

/usr/libexec/qemu-kvm -name acrux -S -M rhel6.6.0 -cpu SandyBridge,+invtsc,+erms,+smep,+fsgsbase,+pdpe1gb,+rdrand,+f16c,+osxsave,+dca,+pcid,+pdcm,+xtpr,+tm2,+est,+smx,+vmx,+ds_cpl,+monitor,+dtes64,+pbe,+tm,+ht,+ss,+acpi,+ds,+vme -enable-kvm -m 24576 -realtime mlock=off -smp 12,sockets=12,cores=1,threads=1 -uuid 4507aaf2-e0db-005c-e863-2016f2779ae8 -nographic -nodefconfig -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/acrux.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x4.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x4 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x4.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x4.0x2 -drive file=/dev/drbd/by-res/acrux.uberspace.de/0,if=none,id=drive-virtio-disk0,format=raw,cache=none,aio=native -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x5,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -netdev tap,fd=22,id=hostnet0,vhost=on,vhostfd=24 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:a7:2d:d8,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -device usb-tablet,id=input0 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x6 -msg timestamp=on

Das mag auf den ersten Blick schlimm aussehen, aber ein Programm kann sich da ganz gut durchwühlen. So ist jetzt auch sichergestellt, dass uns dies zumindest mit diesem CPU-Feature nicht nochmal passiert ohne bemerkt zu werden.)

hm, Reboots, da war ja was…

Das Thema Reboots ist eines das wir ohnehin schon länger mal neu aufrollen wollten. Aktuell fehlt noch die Zeit um das richtig zu tun, aber wir können als Trostpflaster zu den nun anstehenden Reboots schonmal einen Ausblick geben:

Früher haben wir Reboots eher vermieden, weil das mit Downtimes verbunden war und tendentiell mit viel Arbeit. In den letzten Jahren haben wir unsere Haltung dazu aber geändert, da es häufiger Sicherheitslücken gab die nur durch einen Reboot richtig geschlossen werden konnten, entweder weil eine neue Kernel-Version benötigt wurde oder nur so sichergestellt werden konnte, dass alte, verwundbare Versionen von Bibliotheken aus dem RAM verschwinden. Auch sind die Kernel-Entwickler unzuverlässiger geworden was die Beschreibung der gefixten Bugs angeht, es kam häufiger zu Situationen wo ein Update dringend empfohlen wurde, aber der Angriffsvektor der davon geschlossen wurde nicht so detailiert beschrieben wurde, dass eine Abschätzung ob dieser Vektor für unser Setup überhaupt relevant ist möglich gewesen wäre. Deswegen sind wird dazu übergegangen so viele Hürden die Reboots unattraktiv machen wie möglich abzubauen. Wir empfehlen unseren Usern eigene Dienste unter den daemontools zu betreiben und helfen auch das einzurichten, damit sie nach einem Reboot automatisch wieder gestartet werden können (und haben uns eine gesunde Herzlosigkeit angewöhnt bei Diensten die eben nicht unter den daemontoolslaufen). Wir haben Skripte geschrieben die nach kaputten daemontools-Konfigurationen suchen und die User darüber informieren. Nachdem wir feststellen mussten, dass einige User-Dienste beim Starten irrwitzige Dinge tun (z.B. den Sourcecode von nginx runterladen und kompilieren), haben wir Skripte geschrieben mit denen wir den Start der User-Dienste bei einem Reboot staffeln können, damit all diese Dinge nicht auf einmal passieren und den Server überlasten. Wir haben das Prozedere das RHEL / CentOS bei einem Reboot durchläuft analysiert und so gut es ging alles was zu Problemen und Verzögerungen führen kann identifiziert und wo möglich drumherum gearbeitet (bei Fscks und Quota-Checks bleibt uns leider keine Wahl). Wir arbeiten daran den Reboot-Vorgang vollständig zu automatisieren und so mit unserem Monitoring einzutakten, dass wir im unwahrscheinlichen Fall das doch etwas schief gehen sollte, schnell auf den Plan gerufen werden.

Worüber wir uns derzeit noch Gedanken machen ist, wie wir unsere User besser über anstehende Reboots informieren können. Ankündigungen per Twitter und hier im Blog haben sich nicht als optimal erwiesen da längst nicht alle User das mitbekommen. Außerdem skaliert es nicht. Bei mittlerweile über 100 Servern gehen einzelne Reboot-Ankündigungen dann im Rauschen unter. Wir denken daher darüber nach hierfür ein Skript zu schreiben das allen Usern eines Host eine Mail schickt (wahrscheinlich mit Opt-Out für diejenigen die das nicht interessiert). Außerdem überlegen wir für die einzelnen Hosts und kombiniert für alle Hosts RSS-Feeds mit Ankündigungen zu generieren, wofür aber noch eine geeignete Software gefunden (oder geschrieben) werden muss.

So oder so: Stellt Euch auf mehr Reboots ein, wir planen in Zukunft für jedes potentiell relevante Kernel-Update Reboots durchzuführen und selbst wenn die ausbleiben sollten jeden Uberspace Server mindestens einmal im Jahr zu rebooten, eher häufiger. Noch ist das alles aber nicht ganz fertig, unter anderem weil uns Knüppel wie der oben erwähnte Bug zwischen die Beine fallen. Jetzt erfolgen erstmal die Reboots der 24 oben erwähnten Server -- vielleicht zum letzten Mal auf die alte, gewohnte Art und Weise.