Blog

Hosten von GitHub-Runnern auf Kubernetes

Rob Bos

Aktualisiert Oktober 20, 2025
9 Minuten

Wenn Sie Ihr eigenes Hosting benötigen GitHub-Aktionen Runner, die Ihren Workflow ausführen sollen, haben Sie mehrere Möglichkeiten: Sie können den traditionellen Weg gehen und sie auf Ihren eigenen VMs hosten. Sie können sogar mehrere Runner auf derselben VM hosten, wenn Sie die Dichte erhöhen und Ihre CPU- oder RAM-Kapazität auf Ihrer VM wiederverwenden möchten. Der erste Nachteil ist, dass VMs schwieriger zu skalieren sind und oft etwas teurer sind als andere Optionen. Ein noch wichtigerer Grund, VMs nicht zu verwenden, ist die Tatsache, dass sie keine gute Option für eine saubere Umgebung für jeden Lauf sind: GitHub Actions hinterlässt mehrere Dateien auf der Festplatte, die wiederverwendet werden können (z.B. die heruntergeladenen und verwendeten Actions oder die Docker-Images, auf denen Sie laufen). Auch die Zur Kasse Aktion bereinigt den Quellcode nur, wenn sie ausgeführt wird, um sicherzustellen, dass die letzten Änderungen ausgecheckt werden. Sie können eine Bereinigungsaktion am Ende einfügen, aber oft wird diese nicht hinzugefügt.

Noch schlimmer sind die Potenzielle Sicherheitslücken die sich aus der Wiederverwendung einer Umgebung zwischen verschiedenen Durchläufen eines Workflows oder verschiedenen Workflows in verschiedenen Repositories ergeben: Der erste Durchlauf könnte einige Dateien zurücklassen, z.B. von einem Paketmanager, den Sie verwenden, oder ein lokales Docker-Image überschreiben. Nachfolgende Läufe auf diesem Rechner werden zuerst im lokalen Cache nachsehen und die (möglicherweise) kompromittierten Dateien verwenden. Dies sind einige der Beispiele für Angriffe in der Lieferkette, die mehr und mehr heutzutage üblich.

Um diese Risiken zu bekämpfen, sollten Sie ephemere Läufer einsetzen: Die Umgebung existiert nur während der Ausführung des Workflows: Nach der Ausführung wird alles wieder aufgeräumt. Das bedeutet allerdings, dass das Zwischenspeichern von Daten etwas schwieriger wird. Es gibt Möglichkeiten, dem entgegenzuwirken, z.B. mit einem Proxy in der Nähe Ihrer Umgebung, der die Zwischenspeicherung für Sie übernehmen kann (Achtung: dies ist immer noch ein potenzielles Risiko!).

Foto von Luftballons vor einem blauen Himmel
Foto von iandooley auf Unsplash

Flüchtige Läufer

GitHub bietet keine Unterstützung für das Hosten Ihres Runners innerhalb eines Containers mit einigen 'bring your own compute' Optionen. GitHub verwendet dieses Setup für die von GitHub gehosteten Runner, bei dem eine Runner-Umgebung nur für Ihren Lauf erstellt und anschließend zerstört wird, hat aber nichts für seine Kunden veröffentlicht. Wenn Sie nach Optionen suchen, finden Sie eine von der Community kuratierte Liste von Tolle Läufer die von der Community erstellt wurden, um Ihre Runner in k8s, AWS EC2, AWS Lambda's, Docker, GKE, OpenShift oder Azure VM's zu hosten (zum Zeitpunkt des Schreibens).

Aktionen Läufer Controller

Derjenige, der mir empfohlen wurde, war der actions-runner-controller: er hatte eine aktive Community (82 Mitwirkende, einschließlich mir! viele Sterne) mit viel Kommunikation auf der Issues- und Diskussionsliste.

Hosten in Azure Kubernetes Service

Um zu testen, ob ich die Dinge mit meinen minimalen k8s-Kenntnissen zum Laufen bringen kann, habe ich ein Azure Kubernetes Dienst Cluster mit allen Standardeinstellungen erstellt und den Actions Runner Controller darin installiert, mit allen Informationen aus dem Projekt. Ich habe sogar eine GitHub-App für die Authentifizierung verwendet und alles hat sofort funktioniert. Sie können nur einen Runner für schnelle Tests verwenden oder die eingebauten Skalierungsoptionen nutzen, um zwischen den von Ihnen festgelegten Grenzen zu skalieren (z.B. 0 Runner, wenn es nichts zu tun gibt, und 100 Runner als Maximum) oder auf der Grundlage von Zeitfenstern, die Sie definieren, zu skalieren (z.B. bis zu 30 Runner um 7 Uhr morgens an Werktagen, wenn Ihr Unternehmen noch an traditionelle Zeitfenster gebunden ist, an denen die Mitarbeiter arbeiten).

Eine Bemerkung zur Skalierung

Beim Testen habe ich festgestellt, dass die Skalierungsoptionen für actions-runner-controller haben einen Nachteil: Sie können nur die aktuelle Warteschlange von Arbeitsabläufe und nicht die Menge der Arbeitsplätze innerhalb dieser Workflows. Das liegt daran, dass GitHub derzeit das Laden der Warteschlange auf der Grundlage von Aufträgen innerhalb von Workflows nicht unterstützt. Es wird zwar an der Entwicklung dieser Funktion gearbeitet, aber ich habe noch keine Fortschritte gesehen.

Hosting auf internem Rancher-Server

Mein Kunde, der diese Einrichtung für seinen eigenen GitHub Enterprise Server (GHE) haben wollte, wollte auch einen lokalen Runner haben. Das Sicherheitsteam wollte auch eine Sicherheitsüberprüfung der Einrichtung durchführen und wollte dafür nicht AKS verwenden (und Microsoft über aktive Pen-Tests auf dem Cluster informieren). Sie hatten ein internes Rancher Setup zur Verfügung, das ich verwenden sollte. Die Sache ist die, dass dieser Cluster bereits sehr eingeschränkt war: Er konnte nur interne Docker-Images ziehen und hatte eine Menge anderer Einschränkungen, wie die Verwendung eines Proxys für den gesamten Datenverkehr. An dieser Stelle wurden die Dinge ein wenig komplizierter. Ihr interner Images-Host war eine private Registry, die auf Artifactory gehostet wurde, und das Abrufen von öffentlichen Container-Registries war nicht möglich.

Konfigurieren der vom Controller Manager verwendeten Bilder

Das erste Problem, auf das ich stieß, war, dass unsere Rancher-Einrichtung hinter einem internen Proxy/Load Balancer saß, der alle Bilder von einer internen Artifactory-Registry herunterladen musste. Abhängig von der ursprünglichen Registrierung wurde in der Liste der zulässigen Bilder in Artifactory nachgeschaut. Dies waren die Bilder, die wir zur Liste der erlaubten Bilder in Artifactory hinzufügten:

  • summerwind/actions-runner-controller:v0.18.2
  • sommerwind/actions-runner:v0.18.2
  • quay.io/brancz/kube-rbac-proxy:v0.8.0
  • docker:dind

Das einzige Bild, das mit unserem Setup nicht transparent gezogen werden konnte, war das Bild von quay.io: Diese Registrierung wurde nicht transparent gespiegelt, was bedeutete, dass die Bezeichnung in Artifactory anders war. Als erste Lösung habe ich mich entschieden, den Image-Namen zu überschreiben manuell nachdem es bereitgestellt wurde, mit diesem Befehl:

kubectl set image deployment/controller-manager kube-rbac-proxy=registry.artifactory.mydomain.com/brancz/kube-rbac-proxy:v0.8.0 -n actions-runner-system

Das bedeutet, dass dem Controller-Manager-Einsatz ein neues Image mit dem Namen kube-rbac-proxy zugewiesen wird und dieser Container neu geladen wird. Danach liefen die Dinge tatsächlich und ich konnte den Runner entweder auf der Organisations- oder der Repository-Ebene zur Verfügung stellen.

Docker in Docker (DinD) mit internen Zertifikaten

Unser Rancher-Setup verwendete einen internen Proxy, um unsere Images von einem Artifactory-Server zu beziehen, der mit einem internen Zertifikat signiert war (ohne vollständige Vertrauenskette). Das bedeutete, dass der Docker-Client, mit dem die Images abgerufen wurden, so konfiguriert werden musste, dass er auch dem internen Zertifikat vertraute, sonst gab es nur Abruffehler mit einem nicht vertrauenswürdigen Zertifikat. Um dies zu erreichen, habe ich einen neuen DinD-Container auf unseren Runnern erstellt, der auf einer VM lief und die Zertifikate lokal installiert hatte.

Action.yml, die das Image erstellt:

    - uses: actions/checkout@v2
    - name: Build
      run: |
        cd dind
        # certs on RHEL are found here:
        cp -R /etc/pki/ca-trust/source/anchors/ certificates/
        docker build -t ${DIND_NAME}:${TAG} -f Dockerfile --build-arg http_proxy="$http_proxy" --build-arg https_proxy="$http_proxy" --build-arg no_proxy="$no_proxy" .

So können wir den lokalen Ordner certificates in das Image laden (beachten Sie, dass Sie auch den kommentierten RUN-Befehl verwenden können, um ein bestimmtes Zertifikat fest zu programmieren):

FROM docker:dind

# Add the certs from the VM we are running on to this container for secured communication with Artifactory
COPY /certificates /etc/ssl/certs/

# add the crt to the local certs in the image and call system update on it:
#RUN cat /usr/local/share/ca-certificates/docker_registry.crt >> /etc/ssl/certs/ca-certificates.crt
RUN update-ca-certificates

Hinweis: auch mit $DOCKER_TLS_CERTDIR getestet: hat nicht funktioniert

# Add the certs to this image for secured communication with Artifactory
COPY docker_registry.crt $DOCKER_TLS_CERTDIR # Docker should load the certs from here, didn't work

Hinweis: auch mit daemon.json getestet: hat nicht funktioniert

Ich habe auch getestet, indem ich eine daemon.json hinzugefügt habe

{
    "insecure-registries" : []
}

Und kopieren Sie dann diese json-Datei rüber:

COPY daemon.json /etc/docker/daemon.json # see https://docs.docker.com/registry/insecure/,  didn't work

Irgendwie schien diese Datei ignoriert zu werden und das Abrufen der Bilder aus der internen Registrierung schlug trotzdem fehl.

Laden des neuen DinD-Images

Das Laden des neuen DinD-Images konnte nicht mit demselben 'Hack' durchgeführt werden, der für das Image von quay.io verwendet wurde. Nachdem ich mich mit dem Gemeinschaft Sie halfen mir dabei, die Controller-Manager-Installation mit dem Image von quay.io und das neue DinD Bild:

# controller-manager.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    control-plane: controller-manager
  name: controller-manager
  namespace: actions-runner-system
spec:
  replicas: 1
  selector:
    matchLabels:
      control-plane: controller-manager
  template:
    metadata:
      labels:
        control-plane: controller-manager
    spec:
      containers:
      - args:
        - --metrics-addr=127.0.0.1:8080
        - --enable-leader-election
        - --sync-period=10m
        - --docker-image=registry.artifactory.mydomain.com/actions-runner-dind:latest
        command:
        - /manager
        env:
        - name: GITHUB_TOKEN
          valueFrom:
            secretKeyRef:
              key: github_token
              name: controller-manager
              optional: true
        - name: GITHUB_APP_ID
          valueFrom:
            secretKeyRef:
              key: github_app_id
              name: controller-manager
              optional: true
        - name: GITHUB_APP_INSTALLATION_ID
          valueFrom:
            secretKeyRef:
              key: github_app_installation_id
              name: controller-manager
              optional: true
        - name: GITHUB_APP_PRIVATE_KEY
          value: /etc/actions-runner-controller/github_app_private_key
        image: summerwind/actions-runner-controller:v0.18.2
        name: manager
        ports:
        - containerPort: 9443
          name: webhook-server
          protocol: TCP
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 100m
            memory: 20Mi
        volumeMounts:
        - mountPath: /tmp/k8s-webhook-server/serving-certs
          name: cert
          readOnly: true
        - mountPath: /etc/actions-runner-controller
          name: controller-manager
          readOnly: true
      - args:
        - --secure-listen-address=0.0.0.0:8443
        - --upstream=http://127.0.0.1:8080/
        - --logtostderr=true
        - --v=10
        image: registry.artifactory.mydomain.com/brancz/kube-rbac-proxy:v0.8.0
        name: kube-rbac-proxy
        ports:
        - containerPort: 8443
          name: https
      terminationGracePeriodSeconds: 10
      volumes:
      - name: cert
        secret:
          defaultMode: 420
          secretName: webhook-server-cert
      - name: controller-manager
        secret:
          secretName: controller-manager

Danach können wir unsere eigene Single-runner.yaml bereitstellen, die eine Option zur Angabe des für den Runner zu verwendenden Images enthält:

# runner.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: Runner
metadata:
  name: gh-runner
spec:
  repository: robbos/testing-grounds
  image: registry.artifactory.mydomain.com/actions-runner # overwriting the runner to use our own runner image, the DinD runner comes from the controller

Läufer reparieren

Ich musste auch das Runner-Image patchen, weil unser Setup einen tmp-Ordner auf einem anderen Gerät hatte, was zu einer Fehler während des Bootvorgangs des Containers. Ich habe die Container-Definition aus dem actions-runner-controller Repo kopiert und das Skript korrigiert, unser eigenes Image veröffentlicht und die Runner-Bereitstellung angewiesen, es zu verwenden, indem ich die Option spec.image wie in dem obigen Beispiel.

Andere Beobachtungen

Namespace

Es hat eine Weile gedauert, bis ich das herausgefunden habe: der Namespace actions-runner-system ist in allen Bereitstellungsdateien fest einkodiert, so dass Sie es nicht (einfach) ändern können. Denken Sie daran, wenn Sie z.B. in einem bereits bestehenden Namensraum mit internen Pod-Sicherheitsrichtlinien landen möchten.

Gemeinschaft

Die Community, die diese Läufer-Setups erstellt, ist sowohl bei der Erstellung der Lösungen als auch bei der Hilfe für andere aktiv. Die Tatsache, dass das verwendete Setup aktiv gepflegt wird und man mir bei meinen Fragen geholfen hat, ist ein großartiges Zeichen für eine gute Community. Ohne die Community hätte ich das nicht hinbekommen, also vielen Dank!

Zeichnung von zwei nebeneinander gehaltenen Händen in vielen Farben
Foto von Tim Mossholder auf Unsplash

Verfasst von

Rob Bos

Rob has a strong focus on ALM and DevOps, automating manual tasks and helping teams deliver value to the end-user faster, using DevOps techniques. This is applied on anything Rob comes across, whether it’s an application, infrastructure, serverless or training environments. Additionally, Rob focuses on the management of production environments, including dashboarding, usage statistics for product owners and stakeholders, but also as part of the feedback loop to the developers. A lot of focus goes to GitHub and GitHub Actions, improving the security of applications and DevOps pipelines. Rob is a Trainer (Azure + GitHub), a Microsoft MVP and a LinkedIn Learning Instructor.

Contact

Let’s discuss how we can support your journey.