2. HAProxy konfigurieren
2.1. Format der Konfigurationsdatei
Die Konfiguration von HAProxy bezieht Parameter aus 3 wesentlichen Quellen:
- den Befehlszeilenargumenten, die immer Vorrang haben
- den Konfigurationsdateien, deren Format hier beschrieben wird
- der Umgebung des laufenden Prozesses, sofern bestimmte Umgebungsvariablen ausdrücklich referenziert werden
Die Konfigurationsdatei verwendet ein recht einfaches hierarchisches Format mit wenigen Grundregeln:
Mehr ist zum Schreiben eines einfachen, aber zuverlässigen Konfigurationsgenerators nicht nötig. Um jede beliebige Konfiguration zuverlässig auszuwerten oder bestimmte Grenzfälle zu behandeln, reicht dies jedoch nicht aus.
Aus den obigen Regeln ergeben sich zunächst einige Folgerungen. Nach Regel 6 und 7 gelten die Schlüsselwörter zum Definieren eines neuen Abschnitts überall und können in keinem bestimmten Abschnitt eine andere Bedeutung haben. Diese Schlüsselwörter bestehen stets aus einem einzelnen Wort, nicht aus einer Wortfolge. Der nachfolgende Abschnitt wird gewöhnlich mit demselben Namen bezeichnet. So meint „globaler Abschnitt“ den Konfigurationsabschnitt nach dem Schlüsselwort “global”. Diese Bezeichnung wird in Fehlermeldungen häufig verwendet, um die zu bearbeitenden Stellen leichter auffindbar zu machen.
Mehrere Abschnittstypen erzeugen ein internes Objekt oder einen Konfigurationsbereich, der von anderen unterscheidbar sein muss. Dafür nehmen sie ein zusätzliches Wort entgegen, das den Namen dieses Abschnitts festlegt. Bei einigen ist der Abschnittsname zwingend erforderlich. So erzeugt “frontend foo” einen neuen Abschnitt des Typs “frontend” mit dem Namen “foo”. Ein Name gilt gewöhnlich nur für seinen Abschnittstyp; zwei Abschnitte unterschiedlichen Typs können denselben Namen tragen. Davon wird jedoch abgeraten, da es die Konfigurationsverwaltung erschwert.
Aus Regel 7 folgt unmittelbar, dass beim Einlesen mehrerer Dateien jede davon mit einem neuen Abschnitt beginnen muss und jedes Dateiende einen Abschnitt beendet. Eine Datei kann weder Unterabschnitte enthalten noch einen bestehenden Abschnitt beenden und einen neuen beginnen.
Regel 1 besagt, dass die Reihenfolge wichtig ist. Manche Schlüsselwörter definieren Direktiven, die mehrfach wiederholt werden können, um eine geordnete Folge von Regeln zu bilden. Beispielsweise können mit “tcp-request” Regeln für “accept” und “reject” anhand verschiedener Kriterien abwechselnd angewendet werden. Ein Werkzeug zur Bearbeitung von Konfigurationsdateien muss deshalb beim Ändern einer Datei stets die Reihenfolge innerhalb eines Abschnitts erhalten. Die Reihenfolge der Abschnitte ist gewöhnlich unerheblich. Eine Ausnahme bildet der globale Abschnitt: Er muss vor den anderen stehen, kann aber bei Bedarf wiederholt werden. Außerdem können bestimmten erzeugten Objekten wie Proxys automatisch Kennungen zugewiesen werden, die sich beim Umordnen der Abschnitte ändern. Sie erscheinen beispielsweise in der Statistik. In der folgenden Konfiguration erhält “foo” daher eine kleinere ID als “bar”. Werden die beiden Abschnitte vertauscht, kehrt sich dies um:
Ein weiterer wichtiger Punkt: Nach den Regeln 2 und 3 gehören Leerzeilen, Leerzeichen, Tabulatoren und Kommentare nach einem ungeschützten “#” nicht zur Konfiguration, da sie nur als Begrenzungen dienen. Die folgenden Konfigurationen sind daher exakt gleichwertig:
und:
Üblicherweise wird nur das Schlüsselwort, das einen neuen Abschnitt beginnt, linksbündig angeordnet. Alle anderen Schlüsselwörter werden durch einen Tabulator oder einige vorangestellte Leerzeichen eingerückt. So ist sofort erkennbar, dass sie zu demselben Abschnitt gehören, wie im zweiten Beispiel oben. Kommentare vor einem neuen Abschnitt helfen beim Erkennen des gesuchten Abschnitts. Eine Leerzeile am Abschnittsende erleichtert es ebenfalls, beim Bearbeiten dessen Ende zu finden.
Tabulatoren eignen sich gut zum Einrücken, lassen sich aber schlecht kopieren und einfügen. Werden stattdessen Leerzeichen verwendet, werden nur wenige (2 bis 4) empfohlen. So bleibt die Bearbeitung vor Ort auch mit einfachen Editoren ohne automatische Einrückung handhabbar.
Früher wurden Argumente häufig an festen Tabulatorpositionen ausgerichtet, da die meisten Schlüsselwörter höchstens zwei Argumente annahmen. Für moderne Versionen mit komplexen Ausdrücken eignet sich dies nicht mehr und wird nicht empfohlen.
2.2. Anführungszeichen und Maskierung
In modernen Konfigurationen benötigen manche Argumente Zeichen, die zuvor ausschließlich als Trennzeichen dienten. HAProxy unterstützt deshalb die Maskierung eines Zeichens mit einem vorangestellten Backslash (’\’), schwaches Quoting mit doppelten Anführungszeichen ("") um einen Textabschnitt und starkes Quoting mit einfachen Anführungszeichen (’’) um einen Textabschnitt.
Dies ähnelt dem Vorgehen in verschiedenen Programmiersprachen und besonders dem üblichen Verhalten der Bourne-Shell. Beim Zerlegen der Zeilen in Wörter berücksichtigt der Konfigurationsparser auch Anführungszeichen und Backslashes. So entscheidet er, ob ein Zeichen als Trennzeichen oder als wörtliches Zeichen innerhalb des aktuellen Wortes gilt. Anschließend werden das Maskierungszeichen und die Anführungszeichen entfernt; das verbleibende Wort wird unverändert etwa als Schlüsselwort oder Argument verwendet.
Wird innerhalb eines Wortes ein Backslash benötigt, muss er entweder mit sich selbst maskiert werden, also durch einen doppelten Backslash, oder in einfache Anführungszeichen gesetzt werden.
Außerhalb von Anführungszeichen wird ein Sonderzeichen durch einen vorangestellten Backslash (’\’) maskiert:
Zusätzlich lassen sich einige nicht druckbare Zeichen mit der üblichen C-Schreibweise einfügen:
Für schwaches Quoting wird das zu schützende Zeichen oder die Zeichenfolge in doppelte Anführungszeichen ("") gesetzt. Dadurch werden folgende Interpretationen verhindert:
Schwaches Quoting ermöglicht die Auswertung von Umgebungsvariablen, denen ein Dollarzeichen (’$’) vorangestellt wird. Außerhalb von Anführungszeichen werden diese nicht ausgewertet. Wird innerhalb doppelter Anführungszeichen ein Dollarzeichen als solches benötigt, muss es mit einem Backslash maskiert werden.
Für starkes Quoting wird das zu schützende Zeichen oder die Zeichenfolge in einfache Anführungszeichen (’’) gesetzt. Darin wird nichts interpretiert; dies ist eine wirksame Methode, reguläre Ausdrücke zu schützen.
Die folgende Tabelle zeigt daraus abgeleitet, wie Sonderzeichen in verschiedenen Kontexten eingegeben werden können. Nicht druckbare Zeichen sind durch ihren Namen in spitzen Klammern ersetzt. Manche Zeichen lassen sich nur maskiert darstellen und besitzen innerhalb einfacher Anführungszeichen keine mögliche Darstellung; deshalb fehlen sie dort:
Beispiel:
In einem besonderen Fall kann eine zweite Ebene von Anführungszeichen oder Maskierung nötig sein. Manche Schlüsselwörter nehmen Argumente in runden Klammern entgegen, teilweise durch Kommas getrennt. Meist sind dies Ganzzahlen oder vordefinierte Wörter. Bei beliebigen Zeichenketten kann jedoch eine weitere Maskierungsebene erforderlich sein, um Zeichen des Arguments von dessen Begrenzungszeichen zu unterscheiden. Ein häufiges Beispiel ist der Konverter “regsub”. Er nimmt einen regulären Ausdruck als Argument entgegen. Wird darin eine schließende runde Klammer benötigt, muss sie eigene Anführungszeichen erhalten.
Für Anführungszeichen verhält sich der Parser für Schlüsselwortargumente genauso wie der Parser auf oberster Ebene. Lediglich die Escape-Sequenzen \#, \$ und \xNN werden nicht verarbeitet. Weniger offensichtlich ist, dass die innen verwendeten Begrenzungszeichen zunächst maskiert oder in Anführungszeichen gesetzt werden müssen, damit sie nicht bereits auf der obersten Ebene aufgelöst werden.
Das folgende Beispiel verwendet den Konverter “regsub” mit 3 Argumenten: einem regulären Ausdruck, einer Ersetzungszeichenkette und einer Menge von Flags:
Hier waren keine besonderen Anführungszeichen nötig. Soll nun aber sowohl “foo” als auch “bar” durch “blah” ersetzt werden, wird der reguläre Ausdruck “(foo|bar)” benötigt. Folgendes ist nicht möglich:
Denn die Zeichenkette soll so aufgeteilt werden:
Tatsächlich wird jedoch eine Zeichenkette zwischen öffnender und schließender Klammer übergeben, gefolgt von ungültigem Resttext:
Es scheint naheliegend, die schließende Klammer in Anführungszeichen zu setzen. Allein genügt dies aber nicht: Wie oben beschrieben, verarbeitet der Parser auf oberster Ebene die Anführungszeichen und löst sie vor der Verarbeitung dieses Wortes auf:
Für den Argumentparser auf der zweiten Ebene hat sich somit nichts geändert: Er sieht weiterhin einen abgeschnittenen regulären Ausdruck als einziges Argument und ungültigen Resttext am Ende. Werden die Anführungszeichen maskiert, gelangen sie unverändert auf die zweite Ebene:
Alternativ kann die gesamte Zeichenkette außen in einfache und innen in doppelte Anführungszeichen gesetzt werden, damit die doppelten Anführungszeichen nicht wieder entfernt werden:
Dabei ist zu beachten, dass Begrenzungszeichen innerhalb der übergeordneten Zeichenkette gewöhnliche Zeichen bleiben und keine Trennfunktion mehr haben. Insbesondere gehören Leerzeichen und Tabulatoren neben Kommas zur Zeichenkette. Das folgende Beispiel ist an mehreren Stellen falsch:
Allein die Leerzeichen um die Kommas führen dazu, dass diese Teil des Feldes selbst werden. Der Konverter " regsub" mit führendem Leerzeichen wird daher nicht gefunden und löst einen Fehler aus. Weniger offensichtlich ist, dass die Ersetzungszeichenkette " blah" ein Leerzeichen in die Ausgabe einfügt. Eine gute Faustregel lautet deshalb, niemals unnötige Leerzeichen in Ausdrücke einzufügen.
In regulären Ausdrücken kann das Dollarzeichen (’$’) auftreten oder ein Backslash (’\’) in der Ersetzungszeichenkette verwendet werden. Diese Zeichen werden innerhalb doppelter Anführungszeichen ebenfalls verarbeitet. Daher sind einfache Anführungszeichen oder eine doppelte Maskierung vorzuziehen. Beispiel:
Innerhalb einfacher Anführungszeichen sind Backslashes keine Maskierungszeichen. Das gesamte obige Wort ist bereits durch einfache Anführungszeichen vor ihrer Interpretation geschützt. Wäre dagegen der gesamte Ausdruck in doppelte Anführungszeichen gesetzt, würden das Dollarzeichen und die Backslashes bereits auf oberster Ebene aufgelöst und dadurch der Argumentinhalt auf der zweiten Ebene beschädigt.
Da einfache Anführungszeichen innerhalb von starkem Quoting nicht maskiert werden können, müssen sie bei Bedarf im Argument zweimal maskiert oder in Anführungszeichen gesetzt werden. Dafür gibt es mehrere Möglichkeiten:
Im Zweifelsfall zunächst ganz auf Anführungszeichen verzichten und nur Argumente mit Kommas oder schließenden runden Klammern in einfache oder doppelte Anführungszeichen setzen. Enthält die Zeichenkette ein Dollarzeichen oder einen Backslash, müssen diese Anführungszeichen gegebenenfalls mit einem Backslash maskiert werden. Dies ähnelt der doppelten Maskierung eines an “eval” übergebenen Befehls in einer Bourne-Shell. Für API-Autoren ist es vermutlich am besten, ausnahmslos jedes Argument unabhängig von seinem Inhalt mit maskierten Anführungszeichen zu umgeben. Für Anwender sind Konfigurationen wahrscheinlich besser lesbar, wenn der gesamte Ausdruck in einfachen und jedes Argument in doppelten Anführungszeichen steht.
2.3. Umgebungsvariablen
Die HAProxy-Konfiguration unterstützt Umgebungsvariablen. Sie werden ausschließlich innerhalb doppelter Anführungszeichen interpretiert und beim Einlesen der Konfiguration expandiert. Variablennamen müssen mit einem Dollarzeichen ("$") beginnen und können wie in der Bourne-Shell zusätzlich in geschweifte Klammern ("{}") eingeschlossen werden. Sie dürfen alphanumerische Zeichen und Unterstriche ("_") enthalten, sollten aber nicht mit einer Ziffer beginnen. Enthält eine Variable mehrere durch Leerzeichen getrennte Werte, lässt sie sich in einzelne Argumente expandieren: Dazu die Variable in geschweifte Klammern setzen und vor der schließenden Klammer das Suffix ‘[*]’ ergänzen. Ein Standardwert für nicht gesetzte Variablen kann nach einem Bindestrich ‘-’ hinter dem Variablennamen angegeben werden. Er ersetzt nur nicht vorhandene Variablen, keine leeren.
Beispiel:
Einige Variablen werden von HAProxy definiert und können in der Konfigurationsdatei verwendet werden. Sie sind in der folgenden Tabelle aufgeführt und in vier Kategorien eingeteilt:
-
verwendbar: Die Variable ist in der Konfiguration zugänglich. Sie kann direkt aufgelöst oder in bedingten Blöcken und Prädikaten zum Aktivieren oder Deaktivieren von Konfigurationsfragmenten verwendet werden, wie in Abschnitt 2.4 „Bedingte Blöcke“ beschrieben.
-
veränderbar: Die Variable kann in der Konfiguration mit “setenv”/“unsetenv” neu definiert oder entfernt werden.
-
aufgelistet: Die Variable erscheint in der Ausgabe des CLI-Befehls “show env”, der in Abschnitt 9.3 „Unix-Socket-Befehle“ des Verwaltungshandbuchs beschrieben ist.
Daneben gibt es die Unterkategorien „Master“ und „Worker“, die in der folgenden Tabelle mit ‘M’ bzw. ‘W’ gekennzeichnet sind. Sie zeigen die Unterschiede zwischen den beiden Prozessen beim Start von HAProxy im Master-Worker-Modus.
-
Master: Die Variable ist im Master-Prozess gesetzt und zugänglich. Sie erscheint daher in der Ausgabe von “show env” der Master-CLI und kann in bedingten Blöcken oder Direktiven spezielle Einstellungen für den Master aktivieren. Siehe die Beispiele in Abschnitt 2.4 „Bedingte Blöcke“.
-
Worker: Die Variable ist im Worker-Prozess gesetzt und zugänglich. Sie erscheint in “show env” der Worker-CLI oder in “@1 show env” der Master-CLI und kann ebenfalls Bedingungen für Parameter des Worker-Prozesses bilden. Siehe die Beispiele in Abschnitt 2.4 „Bedingte Blöcke“.
Im eigenständigen Betrieb ohne die Option “-W” und ohne das Schlüsselwort “master-worker” verhält sich der Prozess wie ein Worker. Ausgenommen sind die Variablen “HAPROXY_MASTER_CLI” und “HAPROXY_MWORKER”, die dann nicht definiert sind.
Einige Variablen sind als weder verwendbar noch veränderbar gekennzeichnet:
- HAPROXY_CFGFILES
- HAPROXY_MWORKER
- HAPROXY_CLI
- HAPROXY_MASTER_CLI
- HAPROXY_LOCALPEER
Ihre Werte sind beim Einlesen der Konfiguration undefiniert und werden erst später während der Initialisierung gesetzt. Daher wird empfohlen, diese Variablen weder in bedingten Blöcken noch in den Schlüsselwörtern “setenv”/“resetenv”/“unsetenv” des globalen Abschnitts zu referenzieren.
Die folgende Tabelle fasst den Status der Variablen für die verschiedenen Betriebsarten zusammen:
Im Einzelnen handelt es sich um folgende Variablen:
-
HAPROXY_LOCALPEER: beim Prozessstart definiert; enthält den Namen des lokalen Peers. Siehe “-L” im Verwaltungshandbuch.
-
HAPROXY_CFGFILES: durch Semikolons getrennte Liste der von HAProxy geladenen Konfigurationsdateien. Kann bei der Angabe eines Verzeichnisses nützlich sein.
-
HAPROXY_HTTP_LOG_FMT: enthält das HTTP-Standardprotokollformat aus Abschnitt 8.2.3 „HTTP-Protokollformat“. Damit lässt sich das Standardformat überschreiben, ohne die gesamte ursprüngliche Definition kopieren zu müssen.
-
HAPROXY_HTTP_CLF_LOG_FMT: enthält das HTTP-CLF-Standardprotokollformat aus Abschnitt 8.2.3 „HTTP-Protokollformat“. Damit lässt sich das Standardformat überschreiben, ohne die gesamte ursprüngliche Definition kopieren zu müssen.
Beispiel:
-
HAPROXY_HTTPS_LOG_FMT: wie HAPROXY_HTTP_LOG_FMT, jedoch für das HTTPS-Protokollformat aus Abschnitt 8.2.4 „HTTPS-Protokollformat“.
-
HAPROXY_TCP_LOG_FMT: wie HAPROXY_HTTP_LOG_FMT, jedoch für das TCP-Protokollformat aus Abschnitt 8.2.2 „TCP-Protokollformat“.
-
HAPROXY_TCP_CLF_LOG_FMT: wie HAPROXY_HTTP_CLF_LOG_FMT, jedoch für das TCP-CLF-Protokollformat aus Abschnitt 8.2.2 „TCP-Protokollformat“.
-
HAPROXY_KEYLOG_FC_LOG_FMT: enthält das Keylog-Format für die clientseitige TLS-Verbindung des Frontends. Schlüsseleinträge werden durch Zeilenumbrüche getrennt; das Format ist daher möglicherweise nicht mit dem verwendeten syslog-Server kompatibel. “tune.ssl.keylog on” ist erforderlich.
-
HAPROXY_KEYLOG_BC_LOG_FMT: wie HAPROXY_KEYLOG_FC_LOG_FMT, jedoch für die serverseitige TLS-Verbindung des Backends. Schlüsseleinträge werden durch Zeilenumbrüche getrennt; das Format ist daher möglicherweise nicht mit dem verwendeten syslog-Server kompatibel. “tune.ssl.keylog on” ist erforderlich.
-
HAPROXY_MWORKER: Diese Variable wird im Master-Worker-Modus auf 1 gesetzt.
-
HAPROXY_CLI: konfigurierte Listener-Adressen des Statistik-Sockets jedes Prozesses, durch Semikolons getrennt.
-
HAPROXY_MASTER_CLI: im Master-Worker-Modus die durch Semikolons getrennten Listener-Adressen der Master-CLI.
-
HAPROXY_STARTUP_VERSION: enthält die beim Start verwendete Version. Im Master-Worker-Modus ist dies die Version, mit der der Master gestartet wurde, auch nach Aktualisierung der ausführbaren Datei und anschließendem Neuladen.
-
HAPROXY_BRANCH: enthält die Version des HAProxy-Zweigs, etwa “2.8”, jedoch nicht die vollständige Versionsnummer. Dies kann bei einer Migration nützlich sein, wenn Ressourcen wie Maps oder Zertifikate in einem Pfad mit der Nummer des Versionszweigs liegen.
Zusätzlich werden einige Pseudovariablen intern aufgelöst und lassen sich wie gewöhnliche Variablen verwenden. Pseudovariablen beginnen immer mit einem Punkt (’.’); nur in ihnen ist ein Punkt zulässig. Derzeit gibt es folgende Pseudovariablen:
-
.FILE: Name der gerade eingelesenen Konfigurationsdatei.
-
.LINE: Zeilennummer in der gerade eingelesenen Konfigurationsdatei, beginnend bei eins.
-
.SECTION: Name des gerade eingelesenen Abschnitts oder dessen Typ, wenn der Abschnitt keinen Namen hat, etwa “global”. Vor dem ersten Abschnitt ist dies eine leere Zeichenkette.
Diese Variablen werden an der Stelle aufgelöst, an der sie eingelesen werden. Wird beispielsweise “.LINE” in einer “log-format”-Direktive eines defaults-Abschnitts verwendet, steht die Zeilennummer bereits vor dem Parsen und Kompilieren der “log-format”-Direktive fest. Nachfolgende Proxys verwenden daher dieselbe Zeilennummer.
So lassen sich in Variablen, Protokollen, Fehlerstatus, Health Checks und Header-Werten Informationen zum Auffinden einer Regel ausgeben. Zeilennummern können sogar zur Benennung von Konfigurationsobjekten wie Servern dienen.
2.4. Bedingte Blöcke
Manchmal ist es praktisch, beliebige Teile der Konfiguration abhängig von Bedingungen ein- oder auszuschalten. So lassen sich beispielsweise SSL oder Cipher sowie Listener einer Vorproduktionsumgebung ohne Änderung der Konfiguration aktivieren oder deaktivieren. Auch die Konfigurationssyntax kann während einer Migration zwei verschiedene HAProxy-Versionen unterstützen. HAProxy bietet dafür verschachtelbare Direktiven nach Art eines Präprozessors, die Textblöcke einbeziehen oder ignorieren. Diese Direktiven müssen in einer eigenen Zeile stehen und wirken auf die folgenden Zeilen. Zwei davon unterstützen einen Ausdruck; die anderen wechseln lediglich zu einem alternativen Block oder schließen die aktuelle Ebene ab. Bedingte Blöcke werden mit folgenden 4 Direktiven gebildet:
- .if
<condition> - .elif
<condition> - .else
- .endif
“.if” eröffnet eine neue verschachtelte Ebene, “.elif” und “.else” bleiben auf derselben Ebene, und “.endif” schließt eine Ebene. Jedes “.if” muss mit einem zugehörigen “.endif” abgeschlossen werden. “.elif” darf nur auf “.if” oder “.elif” folgen; beliebig viele “.elif” können aufeinanderfolgen. Je “.if” ist nur ein “.else” zulässig. Es muss immer nach dem “.if” oder dem letzten “.elif” eines Blocks stehen.
Kommentare können bei Bedarf nach einem ‘#’ in derselben Zeile stehen und werden ignoriert. Die Direktiven werden wie andere Konfigurationsdirektiven in Tokens zerlegt; daher können Bedingungen Umgebungsvariablen verwenden.
Bedingungen lassen sich auch beim Start mit dem Parameter -cc auswerten. Siehe „3. HAProxy starten“ im Verwaltungshandbuch.
Eine Bedingung ist entweder eine leere Zeichenkette, die falsch ergibt, oder ein Ausdruck aus einer beliebigen Kombination folgender Elemente:
- die Ganzzahl null (‘0’), die immer “false” ergibt
- eine Ganzzahl ungleich null, etwa ‘1’, die immer “true” ergibt
- ein Prädikat, optional gefolgt von Argumenten in runden Klammern
- eine Bedingung in einem Paar runder Klammern ‘(’ und ‘)’
- ein Ausrufezeichen (’!’) vor einem der obigen nicht leeren Elemente, das dessen Wert negiert
- mit logischem AND (’&&’) verknüpfte Ausdrücke, die von links nach rechts ausgewertet werden, bis einer falsch ergibt
- mit logischem OR (’||’) verknüpfte Ausdrücke, die von rechts nach links ausgewertet werden, bis einer wahr ergibt
Dabei werden derselbe Zeilen-Tokenizer und Argumentparser wie in der übrigen Konfigurationssprache verwendet. Wörter werden an Folgen eines oder mehrerer ungeschützter Leerzeichen oder Tabulatoren getrennt und vor der Auswertung mit jeweils einem Leerzeichen wieder zusammengesetzt. So muss nicht die gesamte Zeile in Anführungszeichen gesetzt werden. Allerdings gehören dadurch Leerzeichen um Kommas oder Klammern tatsächlich zum Wert, was nicht immer erwartet wird. Beispielsweise prüft der folgende Ausdruck:
ob die Variable " HAPROXY_MWORKER " mit den Leerzeichen existiert. Dieser Ausdruck:
vergleicht dagegen die Umgebungsvariable “ENABLE_SSL” mit dem Wert " 1", der ein einzelnes führendes Leerzeichen enthält. Denn die Zeile wird zunächst wie folgt in Wörter zerlegt:
Anschließend wird das schwache Quoting verarbeitet und die Umgebungsvariable “$ENABLE_SSL” aufgelöst, beispielsweise mit ENABLE_SSL=0. Schließlich werden die Wörter mit jeweils einem Leerzeichen dazwischen zu einer einzigen Zeichenkette zusammengesetzt:
Erst danach wird diese als einzelner Ausdruck ausgewertet. Das zwischen Komma und “1” eingefügte Leerzeichen gehört weiterhin zum Argumentwert, sodass das Argument " 1" lautet:
Selbst wenn ENABLE_SSL den Wert “1” hätte, würde dieser also nicht mit " 1" übereinstimmen, da sich die Zeichenketten um ein Leerzeichen unterscheiden.
Hinweis: Wie in Abschnitt „2.2. Anführungszeichen und Maskierung“ erläutert, ist es eine gute Faustregel, niemals unnötige Leerzeichen in Ausdrücke einzufügen.
Wie in anderen Sprachen hat AND Vorrang vor OR. Daher wird “A && B || C && D” als “(A && B) || (C && D)” ausgewertet.
Derzeit werden folgende Prädikate unterstützt:
-
awslc_api_atleast(
<ver>): ergibt wahr, wenn die aktuelle awslc-API-Nummer mindestens so neu wie<ver>ist, andernfalls falsch. Beispiel: awslc_api_atleast(35) -
awslc_api_before(
<ver>): ergibt wahr, wenn die aktuelle awslc-API-Nummer strikt älter als<ver>ist, andernfalls falsch. Beispiel: awslc_api_before(26) -
defined(
<name>) : ergibt wahr, wenn die Umgebungsvariable<name>existiert, unabhängig von ihrem Inhalt -
feature(
<name>) : ergibt wahr, wenn die Funktion<name>in der Funktionsliste von “haproxy -vv” als vorhanden aufgeführt ist, also<name>nach einem ‘+’ erscheint -
openssl_version_atleast(
<ver>): ergibt wahr, wenn die aktuelle openssl-Version mindestens so neu wie<ver>ist, andernfalls falsch. Bibliotheken wie LibreSSL, AWS-LC und WolfSSL geben ebenfalls eine Pseudo-OpenSSL-Version an. Beispiel:
-
openssl_version_before(
<ver>): ergibt wahr, wenn die aktuelle openssl-Version strikt älter als<ver>ist, andernfalls falsch. Bibliotheken wie LibreSSL, AWS-LC und WolfSSL geben ebenfalls eine Pseudo-OpenSSL-Version an. Beispiel: openssl_version_before(3.5.0) -
ssllib_name_startswith(
<name>) : ergibt wahr, wenn der Name der mit HAProxy eingebundenen SSL-Bibliothek mit<name>beginnt. Beispiel: ssllib_name_startswith(wolfSSL) -
streq(
<str1>,<str2>) : ergibt nur dann wahr, wenn beide Zeichenketten gleich sind -
strneq(
<str1>,<str2>): ergibt nur dann wahr, wenn sich beide Zeichenketten unterscheiden -
strstr(
<str1>,<str2>): ergibt nur dann wahr, wenn die zweite Zeichenkette in der ersten vorkommt. -
version_atleast(
<ver>): ergibt wahr, wenn die aktuelle haproxy-Version mindestens so neu wie<ver>ist, andernfalls falsch. Die Versionssyntax entspricht der Ausgabe von “haproxy -v”; fehlende Bestandteile werden als null angenommen. -
version_before(
<ver>): ergibt wahr, wenn die aktuelle haproxy-Version strikt älter als<ver>ist, andernfalls falsch. Die Versionssyntax entspricht der Ausgabe von “haproxy -v”; fehlende Bestandteile werden als null angenommen. -
enabled(
<opt>) : ergibt wahr, wenn die Option<opt>zur Laufzeit aktiviert ist. Nur ein Teil der Optionen wird unterstützt:
Beispiel:
Vier weitere Direktiven dienen der Ausgabe von Statusmeldungen:
- .diag “message” : gibt diese Meldung nur im Diagnosemodus (-dD) aus
- .notice “message” : gibt diese Meldung auf der Stufe NOTICE aus
- .warning “message”: gibt diese Meldung auf der Stufe WARNING aus
- .alert “message” : gibt diese Meldung auf der Stufe ALERT aus
Meldungen auf der Stufe WARNING können den Prozessstart verhindern, wenn “zero-warning” aktiviert ist. Meldungen auf der Stufe ALERT führen immer zu einem schwerwiegenden Fehler. Damit lassen sich ungeeignete Bedingungen erkennen und Hinweise für den Anwender ausgeben.
Beispiel:
2.5. Zeitformat
Manche Parameter enthalten Zeitwerte, etwa Zeitüberschreitungen. Sofern nicht ausdrücklich anders angegeben, werden sie gewöhnlich in Millisekunden ausgedrückt. Durch Anhängen einer Einheit an den Zahlenwert können aber auch andere Einheiten verwendet werden. Dies ist zu beachten, da es nicht bei jedem Schlüsselwort wiederholt wird. Unterstützt werden:
- us: Mikrosekunden. 1 Mikrosekunde = 1/1000000 Sekunde
- ms: Millisekunden. 1 Millisekunde = 1/1000 Sekunde. Dies ist der Standard.
- s : Sekunden. 1s = 1000ms
- m : Minuten. 1m = 60s = 60000ms
- h : Stunden. 1h = 60m = 3600s = 3600000ms
- d : Tage. 1d = 24h = 1440m = 86400s = 86400000ms
2.6. Größenformat
Manche Parameter enthalten Größenwerte, etwa Bandbreitengrenzen. Sofern nicht ausdrücklich anders angegeben, werden sie gewöhnlich in Bytes ausgedrückt. Durch Anhängen einer Einheit an den Zahlenwert können aber auch andere Einheiten verwendet werden. Dies ist zu beachten, da es nicht bei jedem Schlüsselwort wiederholt wird. Die folgenden Einheiten werden unabhängig von der Groß-/Kleinschreibung unterstützt:
- k: Kilobytes. 1 Kilobyte = 1024 Bytes
- m: Megabytes. 1 Megabyte = 1048576 Bytes
- g: Gigabytes. 1 Gigabyte = 1073741824 Bytes
Zeit- und Größenformate erfordern Ganzzahlen; Nachkommastellen sind nicht zulässig.
2.7. Namensformat für Maps und ACLs
Für Maps und ACLs können Musterlisten verwendet werden. Eine Musterliste wird durch ihren Namen identifiziert und kann an verschiedenen Stellen der Konfiguration zum Einsatz kommen. Je nach Namensformat gibt es drei Kategorien:
-
Musterlisten aus regulären Dateien: Dies ist der Standardfall. Der absolute oder relative Dateiname dient als Name. Die Datei muss existieren, andernfalls tritt ein Fehler auf; sie darf jedoch leer sein. Das Präfix “file@” kann angegeben werden, gehört aber nicht zum Namen der Liste. Ein Dateiname verweist mit und ohne dieses Präfix auf dieselbe Musterliste.
-
Musterlisten aus optionalen Dateien: Vor dem Dateinamen muss das Präfix “opt@” stehen. Die Datei muss nicht existieren. Ist sie vorhanden, wird ihr Inhalt geladen; fehlt sie, wird kein Fehler gemeldet. Das Präfix gehört nicht zum Namen der Liste. Bei gleichem Dateinamen verweisen optionale und reguläre Dateien daher auf dieselbe Musterliste.
-
Musterlisten aus virtuellen Dateien: Der Name ist lediglich eine Kennung und verweist auf keine Datei. Das Präfix “virt@” muss verwendet werden und gehört zum Namen. Diese Listen lassen sich daher nicht mit den anderen Arten vermischen.
Virtuelle Dateien eignen sich für vollständig dynamisch verwaltete Muster, die beim Start und Neuladen noch nicht vorhanden sind. Optionale Dateien können unter denselben Bedingungen verwendet werden. Muster lassen sich jedoch in die Datei schreiben, etwa mit einem externen Skript auf Basis des CLI-Befehls “show map”. Dadurch können sie beim Neuladen erhalten bleiben.
Hinweis: Auch wenn solche Namen unwahrscheinlich sind, können damit keine regulären Dateien geladen werden, deren Name mit “file@”, “opt@” oder “virt@” beginnt. Dazu muss ausdrücklich “./” vor den Dateinamen gesetzt werden, beispielsweise “file@./virt@map”.
2.8. Variablen
In der HAProxy-Konfiguration lassen sich Variablen in Sample-Abruffunktionen, Konvertern, log-format-Zeichenketten und TCP/HTTP-Aktionen verwenden. Prozessweite Variablen sind während der gesamten Prozesslebensdauer global zugänglich; andere haben eine kürzere Lebensdauer. Wie in Shell-Skripten ist eine Variable ein symbolischer Name für einen Speicherbereich. Ihre Größe ist unbegrenzt, und der Speicher wird dynamisch reserviert. Deshalb müssen Variablen besonders bei intensiver Nutzung mit Vorsicht eingesetzt werden. Über die globalen Parameter “tune.vars” lässt sich jedoch der maximale Speicherverbrauch der Variablen begrenzen.
Variablen müssen im Format “<scope>.<name>” bezeichnet werden. <scope> ist ein einzelnes Wort für die Lebensdauer der Variablen. Der Teil <name> darf innerhalb eines Geltungsbereichs nur ‘a-z’, ‘A-Z’, ‘0-9’ und ‘_’ enthalten. Er ist dort eindeutig. Derselbe Name kann jedoch in verschiedenen Geltungsbereichen vorkommen und bezeichnet dann unterschiedliche Variablen. Unterstützt werden folgende Geltungsbereiche:
-
proc : Variablen, die während der gesamten Prozesslebensdauer bekannt und global zugänglich sind. “proc”-Variablen lassen sich über die CLI mit “get var” und “set var” bearbeiten. In Abschnitten des Typs “global” können sie außerdem mit “set-var” und “set-var-fmt” gesetzt werden.
-
sess : Variablen, die während der gesamten Sitzungslebensdauer bekannt sind. “sess”-Variablen gehören ausschließlich zu einer Sitzung, sind außerhalb davon unsichtbar und werden nicht mit anderen Sitzungen geteilt.
-
txn : Variablen, die während der gesamten Transaktionslebensdauer bekannt sind. “txn”-Variablen gehören ausschließlich zu einem Stream, sind außerhalb davon unsichtbar und werden nicht mit anderen Streams geteilt.
-
req : Variablen, die während der Anfrageverarbeitung eines bestimmten Streams bekannt sind. “req”-Variablen sind vom Erzeugen des Streams bis zum ersten Verbindungsversuch zum Server sichtbar. Sie gehören ausschließlich zu einem Stream, sind außerhalb davon unsichtbar und werden nicht mit anderen Streams geteilt. Zwischen “req”- und “res”-Variablen gibt es keinerlei Überschneidung.
-
res : Variablen, die während der Antwortverarbeitung eines bestimmten Streams bekannt sind. “res”-Variablen sind vom ersten Verbindungsversuch zum Server bis zur Zerstörung des Streams sichtbar. Sie gehören ausschließlich zu einem Stream, sind außerhalb davon unsichtbar und werden nicht mit anderen Streams geteilt. Zwischen “req”- und “res”-Variablen gibt es keinerlei Überschneidung.
-
check: Variablen, die während eines Health Checks bekannt sind. “check”-Variablen gehören ausschließlich zu einem Health Check, sind außerhalb davon unsichtbar und werden nicht mit anderen Health Checks geteilt. Sie können mit speziellen “tcp-check”- oder “http-check”-Direktiven gesetzt werden.
Je nach Kontext können zusätzliche Geltungsbereiche verwendet werden, die auf den Elternstream des aktuellen Streams verweisen:
-
psess: wie “sess”, verwendet jedoch die Sitzung des Elternstreams, sofern vorhanden.
-
ptxn : wie “txn”, verwendet jedoch die Transaktion des Elternstreams, sofern vorhanden.
-
preq : wie “req”, verwendet jedoch den Elternstream, sofern vorhanden. “preq”-Variablen sind nur während der Anfrageverarbeitung des Elternstreams zugänglich.
-
pres : wie “res”, verwendet jedoch den Elternstream, sofern vorhanden. “pres”-Variablen sind nur während der Antwortverarbeitung des Elternstreams zugänglich.
Auf den Elternstream verweisende Geltungsbereiche sind nutzbar, sobald dieser definiert ist. Meist gibt es keinen Elternstream; andernfalls wird dies ausdrücklich angegeben. Derzeit können nur Werte von Variablen aus einem Geltungsbereich des Elternstreams gelesen werden. Solche Variablen lassen sich weder setzen noch entfernen. Ein Kindstream übernimmt gewöhnlich zu einem bestimmten Zeitpunkt eine Verarbeitung für den Elternstream und hält diesen an, bis der Vorgang abgeschlossen ist. Der Elternstream kann daher beispielsweise mitten in der Anfrage- oder Antwortverarbeitung pausieren. Bestimmte Geltungsbereiche sind dann vom Kindstream aus nicht verfügbar. Untersucht etwa ein Kindstream eine Anfrage, findet er keine Variablen im Geltungsbereich “pres”: Der Elternstream verarbeitet gerade keine Antwort und hat deshalb keine Variablen im Bereich “res”.
Der Inhalt einer Variablen ist das Ergebnis eines Sample-Abrufausdrucks und übernimmt dessen Ausgabetyp. Dieser muss zur Verwendung der Variablen passen. Enthält eine Variable beispielsweise eine Zeichenkette und wird im Konverter “add()” verwendet, muss sie für einen erfolgreichen Aufruf in eine gültige Ganzzahl umwandelbar sein. Das ist besonders beim Vergleich von Variablen mit statischen Werten wichtig: Es muss die passende Abgleichmethode verwendet werden.
2.9. Adressformate
Mehrere Direktiven wie “bind”, “server”, “nameserver” und “log” benötigen eine Adresse.
Dies kann ein Hostname, eine IPv4- oder IPv6-Adresse oder ‘’ sein. ‘’ entspricht der speziellen Adresse “0.0.0.0” und kann bei “bind” oder “dgram-bind” verwendet werden, um auf allen IPv4-Adressen des Systems zu lauschen. Die IPv6-Entsprechung ist ‘::’.
Je nach Direktive folgt auf die IP-Adresse ein Port oder Portbereich. Bei ‘bind’ ist dies zwingend erforderlich, bei ‘server’ optional.
Die Adresse kann auch mit einem Schrägstrich ‘/’ beginnen. Dann gehört sie zur Familie “unix”, und ‘/’ mit den folgenden Zeichen muss den Pfad angeben.
Ob standardmäßig der Socket-Typ beziehungsweise die Transportmethode “datagram” oder “stream” verwendet wird, hängt von der Direktive mit der Adresse ab. ‘bind’ und ‘server’ verwenden standardmäßig “stream”, während ’log’, ’nameserver’ und ‘dgram-bind’ “datagram” verwenden.
Optional kann ein Präfix die Adressfamilie und/oder den Socket-Typ sowie die Transportmethode erzwingen.
2.9.1. Präfixe für Adressfamilien
‘abns@<name>’: Der folgende <name> bezeichnet einen abstrakten Namensraum (nur unter Linux).
‘abnsz@<name>’: Der folgende <name> bezeichnet einen nullterminierten abstrakten Namensraum (nur unter Linux).
‘fd@<n>’: Die folgende Adresse ist ein vom Elternprozess geerbter Dateideskriptor <n>. Er muss gebunden sein, kann aber bereits lauschen oder noch nicht.
‘ip@<address>[:port1[-port2]]’: Die folgende <address> gilt je nach Syntax als IPv4- oder IPv6-Adresse. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘ipv4@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv4-Adresse. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘ipv6@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv6-Adresse. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘sockpair@<n>’: Die folgende Adresse ist der Dateideskriptor eines verbundenen Unix-Sockets oder eines Socket-Paars. Beim Verbindungsaufbau erzeugt der Initiator ein Paar verbundener Sockets und übergibt einen davon über den FD an die Gegenstelle. Der Listener wartet auf den FD über den Unix-Socket und verwendet ihn wie den FD eines accept(). Mit Vorsicht verwenden.
Fehler: Dieses Protokoll ist unter macOS bekanntermaßen
unzuverlässig, da die sendmsg(2)-Implementierung von macOS
fehlerhaft ist. Die Verbindung wird möglicherweise nicht korrekt angenommen.
‘unix@<path>’: Die folgende Zeichenkette gilt als UNIX-Socket <path>. Das Präfix eignet sich zur Angabe eines UNIX-Socket-Pfads, der nicht mit ‘/’ beginnt.
2.9.2. Präfixe für Socket-Typen
Den obigen Präfixen für Adressfamilien kann ein weiteres Präfix vorangestellt werden, um Socket-Typ und Transportmethode zu erzwingen. Der Standard hängt von der Direktive mit der Adresse ab, kann aber in manchen Fällen überschrieben werden. So verwendet “log” standardmäßig syslog über UDP, lässt sich aber auf syslog über TCP zwingen.
Diese Präfixe wurden für interne Zwecke entworfen. Anwender sollten stattdessen die Aliase aus dem folgenden Abschnitt „2.9.3. Protokollpräfixe“ verwenden. Gelegentlich sind sie dennoch praktisch, etwa bei geerbten Sockets, die über ihre Dateideskriptornummer bekannt sind. In diesem Fall ist die Adressfamilie “fd”, und der Socket-Typ muss angegeben werden.
Wer eines dieser Präfixe benötigt, weil sich das gewünschte Verhalten nicht mit Protokollpräfixen konfigurieren lässt, sollte dies den Betreuern melden.
‘stream+<family>@<address>’ erzwingt “stream” als Socket-Typ und Transportmethode.
‘dgram+<family>@<address>’ erzwingt “datagram” als Socket-Typ und Transportmethode.
‘quic+<family>@<address>’ erzwingt den Socket-Typ “datagram” und die Transportmethode “stream”.
2.9.3. Protokollpräfixe
‘quic4@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv4-Adresse; der Socket-Typ wird jedoch auf “datagram” und die Transportmethode auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein UDP-Port oder -Portbereich angegeben werden. Dies entspricht “quic+ipv4@”.
‘quic6@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv6-Adresse; der Socket-Typ wird jedoch auf “datagram” und die Transportmethode auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein UDP-Port oder -Portbereich angegeben werden. Dies entspricht “quic+ipv6@”.
’tcp@<address>[:port1[-port2]]’: Die folgende <address> gilt je nach Syntax als IPv4- oder IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘stream+ip@’.
’tcp4@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv4-Adresse; Socket-Typ und Transportmethode werden jedoch auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘stream+ipv4@’.
’tcp6@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘stream+ipv6@’.
‘mptcp@<address>[:port1[-port2]]’: Die folgende <address> gilt je nach Syntax als IPv4- oder IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch mit dem MPTCP-Protokoll auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘mptcp4@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv4-Adresse; Socket-Typ und Transportmethode werden jedoch mit dem MPTCP-Protokoll auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘mptcp6@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch mit dem MPTCP-Protokoll auf “stream” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden.
‘udp@<address>[:port1[-port2]]’: Die folgende <address> gilt je nach Syntax als IPv4- oder IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch auf “datagram” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘dgram+ip@’.
‘udp4@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv4-Adresse; Socket-Typ und Transportmethode werden jedoch auf “datagram” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘dgram+ipv4@’.
‘udp6@<address>[:port1[-port2]]’: Die folgende <address> gilt immer als IPv6-Adresse; Socket-Typ und Transportmethode werden jedoch auf “datagram” festgelegt. Je nach verwendender Direktive kann oder muss ein Port oder Portbereich angegeben werden. Dies ist ein Alias für ‘dgram+ipv6@’.
‘uxdg@<path>’: Die folgende Zeichenkette gilt als Unix-Socket <path>; die Transportmethode wird jedoch auf “datagram” festgelegt. Dies ist ein Alias für ‘dgram+unix@’.
‘uxst@<path>’: Die folgende Zeichenkette gilt als Unix-Socket <path>; die Transportmethode wird jedoch auf “stream” festgelegt. Dies ist ein Alias für ‘stream+unix@’.
In künftigen Versionen könnten weitere Präfixe für Protokolle wie QUIC verwendet werden, das einen Stream-Transport auf Sockets des Typs “datagram” bereitstellt.
2.10. Beispiele
Ist haproxy über $PATH erreichbar, lassen sich diese Konfigurationen in einer Shell wie folgt prüfen: