Jenseits der Basis von GitHub Actions
Die meisten Teams starten mit GitHub Actions, indem sie einen Workflow aus einem Blogpost kopieren — build, test, deploy. Das funktioniert, und für ein einzelnes Repository ist das auch völlig in Ordnung. Doch sobald Ihre Organisation auf Dutzende oder Hunderte von Repos skaliert, stoßen Sie auf reale Probleme:
- Duplizierte Workflow-Dateien über jedes Repository hinweg
- Inkonsistente CI/CD-Praktiken — jedes Team erfindet eigene Patterns
- Langsame Builds, weil jeder Job sequenziell auf den Default-Runnern läuft
- Steigende Kosten durch GitHub-Hosted-Runner-Minuten
- Sicherheitslücken, weil Secrets-Management ad hoc erfolgt
Dieser Leitfaden behandelt fortgeschrittene Patterns, die diese Probleme skalierbar lösen.
Schritt 1: Reusable Workflows aufbauen
Reusable Workflows sind GitHub Actions' Antwort auf DRY (Don't Repeat Yourself). Sie definieren einen Workflow einmal in einem zentralen Repository und rufen ihn aus jedem anderen Repo auf.
Erstellen Sie einen geteilten Workflow in Ihrem .github-Organisations-Repository:
# .github/workflows/docker-build-push.yml
# Organization: myorg/.github repository
name: Docker Build and Push
on:
workflow_call:
inputs:
image_name:
required: true
type: string
description: "Docker image name (e.g., myorg/payment-service)"
dockerfile:
required: false
type: string
default: "Dockerfile"
context:
required: false
type: string
default: "."
platforms:
required: false
type: string
default: "linux/amd64"
push:
required: false
type: boolean
default: true
secrets:
REGISTRY_USERNAME:
required: true
REGISTRY_PASSWORD:
required: true
outputs:
image_digest:
description: "The image digest"
value: ${{ jobs.build.outputs.digest }}
image_tag:
description: "The image tag"
value: ${{ jobs.build.outputs.tag }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build-push.outputs.digest }}
tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.image_name }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and Push
id: build-push
uses: docker/build-push-action@v5
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ inputs.image_name }}@${{ steps.build-push.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.spdx.json
Jedes Repository in Ihrer Organisation ruft diesen Workflow nun mit minimaler Konfiguration auf:
# payment-service/.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- run: go test ./...
build:
needs: test
uses: myorg/.github/.github/workflows/docker-build-push.yml@main
with:
image_name: myorg/payment-service
platforms: "linux/amd64,linux/arm64"
secrets:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
deploy:
needs: build
if: github.ref == 'refs/heads/main'
uses: myorg/.github/.github/workflows/deploy-k8s.yml@main
with:
service: payment-service
image_tag: ${{ needs.build.outputs.image_tag }}
environment: production
secrets: inherit
Der aufrufende Workflow ist klar, lesbar und konsistent über jeden Service in Ihrer Organisation hinweg.
Schritt 2: Dynamische Matrix-Strategien
Matrix-Builds ermöglichen es Ihnen, parallel über mehrere Konfigurationen hinweg zu testen. Die wahre Stärke entfaltet sich mit dynamischen Matrizen, die zur Laufzeit generiert werden.
Beispiel für eine statische Matrix
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [18, 20, 22]
exclude:
- os: macos-latest
node-version: 18
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
Dynamische Matrix aus geänderten Dateien
Dieses Pattern führt Tests nur für geänderte Services aus — entscheidend in Monorepos:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.changes.outputs.matrix }}
has_changes: ${{ steps.changes.outputs.has_changes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect changed services
id: changes
run: |
# Find which service directories have changes
SERVICES=""
for dir in services/*/; do
service=$(basename "$dir")
if git diff --name-only origin/main...HEAD | grep -q "^services/${service}/"; then
SERVICES="${SERVICES}\"${service}\","
fi
done
if [ -n "$SERVICES" ]; then
# Remove trailing comma and build matrix JSON
SERVICES="${SERVICES%,}"
echo "matrix={\"service\":[${SERVICES}]}" >> "$GITHUB_OUTPUT"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
test:
needs: detect-changes
if: needs.detect-changes.outputs.has_changes == 'true'
strategy:
matrix: ${{ fromJson(needs.detect-changes.outputs.matrix) }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/${{ matrix.service }}
steps:
- uses: actions/checkout@v4
- run: make test
- run: make lint
In einem Monorepo mit 20 Services bedeutet das: Eine Änderung am payment-service testet nur diesen Service, nicht alle 20. Die Build-Zeiten fallen von 30 Minuten auf 3.
Schritt 3: Self-Hosted Runner auf Kubernetes
GitHub-Hosted Runner sind bequem, aber in der Skalierung teuer und in der Anpassbarkeit eingeschränkt. Self-Hosted Runner auf Kubernetes geben Ihnen Kontrolle über Kosten, Performance und Sicherheit.
Der Actions Runner Controller (ARC) ist die offizielle Möglichkeit, Self-Hosted Runner auf Kubernetes zu betreiben:
# Install ARC using Helm
helm install arc \
--namespace arc-systems \
--create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
# Create a GitHub App for authentication (more secure than PAT)
# Then configure the runner scale set
helm install arc-runner-set \
--namespace arc-runners \
--create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
--set githubConfigUrl="https://github.com/myorg" \
--set githubConfigSecret.github_app_id="12345" \
--set githubConfigSecret.github_app_installation_id="67890" \
--set githubConfigSecret.github_app_private_key="$(cat private-key.pem)" \
--set maxRunners=20 \
--set minRunners=2
Für feingranulare Kontrolle definieren Sie Runner Scale Sets mit Custom Resources:
# runner-scale-set-values.yaml
githubConfigUrl: "https://github.com/myorg"
maxRunners: 20
minRunners: 2
containerMode:
type: "kubernetes"
kubernetesModeWorkVolumeClaim:
accessModes: ["ReadWriteOnce"]
storageClassName: "gp3"
resources:
requests:
storage: 10Gi
template:
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
resources:
limits:
cpu: "4"
memory: 8Gi
requests:
cpu: "2"
memory: 4Gi
nodeSelector:
workload-type: ci-runners
tolerations:
- key: "ci-runners"
operator: "Equal"
value: "true"
effect: "NoSchedule"
Kostenoptimierung mit Spot Instances
Betreiben Sie Ihre CI-Runner auf Spot-/Preemptible-Instances und sparen Sie 60–90 % der Kosten:
# eks-nodegroup-spot.yaml (for AWS EKS)
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ci-cluster
region: us-east-1
managedNodeGroups:
- name: ci-runners-spot
instanceTypes:
- m5.xlarge
- m5a.xlarge
- m6i.xlarge
capacityType: SPOT
minSize: 0
maxSize: 30
desiredCapacity: 2
labels:
workload-type: ci-runners
taints:
- key: ci-runners
value: "true"
effect: NoSchedule
CI-Workloads sind ideal für Spot Instances, weil sie kurzlebig, zustandslos sind und Unterbrechungen tolerieren können (der Job wird einfach wiederholt).
Schritt 4: Composite Actions für gemeinsame Steps
Während Reusable Workflows ganze Pipelines teilen, teilen Composite Actions einzelne Steps. Verwenden Sie sie für gängige Patterns wie Setup, Caching und Notifications:
# .github/actions/setup-go-project/action.yml
name: "Setup Go Project"
description: "Checks out code, sets up Go, restores cache, and installs dependencies"
inputs:
go-version:
description: "Go version to install"
required: false
default: "1.22"
runs:
using: "composite"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ inputs.go-version }}-
- name: Download dependencies
shell: bash
run: go mod download
- name: Install tools
shell: bash
run: |
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install gotest.tools/gotestsum@latest
In jedem Workflow einsetzbar:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: myorg/.github/.github/actions/setup-go-project@main
with:
go-version: "1.22"
- run: gotestsum --format pkgname ./...
- run: golangci-lint run
Schritt 5: Security-Hardening
Fortgeschrittene GitHub-Actions-Sicherheit geht über einfaches Secret-Management hinaus:
Actions an SHA-Hashes pinnen
# Bad: vulnerable to tag hijacking
- uses: actions/checkout@v4
# Good: pinned to exact commit SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Nutzen Sie Dependabot, um gepinnte SHAs aktuell zu halten:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
Workflow-Berechtigungen einschränken
Wenden Sie das Prinzip der minimalen Rechte an:
# At the workflow level
permissions:
contents: read
packages: write
# Or at the job level for more granularity
jobs:
deploy:
permissions:
contents: read
id-token: write # For OIDC authentication
steps:
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1
OIDC-Authentifizierung (keine langlebigen Secrets mehr)
Ersetzen Sie statische Cloud-Credentials durch OIDC-Föderation:
# AWS: Create an IAM OIDC provider for GitHub Actions
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
Das beseitigt die Notwendigkeit, AWS-Access-Keys als GitHub Secrets zu speichern, vollständig.
Performance-Tipps
- Caching aggressiv nutzen —
actions/cachefür Abhängigkeiten, Docker-Layer-Caching für Builds - Unabhängige Jobs parallel laufen lassen — Verketten Sie keine Jobs, die keine echte Abhängigkeit haben
paths- undpaths-ignore-Trigger nutzen — Überspringen Sie CI bei reinen Dokumentationsänderungen- Concurrency-Gruppen setzen — Laufende Runs abbrechen, wenn neue Commits gepusht werden
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths-ignore:
- "docs/**"
- "*.md"
- ".github/ISSUE_TEMPLATE/**"
Fazit
GitHub Actions ist weitaus mächtiger, als die meisten Teams denken. Durch Investitionen in Reusable Workflows, dynamische Matrizen, Self-Hosted Runner und solides Security-Hardening verwandeln Sie Ad-hoc-CI/CD in eine skalierbare, kosteneffiziente Plattform, die Ihrer gesamten Engineering-Organisation dient.
Bei DevOpsVibe helfen wir Teams, GitHub-Actions-Pipelines zu designen und umzusetzen, die vom Start-up bis zum Enterprise skalieren. Von der Migration weg von Jenkins über den Aufbau von Reusable-Workflow-Bibliotheken bis zum Deployment von Self-Hosted Runnern auf Kubernetes bringen wir praxiserprobte Patterns mit, die Ihre Entwicklungsgeschwindigkeit beschleunigen. Sprechen Sie mit unseren CI/CD-Experten.