LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog-Übersicht /Cloud & AI/Tutorials / Serverless Microservices: Skalierung in Google Cloud Run...
Diesen Artikel in anderen Sprachen lesen:

Serverless Microservices: Skalierung in Google Cloud Run mit GitHub Actions

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
  1. 1. Voraussetzungen und getrennte Identitäten
  2. 2. WIF auf Organisation, Repository und Main-Branch begrenzen
  3. 3. Image bauen, anmelden und eine Revision vorbereiten
  4. 4. Skalierung und Kosten anhand der Anwendung einstellen
  5. Fragen und Antworten
  6. Quellen

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-deployer benö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-runtime erhä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:$PORT lauschen und Sitzungen oder dauerhafte Daten außerhalb des flüchtigen Containers speichern. Ein Dockerfile und ein projektspezifisches, ausführbares Testskript ci/test.sh werden vorausgesetzt.
Vor der Cloud-Anmeldung bauen, einen Image-Digest bereitstellen, anschließend testen und freigeben
Der Build läuft vor dem Erzeugen der Cloud-Anmeldedatei. WIF begrenzt den Cloud-Zugriff; Anwendungstests und gezielte Verkehrsumschaltung vervollständigen die Freigabe.

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.

Shell
#!/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.

Text
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.

YAML
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 \
            --quiet

Die 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.

Shell
# 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=100

4. 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=0 gilt 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=50 ist 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-throttling aktiviert 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.

Drei Klapptische tragen je dieselbe Höchstzahl Teller, ein vierter lehnt zusammengeklappt an der Wand. Genauso legt Concurrency fest, wie viele Anfragen eine Instanz zugleich bearbeitet; ungenutzte kosten nichts.
Drei Klapptische tragen je dieselbe Höchstzahl Teller, ein vierter lehnt zusammengeklappt an der Wand. Genauso legt Concurrency fest, wie viele Anfragen eine Instanz zugleich bearbeitet; ungenutzte kosten nichts.

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.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Kontakt

Eine Frage oder eine Idee für ein Webprojekt? Schreib mir kurz, worum es geht. Das Formular nutzt reCAPTCHA und formconfirmed zum Schutz vor Spam.

Kommentar schreiben

Erfahrungen mit anderen Modellen oder Anbietern und Rückfragen zur Umsetzung sind hier willkommen.

Die E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind mit einem Stern versehen.

LUKASWOJCIK.COM · NEWSLETTER

Neue Ideen im Postfach

Neue Blogartikel, praktische Tools und Einblicke in die Projekte.

Themen auswählen (optional)
Ohne Auswahl: alle Themen.

Erst nach Bestätigung per E-Mail. · Datenschutz

Datenschutz · Newsletter

E-Mail-Adresse, Sprache, Themenauswahl sowie Anmelde- und Bestätigungszeit werden für den Newsletter gespeichert. Versand nur nach Double-Opt-in. Jede Ausgabe enthält eine sofortige Abmeldung. Formconfirmed prüft die Anmeldung auf Spam; dafür werden Formularsignale und die E-Mail-Adresse verarbeitet. Keine Öffnungs- oder Klicktracking-Pixel im Newsletter.

Vollständige Datenschutzerklärung öffnen ↗