Pi Hole auf dem Raspberry Pi + Kubernetes Hosten

Pi-Hole ist eine nette Software um Tracking und Werbung zu blockieren. Das Prinzip ist einfach. Pi Hole wird als Standard DNS Server im lokalen Netzwerk konfiguriert und liefert für Werbedomains eine unbrauchbare IP. Die Domainlisten werden von freundlichen Internet Personen gepflegt und das System updated diese regelmäßig. Ich habe die Software seit Jahren ohne Probleme auf einem alten RaspberryPi gehostet bis die SD Karte vor 4 Stunden kaputt ging.

Inzwischen können RaspberryPi’s Container ausführen und es gibt Kubernetes Distributionen die wunderbar auf darauf laufen. Ich nutze MicroK8S auf einem einzelnen 4GB Raspberry Pi4.

Eine komplette Anleitung zu schreiben macht wenig Sinn und sprengt jegliche Rahmen, ich möchte Dir grob erklären, wo man die Dokumentationen findet und was ungefähr im Hintergrund passiert. Schreib mir gerne einen Kommentar wenn Du mehr über Kubernetes und Container Images wissen möchtest.

Setup

System + Kubernetes

Installier erst Ubuntu auf dem Raspberry und dann nach dieser Anleitung MicroK8s.

Helm, MetalLB und Pi-Hole

Helm

Helm ist ein Package Manager für Kubernetes. Er besteht nur aus einem Binary, welches sich gegen einen Cluster verbindet und dort Konfigurationsdateien ablegt. Kubernetes erzeugt aus diesen Konfigurationen Ressourcen in denen am Ende die Container und damit Anwendungen laufen. Helm zieht diese Konfigurationen (Helm Charts) aus Repos im Internet. Helm managed Installation, Updates und Deinstallationen über Annotationen in Kubernetes Resourcen:

kubectl get deployments.apps -n pihole -oyaml |head
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "4"
      meta.helm.sh/release-name: pihole
      meta.helm.sh/release-namespace: pihole
    creationTimestamp: "2022-01-15T17:51:07Z"

Wenn Du direkt auf dem Pi arbeitest kannst Du das Binary via Snap installieren. Doku

MetalLB

MetalLB ist ein LoadBalancer für Kubernetes. Ich nutze den OSI Layer 2 Mode um IPs im lokalen Netzwerk zu erzeugen auf denen die Kubernetes Services dann verfügbar sind.

Installation

# actually apply the changes, returns nonzero returncode on errors only
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/metallb.yaml

Konfiguration

Hier müßt Ihr selbst Hand anlegen. Mein Netz zu Hause hat die Range 192.168.178/24 und MetalLB soll in der Range 1192.168.178.240-192.168.178.250 erzeugen.

192.168.178.240-192.168.178.250

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.178.240-192.168.178.250
EOF

Pi Hole

Ich nutze das Helm Chart von mojo2600.

Diese 3 Befehle reichen auführen:

cat <<EOT >> pihole-values.yaml 
serviceWeb:
	type: LoadBalancer
serviceDns:
	type: LoadBalancer
EOT

helm repo add mojo2600 https://mojo2600.github.io/pihole-kubernetes/

helm upgrade --install pihole  mojo2600/pihole -n pihole -f pihole-values.yaml 

Was passiert hier?

  1. Wir fügen analog zu apt-add-repository für APT einen neuen Kanal für Software hinzu.
  2. Wir legen eine Konfigurationsdatei an welche die Default Values für ServiceWeb & ServiceDns des Charts überschreibt.
  3. Wir installieren mojo2600/pihole in den Namespace (-n) pihole und nennen die Installation auch PißHole und referenzieren die erzeugte Values Datei.

Wir können die Installation überprüfen indem wir schauen ob der Container läuft und was es so an Logs gibt:

kubectl get pods -n pihole
NAME                      READY   STATUS    RESTARTS   AGE
pihole-545cb64d94-zsgxn   1/1     Running   0          4h17m
kubectl logs -n pihole pihole-545cb64d94-zsgxn  |head
[s6-init] making user provided files available at /var/run/s6/etc...exited 0.
[s6-init] ensuring user provided files have correct perms...exited 0.
[fix-attrs.d] applying ownership & permissions fixes...
[fix-attrs.d] 01-resolver-resolv: applying... 
[fix-attrs.d] 01-resolver-resolv: exited 0.
[fix-attrs.d] done.
[cont-init.d] executing container initialization scripts...
[cont-init.d] 20-start.sh: executing... 
 ::: Starting docker specific checks & setup for docker pihole/pihole

sieht gut aus. Nun brauchen wir noch die IP des Pi Hole DNS Service um unseren Router zu konfigurieren:

kubectl get service -n pihole
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)                      AGE
pihole-dhcp      NodePort       10.105.187.249   <none>            67:30482/UDP                 4h54m
pihole-dns-tcp   LoadBalancer   10.105.227.20    192.168.178.243   53:31115/TCP                 4h54m
pihole-dns-udp   LoadBalancer   10.103.205.155   192.168.178.244   53:31516/UDP                 4h54m
pihole-web       LoadBalancer   10.106.223.39    192.168.178.245   80:30713/TCP,443:32113/TCP   4h54m

In meinem Fall muß ich den DNS im Router auf die 192.168.178.244 konfigurieren – DNS läuft Traditionell auf Port 53 und ich mag UDP.

Router Setup

Das ist der Punkt an dem Ihr euch selbst schlau machen müßt. Für AVM Geräte ist hier eine nette Anleitung https://docs.pi-hole.net/routers/fritzbox-de/

Warum der ganze Overhead? Das hätte ich auch schnell mit dem SH Installer machen können!

Guter Punkt. So habe ich meine letzte Installation von Pi-hole vor ein paar Jahren auch gemacht und man spart sich die Installation von viel Software. Die Vorteile merkt man wenn auf dem Kubernetes Cluster weitere Software läuft. Upgrades sind standardisiert ( helm upgrade –install) und wenn man GitOps mit z.B. ArgoCD macht hat man direkt ein Backup aller Softwareinstallationen und Konfigurationen in einem Git Repository. Ich muß mir nicht merken wie der Upgrade Prozess für Software XY läuft sondern aktualisiere Charts oder ändere das Tag eines Images – ich verwende gerne die Metapher „Leg einfach die Floppydisk mit der neuesten Version ein. Wenn Dein Kubernetes Cluster aus mehreren Rechnern besteht läuft Pi-hole auch noch wenn einer ausfällt – Kubernetes sorgt automatisch dafür, dass die Container umgezogen werden und wenn Du einen Monitoring / Alerting Stack installiert hast bekommst Du eine nette Nachricht. Natürlich verbraucht der Kubernetes Stack mehr Ressourcen als eine triviale Pi-hole Installation aber mittel- langfristig überwiegen meiner Meinung nach die Vorteile.

Helm Charts verwenden

Es gibt viele Wege Anwendungen und Konfigurationen in einen Kubernetes Cluster zu bringen.

Der Weg ueber Helm3 gefaellt mir besonders fuer third Party Software. Man fuegt das entsprechende Repository zu Helm hinzu, passt die Variablen ueber eine Konfigurationsdatei oder Parameter an und installiert das Chart. Der Uninstall Befehl hinterlaesst den Cluster meist besenrein.

Falls Dir das Thema Kubernetes fremd ist kannst Du innerhalb von ca 5. Minuten einen MicroK9s Cluster mit diesem Tutorial installieren und meine Schritte durchgehen. Es wird ein Monitoring Stack mit Grafana und den Backends Loki + Prometheus sowie einigen Dashboards installiert und konfiguriert. Hier die Dokumentation von den Grafanalabs.

Repo hinzufuegen

# Bitte das Kommando microk8s.helm3 statt helm3 verwenden falls Du microk8s nutzt.
helm3 repo add grafana https://grafana.github.io/helm-charts
helm3 repo update

Grafana, Loki, Prometheus, Fluent.d und den Alertmanager installieren

Um eine Anwendung zu testen starte ich gerne auf der Kommandozeile und Default Parametern in den Namespace

# Namespace anlegen microk8s.kubectl statt kubectel verwenden falls Du microk8s nutzt.
kubectl create  namespace monitoring

helm upgrade --install loki grafana/loki-stack \
  --set fluent-bit.enabled=true,promtail.enabled=false,grafana.enabled=true,prometheus.enabled=true,prometheus.alertmanager.persistentVolume.enabled=false,prometheus.server.persistentVolume.enabled=false

Nach ein paar Minuten sollten alle Pods hochgefahren sein und die Anwendung verfuegbar und man kann sich ein nacktes Grafana anschauen

# Passwort besorgen 

kubectl get secret --namespace monitoring loki-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

# Port forwarden 
kubectl port-forward --namespace monitoring service/loki-grafana 3000:80


Jetzt solltest Du dich auf http://localhost:3000/ mit username: admin und dem Passwort aus der Commandline anmelden koennen.

Anwendung loeschen

microk8s.helm3 uninstall loki  -n monitoring

Anwendungskonfiguration als Yaml File

Cli Parametern sind pratkisch um mal etwas auszuprobieren aber bei komplexeren Einstellungen wird es schnell unuebersichtlich und eine Textdatei die man Versionieren kann ist ungemein praktischer. In ~ 50 Zeilen Yaml kann man die gleichen Einstellungen deployen mit ein paar Extras wie:

  • Plugins installieren
  • Admin Passwort setzen (bitte nur auf localhost so laufen lassen)
  • Vorgefertigte oder selbst erstellte Dashboards installieren

values.yaml

fluent-bit:
  enabled: true
promtail:
  enabled: false
grafana:
  enabled: true
  persistence:
    enabled: false 
  adminPassword: 1q2w3e4r
  plugins:
  - grafana-piechart-panel
  dashboardProviders:
    dashboardproviders.yaml:
      apiVersion: 1
      providers:
        - name: default
          orgId: 1
          folder:
          type: file
          disableDeletion: true
          editable: false
          options:
            path: /var/lib/grafana/dashboards/default
  dashboards:
    default:
      Logging:
        gnetId: 12611
        revison: 1
        datasource: Loki
      K8health:    
        gnetId: 315
        revison: 1
        datasource: Prometheus
      ApiServer:  
        gnetId: 12006
        revison: 1
        datasource: Prometheus
prometheus:
  enabled: true
  server:
    persistentVolume:
      enabled: false
  alertmanager:
    persistentVolume:
      enabled: false

Nun den Stack erneut installieren

microk8s.helm3 upgrade --install --namespace=monitoring loki grafana/loki-stack  -f values.yaml

Analog zu den Dashboards koennen alle moeglichen und unmoeglichen Settings der Anwendung, Versionen bis zu persistant Volumes konfiguiert werden. Mir gefaellt besonders, dass wesentlich weniger Yaml produziert werden muss als z.B. bei Kustomize und man sehr schnell von „ich bastel ein wenig rum“ in den Zustand „ich habe eine wiederverwendbare Komponente die ich leicht modifizieren kann“ iterieren kann. Im naechsten Schritt kann man dann z.B. mit ArgoCD wunderbar continuous Delivery betreiben.

Jupyter Notbooks in Virtualenvs ausführen

1. Virtualenv anlegen

virtualenv   -p python3 venv    #if python2 is the default interpreter

2. Libraries installieren (ipykernel nicht vergessen)

pip3 install -r requirements.txt #dependencies

3. Kernel anlegen

python -m ipykernel install --user --name=venv

4. Virtual Env aktivieren

source venv/bin/activate

5. Im Playbook unter Kernel -> Change Kernel den Kernel venv wählen
6. Session beenden wenn Du fertig bist

deactivate

SSh Client updaten

Mein Linux Mint ist mittlerweile recht antiquiert und ebenso die Software die mitkommt. Auf ein Distupgrade habe ich keine Lust, da die / Partition voll ist und mein Rechner mit 4 Jahren bald ersetzt wird.
Mit Linux Mint 17 bekommt man openssh in der Version 6.6 aber ich brauche das super coole Feature Proxy Jump. Der Spaß ist recht schnell kompiliert:

wget http://mirror.exonetric.net/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
tar -zxvf openssh-7.5p1.tar.gz
 cd openssh-7.5p1/
./configure
make

Da ich nur den ssh client benötige mache ich statt

sudo make install

einfach

sudo cp ssh /usr/bin/

Fertig.

ms@w530~/Downloads/openssh-7.5p1$ ssh -V
OpenSSH_7.5p1, OpenSSL 1.0.2l  25 May 2017

Debian Datum einstellen

Wenn die Uhr nicht korrekt auf dem Rechner eingestellt ist geschehen die lustigsten Dinge.
Deshalb synchronisiert sich Debian Linux per NTP gegen einen Zeitserver. Manchmal muß man jedoch an sowas vorbei arbeiten.
Um ein Script in der Vergangenheit oder Zukunft auszuführen muß NTP deaktiviert werden.

Unter Debian / Ubuntu funktioniert das so:

root@server:~# timedatectl set-ntp 0
root@server:~# timedatectl set-time 2017-06-20
root@server:~# date
Tue Jun 20 00:00:01 CEST 2017

Im Anschluss auf keinen Fall vergessen NTP wieder zu aktivieren und die Uhr prüfen!

root@server:~# timedatectl set-ntp 1
root@server:~# date
Wed Jun 21 12:30:45 CEST 2017

Ansible Vaults bequem editieren

Ansible ist eine ganz nette Software um Serverlandschaften deklarativ zu administrieren /konfigurieren. Nicht so schön ist die Tatsache, daß alles per Yaml definiert wird – einer Auszeichnungssprache die mit Einrückungen arbeitet. Unangenehm wird es, wenn man Vault Files editieren möchte. Diese sind mit einem Passwort verschlüsselt und müssen mit dem Befehl

ansible-vault

entschlüsselt werden. Im Erfolgsfall öffnet sich nun der Default Editor (konfiguriert über update-alternatives –config editor unter Debian oder Ubuntu).

Das funktioniert prinzipiell, ist aber sehr fehleranfällig beim Anlegen von neuen Strukturen. Um z.B. Sublime Text für das Editieren zu nutzen muß nur die EDITOR Variable gesetzt werden :

EDITOR="subl -n -w"

Um den Editor dauerhaft auf Sublime umzustellen muß die Variable nur in die Shell Konfiguration ( ~/.bashrc) eingetragen werden:

export EDITOR="subl -n -w"

Pipe Operator in Ansible nutzen

Ansible ist ein nettes Tools  um deklarativ Rechner über SSH zu administrieren oder aufzusetzen. Deklarativ meint, daß man Zustände definiert die Ansible herbeiführen soll. Man erklärt in Tasks welche Software auf einem Rechner installiert sein soll und Ansible führ diesen Zustand herbei.

 

Ab und an muß man diesen deklarativen Pfad leider verlassen. Im konkreten Beispiel müßen alte Datenbanken (benannt nach dem Schema YYYY-mm-dd-hh-i-s)  gelöscht werden damit das RDBMS nicht volläuft. Wichtig ist es statt command shell zu nutzen, da es sonst mit dem Pipen nicht klappt.

- name: remove old databases
  shell: mysql  --user={{ vault_mysql_server[env][tld].wp_content_user }} --password='{{ vault_mysql_server[env][tld].wp_content_password }}'  --batch --execute "show databases;" |grep wp_content|sort -hr  |tail -n+4|while read line; do mysql  --user={{ vault_mysql_server[env][tld].wp_content_user }} --password='{{ vault_mysql_server[env][tld].wp_content_password }}'  --batch --execute  "DROP DATABASE $line;" 2>/dev/null;done
  register: debugOutput

LibreELEC Kodi bequem updaten

Für bewegte Bilder auf der Glotze ist bei mir Kodi auf einem Raspberry Pi2 zuständig. Ich nutze dafür die Testbuilds von Milhouse weil man damit auch den Streaming Dienst von Amazon nutzen kann. Wie man das initial einrichtet erklärt Christoph von bei Linuxundich ganz gut. Da es sich um Testbuilds handelt macht es Sinn öfter mal ein Update einzuspielen. Wenn man das händisch macht ist es halt mit viel Arbeit verbunden und Arbeit sollten man vermeiden wo es nur geht. Deshalb habe ich ein kleines Script gebastelt welches mir diese Arbeit abnimmt. Die Geschichte ist natürlich bei weitem nicht perfekt. Ich habe aufgehört als es funktioniert hat.

Wichtig: mein Script lädt die Files für den Raspberry PI2 /3 wenn du einen Raspberry Pi 1 hast solltest Du dieses Script auf keinen Fall verwenden!

Benötigte Software

Um das ganze zum laufen zu bringen brauchst du PHP und Bash.

Die Scripte liegen in einem Repo auf Github wenn Du dir das genauer anschauen magst.

Falls nicht mußt du nur dieses Phar File  und ein Bash Script downloaden und in ein bin folder legen – $USER/bin wäre ein schöner Ort dafür.

Nun muß noch das executable flag gesetzt werden:

chmod +x today-release.phar 
chmod +x update-kodi.sh

Nutzung

Einfach das Script mit dem ssh String zum Raspberry aufrufen

update-kodi.sh kodi@192.168.0.89

Tipps zur Nutzung

  • Generell solltest Du Deinen public SSH Key auf dem Raspberry ablegen. Das erspart das Eintippen des Passworts bei jedem Aufruf. Zu dem Thema gibt es wohl eine Million Blog Posts. Hier ist einer.
  • Das SSH Config File nutzen.
  • Einen Cronjob in der Nacht einrichten der dann die Arbeit für einen erledigt –  00 00 * * * $HOME/bin/update-kodi.sh kodi

Was noch fehlt

Die Geschichte mit den Scripts ist schon ein wenig smelly. Eigentlich wollte ich mir das mit Ansible aufsetzen und das ADHS hat mich gezwungen  diese Scripte zu erzeugen. Sinnvoll wäre es auch, wenn vor dem Einspielen des Updates die /etc/issue geprüft wird um zu schauen ob das Gerät schon auf der aktuellen Version läuft.

Hat jemand einen Tipp für mich?

Ich parse die Url http://milhouse.libreelec.tv/builds/master/RPi2/ das ist ziemlich unelegant wie ich finde. Gibt es da vielleicht eine Art Http Standard um sowas direkt lesen zu können?

 

Bash – Touchpad bei Bedarf deaktivieren

Langjährige Büroarbeit hat mir einen ordentlichen Bauch verschafft der ab und an die Kontrolle über die Tasten meines Touchpads übernimmt. Abhilfe schafft hier Sport und ein kleines Script um das Touchpad bei Bedarf zu deaktivieren. Das Script liest den Status des Touchpads aus und invertiert ihn.

Quelle: Lenovo.com
Quelle: Lenovo.com
#!/bin/bash
 
DEVICE_NAME="SynPS/2 Synaptics TouchPad"
 
#read state of the touchpad
STATE=$(xinput list-props "$DEVICE_NAME" |grep "Device Enabled"|cut -f3)
#flip the variable
if [ $STATE = 1 ]; then
	echo disable $DEVICE_NAME
	STATE=0
    else
       	STATE=1
       	echo enable $DEVICE_NAME
fi
 
xinput set-prop  "$DEVICE_NAME"  "Device Enabled" $STATE