ein paar Worte über Infrastruktur…

ein paar Worte über Infrastruktur…

Im Zeitalter von public Clouds und Konzepten wie Continous Delivery und Continous Deployment erhalten grundlegende Infrastruktur-Überlegen einen immer geringeren Stellenwert. Getrau nach dem Motto „Es sich jemand darüber Gedanken gemacht und mein Problem gelöst“.

Auf der anderen Seite macht es hin und wieder auch Sinn, sich bewusst zu werden, wie sich bisherige Ansätze anpassen lassen um aktuellen Anforderungen gerecht zu werden. Zum einen natürlich, weil es möglich ist, zum anderen um sich nicht blindlings auf die angebotenen Services verlassen zu müssen.

Nachfolgend also ein Konzept mit dem sich Dienste in Containern (unabhängig ob OpenVZ, LXC, Zones oder Docker) effizient organisieren und als externe Dienste zur Verfügung stellen lassen.

Folgende Annahmen werden getroffen:

  • alle Container haben eine lokale IP und stellen über einen Port den Dienst zur Verfügung
  • der Host erlaubt per iptables ein IP forwarding
  • dieser Ansatz gilt aktuell nur für IPv4

Am Beispiel diese Blogs soll gezeigt werden, wie ein verteiltes Deployment stattfinden:

Das Deployment besteht aus ein paar geteilten System:

ldap1.example.com:

Zentrale Benutzerdatenbank. Je nachdem in welcher Gruppe sich ein Account befindet, erhält dieser Benutzer Zugriff auf das jeweilige System. Hier heißt die Gruppe z.B. „entwickler-gilde.de“.

mysql1.example.com:

Um eine zentralge Verwaltung (Backup/Restore/Failover) aller Datenbanken zu ermöglichen, werden alle Datenbanken nicht direkt im eigenen Container betrieben sondern auf einem zentralen DB-Cluster. Die Zugriffsrechte sind jeweils nur für einen bestimmten Benutzer des Containers auf die jeweilige Datenbank.
(hier: z.B. user wordpress@10.10.10.120 für db wp_entwicklergilde.de )

multiplexer1.example.com

Der Multiplexer ist eigentlich nur ein NGinx der als SSL-Terminator für mehrere Backend Services dient.
Hierbei wird SNI (Server Name Indication) verwendet, um mehrere SSL-Terminierung auf einer IP zu ermöglichen. Aus mehreren Gründen ist eine (Sub-)Domain einer Lösung mit Kontextpfaden (example.com/app1, example.com/app2,…) vorzuziehen – u.a. um nicht Coockies über mehrere Services geteilt zu haben)

Für jeden Backend Service wird eine Konfiguration wie diese hier angelegt:

cat /etc/nginx/sites-available/entwickler-gilde.de.conf

server {
    listen 443;
    server_name www.entwickler-gilde.de entwickler-gilde.de;
    ssl on;
    ssl_certificate /etc/letsencrypt/live/apps.consolving.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/apps.consolving.net/privkey.pem;

    # enable session resumption to improve https performance
    # http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 5m;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;

    # enables server-side protection from BEAST attacks
    # http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
    ssl_prefer_server_ciphers on;
     # disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    # ciphers chosen for forward secrecy and compatibility
    # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
    ssl_ciphers "EECDH+AESGCM:…!RC4";
    # enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
    # http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
    resolver 8.8.8.8;
    ssl_stapling on;

    access_log            /var/log/nginx/entwickler-gilde.de_access.log as_json;

    location / {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        # Fix the 'It appears that your reverse proxy set up is broken' error.
        proxy_pass                http://10.10.10.120:80;
        proxy_read_timeout  90;
        proxy_redirect  http://10.10.10.120:80 https://entwickler-gilde.de;

        proxy_cache            STATIC;
        proxy_cache_valid      200  1h;
        proxy_cache_use_stale  error timeout invalid_header updating
                               http_500 http_502 http_503 http_504;            
    }
}

Je nach Backend Service gibt es eine spezifische Konfiguration.
Weiterhin gibt es eine let’s encrypt Konfiguration, die automatisch ein Zertifikat requested, welches für jede (Sub-)Domain korrekt terminiert:

cat /etc/cron.daily/renew-ssl-certificates

  #!/bin/bash

set -e

service nginx stop 
certbot certonly -n --expand --agree-tos --email admin@example.com --standalone -d apps.consolving.net -d entwickler-gilde.de 
service nginx start

NGinx sollte vorher deaktiviert sein, weil der certbot über Port 80 einen Validierungs-Dienst startet. Da der Request normalerweise innerhalb von Sekunden möglich ist, ist der Ausfall aber verkraftbar. Dies stellt sicher, dass immer ein gültiges Zertifikat verwendet wird.
Es mag nun Gerüchte geben, dass ein Zertifikat für mehrere Domains „weniger gut“ als ein einzelnes Zertifikat für eine IP oder ein wildcard (*.example.com) ist, allerding zeigt ein Check bei SSL-Labs ebenfalls ein positives Rating:

entwickler-gilde.de

Der letzte Container ist der einzige, der spezifisch in diesem Deployment ist.
WordPress wird hier auf php-fpm hinter einem nginx betrieben. Der beschriebene Ansatz ermöglichte es z.B. sehr einfach die WordPress Installation von Apache2 und PHP5 auf nginx und PHP7 zu aktualisieren und die Konfiguration in Grenzen zu halten.

externe IP

Als letzter Schritt bleibt noch das Anbinden des multiplexer1.example.com Containers an eine externe IP. Dies wird über eine spezifische iptables Konfiguration auf dem eigentlichen Container Host ermöglicht. iptables regelt ebenfalls den Datenaustausch zwischen den einzelnen Containern. Diese Konfiguration wird aus mehreren Quellen generiert. Ein Auszug der notwendigen Regeln sieht z.b. so aus:

#!/bin/bash

set -e

# clear Config first
iptables -t nat --flush

# map external IPs
MPLX_IP1=5.9.107.35

## multiplexer1.example.com

### nginx http
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -t nat -A PREROUTING -d ${MPLX_IP1} -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.10.10.107:80

### nginx https
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -t nat -A PREROUTING -d ${MPLX_IP1} -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.10.10.107:443

### access to Internet
iptables -t nat -A POSTROUTING -s 10.10.10.107 -o eth0 -j SNAT --to ${MPLX_IP1} 


# show what was configured
iptables -L
iptables-save > /etc/iptables.rules

Sowohl die NGinx Konfiguration, als auch das iptables setup-script werden von einem internen Web-Service basierend auf einem internen Model für jedes Host generiert und periodisch (z.B. per wget) auf dem jeweiligen System aktuell gehalten.

Die DNS Einträge lassen sich schon teilweise halb-automatisch über einen eigenen dyndns Service verwalten. Für spezifische Domains erfolgt die Konfiguration einmalig manuell.

TL;DR

  • durch ein paar Konventionen lässt sich der Betrieb und das Deployment von Services vereinfachen
  • die Kombination von offenen und standardisierten Techniken ermöglicht ein schnelles und unkompliziertes Anpassen an neue Anforderungen
  • neue Wege wie SNI („neu“ ist relativ) und let’s encrypt Zertifikate ermöglichen einen kostenfreien, SSL-only Betrieb von WebServices

Ausblick

Der vorgestellt Ansatz bietet noch reichlich Raum für Verbesserungen und Erweiterungen.
Zum einen wird bereits jetzt der verwendete NGinx als Cache für die Backend Services dahinter verwendet. Dies führt dazu, dass die Webseiten auch dann noch Inhalte liefern können, wenn der entsprechende Container gesichert oder aktualisiert wird.

Ein weiteres Szenario würde zum Beispiel durch die Nutzung von Loadbalancern und weiteren Cluster Setups einen Failover-Betrieb ermöglichen:

Natürlich wird dann noch ein geteilter Storage für beide WordPress Instanzen benötigt (z.B. GlusterFS) über den alle Uploads (Attachments, Plugins) geteilt werden können.

Weiterhin lässt sich das Deployment auch auf mehrere Hosts verteilen:

Die Hosts können sich dann entweder in einem gemeinsamen Rack befinden, oder – wenn sie sich an verschiedenen Orten befinden in einem gemeinsamen VPN (z.B. über tinc).

Philipp Haußleiter

Ein Gedanke zu “ein paar Worte über Infrastruktur…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert