Serverless Microservices: Skalierung in Google Cloud Run mit GitHub Actions

Management Summary
Google Cloud Run betreibt Anwendungen in der Cloud und passt die Zahl laufender Kopien an die Nachfrage an. Das verringert den Aufwand für eigene Server und eignet sich für Dienste mit wechselndem Verkehr. GitHub Actions automatisiert Vorbereitung und Bereitstellung neuer Versionen und macht Veröffentlichungen nachvollziehbarer. Sinnvolle Leistungs- und Kostengrenzen bleiben nötig: Automatische Anpassung ersetzt keine Überwachung, und Speicher oder weitere Dienste können auch im Leerlauf Kosten verursachen.
Inhalt
Google Cloud Run betreibt Container-Webdienste ohne eigenen Server- oder Kubernetes-Betrieb. Bei passender Konfiguration kann ein Dienst ohne Anfragen auf null Instanzen schrumpfen. Bei Last wächst die Zahl der Instanzen innerhalb der verfügbaren Kontingente und Skalierungsgrenzen. Eine Instanz ist eine laufende Kopie der Anwendung. Das Starten einer neuen Kopie braucht Zeit; diese Verzögerung heißt Kaltstart. Anwendungspflege und laufende Kosten anderer Dienste bleiben ebenfalls relevant.
Ein Image enthält die Anwendung und die für den Betrieb benötigten Dateien. Eine Revision ist eine bereitgestellte Version mit einem bestimmten Image und bestimmten Einstellungen. GitHub Actions kann Images bauen und über Workload Identity Federation (WIF) in Google Cloud bereitstellen. WIF ersetzt einen dauerhaft hinterlegten Google-Service-Account-Schlüssel durch kurzlebige Anmeldedaten. Kompromittierte Workflows, zu breite Rechte und versehentlich mitgepackte Zugangsdaten bleiben Risiken. Das folgende Beispiel trennt deshalb den Build von der Cloud-Anmeldung und stellt eine neue Revision zunächst ohne regulären Verkehr bereit.
1. Voraussetzungen und getrennte Identitäten
Vor dem Workflow müssen Projekt, Abrechnung, erforderliche APIs, Artifact-Registry-Repository services, Deployer-Konto und separates Laufzeitkonto existieren. Das Beispiel aktualisiert einen bereits angelegten Dienst api-service in us-central1. Dessen IAM-Aufrufprüfung ist aktiviert; öffentliche Aufrufrechte sind nicht vergeben. Die initiale Einrichtung und spätere Änderungen des öffentlichen Zugriffs erfolgen mit passenden administrativen Rechten außerhalb der Pipeline.
- Deployer:
ci-cd-deployerbenötigt Artifact Registry Writer auf dem Image-Repository, Cloud Run Developer auf dem bestehenden Dienst und Service Account User auf dem Laufzeitkonto. Writer umfasst auch die für das Deployment benötigten Leserechte auf Images. - Laufzeitkonto:
api-runtimeerhält nur die Rechte der Anwendung, beispielsweise Zugriff auf einen bestimmten Datenspeicher. Deployment-Rechte gehören nicht zu dieser Aufgabe. - Anwendung: Der Container muss auf
0.0.0.0:$PORTlauschen und Sitzungen oder dauerhafte Daten außerhalb des flüchtigen Containers speichern. Ein Dockerfile und ein projektspezifisches, ausführbares Testskriptci/test.shwerden vorausgesetzt.

2. WIF auf Organisation, Repository und Main-Branch begrenzen
Die folgenden Befehle richten eine neue Vertrauensbeziehung ein. Alle Beispielkennungen müssen vorher ersetzt werden. PROJECT_NUMBER ist die numerische Google-Projektnummer, keine Projekt-ID. OWNER_ID und REPOSITORY_ID sind die numerischen GitHub-Kennungen. Damit erhält ein anderes Konto keinen Zugriff allein dadurch, dass ein gelöschter Organisations- oder Repository-Name erneut vergeben wird.
#!/usr/bin/env bash
set -euo pipefail
# Replace all example identifiers before administrative setup.
PROJECT_ID="enterprise-ai-project"
PROJECT_NUMBER="1234567890"
OWNER_ID="11111111"
REPOSITORY_ID="22222222"
DEPLOYER="ci-cd-deployer@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam workload-identity-pools create github-actions-pool \
--project="$PROJECT_ID" --location=global \
--display-name="GitHub Actions Pool"
gcloud iam workload-identity-pools providers create-oidc github-provider \
--project="$PROJECT_ID" --location=global \
--workload-identity-pool=github-actions-pool \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository_id=assertion.repository_id,attribute.repository_owner_id=assertion.repository_owner_id,attribute.ref=assertion.ref,attribute.event_name=assertion.event_name" \
--attribute-condition="assertion.repository_owner_id == '${OWNER_ID}' && assertion.repository_id == '${REPOSITORY_ID}' && assertion.ref == 'refs/heads/main' && assertion.event_name == 'push'"
gcloud iam service-accounts add-iam-policy-binding "$DEPLOYER" \
--project="$PROJECT_ID" --role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-actions-pool/attribute.repository_id/${REPOSITORY_ID}"Die Provider-Bedingung erlaubt nur Push-Ereignisse im festgelegten Repository auf main. Der Workflow-Trigger allein wäre keine Google-seitige Zugriffsbeschränkung. Änderungen an WIF und IAM können verzögert wirksam werden. Die Bindung mit roles/iam.workloadIdentityUser erlaubt die Übernahme des Deployer-Kontos; die eigentlichen Ressourcenrechte bleiben zusätzlich erforderlich.
Branchschutz und überprüfte Änderungen an Workflow-Dateien bleiben wichtig: Auch ein anderer entsprechend berechtigter Workflow auf main kann dieselben Bedingungen erfüllen. Die GitHub-Umgebung production muss mit passenden Schutzregeln eingerichtet werden. Der bloße Umgebungsname im YAML erzeugt noch keine Freigaberegel.
3. Image bauen, anmelden und eine Revision vorbereiten
Vor dem ersten Lauf gehören gha-creds-*.json in .gitignore und die folgenden Ausschlüsse in .dockerignore. Weitere Geheimnisdateien und Dockerfile-spezifische Ignore-Dateien müssen zum Projekt passen; keine späteren Ausnahmeregeln dürfen die Zugangsdaten wieder einschließen.
gha-creds-*.json
.git
.env
.env.*Die Auth-Action schreibt standardmäßig eine kurzlebige Anmeldedatei in den Arbeitsbereich. Ein nachfolgendes docker build mit breitem COPY könnte diese Datei ins Image übernehmen. Deshalb laufen Tests und Build hier vor der Cloud-Anmeldung. Die Actions verweisen auf geprüfte, genau festgelegte Versionsstände (Commits); Updates benötigen weiterhin eine bewusste Prüfung.
name: Build and stage a Cloud Run revision
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
concurrency:
group: cloud-run-api-service
cancel-in-progress: false
env:
PROJECT_ID: enterprise-ai-project
REGION: us-central1
IMAGE: us-central1-docker.pkg.dev/enterprise-ai-project/services/api-service
PROVIDER: projects/1234567890/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider
DEPLOYER: ci-cd-deployer@enterprise-ai-project.iam.gserviceaccount.com
RUNTIME: api-runtime@enterprise-ai-project.iam.gserviceaccount.com
jobs:
stage:
runs-on: ubuntu-latest
timeout-minutes: 30
environment: production
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Application tests and build before cloud authentication
run:
set -euo pipefail
./ci/test.sh
docker build --tag "$IMAGE:$GITHUB_SHA" .
- name: Authenticate for image push
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
with:
workload_identity_provider: ${{ env.PROVIDER }}
service_account: ${{ env.DEPLOYER }}
project_id: ${{ env.PROJECT_ID }}
- name: Set up gcloud
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3
- name: Push image
run:
set -euo pipefail
gcloud auth configure-docker "$REGION-docker.pkg.dev" --quiet
docker push "$IMAGE:$GITHUB_SHA"
- name: Refresh authentication before deployment
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
with:
workload_identity_provider: ${{ env.PROVIDER }}
service_account: ${{ env.DEPLOYER }}
project_id: ${{ env.PROJECT_ID }}
- name: Configure refreshed gcloud credentials
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3
- name: Stage revision without changing traffic
run:
set -euo pipefail
gcloud run services describe api-service \
--project="$PROJECT_ID" --region="$REGION" >/dev/null
DIGEST="$(gcloud artifacts docker images describe "$IMAGE:$GITHUB_SHA" \
--project="$PROJECT_ID" --format='value(image_summary.digest)')"
[[ "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]
gcloud run deploy api-service \
--project="$PROJECT_ID" --region="$REGION" \
--image="$IMAGE@$DIGEST" --service-account="$RUNTIME" \
--no-traffic --tag=candidate \
--min-instances=0 --max-instances=50 \
--concurrency=80 --memory=512Mi --cpu=1 --cpu-throttling \
--quietDie Pipeline pusht ein Image mit Commit-Tag und stellt anschließend den aufgelösten Digest bereit. Der Digest bezeichnet den konkreten Image-Inhalt; ein Tag allein kann geändert werden. Die Anmeldung wird vor dem Deployment erneuert, damit die Build- und Push-Zeit nicht unnötig das kurze Anmeldefenster verbraucht. Sehr lange Uploads oder Deployments können dennoch eine erneute Anmeldung und einen kontrollierten Wiederholungsablauf benötigen.
--no-traffic belässt den regulären Verkehr bei den bisherigen Revisionen. Der Tag candidate erzeugt eine gesonderte Testadresse, die beim nächsten Lauf auf die neue Revision zeigt. Diese Adresse verwendet weiterhin die Zugriffsregeln des Dienstes. Smoke-Tests prüfen grundlegende Funktionen, beispielsweise eine typische API-Anfrage. Dafür ist ein zum Aufruf berechtigtes Konto erforderlich, typischerweise mit Cloud Run Invoker und einem passenden ID-Token. Ein erfolgreicher Starttest allein beweist keine funktionierende Anwendung.
Nach Tests und Prüfung der exakten Revisionskennung kann der Verkehr gezielt umgeschaltet werden. Der Platzhalter im folgenden Befehl ist durch die tatsächlich geprüfte Revision zu ersetzen. Ein Rücksprung verwendet entsprechend eine zuvor geprüfte alte Revision. Änderungen an Datenbanktabellen und bereits ausgeführte Aktionen in anderen Systemen bleiben bestehen. Beispielsweise wird eine bereits verschickte Nachricht durch den Rücksprung nicht zurückgeholt.
# After authenticated smoke tests and approval of this exact revision:
gcloud run services update-traffic api-service \
--project=enterprise-ai-project --region=us-central1 \
--to-revisions=REVIEWED_REVISION_NAME=1004. Skalierung und Kosten anhand der Anwendung einstellen
- Concurrency 80: Das ist die Obergrenze gleichzeitiger Anfragen pro Instanz im Beispiel, kein Schwellwert, der erst vollständig erreicht werden muss. Der Autoscaler berücksichtigt unter anderem CPU- und Concurrency-Auslastung. CPU-intensive oder speicherhungrige Anfragen benötigen häufig niedrigere Werte; mehr Parallelität senkt den Speicherbedarf nicht automatisch.
- Null Mindestinstanzen:
--min-instances=0gilt für die neue Revision. Dienstweite Mindestinstanzen müssen ebenfalls geprüft werden. Bei anfragebasierter Abrechnung entstehen für gewöhnliche untätige Instanzen ohne Mindestvorhaltung keine Idle-Rechenkosten; Start, Herunterfahren und Anfragen können trotzdem Kosten verursachen. Registry-Speicher, Netzwerk, Logs und GitHub Actions kommen hinzu. - Maximal 50:
--max-instances=50ist hier eine Revisionsgrenze. Mehrere Revisionen und kurzfristige Überschreitungen können die Gesamtzahl erhöhen. Eine zusätzlich passende Dienstgrenze schützt abhängige Systeme besser; keine dieser Einstellungen ist eine harte Rechnungssperre. - Hintergrundarbeit:
--cpu-throttlingaktiviert anfragebasierte CPU-Zuteilung. Instanzbasierte Abrechnung mit dauerhaft zugeteilter CPU und Mindestinstanzen ermöglicht Hintergrundarbeit, garantiert aber keinen ununterbrochenen Prozess. Instanzen können beendet werden. Wiederholbare Arbeit gehört beispielsweise in eine dauerhaft gespeicherte Aufgabenwarteschlange mit Cloud Tasks oder in passende Cloud Run Jobs.
Die Werte 80, 50 und 512 MiB sind Ausgangswerte für dieses Beispiel. Lasttests müssen Latenz, Fehlerquote, Speicherverbrauch und Datenbankverbindungen messen. Cloud Run kann neue Revisionen ohne geplante Unterbrechung übernehmen; fehlerhafter Code, inkompatible Migrationen oder erschöpfte Kontingente können trotzdem Ausfälle verursachen.

Fragen und Antworten
Ist schlüsselloses Deployment wartungsfrei?
Nein. WIF entfernt den dauerhaft gespeicherten Google-Schlüssel aus diesem Ablauf. Kurzlebige Tokens, GitHub-Zugänge und Laufzeitrechte müssen trotzdem geschützt werden. Abhängigkeiten, Container-Basisimages, Action-Commits und IAM-Regeln benötigen Pflege.
Warum fehlt –allow-unauthenticated?
Der Schalter würde öffentliche Aufrufrechte vergeben und benötigt zusätzliche IAM-Berechtigungen. Dieses Beispiel setzt einen bereits privat konfigurierten Dienst voraus und ändert dessen IAM-Aufrufregeln nicht. Das Weglassen des Schalters macht einen bereits öffentlichen Dienst nicht nachträglich privat.
Was bleibt nach dem Abschalten aller Instanzen kostenpflichtig?
Unter anderem gespeicherte Images und weitere verwendete Cloud-Dienste. Eine Registry-Bereinigungsregel sollte benötigte Release- und Rollback-Images gezielt erhalten. Scale-to-zero bedeutet weder kostenlose Deployments noch kostenlose Gesamtinfrastruktur.
Quellen
- Cloud-Run-Dokumentation
- Docker-Dokumentation
- WIF für Bereitstellungsabläufe
- Auth-Action: Zugangsdaten in Build-Ergebnissen
- Google-Cloud-Anmeldung für GitHub Actions
- Gleichzeitige Anfragen in Cloud Run
- Abrechnungseinstellungen von Cloud Run
- Obergrenzen für Instanzen
- Revisionen testen, Verkehr umschalten und Versionen zurücksetzen
- Berechtigungen für Cloud-Run-Bereitstellungen
- Laufzeitanforderungen an Cloud-Run-Container
- GitHub Actions auf feste Commits begrenzen


