Backup mein Kubernetes - aber sicher!

Mit einiger Zeit sammelt sich so einiges im Kubernetes Cluster an. Sachen, die man nicht sauber mit Helm installiert hat. Oder die man irgendwie mal zwischendurch zum Ausprobieren geaendert und dann doch so belassen hat. Uns graust es schon vorm Datenverlust. Wie kann man sowas alles backupen?

Posted by on October 04, 2019 · 5 mins read

Wie wir vielleicht schon mitgekriegt haben, liegen die meisten Resourcen in Kubernetes in YAML-Files vor, Damit wurden zumindest die meisten Sachen erzeugt - sei es als HELM Chart oder einfaches Deployment-File - local oder uebers Netz, einerlei. Schmutzig, klein, genial ist Kube-Backup, ein handliches Backup-Tool fuer Kubernetes. Voraussetzungen sei wieder unser laufender Minikube, es funktioniert aber mit jedem anderen Kubernetes. Zielbild ist es, unsere Cluster-Konfiguration auf Github zu sichern. Immer und immer wieder. Wie machen wir das? Mit einem Cronjob! Also automatisch. Als erstes erstellen wir ein SSH-Schluesselpaar auf unserem, Minikuberechner:

  ssh-keygen -f ./id_rsa
  ssh-keyscan github.com > known_hosts

Dann erstellen wir ein neues Github-Repository. Gerne auch private, denn das ist neuerdings erlaubt. Unter Deploykeys fuegen wir den frisch erstellten SSH-Public-Key unseres Minikuberechners id_rsa.pub hinzu.

Vom SSH-Private-Key und die known_hosts erstellen wir eine Secret im Kubernetes:

kubectl create secret generic kube-backup-ssh -n kube-system --from-file=id_rsa --from-file=known_hosts

Wenn wir schon dabei sind, erstellen wir noch mit gpg --generate-keyden GPG-Key und auch daraus ein Secret:

gpg --export-secret-key -a root@minikube > gpg-private.key
kubectl create secret generic kube-backup-gpg -n default --from-file=gpg-private.key

Das Git-Repo mit unserer Kubernetes ist mit git-crypt entsprechend zu preparieren:

cd git/minikube
git-crypt init
cat <EOF> .gitattributes
*.secret.yaml filter=git-crypt diff=git-crypt
.gitattributes !filter !diff
EOF
git-crypt add-gpg-user eumel@eumel.gnuu.de

Den GPG-Public-Key vom Minikube-Rechner muss ich erstmal von seinem GPG-Schluesselbund exportieren:

gpg --armor --output minikube.asc --export root@minikube

und in meinem persoenlichen GPG-Schluesselbund importieren:

gpg --import minikube.asc

Um den Schluessel wie oben mit git-crypt add-gpg-user root@minikube hinzufuegen zu koennen, muss ich diesem noch vertrauen:

gpg --edit-key root@minikube
>trust
5
y

Zurueck zum Kubernetes-Cluster. Dort gibts erstmal ein ClusterRoleBinding zu deployen:

rbac.yaml

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kube-backup-reader
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs: ["get", "list"]
---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kube-backup
  namespace: default
subjects:
- kind: ServiceAccount
  name: kube-backup
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: kube-backup-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f rbac.yaml

Nun das Herzstueck, der CronJob:

cronjob-ssh.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kube-backup
  namespace: kube-system

---

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: kube-state-backup
  namespace: kube-system
  labels:
    app: kube-backup
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Replace
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: kube-backup
          name: kube-backup
        spec:
          containers:
         # - image: quay.io/plange/kube-backup:1.12.0-1
          - image: eumel8/kube-backup:latest
            imagePullPolicy: Always
            name: backup
            resources: {}
            securityContext:
              runAsUser: 1000
            env:
            - name: GIT_REPO
              value: "git@github.com:eumel8/minikube.git"
            - name: GITCRYPT_ENABLE
              value: "true"
            - name: RESOURCETYPES
              value: "ingress deployment configmap secret pvc svc rc ds thirdpartyresource networkpolicy statefulset storageclass cronjob"
            volumeMounts:
            - mountPath: /backup/
              name: cache
            - mountPath: /backup/.ssh
              name: sshkey
            - mountPath: /secrets
              name: gpgkey
          dnsPolicy: ClusterFirst
          terminationGracePeriodSeconds: 30
          serviceAccountName: kube-backup
          volumes:
          - name: sshkey
            secret:
              defaultMode: 420
              secretName: kube-backup-ssh
          - name: gpgkey
            secret:
              defaultMode: 420
              secretName: kube-backup-gpg
          - name: cache
            emptyDir: {}
          restartPolicy: OnFailure

Ein kurzer Blick auf das File, bevor wir es deployen. Ich habe das Repo geforked und ein eigenes Image gebaut und auf Dockerhub gepusht. Das geht mit ein paar Klicks, wenn man einen Gihub- und einen Dockerhub-Account hat. Im Dockerfile kann man schauen, was im Image alles drin ist. Ich habe Alpine und git-crypt aktualisiert. Auch eine neuere Version von kubectl ist drin. Ob es das richtige File aus dem Internet ist, wird mit einer sha256 Pruefsumme ueberprueft. So kann uns niemand Schadsoftware unterschieben.

Das entrypoint.sh ist die Arbeitslogik. Dort habe ich mal ein paar Debug-Marker gesetzt, als es auf den ersten Versuch nicht funktionierte. Sollte aber wieder alles wie im Original sein und veranschaulichen, wie so ein Dockerfile bzw. Docker-Image fuer Kubernetes funktioniert.

Jetzt aber los:

kubectl apply -f cronjob-ssh.yaml

Wenn alles klappt, sollte in unserem Github-Repository mit der Kubernetes-Config etwas passieren.

commit d685ad67d808842258622a6f07326b5c0c3f2a56
Author: kube-backup <kube-backup@example.com>
Date:   Fri Oct 4 18:23:32 2019 +0000

    Automatic backup at Fri Oct  4 18:23:32 UTC 2019

Auf der Github-Webseite sollten wir nur Datensalat bei den Secrets sehen (bin). Im Klartext (also base64 Inhalt) sehen wir die Dateien auf unserem eigenen Rechner und auf dem Minikube-Rechner wegen git-crypt.

Im Cluster sehen wir sowas wie

# kubectl get jobs -n kube-system
NAME                           COMPLETIONS   DURATION   AGE
kube-state-backup-1570216080   1/1           29s        3m3s
kube-state-backup-1570216140   1/1           29s        2m3s
kube-state-backup-1570216200   1/1           29s        63s
kube-state-backup-1570216260   0/1           3s         3s

Sicherlich keine perfekte Loesung, aber eine automatisierte. Ueber die Variable RESOURCETYPES kann ich noch steuern, welche Resourcetypen ich backupen moechte. Eine kleine Auswahl ist schon dabei.

Credits gehen an https://github.com/pieterlange https://www.fullstaq.com https://twitter.com/fullstaq