Lizenzkostenfreier Virtualisierungscluster mit CentOS 6.2

Es gibt ein paar Techniken, die habe ich während meiner Zeit in der IT sehr zu schätzen gelernt - besonders solche, die mir das Leben leichter machen. Für diesen Blog-Eintrag relevant sind unter anderem:

  1. der Pacemaker-Cluster
  2. libvirt als einheitliche Schnittstelle zu Virtualisierungstechniken
  3. KVM als zuverlässige und im Kernel integrierte Virtualisierungstechnik
  4. der Device Mapper im allgemeinen und Multipathing im speziellen
  5. der Cluster LVM sowie der Cluster Manager CMAN von RedHat

Seit der Version 6 von RHEL (RedHat Enterprise Linux) ist Pacemaker in der Distribution als Tech-Preview enthalten. Und während man auch früher schon über Repositories von Dritten sehr leicht eine Umgebung aus Corosync (also Kommunikationsschicht) und Pacemaker aufbauen konnte, so ist die wirklich große Neuerung in dieser Version die Integration von Pacemaker in den bestehenden CMAN-Stack. Diese Integration ermöglicht es nämlich endlich, Dinge wie geclustertes LVM oder GFS2 zu nutzen (die beide auf den DLM, den “Distributed Lock Manager” angewiesen sind), ohne auch nur ein Stück Zusatzsoftware von dritter Seite aus beschaffen zu müssen. In diesem Eintrag möchte ich mal zeigen, wie einfach es im Prinzip geht, einen Cluster aufzubauen, den man dann z.B. benutzen kann, um unwichtige Systeme zu virtualisieren.

Das Prinzip soll dabei so KISS wie möglich bleiben:

  • Jede virtuelle Maschine bekommt einen eindeutigen Namen.
  • Jeder Maschine wird als “Systemplatte” mindestens ein eigenes LV zugewiesen (die Maschine brownout05 bekommt das LV kvm_brownout05).

Die eine Sache, auf die ich nicht genauer eingehen werde, ist die Einrichtung des Resource Agents “VirtualDomain” - dazu gibt’s Wiki-Dokumentation. Ich will hier wirklich nur über die Einrichtung des Clusters sprechen.

Voraussetzungen

  • zwei Server, CentOS 6.2, mit Namen magma1 und magma2, mit jeweils vier Ethernet-Ports und zwei FC-Ports
  • die vier Ethernet-Ports wurden via Bonding zu zwei redundanten Interfaces kombiniert (bond0 und bond1)
  • die physikalischen Anschlüsse des Interfaces bond0 sind an zwei verschiedene Switches angeschlossen und dienen der Kommunikation mit dem Rest der Welt
  • die beiden Maschinen können sich gegenseitig unter den Namen magma1 und magma2 anpingen
  • für KVM/libvirt wurde eine Bridge eingerichtet, die es erlaubt, virtuelle Maschinen in allen Netzwerken einzurichten, die die Maschine bedient (read: 802.1q beachten!)
  • die physikalischen Anschlüsse des Interfaces bond1 sind an zwei miteinander verbundene Switches angeschlossen und dienen als dedizierter(!) Heartbeat-Link
  • die beiden Maschinen können sich via bond1 gegenseitig unter den Namen magma1-hb und magma2-hb anpingen
  • es steht eine externe Storage mit zwei Fibre-Channel-Controllern mit je zwei Ports zur Verfügung
  • die Controller der Storage wurden redundant mit zwei SAN-Switchen verbunden
  • die FC-Anschlüsse der Server wurden ebenfalls redundant mit den SAN-Komponenten verbunden
  • die /etc/multipath.conf ist so eingerichtet, daß mindestens eine LUN auf dem Linux-System verfügbar ist, z.B. als /dev/mpath/msa1_lun1
  • die /etc/lvm.conf wurde so eingerichtet, daß Volume Groups (VGs) nur auf den Multipath-Devices, nicht den “darunter” liegenden SCSI-Devices gesucht werden

Ein erfahrener Admin dürfte für die Konfiguration dieses Setups (ohne HW-Einbau) wohl kaum länger als einen halben Tag benötigen.

Grundkonfiguration des Clusters:

Der erste Schritt stellt auf beiden Knoten die Installation der benötigten Software dar:

yum install lvm2-cluster cman pacemaker
for i in pacemaker corosync clvmd cman; do service $i stop; chkconfig $i off; done

Der erste Schritt ist das einrichten von CMAN. Als Kommunikationsschicht verwendet dieser Corosync/TOTEM, und wir wollen hier auch gleich ein Corosync-Feature verwenden, das die Zuverlässigkeit weiter erhöhen soll: Die Kommunikation zwischen den beiden Knoten soll zwar primär über den HB-Switch erfolgen, wenn dieser jedoch wegfällt ist das Ziel, einen redundanten Kommunikationsring zu etablieren, der die Netzwerkstrecke benutzt, über die die beiden Maschinen auch mit der Aussenwelt kommunizieren. Und ferner wollen wir sicher gehen, daß CMAN auch startet, wenn kein Quorum vorhanden ist - bei einem Cluster mit nur zwei Knoten ist das ja immer so eine Sache. Um diese Anforderungen abzudecken legen wir eine erste Version der Datei /etc/cluster/cluster.conf an:

<cluster name="magma" config_version="2">
  <cman two_node="1" expected_votes="1"/>
  <clusternodes>
    <clusternode name="magma1-hb" nodeid="1">
      <altname name="magma1" port="5406" mcast="239.192.122.46"/>
    </clusternode>
    <clusternode name="magma2-hb" nodeid="2">
      <altname name="magma2" port="5406" mcast="239.192.122.46"/>
    </clusternode>
  </clusternodes>
  <dlm protocol="sctp"/>
  <totem rrp_mode="passive"/>
</cluster>

Was machen wir da nun genau?

  • In der ersten Zeile legen wir den Namen des Clusters fest. Die Option config_version ist eine manuelle Versionierung der Konfiguration, die sollten (müssen) wir bei jeder Änderung erhöhen.
  • In der zweiten Zeile definieren wir, daß der Cluster handlungsfähig sein soll, auch wenn er kein Quorum hat.
  • In den clusternodes-Blöcken legen wir die Mitglieder des Clusters fest. Wir verwenden für den Namen die HB-Interface, da diese den primären Kommunikationsring bilden sollen.
  • Mit der altname-Anweisung definieren wir eine zweite Multicast-Gruppe auf einem dedizierten Port und über ein anderen Interface (wir verwenden hier den Namen, unter dem der Server auch extern erreichbar ist).
  • Wir setzen das DLM-Protokoll auf sctp, da TCP mit mehr als einem Kommunikationsring nicht funktioniert.
  • Mit der totem-Zeile legen wir fest, daß der sekundäre Ring als Failover dienen soll.

Hinweis: Viele “Security”-Guidelines empfehlen, das SCTP-Protokoll zu deaktivieren bzw. das Laden des Kernel-Moduls sctp zu verhindern. Hat man ein zentrales Konfigurationsmanagement wie Puppet, so tut man gut daran, hier eine Ausnahme einzurichten. Anbieten würde sich z.B. ein facter-Plugin, daß auf das Vorhandensein der /etc/cluster/cluster.conf prüft, das kann man dann recht leicht auswerten.

Jetzt ist der Zeitpunkt gekommen, die Datei cluster.conf auf beide Cluster-Knoten zu verteilen und den CMAN dann via service cman start zu starten. Danach überprüft man, ob sich beide Cluster-Knoten sehen und ob die Kommunikation beide Interfaces benutzt:

# cman_tool nodes

Node  Sts   Inc   Joined               Name
   1   M     12   2012-03-24 13:10:50  magma-hb1
   2   M     20   2012-03-24 13:10:53  magma-hb2

# corosync-cfgtool -s
Printing ring status.
Local node ID 1
RING ID 0
        id      = 10.0.0.1
        status  = ring 0 active with no faults
RING ID 1
        id      = 192.168.11.6
        status  = ring 1 active with no faults

Die IP-Adressen werden sich natürlich unterscheiden, aber das Prinzip sollte klar sein. Jetzt ist der Moment gekommen, um zu überprüfen, wie redundant die Ringe wirklich sind. Man sabotiert also z.B. eines der HB-Interfaces (“ifdown bond1” kann das schnell erledigen), wartet ein paar Sekunden und überprüft dann erneut den Status der Ringe, um im zweiten Schritt die automatische Wiederherstellung zu testen:

# ifdown bond1
# grep FAULTY /var/log/messages  | tail -1
Mar 24 13:13:54 magma1 corosync[3087]:   [TOTEM ] Marking ringid 0 interface 10.0.0.1 FAULTY
# corosync-cfgtool -s
Printing ring status.
Local node ID 1
RING ID 0
        id      = 10.0.0.1
        status  = Marking ringid 0 interface 10.0.0.1 FAULTY
RING ID 1
        id      = 192.168.11.6
        status  = ring 1 active with no faults
# cman_tool nodes
Node  Sts   Inc   Joined               Name
   1   M     12   2012-03-24 13:10:50  magma1
   2   M     20   2012-03-24 13:10:53  magma2
# ifup bond1
# grep "recovered" /var/log/messages | tail -1

Damit haben wir den unerfreulichen CMAN-Teil fast geschafft, es fehlt nur noch eine Sache: Wir benötigen noch einen Mechanismus, mit dem sich die beiden Cluster-Knoten bei Bedarf gegenseitig “in einen definierten Zustand” versetzen können, sprich, wir brauchen STONITH. Warum wir das brauchen, kann man in diesem Comic am Beispiel einer Zombie-Apokalypse sehr schön sehen. Und da wir mit CMAN eigentlich nicht allzuviel zu tun haben wollen, verwenden wir hier einen Trick: Wir reichen die Fencing-Requests einfach an Pacemaker weiter. Die vorläufig finale Version der “cluster.conf” sieht dann so aus:

cluster name="magma" config_version="3">
  <cman two_node="1" expected_votes="1"/>
  <clusternodes>
    <clusternode name="magma1-hb" nodeid="1">
      <altname name="magma1" port="5406" mcast="239.192.122.46"/>
      <fence>
        <method name="pcmk-redirect">
          <device name="pcmk" port="magma1-hb"/>
        </method>
      </fence>
    </clusternode>
    <clusternode name="magma2-hb" nodeid="2">
      <altname name="magma2" port="5406" mcast="239.192.122.46"/>
      <fence>
        <method name="pcmk-redirect">
          <device name="pcmk" port="magma2-hb"/>
        </method>
      </fence>
    </clusternode>
  </clusternodes>
  <dlm protocol="sctp"/>
  <totem rrp_mode="passive"/>
  <fencedevices>
    <fencedevice name="pcmk" agent="fence_pcmk"/>
  </fencedevices>
</cluster>

Nachdem wir die Konfiguration auf beide Knoten verteilt haben, können wir CMAN ohne Neustart dazu bewegen, die Konfiguration neu einzulesen (man achte auf die erhöhte config_version-Nummer):

cman_tool version -r -S

Falls das wider Erwarten nicht klappt: Restart, Reboot… Und damit wären wir dann auch mit CMAN fertig.

Pacemaker-Grundkonfiguration

Tech-Preview sei Dank ist die Einrichtung von Pacemaker relativ trivial geworden. Nach dem Start auf beiden Knoten mit “service pacemaker start” und bis zu 30 Sekunden warten überprüfen wir den Status des Clusters:

# crm_mon -1rf

============
Last updated: Sat Mar 24 13:50:44 2012
Last change: Sat Mar 24 13:37:13 2012 via cibadmin on magma2-hb
Stack: cman
Current DC: magma1-hb - partition with quorum
Version: 1.1.6-3.el6-a02c0f19a00c1eb2527ad38f146ebc0834814558
2 Nodes configured, unknown expected votes
0 Resources configured.
============

Online: [ magma1-hb magma2-hb ]

Full list of resources:


Migration summary:
* Node magma2-hb:
* Node magma1-hb:

Wir müssen nun als erstes festlegen, daß der Cluster auch dann weiter funktionieren soll, wenn er kein Quorum hat (sind ja nur zwei Knoten) und wir müssen STONITH konfigurieren. Ersteres ist trivial:

crm configure property no-quorum-policy="ignore"

Um ein geeignetes STONITH-Medium zu finden ist es hilfreich, sich zunächst einen Überblick über alle vorhandenen Agents zu verschaffen:

stonith_admin --list-installed

Hat meinen einen Agent gefunden, der vielversprechend aussieht, so kann man sich die Detail-Informationen dazu anzeigen lassen. Für den Agent “fence_ilo_mp” (iLO ist die “Fernwartung” von HP) .z.B:

stonith_admin --metadata --agent fence_ilo_mp

Die Fence-Devices werden jetzt in Pacemaker via der “crm”-Shell eingerichtet. Gehen wir mal davon aus, daß die iLOs via SSH angesprochen werden sollen und daß sie unter “magma1-ilo” und “magma2-ilo” erreichbar sind. Dann sieht das folgendermaßen aus:

crm(live)configure# primitive fence-magma1 stonith:fence_ilo_mp params ipaddr="magma1-ilo" \
> login="fence" passwd="secret" pcmk_host_list="magma1-hb" \
> pcmk_host_check="static-list" action="reboot"
WARNING: fence-magma1: action start not advertised in meta-data, it may not be supported by the RA
WARNING: fence-magma1: action stop not advertised in meta-data, it may not be supported by the RA
crm(live)configure# primitive fence-magma2 stonith:fence_ilo_mp params ipaddr="magma2-ilo" \
> login="fence" passwd="secret" pcmk_host_list="magma2-hb" \
> pcmk_host_check="static-list" action="reboot"
WARNING: fence-magma2: action start not advertised in meta-data, it may not be supported by the RA
WARNING: fence-magma2: action stop not advertised in meta-data, it may not be supported by the RA
crm(live)configure# location loc-fence-magma1 fence-magma1 -inf: magma1-hb
crm(live)configure# location loc-fence-magma2 fence-magma1 -inf: magma2-hb
crm(live)configure# commit
crm(live)configure# commit
WARNING: Resources fence-magma1,fence-magma2 violate uniqueness for parameter "action": "reboot"
WARNING: Resources fence-magma1,fence-magma2 violate uniqueness for parameter "login": "fence"
WARNING: Resources fence-magma1,fence-magma2 violate uniqueness for parameter "passwd": "secret"
WARNING: fence-magma1: action start not advertised in meta-data, it may not be supported by the RA
WARNING: fence-magma1: action stop not advertised in meta-data, it may not be supported by the RA
WARNING: fence-magma2: action start not advertised in meta-data, it may not be supported by the RA
WARNING: fence-magma2: action stop not advertised in meta-data, it may not be supported by the RA
Do you still want to commit? y

Wir haben jetzt also zwei STONITH-Devices implementiert und mit den Location-Constraints dafür gesorgt, daß der Agent, der magma1-hb abschießt, auf keinen Fall auf magma1-hb (man beachte die Node-Namen in der pcmk_host_list - da muß der richtige Name rein, also der mit “-hb” dran…) selbst läuft. Damit sind wir durch - der Cluster ist einsatzbereit, wir können jederzeit logische Volumes erzeugen und diese dann beliebigen virtuellen Maschinen zuordnen. Dafür benutzen wir einfach den VirtualDomain-Agent und kleben ein bißchen Shell-Kleister ausßenrum - das sei aber jedem selbst zur Übung überlassen. Natürlich ist hier noch nicht Schluß, man kann z.B. noch die Verschlüsselung des Traffics aktivieren, aber für ein paar erste Schritte reicht das Konzept dicke. Derzeit sollte das ganze so aussehen:

# crm_mon -1rf
============
Last updated: Sat Mar 24 14:39:51 2012
Last change: Sat Mar 24 14:39:36 2012 via cibadmin on magma1-hb
Stack: cman
Current DC: magma1-hb - partition with quorum
Version: 1.1.6-3.el6-a02c0f19a00c1eb2527ad38f146ebc0834814558
2 Nodes configured, unknown expected votes
2 Resources configured.
============

Online: [ magma1-hb magma2-hb ]

Full list of resources:

 fence-magma1   (stonith:fence_ilo_mp): Started magma2-hb
 fence-magma2   (stonith:fence_ilo_mp): Started magma1-hb

Migration summary:
* Node magma2-hb:
* Node magma1-hb:

Hinweis: Dieser Artikel einhält eine Reihe von Tippfehlern, die es unmöglich machen, den Code wie abgebildet einfach zu pasten. Ebenso ist in der Konfiguration ein kleiner Denkfehler enthalten. Das ist Absicht - Cluster sind eine ernste Sache und jeder, der sich für einen verantwortlich zeigt, sollte gezwungen sein, seinen eigenen Hirnschmalz einzusetzen!

Ich höre mir jetzt von Chopin die “Prelude in Cis-Dur, op. 45” an, mit Vladimir Ashkenazy am Flügel… und glaubt mir, da freue ich mich richtig drauf! Schönes Wochenende!