Debian 9 : Apache 2.4 Load Balancer für statischen Inhalt

Einen Load Balancer auf Basis von Apache 2.4 für statischen Inhalt aufzubauen ist kein Hexenwerk. Für dynamische Inhalte sieht das etwas anders aus ;-) aber wir fangen erstmal klein an. Wenn ihr auch den Artikel "Debian 9 : Gateway mit DNS und DHCP" druchgespielt habt sieht euer Netz wenn ihr fertig seit so aus.
Topologie Testaufbau
Auf die beiden Server web-01 und web-02 gehe ich nicht weiter ein, das sind Standard Installationen Debian 9 mit Apache

erstmal brauchen wir auf dem Server web-lb der das Apache Loadbalancing übernehmen soll natürlich Apache. das übernimmt dieser Befehl unter root.
apt update && apt install apache2
danach müssen wir noch die benötigten Module aktivieren
a2enmod proxy_balancer proxy lbmethod_* headers status
wie man hier sieht aktiviere ich immer gleich alle lbmethod_* für den Balancer.
Nun folgt ein Neustart des Apache Servers
systemctl restart apache2.service
Nun müssen wir die site für den Balancer konfigurieren. Das erledigen wir in vi mit dem Befehl
vi /etc/apache2/sites-available/balancer.conf
Hier ist meine Beispiel Konfiguration, ich hab hier absichtlich nur eine simple Grundkonfiguration erstellt.
[VirtualHost *:80]
        ServerAdmin webmaster@localhost
        Header add X-Balancer "%{BALANCER_WORKER_NAME}e"
        [Proxy balancer://webintern]
                BalancerMember http://172.16.0.20:80
                BalancerMember http://172.16.0.21:80
                ProxySet lbmethod=byrequests
        [/Proxy]
        ProxyPass "/balancer-manager" !
        ProxyPass "/" "balancer://webintern/"
        ProxyPassReverse "/" "balancer://webintern"
        [Location "/balancer-manager"]
                SetHandler balancer-manager
                Require ip 192.168.2
        [/Location]
        ErrorLog ${APACHE_LOG_DIR}/balancer_error.log
        CustomLog ${APACHE_LOG_DIR}/balancer_access.log combined
[/VirtualHost]
Syntax Highlighting versagt hier leider [ = < und ] = >

Jetzt müssen wir die Site noch aktivieren das erledigt a2ensite für uns.
a2ensite balancer
und noch ein Neustart des Apache Servers
systemctl restart apache2.service
Zur Erklärung :
Header add X-Balancer "%{BALANCER_WORKER_NAME}e"
dient nur dem debuggen und kann nach erfolgreichen einrichten auskommentiert werden. Wenn alles eingerichtet ist und euer Loadbalancer steht bekommt ihr bei jedem Request einen Eintrag im Header mit dem Namen X-Balancer der verifiziert von welchem Server ihr die Antwort erhalten habt.

Balancer Header 01

Das funktioniert solange ihr keine Sessions oder dynamischen Content habt. Wenn ihr das benötigt muss man dafür Sorgen das der Client der den Loadbalancer anfragt auch immer auf dem selben Webserver raus kommt. Ist klar sonst sind die Sessions ja nicht mehr gültig ;-)

Apache Dokumentation zum Modul proxy_balancer

Debian 9 : Gateway mit DNS und DHCP

1.) Aufsetzten des Gateways mit DNS und DHCP für das interne Netzwerk
2.) installieren des Debian Grundsystems und aktualisieren auf den aktuellsten Stand
3.) installieren der benötigten Packete für den Gateway
apt install dnsmasq vim resolvconf iptables-persistent
benötigte Einstellungen im Kernel vornehmen.
vi /etc/sysctl.conf
# auskommentieren von 
net.ipv4.ip_forward=1
benötigte Einstellungen in den IP-Tables vornehmen
vi /etc/iptables/rules.v4
das reinkopieren, hier ist gleich der port 22 für ssh geöffnet und die Weiterleitung von Port 80 und 443
auf den internen Server 172.16.0.10 umgesetzt.

enp0s3 und enp0s8 sind meine Netzwerkkarten sollten die bei euch einen anderen Namen tragen
Muss dieser natürlich angepasst werden

*nat
-A POSTROUTING -o enp0s3 -j MASQUERADE
-A PREROUTING -i enp0s3 -p tcp --dport 80 -j DNAT --to 172.16.0.10:80
-A PREROUTING -i enp0s3 -p tcp --dport 443 -j DNAT --to 172.16.0.10:443
COMMIT
# -----------------------------------------------------
*filter
-A INPUT -i lo -j ACCEPT
# wenn angefragt dann erlauben
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# ssh erlauben
-A INPUT -i enp0s3 -p tcp -m tcp --dport 22 -j ACCEPT
# Webserver
-A FORWARD -p tcp -d 172.16.0.10 --dport 80 -j ACCEPT
-A FORWARD -p tcp -d 172.16.0.10 --dport 443 -j ACCEPT
# Alles andere DROP
-A INPUT -i enp0s3 -j DROP
COMMIT
Aktivieren der IPTABLES mit
iptables-restore /etc/iptables/rules.v4
Konfigurieren der Netzwerkschnittstelle ins interne LAN
vi /etc/network/interfaces
das reinkopieren
# internal LAN (int)
allow-hotplug eth1
iface enp0s8 inet static
address 172.16.0.1
broadcast 172.16.0.254
netmask 255.255.255.0
dann kümmern wir uns direkt um die dnsmasq.conf
vi /etc/dnsmasq.conf
Da jedes Netzwerk anders ist kann meine nur als Anregung verstanden werden, für meinen Test habe ich alle Kisten in der Domain ph.lan
# DNS CONFIG
domain-needed
bogus-priv
interface=enp0s8
listen-address=127.0.0.1
listen-address=172.16.0.1
bind-interfaces
domain=ph.lan
local=/ph.lan/
# DNS Weiterleitung
server=8.8.8.8
server=8.8.4.4
filterwin2k
# -------------------------------------------------------------
# DHCP CONFIG
# -------------------------------------------------------------
dhcp-range=lan,172.16.0.2,172.16.0.254,12h
dhcp-option=lan,3,172.16.0.1
dhcp-option=lan,6,172.16.0.1
# FESTE HOSTS
# dhcp-host=[E/A nach dem Muster XX:XX:XX:XX:XX:XX],[HOSTNAME],[IP-ADRESSE],infinite
dhcp-host=08:00:27:cc:69:b0,web-lb,172.16.0.10,infinite
so damit wird uns jetzt nicht selbst verarschen editieren wir noch die Datei /etc/hosts
127.0.0.1       localhost
#127.0.1.1      web-gw.ph.lan   web-gw
172.16.0.1      web-gw.ph.lan   web-gw
# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
wenn wir das nicht machen antwortet auf einem Ping des Gateways immer die 127.0.1.1 die Datei wird auch zur Auflösung der Adressen im Netzwerk hergezogen.

Jetzt passen wir noch die Datei /etc/resolvconf/resolv.conf.d/head an. Hier mein Inhalt.
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 172.16.0.1
search ph.lan
keine Angst die Warnung können wir hier ignorieren, beim start wird die Datei /etc/resolv.conf gebaut, damit man in dieser Datei (etc/resolv.conf) nichts ändert ist diese Warnung da.

Jetzt starten wir mal die Kiste durch
systemctl restart

Wenn hier alles richtig gemacht wurde ist der web-gw jetzt konfiguriert jetzt könnt ihr einen 2. Server aufsetzen der seine Netzwerkkarte nur im internen Netzwerk hat. Alle Rechner/Server bekommen jetzt im internen Lan ihre DHCP / DNS Einstellungen vom Server web-gw, jetzt kann man anfangen das Apache Loadbalancing aufzubauen. Dazu folgt aber ein weiterer Eintrag von mir.

PostgreSQL : Backup einer Datenbank incl. Indexes

Problem : Man möchte eine postgreSQL Datenbank inkl. der Indexes sichern, normalerweise ist die Idee suboptimal aber wenn die Datenbank so groß ist das ein Berechnen der Indexes schon 3 Tage benötigt sichert man diese eben mit.

Lösung : Hier zeige ich einen Weg auf der bei mir funktioniert.

Als erstes legen wir die benötigten Ordner an
mkdir -p /postgres/archives
mkdir -p /postgres/backup
dann passen wir die Konfiguration des Servers an
vi /etc/postgresql/9.4/main/postgresql.conf
ans Ende kommt unsere zusätzliche Konfiguration
max_wal_senders=1
wal_level=hot_standby
archive_mode=on
archive_command='cp %p /postgres/archives/%f'
dann passen wir noch die pg_hba.conf an
vi /etc/postgresql/9.4/main/pg_hba.conf
dort schreiben wir an das Ende
local   replication     postgres                                trust
dann starten wir den Dienst erstmal neu
systemctl restart postgresql
Jetzt können wir ein Backup mit dem Tool pg_basebackup durchführen
pg_basebackup -x -Ft -P -p 5432 -D /postgres/backup/$(date +%Y%m%d)
Wenn der Befehl durchgelaufen ist sollte im Ordner /postgres/backup/[DATUM]/ die Datei base.tar vorhanden sein


Einspielen des Backups
systemctl stop postgresql
in den Data Ordner wechseln z.b. /var/lib/postgresql/9.4/main dort mit tar zurückspielen
tar -xvf /backup/20170726/base.tgz
dann den Server neu starten und glücklich sein
systemctl start postgresql

PostgreSQL : 2. Instanz mit eigener Config auf Server

Problem : Für Testzwecke habe ich auf einem Server eine 2 postgresql Instanz benötigt.

Lösung : Man kann bei Debian basierenden Systemen eine 2. Instanz mit dem Befehl pg_createcluster erzeugen.

In meinem Fall hab ich das so gemacht :
pg_createcluster -u postgres -g postgres -d /var/lib/postgresql/9.4/main-2 -l /var/log/postgresql/ -s /var/run/postgresql/postgresql-9.4-main-2.log -p 5433 --start-conf auto 9.4 main-2 --start

Zur Erklärung :
-u besitzer des clusters (default ist postgres)
-g gruppe der cluster files (default gruppe von postgres also postgres ;-))
-d dort speichert postgres die dazugehörigen Files zu dem Cluster
-l hier wird das Logfile hingeschrieben
-s hier wird das Unix Socketfile geschrieben
--start-conf auto (default ist auto das bedeutet das das normale init Script die Instanz mitstartet)
9.4 main-2 Version der Postgres DB und der Name (main-2) der Instanz
--start startet den node automatisch nach dem erstellen

Nachdem der Befehl durchgelaufen kann man sich den Stand der jeweiligen Instanz mit dem Befehl pg_lsclusters anzeigen.

pg_lsclusters Beispiel
Verbinden kann man sich auf die jeweiligen Instanzen mit dem Befehl
sudo -u postgres psql -p [PORT]

Starten oder Beenden eines Cluster Nodes
sudo -u postgres pg_ctlcluster 9.4 main-2 start
sudo -u postgres pg_ctlcluster 9.4 main-2 stop
Verfügbare Aktionen sind : start / stop / restart / reload / status / promote

AWS : automatisierter start der aws instanzen

Problem : Automatisches Starten der Instanzen bei AWS
Lösung : Man lässt dieses Skript auf einer Maschine/Instanz laufen auf der die aws cli konfiguriert ist.
#!/bin/bash
set -o nounset
set -o pipefail
# Script startet alle definierten Maschinen
AWS_PATH="/root/.local/bin/"
# -----------------------------------------------------------
# Definieren der Maschinen die gestartet werden müssen
# -----------------------------------------------------------
# z.B. declare -a TO_START=("i-xxxxxxxxxxxxxxxxx" "i-xxxxxxxxxxxxxxxxx" "i-xxxxxxxxxxxxxxxxx" "i-xxxxxxxxxxxxxxxxx")
declare -a TO_START=("")
# ------------------------------------------------------------
# nichts mehr ändern
# ------------------------------------------------------------
LOG_PATH="$(dirname $(readlink -f $0))/logs"
LOG_FILE="$(date +%Y%m%d)-awscli.log"
# ------------------------------------------------------------
# Funktionen
# ------------------------------------------------------------
function logger () {
        mkdir -p ${LOG_PATH}
        if [ -z ${2} ]; then
                STATI="INFO"
        else
                STATI="${2}"
        fi
        echo "$(date +%Y-%m-%d) | $(date +%R) | ${STATI} | ${1}" >> ${LOG_PATH}/${LOG_FILE}
}
# ------------------------------------------------------------
logger "Starte -> ${0##*/}" "INFO"
cnt=${#TO_START[@]}
for ((i = 0 ; i < cnt ; i++ )); do
        logger "${TO_START[i]} wird gestartet" "INFO"
        ${AWS_PATH}aws ec2 start-instances --instance-ids ${TO_START[i]} > /dev/null 2>&1
done
logger "Beende -> ${0##*/}" "INFO"
exit 0

AWS : automatisierter shutdown der aws instanzen

Problem : Man hat ein paar Instanzen in der AWS installiert, diese werden aber nur für die Entwicklung bzw. Tests benötigt. Um zusätzliche Kosten zu sparen kann man diese automatisiert außerhalb der benötigten Zeiten herunterfahren.

Lösung : Man lässt dieses Skript auf einer Maschine/Instanz laufen auf der die aws cli konfiguriert ist. Das Beispiel hier läuft selbst eine AWS Instanz. Aus diesem Grund wird mit
OWN_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
die eigene ID ermittelt.
Das Skript fährt alle Instanzen herunter außer diese die in DO_NOT_SHUTDOWN definiert sind.

#!/bin/bash
# ----------------------------------
set -o nounset
set -o pipefail
# ----------------------------------
#
# Script fährt alle Instanzen herunter bis auf die unter DO_NOT_SHUTDOWN definierten
# die eigene Maschine bleibt natürlich auch online
# PFAD zu awscli
AWS_PATH="/root/.local/bin/"
OWN_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
# -----------------------------------------------------------
# Definieren der Maschinen die NICHT heruntergefahren werden
# -----------------------------------------------------------
# wenn die eigene Instanz geschützt werden soll diese mit ins Array aufnehmen. Nur so ist gewährleistet das alles abgearbeitet wird
# z.B. declare -a DO_NOT_SHUTDOWN=("${OWN_INSTANCE_ID}" "i-xxxxxxxxxxxxx" "i-xxxxxxxxxxxxx" "i-xxxxxxxxxxxxx")
#
declare -a DO_NOT_SHUTDOWN=("${OWN_INSTANCE_ID}")
# -----------------------------------------------------------
# nichts mehr ändern
# -----------------------------------------------------------
LOG_PATH="$(dirname $(readlink -f $0))/logs"
LOG_FILE="$(date +%Y%m%d)-awscli.log"
# -----------------------------------------------------------
# Funktionen
# -----------------------------------------------------------
function is_inList ()
{
  SERVER=${1}
  max=${#DO_NOT_SHUTDOWN[@]}
  RET=1

  for ((i = 0 ; i < max ; i++ )); do
    if [ "${DO_NOT_SHUTDOWN[i]}" = "${SERVER}" ]; then
      RET=0
      break
    fi
  done

  return $RET
}
function logger () {
        mkdir -p ${LOG_PATH}
        if [ -z ${2} ]; then
                STATI="INFO"
        else
                STATI="${2}"
        fi
        echo "$(date +%Y-%m-%d) | $(date +%R) | ${STATI} | ${1}" >> ${LOG_PATH}/${LOG_FILE}
}
# -----------------------------------------------------------
# Prüfen ob OWN_INSTANCE_ID einen Wert enthält
# -----------------------------------------------------------
logger "Starte -> ${0##*/}" "INFO"
if [ -z ${OWN_INSTANCE_ID} ]; then
        logger "OWN_INSTANCE_ID ist leer Skript wird nicht gestartet" "ERROR"
        logger "Beende AWS Script" "ERROR"
        exit 99
fi

for item in $(${AWS_PATH}aws ec2 describe-instances --query 'Reservations[].Instances[][InstanceId,Tags[?Key==`Name`].Value | [0],LaunchTime,State.Name]' --filters "Name=instance-state-name,Values=running" --output text | awk '{print $1}')
do
        is_inList ${item}
        RET=$?
        if [ $RET -eq 0 ]; then
                logger "${item} wird nicht heruntergefahren definiert in OWN_INSTANCE_ID oder DO_NOT_SHUTDOWN" "INFO"
                continue
        else
                logger "${item} wird heruntergerfahren" "INFO"
                #----  aws cli fährt kiste herunter
                ${AWS_PATH}aws ec2 stop-instances --instance-ids ${item} > /dev/null 2>&1
        fi
done
logger "Beende -> ${0##*/}" "INFO"
exit 0


“Das einzig sichere System müsste ausgeschaltet, in einem versiegelten und von Stahlbeton ummantelten Raum und von bewaffneten Schutztruppen umstellt sein.”
Gene Spafford (Sicherheitsexperte)