K3D Kubernetes Cluster

Ein Kubernetes Cluster ist nach außen hin für die laufenden Services abgeschottet. Das heißt man kann nicht von außen auf die laufenden Dieste zugreifen.

Um Dienste zugänglich zu machen, kann man Ports exposen oder ein Ingress verwenden. Mit Ingress (auf einem LoadBalancer) kann über den Host im Header der Service identifiziert werden. Erfolgt nun eine Namensauflösung z.B. https://demo.k3d.duckdns.org auf ihre öffentliche IP Adressen, dann kann ein Reverse Proxy die Anfragen entgegennehmen und an den Cluster intern weiterleiten.

Der Ingress-Controller wertet nun die Information in dem Header aus und entscheidet das Routing innerhalb des Clusters.

In diesem Artikel möchte ich zeigen, wie man mit NginX, Duckdns und K3D eine flexible Lösung erstellt, in der automatisch alle Endpoints über TLS gesichert sind.

NGinX

Damit das Ganze funktioniert sind ein paar Voraussetzungen zu erfüllen:

  • Es muss ein gültiges Zertifikat für die Domain vorliegen (siehe Wildcard Zertifikat mit Lets Encrypt)
  • Eine DynDNS oder entsprechend konfigurierter DNS Server oder Host Einträge müssen vorliegen
  • Der Server Block in NginX muss die Anfrage matchen
  • Ein paar nötige Header müssen gesetzt werden

HTTP auf HTTPS umlenken

Mit einer 301 Antwort kann den Traffic auf HTTPS umlenken.

    # Redirect http -> https
    server {
        listen 80;
        server_name "~^.+\.k3d\.duckdns\.org$";
        return 301 https://$host$request_uri;
    }

Wichtig: Es darf hier nicht $server_name verwendet werden! Es muss $host genommen werden, da es sonst zu Fehlern im Browser kommt. Dann enthält der Header location die RegEx und der Browser spuckt eine Fehlermeldung aus.

Server_Name matching

Da ich nicht für jede neue Anwendung eine eigenständige Konfiguration für NginX schreiben möchte, habe ich NginX so konfiguriert, dass es Pattern Matching in dem Server_Name betreibt.

Ich habe für den lokalen Kubernetes Cluster das Schema https://keycloak.k3d.duckdns.org verwendet.

server {
    listen 443 ssl http2;

    server_name "~^+\.k3d\.duckdns\.org$";
...
}

Die RegEx nimmt alle Anfragen für https://XXXXXX.k3d.duckdns.org entgegen.

Header setzen

Im NGINX Server Block müssen folgende Header gesetzt sein, damit der Ingress-Controller das Routing vornehmen kann.

proxy_set_header X-Forwarded-For $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection ‘upgrade’;
proxy_set_header Host $host;

Die PROXY_PASS Anweisung leitet nun Anfragen an den Cluster weiter.

proxy_pass http://10.10.10.10:4000;

Der Cluster läuft in diesem Fall auf dem Server mit der IP Adresse 10.10.10.10 und lauscht auf dem Port 4000.

TLS konfigurieren

Für die Absicherung der Verbindung mit TLS, kommt folgender Block in der Konfiguration zum Einsatz. Per Default werden die von dem Cert-Bot aktualiesierten SSL Zertifikate unter halb den Verzeichnisses /etc/letsencryp/live/DOMAIN/ abgelegt. Die Fullchain und der private key werden eingebunden und somit der Endpoint mit TLS abgesichert.

# SSL
ssl_certificate /etc/letsencrypt/live/k3d.duckdns.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/k3d.duckdns.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

Zusammenfassung der Konfiguration von NginX

Hier nun die gesamte Konfiguration von NginX.

###################################
#                                 #
# Kubernetes Cluster              #
#                                 #
#                                 #
###################################

# Redirect http -> https
server {
    listen 80;
    server_name "~^.+\.k3d\.duckdns\.org$";
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;

    server_name "~^-.+\.k3d\.duckdns\.org$";

    # logging
    error_log /var/log/nginx/kubernetes_error.log debug;
    access_log /var/log/nginx/kubernetes_access.log;

    location / {
        proxy_pass http://10.10.10.10:4000;
        proxy_set_header Connection ""; # to keep alive

        proxy_set_header X-Forwarded-For $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection ‘upgrade’;
        proxy_set_header Host $host;
    }

    # SSL
    include ssl/*.k3d.duckdns.org;
}

Szenrio

Gegeben sei eine Domain die bei dem DynDNS Provider duckdns.org gehostet wird. Es sind aber als Einschränkung bei DuckDNS nur maximal 5 SubDomains erlaubt.

Eine Lösung – Pfade verwenden

Eine Lösung kann sein, dass man eine SubDomain verwendet, um über die Pfade den Dienst mit Hilfe des Reverse Proxys aufzulösen.

Hier nun der Server Abschnitt um einen Dienst der in dem lokalen Netz auf der IP Addresse 192.168.2.2 Port 4711 lauscht. Diesen werden wir auf der Domain xxx.duckdns.org/service bekannt machen.

server {
    listen 80;
    server_name xxx.duckdns.org;

    #
    # Serve service on path http://xxx.duckdns.org/service and map it to 192.168.2.2:4711
    #
    location /service/ 
    {
        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;

        proxy_pass              http://192.168.2.2:4711/;

        proxy_read_timeout      600s;
        proxy_send_timeout      600s;
    }
}

Die proxy_pass Anweisung ist hier der wichtige Teil. Hiermit bestimmt man wo der Dienst erreichbar ist.