← Wróć do artykułów

Go i Google Cloud Run jako idealny duet dla mikroserwisów

Projektując nowoczesne systemy w architekturze mikroserwisowej, inżynierowie stają przed dwoma kluczowymi wyzwaniami: jak zapewnić maksymalną wydajność i niskie opóźnienia oraz jak nie przepłacać za infrastrukturę chmurową w okresach niższego ruchu.

Połączenie języka Go (Golang) ze środowiskiem Google Cloud Run to jeden z najbardziej efektywnych wzorców architektonicznych w inżynierii backendowej. W tym artykule wyjaśniamy, dlaczego ten duet deklasuje tradycyjne podejścia oparte na ciężkich maszynach wirtualnych lub klastrach Kubernetes.


1. Błyskawiczny start: Zimne starty (Cold Starts) poniżej 300 ms

Jedną z największych bolączek architektury Serverless (takiej jak Cloud Functions czy Cloud Run) w technologiach takich jak Java, Python czy Node.js jest czas uruchamiania nowej instancji kontenera po skalowaniu od zera (Cold Start).

  • Java / Spring Boot: Czas startu JVM i kontekstu frameworka często wynosi 5–15 sekund.
  • Node.js / Python: Wymagają interpretacji kodu i ładowania setek modułów z node_modules (1.5–3 sekundy).
  • Go: Kompiluje się do jednego, natywnego pliku binarnego.

Obraz kontenera w Go oparty na scratch lub distroless waży zaledwie 15–25 MB. Gdy Cloud Run otrzymuje nowe żądanie i musi powołać nową instancję kontenera, proces w Go jest gotowy do obsługi ruchu w czasie 150–300 ms. Dla użytkownika końcowego zimny start jest praktycznie niezauważalny.

# Przykładowy wieloetapowy build Dockera dla serwisu Go
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

2. Model kosztowy: Realne Scale-to-Zero

Tradycyjne klastry Kubernetes (GKE, EKS) generują wysokie koszty stałe - węzły klastra muszą działać 24/7, nawet w nocy, gdy ruch spada do zera.

Google Cloud Run rozlicza się w modelu Pay-per-use:

  1. Płatność za ułamki sekund: Płacisz wyłącznie za czas, w którym procesor faktycznie przetwarza żądanie (z dokładnością do 100 ms).
  2. Scale to Zero: Jeśli przez godzinę serwis nie otrzymał ani jednego zapytania, liczba działających instancji spada do 0. Koszt infrastruktury wynosi dokładnie 0 PLN.
  3. Darmowy pakiet (Free Tier): Każdego miesiąca Cloud Run oferuje 2 miliony darmowych żądań oraz pulę darmowych vCPU-sekund i RAM-sekund.

Dla średniej wielkości mikroserwisu backendowego miesięczny rachunek z Cloud Run może spaść z kilkuset dolarów (na dedykowanym klastrze VM) do zaledwie kilku dolarów, przy zachowaniu gotowości na gwałtowne skoki ruchu.


3. Współbieżność (Concurrency): Jedna instancja, tysiące połączeń

Większość środowisk serverless (np. tradycyjne AWS Lambda) działa w modelu 1 żądanie = 1 kontener. Jeśli w jednej chwili pojawi się 500 zapytań, chmura musi uruchomić 500 kontenerów, co drastycznie podnosi koszty i potęguje problem zimnych startów.

Cloud Run wspiera wielowątkową współbieżność (Concurrency do 1000 zapytań na jeden kontener). I tutaj Go błyszczy najbardziej:

  • Lekkie Goroutines: W Go pojedynczy wątek logiczny zajmuje zaledwie ~2 KB pamięci RAM (w porównaniu do 1–2 MB dla wątku systemowego w Javie).
  • Kontener z 1 vCPU i 256 MB RAM napisanym w Go potrafi równolegle obsługiwać 200–500 zapytań HTTP/gRPC bez degradacji wydajności i bez wycieków pamięci.
  • W rezultacie do obsłużenia dużego ruchu wystarczy zaledwie 2–3 instancje Cloud Run zamiast setek pojedynczych instancji.
package main

import (
	"context"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})
	mux.HandleFunc("/api/v1/orders", handleOrders)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	server := &http.Server{
		Addr:         ":" + port,
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  120 * time.Second,
	}

	// Błyskawiczny start i graceful shutdown
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			panic(err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	server.Shutdown(ctx)
}

4. Brak Vendor Lock-in: Czyste kontenery OCI

Wybierając Cloud Run nie wiążesz się z autorskim API dostawcy chmurowego (jak w przypadku dedykowanych frameworków serverless):

  • Usługa to zwykły kontener Docker spełniający standard OCI i nasłuchujący na zmiennej środowiskowej PORT.
  • Ten sam kontener możesz uruchomić lokalnie (docker run), przenieść na Kubernetes (KNative, vanilla k8s), AWS ECS, czy dowolny serwer VPS.

Podsumowanie

W itbsv rekomendujemy zestawienie Go + Google Cloud Run wszędzie tam, gdzie liczy się:

  • Bardzo niski narzut operacyjny (brak konieczności łatania systemów operacyjnych maszyn wirtualnych).
  • Odporność na nagłe skoki ruchu (ruch e-commerce, kampanie reklamowe).
  • Optymalizacja kosztów chmurowych z gwarancją czasu odpowiedzi w milisekundach.