VLAN abhängiges Failover über mehrere Switche hinweg

Einige Kunden beschweren sich über erhöhte Latenz in den Hochlaststunden. Es betrifft ausschließlich Kunden, die per DHCP eine CGN-Adresse (100.64.0.0/10) bekommen haben. Irgendwie sind also die NAT Kisten am Ende.

Der Krux mit dem Interrupt

das Problem reduziert sich bei näherer Betrachtung auf eine Interrupt-Routine des FreeBSD-Kernels, die die Pakete entgegen nimmt und komplett verarbeitet. Dort wird alles gemacht, was ohne Wartezeit möglich ist, also auch die NAT Funktion.

Zuerst einmal ein Blick auf die Auslastung der CPU Kerne durch einzelne Threads: top -nCHSIzs1 Der Plot über die Zeit demonstriert das Problem deutlich.

lns-stats-threat-single

Die grüne Kurve ist der Interrupt-Handler eines einzelnen Interfaces (und dort von einer einzeln Empfangsqueue, der bis zu 100% seiner CPU auslastet. Ist der Punkt erreicht, wird es langsam oder es kommt gar zu Paketverlust.

Die Tatsache, daß der MPD selbst 100% CPU macht, liegt in der verwendeten Thread-Blibiothek vergraben und soll jetzt nicht Thema sein. Im Endeffekt handelt es sich um eine Art aktiver "idle Thread", was der vornehme Ausdruck für aktives Polling sein soll.

Die Lösung des NAT Problem muß also darin bestehen, mehr Interfaces und mehr Empfangsqueues zu aktivieren. Dann verteilt sich die Last auf mehr CPU Kerne.

Normalerweise ist das ganz einfach: Man bündelt die Interfaces mit LACP zu einem virtuellen Interface zusammen. Dann kommen auf verschiedenen Interfaces die Pakete an und können auf mehrere CPUs verteilt werden.

Ausfallszenarien

Unglücklicherweise soll die Plattform weiter laufen, auch wenn ein Port, ein ganze Baugruppe oder gar ein ganzer Switch ausfällt. Die vier Interfaces jedes Servers sind also so verkabelt, daß beim Ausfall einer (größeren) Komponente immer noch möglichst viel der Topologie übrig bleibt.

nat-server-an-zwei-switchen

Die em und die igb Karten liegen auf verschiedenen Steckplätzen im Server. Deswegen werden sie über Kreuz als Failover-Paare (lagg) definiert:

  • em0 und igb1 bilden ein lagg Failover-Paar für den Traffic vom und zum Kunden (VLAN100).
  • em1 und igb0 bilden ein lagg Failover-Paar für den Traffic vom und zum Internet (VLAN200).

Auf diese Weise wird jedes VLAN von jedem Switch zugeführt, ohne daß dabei die Switche von den anderen Querverbindungen wissen müßten. Im Normalfall wären also nur beispielsweise nur die em Interfaces aktiv. Jeder Switch hat also nur eine aktive Verbindung zu dem Server. Die MAC Adresse des Servers taucht insgesamt im Netz nur einmal auf, an dem Switch, wo das jeweilige Interface aktiv ist.

Es ist nun aber nicht möglich, die Interfaces zu LACP-Tunneln zusammen zu fassen.

  • Die Switche unterstützen kein gemeinsames Multichassis-LACP.
  • Auf dem LACP Tunnel zu einem Switch müssen immer beide VLANs aktiv sein, um den Ausfall des anderen Switches zu kompensieren.
  • FreeBSD unterstützt lagg Interfaces nur auf physikalischen Ethernets. Lagg-Stacking ist nicht möglich.

Genau genommen ist die Situation noch viel komplizierter. Es gibt VLANs, deren Uplink nur auf einem der beiden Switche anliegt, so daß es sinnlos wäre diese auf den anderen Switch zu aktivieren. Dann gibt es VLANs, die nur vorzugsweise auf einem der beiden Switche landen sollen (Managment), aber im Fehlerfall auch von dem anderen Switch übernommen werden müssen.

Was schön wäre, wäre also die Interfaces pro Switch per LACP zu aggregieren (mehr Interrupts). Auf diese Bündel müßten dann die VLANs gebunden werden (was problemlos geht). Und für einige VLANs soll ein Failover zwischen den Switchen aktiv sein.

Man müßte also ein lagg über VLAN-Interfaces erlauben. Glücklicherweise ist das aber recht einfach.

--- -   2014-07-18 17:21:12.580999119 +0200
+++ 8.3/sys/net/if_lagg.c       2014-07-07 11:08:06.000000000 +0200
@@ -513,8 +513,15 @@
                return (EBUSY);
 
        /* XXX Disallow non-ethernet interfaces (this should be any of 802) */
-       if (ifp->if_type != IFT_ETHER)
+       switch (ifp->if_type) {
+       case IFT_ETHER: case IFT_L2VLAN:
+               /* allowed, fall through */
+               break;
+       default:
+               printf("Unsupported lagg parent %s of type %x (sys/net/if_types.h)\n",
+                      ifp->if_xname, ifp->if_type);
                return (EPROTONOSUPPORT);
+       }
 
        /* Allow the first Ethernet member to define the MTU */
        if (SLIST_EMPTY(&sc->sc_ports))

Und plötzlich tut es!

Startprobleme

Was allerdings nicht ohne weiteres tut, ist der Reboot.

Hier gilt es jetzt sehr vorsichtig zu sein, um die Interfaces in der richtigen Reihenfolge anzulegen (wird durch cloned_interfaces festgelegt). Bereits beim Anlegen müssen die lagg Interfaces mit den darunter liegenden Interfaces verbunden werden. Andernfalls wird das Binden der VLAN Interfaces die spätere Festlegung der lagg-Parameter unterbinden. Die Verknüpfung der Interfaces untereinander muß also mit create_args_*  erfolgen, und alle anderen Eigenschaften können später perifconfig_* nachgeliefert werden.

Eine passende rc.conf könnte also so aussehen:

cloned_interfaces="lagg3 lagg4"
cloned_interfaces="${cloned_interfaces} vlan3044 vlan4044 lagg44"
cloned_interfaces="${cloned_interfaces} vlan3115 vlan4115 lagg115"
cloned_interfaces="${cloned_interfaces} vlan3140 vlan4140 lagg140"

# LACP per switch
create_args_lagg3="laggproto lacp laggport igb1 laggport em0"
ifconfig_lagg3="up"
create_args_lagg4="laggproto lacp laggport igb0 laggport em1"
ifconfig_lagg4="up"

# VLANs useful only on a single switch
create_args_vlan143="vlan 143 vlandev lagg3"
create_args_vlan144="vlan 144 vlandev lagg4"

# VLANs available on both switches (preference on first switch)
create_args_vlan3140="vlan 140 vlandev lagg3"
create_args_vlan4140="vlan 140 vlandev lagg4"
create_args_lagg140="laggproto failover laggport vlan4140 laggport vlan3140"
ifconfig_lagg140="up"

create_args_vlan3044="vlan 44 vlandev lagg3"
create_args_vlan4044="vlan 44 vlandev lagg4"
create_args_lagg44="laggproto failover laggport vlan3044 laggport vlan4044"
ifconfig_lagg44="up"

# VLANs available on both switches (preference on second switch)
create_args_vlan3115="vlan 115 vlandev lagg3"
create_args_vlan4115="vlan 115 vlandev lagg4"
create_args_lagg115="laggproto failover laggport vlan3115 laggport vlan4115"
ifconfig_lagg115="up"

Auf diese Weise kommen alle Interfaces automatisch korrekt hoch.

Wirkung

Und das Ergebnis? Das schaut super aus!

lns-stats-thread-lacp

Die CPU Last verteilt breit über vier Interrupt-Routinen. Diese nehmen sich kaum etwas, weil sie nicht mehr nach Aufgaben getrennt (Blick zum Kunden oder ins Internet) ihre Datenpakete erhalten. Stattdessen kommt alles irgendwie bunt gemischt rein und lastet die CPU Cores viel gleichmäßiger aus.

Problem gelöst!

Avatar
Lutz Donnerhacke 30/01/2017 2:58 pm
Und es ist im Kernel drin ...
https://github.com/freebsd/freebsd/commit/a23a95f981ff4d4789caf8a8349c8b017b1ac351
Avatar
Lutz Donnerhacke 16/01/2016 11:39 pm
Und tut mit 10.3-PRERELEASE immer noch.

Total 2 comments

Post a comment

Related content