Deploy de microsserviços Go em Kubernetes: Do zero à produção

Você escreveu seu microsserviço em Go. Ele é rápido, concorrente e confiável. Agora vem a parte que separa os projetos amadores dos profissionais: colocar isso em produção, em escala, com tolerância a falhas, atualizações sem downtime e monitoramento.

É aí que o Kubernetes entra. O orquestrador de contêineres se tornou o padrão para rodar microsserviços. Mas Go e Kubernetes juntos exigem algumas boas práticas específicas: health checks que realmente funcionam, graceful shutdown para não perder requisições, gerenciamento de configuração com ConfigMaps, e sidecars para observabilidade.

Neste post, vamos construir o caminho do zero até a produção de um microsserviço Go no Kubernetes, com exemplos práticos e as armadilhas que a Jacobus Software aprendeu na prática.

1. O microsserviço Go pronto para Kubernetes

Antes de containerizar, seu código Go precisa estar preparado para o ambiente orquestrado. Os requisitos mínimos:

Graceful shutdown (indispensável)

Quando o Kubernetes vai matar um pod (por escala, atualização ou falha), ele envia um sinal SIGTERM. Seu serviço precisa capturar esse sinal, parar de aceitar novas conexões, finalizar as requisições em andamento e então se encerrar.

package main

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

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/health", healthHandler)
    mux.HandleFunc("/ready", readyHandler)
    mux.HandleFunc("/api", apiHandler)

    srv := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // Goroutine para rodar o servidor
    go func() {
        log.Println("Servidor iniciado na porta 8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("erro no servidor: %v", err)
        }
    }()

    // Aguarda sinal de término (SIGTERM do Kubernetes)
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
    <-quit

    log.Println("SIGTERM recebido, desligando graciosamente...")

    // Tempo máximo para finalizar conexões existentes
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("erro no shutdown: %v", err)
    }

    log.Println("Servidor finalizado")
}

Health probes: readiness e liveness

O Kubernetes usa duas sondas:

  • livenessProbe: verifica se o pod está vivo. Se falhar, o pod é reiniciado.
  • readinessProbe: verifica se o pod está pronto para receber tráfego. Se falhar, o pod é removido do balanceamento.
// Endpoint de liveness (apenas se o processo está rodando)
func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

// Endpoint de readiness (verifica dependências: banco, cache, etc.)
func readyHandler(w http.ResponseWriter, r *http.Request) {
    // Verifica conexão com banco, Redis, etc.
    if err := checkDatabase(); err != nil {
        w.WriteHeader(http.StatusServiceUnavailable)
        w.Write([]byte(err.Error()))
        return
    }
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("READY"))
}

2. Containerizando a aplicação Go

O Dockerfile para Go deve ser multi-stage para gerar binários pequenos e seguros.

# Estágio 1: builder
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/service ./cmd/api

# Estágio 2: imagem final (distroless ou alpine mínimo)
FROM alpine:3.19

RUN apk add --no-cache ca-certificates
COPY --from=builder /app/service /service

# Usuário não-root para segurança
RUN adduser -D appuser
USER appuser

EXPOSE 8080

ENTRYPOINT ["/service"]

Dicas:

  • CGO_ENABLED=0 gera binário estático, sem dependências C.
  • -ldflags="-s -w" remove símbolos de debug, reduzindo tamanho.
  • Use imagens scratch ou distroless para segurança máxima (sem shell).

3. Manifestos Kubernetes

Agora, os arquivos YAML que definem seu microsserviço.

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: go-api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # quantos pods extras durante atualização
      maxUnavailable: 0  # garante zero downtime (mantém pelo menos réplicas ativas)
  template:
    metadata:
      labels:
        app: go-api
    spec:
      containers:
      - name: api
        image: jacobus/go-api:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url
        - name: REDIS_ADDR
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: redis_addr
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        # Probes essenciais
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        # Lifecycle para garantir shutdown gracioso
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      restartPolicy: Always

Explicações importantes:

  • maxUnavailable: 0 garante que nunca fique com menos pods que o desejado.
  • preStop sleep 15 dá tempo para o Service remover o pod do balanceamento antes de enviar SIGTERM.
  • Recursos (requests/limits) são obrigatórios para o escalonador funcionar bem.

Service (expor o microsserviço)

apiVersion: v1
kind: Service
metadata:
  name: go-api-service
  namespace: production
spec:
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  type: ClusterIP  # interno ao cluster

ConfigMap e Secret

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  namespace: production
data:
  redis_addr: "redis-service.default:6379"
  log_level: "info"

# secret.yaml (valores em base64)
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri

4. Escalabilidade horizontal em Go

Kubernetes escala facilmente:

kubectl scale deployment go-api --replicas=10

Mas seu código Go precisa ser stateless (não armazenar estado local que deveria ser compartilhado). Se precisar de estado, use Redis, banco de dados ou cache distribuído.

HPA (Horizontal Pod Autoscaler)

Para escalar automaticamente baseado em métricas (CPU, memória ou custom):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: go-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: go-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Em Go, monitore também a latência e o número de goroutines – são métricas melhores que CPU para muitos workloads.

5. Sidecars: o parceiro do microsserviço

O padrão sidecar é um contêiner auxiliar que roda no mesmo pod. Muito usado para:

  • Observabilidade: Envoy, Linkerd (service mesh) ou um agente de logs.
  • Proxy de banco de dados: Cloud SQL Auth Proxy.
  • Rate limiting / autorização: Sidecar que verifica tokens antes de encaminhar.

Exemplo: adicionar um sidecar que envia métricas para o Prometheus (já que o app pode não ter suporte nativo).

containers:
- name: api
  image: jacobus/go-api:latest
  # ...
- name: metrics-sidecar
  image: prometheus/statsd-exporter
  args: ["--statsd.listen-udp=:8125"]

6. Configuração avançada: Init containers

Às vezes você precisa executar tarefas antes do app principal iniciar: migrações de banco, download de arquivos, etc. Use init containers:

initContainers:
- name: migrations
  image: jacobus/go-migrate:latest
  env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: url

O init container roda até completar com sucesso antes dos contêineres principais iniciarem.

7. Observabilidade no Kubernetes (essencial)

Nunca coloque microsserviços em produção sem:

  • Logs estruturados (JSON) enviados para stdout/stderr – o Kubernetes coleta automaticamente.
  • Métricas Prometheus exportadas no endpoint /metrics.
  • Tracing distribuído (Jaeger) com headers propagados.

Go + Prometheus é trivial:

import "github.com/prometheus/client_golang/prometheus/promhttp"

http.Handle("/metrics", promhttp.Handler())

8. Boas práticas para Go no Kubernetes

PráticaPor quê
Use GOMAXPROCS automáticoGo 1.19+ detecta limites de CPU do container. Antes, use automaxprocs library.
Configure GOMEMLIMITEvita que Go consuma memória além do limit do container e seja morto pelo OOMKiller.
Evite os.Exit(1)Deixe o K8s reiniciar o container – não aborte o processo sem cleanup.
Use context com timeoutEm calls HTTP, banco, etc. Para não vazar goroutines.
Graceful shutdown para conexões externasFeche pools de conexões no SIGTERM.
Não escreva arquivos locaisPods são efêmeros. Use volumes ou buckets.
Teste localmente com Kind ou MinikubeReproduza o ambiente antes de deployar.

9. Pipeline CI/CD (GitHub Actions exemplo)

name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-go@v5
      with:
        go-version: '1.22'
    - run: go test ./...
    - run: go build -o app ./cmd/api
    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: jacobus/go-api:${{ github.sha }}
    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/go-api api=jacobus/go-api:${{ github.sha }}
        kubectl rollout status deployment/go-api

Caso real: deploy de fintech em Go na Jacobus

Uma plataforma de pagamentos precisava de zero downtime absoluto e escalabilidade instantânea para black friday.

Implementação:

  • Microsserviços Go com graceful shutdown e health probes.
  • Deploy no Kubernetes com maxUnavailable: 0 e preStop sleep.
  • HPA baseado em CPU e requisições por segundo (custom metric).
  • Sidecar do Cloud SQL Proxy para conexão segura com banco.
  • Istio como service mesh para controle de tráfego e mTLS.

Resultado: 200k requisições por minuto em pico, zero incidentes de deploy, rollback de 30 segundos quando necessário.

Conclusão: Go e Kubernetes – uma parceria poderosa

Kubernetes oferece a plataforma; Go oferece eficiência e concorrência. Juntos, eles formam a espinha dorsal de sistemas modernos e escaláveis. Mas é preciso mais do que kubectl run – as boas práticas de health checks, graceful shutdown, recursos, sidecars e autoscaling fazem a diferença entre um sistema frágil e um que realmente aguenta produção.

Na Jacobus Software, usamos essa stack diariamente para clientes que exigem alta disponibilidade e escala. Com os exemplos deste post, você tem um roteiro sólido para levar seu microsserviço Go do zero à produção com confiança.


☸️ Quer colocar seus microsserviços Go no Kubernetes com segurança?

Nossos especialistas projetam pipelines, manifestos e autoscaling sob medida – garantindo zero downtime e operação tranquila.

👉 Fale com a Jacobus Software

Rolar para cima