Per què Go encaixa tan bé en microserveis i cloud-native
Per què Go domina l'ecosistema cloud-native: Kubernetes, Docker, Terraform. Binaris simples, concurrència integrada i bon rendiment.

Mira les eines que sostenen la infraestructura moderna: Kubernetes, Docker, Terraform, Prometheus, Grafana Agent, CockroachDB, Vault, etcd, Traefik, containerd. Totes escrites en Go. Si sumes els projectes de la CNCF (Cloud Native Computing Foundation), la majoria estan en Go. I sent honestos, quan veus una concentració així, és difícil no preguntar-se per què. Crec que hi ha raons tècniques concretes darrere, i entendre-les ajuda a prendre millors decisions quan toca escollir stack per al teu proper servei.
Vinc de treballar amb Kotlin i Java en backend. He muntat serveis Spring Boot, he lluitat amb JVM tuning, i he vist contenidors Java menjar-se 512 MB de RAM només per existir. Quan vaig començar a explorar Go, el primer que em va cridar l’atenció no va ser la sintaxi ni les goroutines: va ser com de petit i previsible que era tot. Un binari. Sense dependències. Arrencant en mil·lisegons. Això canvia coses a nivell d’infraestructura que no són evidents fins que les vius.
Per què Go es va convertir en el llenguatge de la infraestructura
Go va néixer dins de Google l’any 2009 amb un objectiu molt concret: resoldre problemes d’enginyeria de sistemes a escala. Robert Griesemer, Rob Pike i Ken Thompson el van dissenyar pensant en serveis de xarxa, eines de línia de comandes i sistemes concurrents. No és un llenguatge acadèmic ni un experiment: és una eina d’enginyeria. I crec que això es nota en cada decisió de disseny.
Això es nota en les decisions de disseny:
- Compilació a binari estàtic: sense runtime pesat, sense màquina virtual, sense intèrpret.
- Concurrència com a ciutadà de primera classe: goroutines i channels integrats al llenguatge.
- Llibreria estàndard completa: servidor HTTP, JSON, crypto, testing, tot inclòs.
- Sintaxi deliberadament simple: menys formes de fer el mateix, més fàcil de llegir i mantenir.
- Cross-compilation trivial: compilar per a Linux des de macOS amb una variable d’entorn.
Cap d’aquestes característiques és espectacular per separat. Tècnicament, altres llenguatges en tenen alguna. Però juntes, creen una combinació que encaixa de forma natural en el que necessita l’ecosistema cloud-native: serveis de xarxa que es despleguen fàcil, s’executen amb pocs recursos i els pot mantenir un equip que canvia de membres.
Go no és el millor llenguatge per a tot. Però per a serveis de xarxa, CLIs d’infraestructura i eines de plataforma, és difícil de superar en la combinació de simplicitat, rendiment i operabilitat.
Binari únic: sense JVM, sense intèrpret, sense dependències
Si véns del món Java/Kotlin, crec que això és el que més t’impacta al principi. Almenys a mi em va passar. La teva aplicació Go compila a un únic binari executable. Sense JDK, sense classpath, sense fat JARs de 80 MB amb mig Maven dins.
// main.go — un servei HTTP complet
package main
import (
\"fmt\"
\"log\"
\"net/http\"
)
func main() {
http.HandleFunc(\"/health\", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{\"status\":\"ok\"}`)
})
log.Println(\"Servidor escoltant a :8080\")
log.Fatal(http.ListenAndServe(\":8080\", nil))
}Compiles amb go build -o server . i tens un executable. El copies a qualsevol màquina Linux i funciona. No hi ha java -jar, no hi ha python -m, no hi ha node index.js. Només el binari.
# Cross-compilar per a Linux des de macOS
GOOS=linux GOARCH=amd64 go build -o server .
# El resultat
ls -lh server
# -rwxr-xr-x 1 roger staff 6.2M server6 MB per a un servei HTTP funcional. Un Spring Boot equivalent amb l’starter web ocupa 20-40 MB com a mínim i necessita una JVM de 200+ MB a sobre.
La pregunta interessant és una altra: això importa quan tens 50 microserveis en un cluster de Kubernetes. L’emmagatzematge d’imatges, el temps de pull, l’arrencada en fred: tot escala amb la mida de l’artefacte. I és aquí on les diferències deixen de ser teòriques.
Concurrència: les goroutines fan que els serveis de xarxa siguin naturals
Un servei cloud-native és, en essència, un programa que rep peticions per xarxa, fa operacions d’I/O (base de dades, altres serveis, cues) i retorna respostes. La concurrència no és un extra: és el comportament per defecte.
A Go, cada petició HTTP es gestiona en la seva pròpia goroutine automàticament. No cal configurar thread pools, no cal escollir entre models reactius i bloquejants, no cal importar frameworks de concurrència. És part del llenguatge.
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Cridar dos serveis en paral·lel
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
result, _ := fetchUserProfile(r.Context())
ch1 <- result
}()
go func() {
result, _ := fetchUserOrders(r.Context())
ch2 <- result
}()
profile := <-ch1
orders := <-ch2
fmt.Fprintf(w, `{\"profile\":%s,\"orders\":%s}`, profile, orders)
}Una goroutine ocupa ~2-8 KB d’stack inicial (creix dinàmicament). Un thread de Java arrenca amb 512 KB-1 MB. Pots tenir centenars de milers de goroutines actives sense problemes. Intenta tenir centenars de milers de threads a la JVM i veuràs què passa. No perquè la JVM sigui dolenta, sinó perquè el model de concurrència és fonamentalment diferent.
Si vols aprofundir en patrons de concurrència pràctics, tinc un article dedicat a worker pools a Go on s’explora com gestionar càrregues de treball amb goroutines de forma controlada.
Compilació ràpida: el temps de compilació importa en CI/CD
Go compila ràpid. Sorprenentment ràpid. Un projecte mitjà (20-30 paquets) compila en 2-5 segons. Un projecte gran com Kubernetes compila en menys d’un minut en una màquina raonable.
I sent honestos, això importa més del que sembla:
- Cicle de desenvolupament local: canvi, compilo, provo. A Go és gairebé instantani. En un projecte Spring Boot gran, l’arrencada del context ja són 10-20 segons.
- CI/CD pipelines: cada segon de compilació multiplicat per centenars de builds al dia és temps i diners reals.
- Docker builds: la capa de compilació en un Dockerfile multi-stage és ràpida i cacheable.
# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server .
# Runtime stage
FROM scratch
COPY --from=builder /server /server
EXPOSE 8080
ENTRYPOINT [\"/server\"]Compara això amb un Dockerfile de Java on necessites una imatge base amb JDK per compilar i almenys un JRE per executar.
Memòria: serveis Go vs serveis JVM
Aquest és un punt que no es pot ignorar quan parles de cloud-native, tot i que de vegades es subestima. Els recursos costen diners, i a Kubernetes pagues pel que reserves, no només pel que uses.
Un servei HTTP bàsic a Go consumeix entre 5-15 MB de RAM en repòs. Sota càrrega, puja proporcionalment al treball real que està fent. El garbage collector de Go és de baixa latència (pauses de microsegons) i està dissenyat per no necessitar tuning manual.
Un servei equivalent a Spring Boot arrenca consumint 150-300 MB. Amb JVM tuning agressiu i frameworks reactius pots baixar-lo, però estàs lluitant contra la naturalesa del runtime.
# Límits de recursos típics a Kubernetes
# Servei Go
resources:
requests:
memory: \"32Mi\"
cpu: \"50m\"
limits:
memory: \"128Mi\"
cpu: \"200m\"
# Servei Spring Boot equivalent
resources:
requests:
memory: \"256Mi\"
cpu: \"200m\"
limits:
memory: \"512Mi\"
cpu: \"500m\"Amb 20 microserveis, la diferència entre demanar 640 MB i 5 GB de RAM al cluster és significativa. Especialment quan aquests serveis són APIs internes que reben 10 peticions per segon.
No estic dient que Java no serveixi per a microserveis. GraalVM native images, Quarkus i Micronaut han millorat molt això. Però Go no necessita aquests workarounds perquè el seu model d’execució ja és lleuger per defecte.
La llibreria estàndard: suficient per a la majoria de serveis
Això em va sorprendre bastant quan vaig començar: fins on pots arribar sense dependències externes. La llibreria estàndard inclou:
net/http: servidor i client HTTP complets. Producció-ready. Sense framework.encoding/json: serialització i deserialització de JSON.database/sql: interfície per a bases de dades amb connection pooling inclòs.crypto: TLS, hashing, xifrat.testing: framework de testing integrat, amb benchmarks i fuzzing.context: propagació de cancel·lació i timeouts.
// Un servidor HTTP amb middleware, sense frameworks externs
func main() {
mux := http.NewServeMux()
mux.HandleFunc(\"GET /api/users/{id}\", getUser)
mux.HandleFunc(\"POST /api/users\", createUser)
mux.HandleFunc(\"GET /health\", healthCheck)
// Middleware de logging i timeout
handler := withLogging(withTimeout(mux, 5*time.Second))
server := &http.Server{
Addr: \":8080\",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf(\"%s %s %v\", r.Method, r.URL.Path, time.Since(start))
})
}
func withTimeout(next http.Handler, d time.Duration) http.Handler {
return http.TimeoutHandler(next, d, \"timeout\")
}A la pràctica, molts serveis Go usen només la llibreria estàndard més un driver de base de dades i potser un router. Si vols un framework més complet, Gin és l’opció més popular, però no és obligatori. La llibreria estàndard de Go 1.22+ amb el nou patró de rutes cobreix la majoria de casos.
Exemples reals: eines en Go que probablement ja uses
Si treballes amb infraestructura moderna, ja estàs usant Go sense saber-ho:
| Eina | Què fa | Per què Go |
|---|---|---|
| Kubernetes | Orquestració de contenidors | Necessitava concurrència massiva i binaris desplegables |
| Docker (containerd) | Runtime de contenidors | Interacció amb el kernel Linux, rendiment crític |
| Terraform | Infraestructura com a codi | Plugins com a binaris independents, cross-platform |
| Prometheus | Monitorització i alertes | Ingestió de mètriques d’alta freqüència |
| Grafana Agent/Alloy | Col·lector de telemetria | Baix consum de recursos, fàcil de desplegar |
| etcd | Magatzem distribuït clau-valor | Consens distribuït (Raft), baixa latència |
| Traefik | Reverse proxy / ingress | Recàrrega dinàmica, integració amb service discovery |
| Vault | Gestió de secrets | Seguretat, plugins com a binaris, auditabilitat |
| CockroachDB | Base de dades SQL distribuïda | Concurrència, rendiment, complexitat distribuïda |
| Hugo | Generador de llocs estàtics | Velocitat de generació brutal |
El patró, almenys com jo el veig, és bastant clar: quan necessites eines que es despleguen fàcil, consumeixen pocs recursos, gestionen concurrència de xarxa i les manté una comunitat gran, Go apareix una i altra vegada. No crec que sigui l’única opció possible, però sí que és la que més s’ha consolidat en aquest nínxol.
Microserveis amb Go: consideracions pràctiques
Escriure un microservei a Go és diferent a fer-ho a Spring Boot o Django. No hi ha injecció de dependències automàtica, no hi ha anotacions màgiques, no hi ha generació de codi. És més explícit i manual, i sent honestos, això té avantatges i desavantatges que convé entendre abans de llançar-se.
Estructura típica d’un microservei Go
service/
├── cmd/
│ └── server/
│ └── main.go # Punt d'entrada
├── internal/
│ ├── handler/ # HTTP handlers
│ │ └── user.go
│ ├── service/ # Lògica de negoci
│ │ └── user.go
│ ├── repository/ # Accés a dades
│ │ └── user.go
│ └── model/ # Structs del domini
│ └── user.go
├── go.mod
├── go.sum
├── Dockerfile
└── MakefileSi vols aprofundir en com estructurar un projecte Go real i les convencions del llenguatge, tinc un article dedicat a aprendre Go on es cobreixen les bases que necessites abans de construir serveis.
El que trobes a faltar venint de Spring
- Injecció de dependències: a Go la fas a mà. Passes les dependències per constructor. És més verbós, però sempre saps d’on ve cada cosa.
- ORM potent: Go té
sqlxiGORM, però cap s’acosta al que fa Hibernate/JPA. Per bé i per mal. - Validació declarativa: no hi ha
@Validni@NotNull. Uses llibreries comgo-playground/validatoro valores a mà. - Documentació d’API: no hi ha Swagger automàtic a partir d’anotacions. Necessites
swago definir OpenAPI manualment.
El que guanyes
- Claredat: si llegeixes
main.go, veus exactament com es connecta tot. Sense màgia, sense proxies invisibles, sense arrencades de context de 15 segons. - Testing senzill:
go test ./...executa tots els tests. Sense configurar frameworks, sense contextos de Spring, sense que el test tardi 10 segons en arrencar. - Desplegament trivial: un binari. Sense
JAVA_OPTS, sense perfils de Spring, sense classpath hell.
Per a una guia pràctica de com construir una API REST completa, pots veure microserveis amb Go.
Go + Docker: imatges petites, builds ràpids
Docker i Go es complementen de forma natural. El binari estàtic de Go permet usar scratch o distroless com a imatge base, cosa que elimina absolutament tot excepte el teu executable.
# Multi-stage build per a producció
FROM golang:1.23-alpine AS builder
WORKDIR /app
# Caché de dependències
COPY go.mod go.sum ./
RUN go mod download
# Compilació
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags=\"-s -w\" -o /server ./cmd/server
# Imatge final des de scratch (0 bytes de base)
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /server /server
EXPOSE 8080
ENTRYPOINT [\"/server\"]El resultat és una imatge Docker de 8-15 MB. Una imatge equivalent a Java amb JRE slim són 200+ MB. Amb GraalVM native pots baixar a 50-80 MB, però el temps de compilació se’n va a diversos minuts.
# Mides típiques d'imatges Docker
docker images
# REPOSITORY TAG SIZE
# go-service latest 12MB
# java-service latest 285MB
# python-service latest 180MB
# node-service latest 150MBLa clau tècnica és CGO_ENABLED=0: això desactiva la compilació amb codi C i permet generar un binari completament estàtic que no necessita libc. Si el teu servei no depèn de llibreries C (i la majoria no ho fan), això funciona perfectament.
Els flags -ldflags=\"-s -w\" eliminen la taula de símbols i la informació de debug, reduint la mida del binari un 20-30%.
Si vols veure això en pràctica amb un exemple pas a pas, tinc una guia completa sobre dockeritzar una API Go.
Go + Kubernetes: per què encaixen de forma natural
No és casualitat que Kubernetes estigui escrit en Go. Les mateixes propietats que fan de Go un bon llenguatge per escriure Kubernetes el fan un bon llenguatge per escriure serveis que corren dins de Kubernetes.
Health checks natius
mux.HandleFunc(\"GET /healthz\", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc(\"GET /readyz\", func(w http.ResponseWriter, r *http.Request) {
if err := db.PingContext(r.Context()); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})Graceful shutdown
Kubernetes envia un SIGTERM abans de matar un pod. Un servei Go gestiona això amb unes poques línies:
func main() {
server := &http.Server{Addr: \":8080\", Handler: mux}
// Arrencar en goroutine
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf(\"Error al servidor: %v\", err)
}
}()
// Esperar senyal de parada
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
log.Println(\"Apagant servidor...\")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf(\"Shutdown forçat: %v\", err)
}
log.Println(\"Servidor apagat correctament\")
}Arrencada ràpida
Un servei Go arrenca en mil·lisegons. Això és crític per a:
- Horizontal Pod Autoscaler (HPA): quan hi ha un pic de trànsit, els nous pods han d’estar llestos ràpid. Un servei Go està llest en 50-100 ms. Un servei Spring Boot necessita 5-20 segons.
- Rolling deployments: com més ràpid arrenca un pod nou, més ràpid acaba el deployment.
- CrashLoopBackOff recovery: si un pod falla i reinicia, la velocitat d’arrencada determina quant tarda a tornar a servir trànsit.
Baix consum de recursos
Amb límits de 64-128 Mi de RAM, un servei Go funciona perfectament. Això et permet:
- Ficar més serveis per node.
- Usar nodes més petits (i barats) al cluster.
- Tenir rèpliques d’alta disponibilitat sense multiplicar costos.
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
template:
spec:
containers:
- name: user-service
image: registry.example.com/user-service:v1.2.0
ports:
- containerPort: 8080
resources:
requests:
memory: \"32Mi\"
cpu: \"50m\"
limits:
memory: \"128Mi\"
cpu: \"200m\"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 1
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 1
periodSeconds: 5Fixa’t en initialDelaySeconds: 1. Amb Java, normalment necessites 15-30 segons. Amb Go, un segon és més que suficient.
Limitacions: quan cloud-native no significa “usa Go”
Go no és la resposta a tot, i ser honest sobre les seves limitacions és important per prendre bones decisions.
Quan Go no és la millor opció
- Machine Learning / Data Science: Python domina aquí. Go no té un ecosistema comparable a NumPy, Pandas, TensorFlow o PyTorch. Ni de lluny.
- Aplicacions CRUD complexes amb relacions: si el teu servei és bàsicament un CRUD sobre un model relacional complex, un ORM madur com JPA o Django ORM t’estalvia molt de treball. Go t’obliga a escriure més SQL a mà.
- Interfícies d’usuari riques: Go no té frameworks de frontend. HTMX amb templates de Go funciona per a coses simples, però no construiràs una SPA complexa amb Go.
- Prototipat ràpid: Python o JavaScript segueixen sent més ràpids per prototipar perquè tenen més llibreries d’alt nivell i menys boilerplate.
- Sistemes amb requisits de latència extrema: si necessites control absolut de la memòria i zero pauses de GC, Rust és millor opció. El GC de Go és bo, però existeix.
L’opinió que pocs diuen
Go és verbós. La gestió d’errors amb if err != nil és repetitiva. La falta de genèrics fins fa poc (Go 1.18, març 2022) va deixar anys de codi ple d’interface{}. No té enums reals. No té pattern matching. No té tipus suma. Si véns de Kotlin, Rust o fins i tot Java modern amb sealed classes, hi ha moments en què Go es sent limitat. I no faré veure que això no molesta.
Però crec que aquesta mateixa simplicitat és el que fa que el codi Go sigui fàcil de llegir sis mesos després, que un enginyer nou pugui incorporar-se ràpid al projecte, i que les revisions siguin més directes. És un tradeoff conscient, i cada equip ha de decidir si aquest tradeoff els compensa.
Go no és un llenguatge que et deixi escriure abstraccions elegants. És un llenguatge que et deixa escriure codi que qualsevol del teu equip pot entendre i mantenir. En el context de cloud-native, on els serveis canvien de propietari i els equips roten, això val més que l’elegància.
Conclusió
Crec que Go domina l’ecosistema cloud-native per raons pràctiques, no ideològiques. Binaris petits que es despleguen fàcil. Concurrència integrada que fa natural escriure serveis de xarxa. Compilació ràpida que accelera CI/CD. Consum de memòria baix que redueix costos d’infraestructura. Una llibreria estàndard que cobreix el 80% del que necessites.
No és el millor llenguatge per a tot. No té l’ecosistema de Python per a dades, ni l’expressivitat de Kotlin, ni el control de Rust. Però per al nínxol específic de serveis backend, eines d’infraestructura i plataformes cloud-native, la combinació de simplicitat, rendiment i operabilitat és difícil d’igualar.
Llavors la pregunta ja no és tan simple com: “Go o no Go”. És més aviat si el teu cas d’ús es beneficia de les propietats que Go aporta: desplegament simple, baix consum, concurrència nativa i codi mantenible. Si la resposta és sí, probablement entendràs per què tanta gent ha pres aquesta mateixa decisió abans.
Si vols començar amb Go des de zero, et recomano aprendre Go com a punt de partida. I si ja tens les bases i vols veure codi real, pots anar directament a construir una API REST i després ficar-la en Docker.


