Go e Kubernetes: Deploy de Containers

Neste guia completo, você aprenderá a fazer deploy de aplicações Go em Kubernetes, desde a containerização até a configuração de health checks e boas práticas de produção. Se você está construindo microserviços ou APIs REST em Go, o Kubernetes é a plataforma ideal para orquestrar seus containers.

Por que Kubernetes para Go?

Go foi projetada para a era dos containers. Sua compilação para binário único, baixo consumo de memória e startup instantâneo tornam aplicações Go perfeitas para Kubernetes:

  • Binários estáticos: Containers menores (imagem Alpine de ~10MB)
  • Startup rápido: Escalamento horizontal instantâneo
  • Baixo consumo: Cada pod usa menos recursos, permitindo mais densidade
  • Concorrência nativa: Goroutines aproveitam múltiplos cores dos nodes K8s

1. Containerizando Aplicações Go

Criando uma API Go Simples

Vamos começar com uma API REST que usaremos como exemplo:

// main.go
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gorilla/mux"
)

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/health", HealthHandler).Methods("GET")
	r.HandleFunc("/api/users", UsersHandler).Methods("GET")
	r.HandleFunc("/", HomeHandler).Methods("GET")

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

	srv := &http.Server{
		Addr:         ":" + port,
		Handler:      r,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// Graceful shutdown
	go func() {
		log.Printf("Servidor iniciado na porta %s", port)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Erro ao iniciar servidor: %v", err)
		}
	}()

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

	log.Println("Desligando servidor...")
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		log.Fatalf("Erro ao desligar servidor: %v", err)
	}
	log.Println("Servidor encerrado com sucesso")
}

func HealthHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status": "healthy"}`))
}

func UsersHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(`{"users": []}`))
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("API Go rodando no Kubernetes!"))
}

Multi-stage Dockerfile Otimizado

# Dockerfile
# Stage 1: Build
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Instalar dependências de build
RUN apk add --no-cache git

# Copiar módulos primeiro (cache eficiente)
COPY go.mod go.sum ./
RUN go mod download

# Copiar código fonte
COPY . .

# Build otimizado para produção
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags='-w -s -extldflags "-static"' \
    -a -installsuffix cgo \
    -o server .

# Stage 2: Runtime
FROM gcr.io/distroless/static:nonroot

# Copiar binário do stage de build
COPY --from=builder /app/server /server

# Porta exposta
EXPOSE 8080

# Usar usuário não-root
USER nonroot:nonroot

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/server"] || exit 1

ENTRYPOINT ["/server"]

Estrutura do Projeto

myapp/
├── Dockerfile
├── go.mod
├── go.sum
├── main.go
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   └── secret.yaml
└── Makefile

Build e Push da Imagem

# Build da imagem
export IMAGE_NAME=myapp
export VERSION=1.0.0
export REGISTRY=gcr.io/myproject

docker build -t $REGISTRY/$IMAGE_NAME:$VERSION .
docker tag $REGISTRY/$IMAGE_NAME:$VERSION $REGISTRY/$IMAGE_NAME:latest

# Push para registry
docker push $REGISTRY/$IMAGE_NAME:$VERSION
docker push $REGISTRY/$IMAGE_NAME:latest

2. Kubernetes Básico

Conceitos Fundamentais

ComponenteDescriçãoUso para Go
PodMenor unidade deployável1 container Go por pod
DeploymentGerencia réplicas dos podsEscalar horizontalmente
ServiceExpõe pods para acessoBalancear carga entre pods
ConfigMapConfigurações não-sensíveisVariáveis de ambiente
SecretDados sensíveisSenhas, tokens API
IngressRoteamento HTTPExpor APIs externamente

Deployment Manifest

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-api
  labels:
    app: myapp
    component: api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: myapp
      component: api
  template:
    metadata:
      labels:
        app: myapp
        component: api
    spec:
      containers:
      - name: api
        image: gcr.io/myproject/myapp:1.0.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          protocol: TCP
        env:
        - name: PORT
          value: "8080"
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: log_level
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: database_url
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 60

Service Manifest

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-api-service
  labels:
    app: myapp
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: myapp
    component: api

3. ConfigMaps e Secrets

ConfigMap para Configurações

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  log_level: "info"
  max_connections: "100"
  request_timeout: "30s"
  feature_flags: |
    {
      "enable_cache": true,
      "enable_metrics": true
    }

Secrets para Dados Sensíveis

# Criar secret via linha de comando (não commite isso!)
kubectl create secret generic myapp-secrets \
  --from-literal=database_url="postgres://user:pass@db:5432/myapp" \
  --from-literal=api_key="sk-123456789" \
  --from-literal=jwt_secret="my-super-secret-key"

Ou via manifesto (encode em base64):

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
type: Opaque
data:
  database_url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAZGI6NTQzMi9teWFwcA==
  api_key: c2stMTIzNDU2Nzg5
  jwt_secret: bXktc3VwZXItc2VjcmV0LWtleQ==

4. Health Checks

Health checks são essenciais para que o Kubernetes saiba quando seus pods estão saudáveis.

Implementando Health Checks em Go

package main

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"time"
)

type HealthResponse struct {
	Status    string            `json:"status"`
	Timestamp time.Time         `json:"timestamp"`
	Checks    map[string]string `json:"checks,omitempty"`
}

type Server struct {
	db *sql.DB
}

func (s *Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
	status := "healthy"
	checks := make(map[string]string)

	// Check database
	if s.db != nil {
		if err := s.db.Ping(); err != nil {
			checks["database"] = "unhealthy: " + err.Error()
			status = "unhealthy"
		} else {
			checks["database"] = "healthy"
		}
	}

	// Check external dependencies
	checks["timestamp"] = time.Now().Format(time.RFC3339)

	response := HealthResponse{
		Status:    status,
		Timestamp: time.Now(),
		Checks:    checks,
	}

	w.Header().Set("Content-Type", "application/json")
	if status != "healthy" {
		w.WriteHeader(http.StatusServiceUnavailable)
	}
	json.NewEncoder(w).Encode(response)
}

// Readiness check - indica se o pod pode receber tráfego
func (s *Server) ReadinessHandler(w http.ResponseWriter, r *http.Request) {
	// Verifica se está pronto para receber requests
	ready := true

	if s.db != nil {
		if err := s.db.Ping(); err != nil {
			ready = false
		}
	}

	if ready {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"ready": true}`))
	} else {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(`{"ready": false}`))
	}
}

// Liveness check - indica se o pod precisa ser reiniciado
func (s *Server) LivenessHandler(w http.ResponseWriter, r *http.Request) {
	// Verificação básica - se consegue responder, está vivo
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"alive": true}`))
}

Configurando Probes no Deployment

livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 3

startupProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 30  # Dá 150 segundos para iniciar

5. Boas Práticas para Produção

1. Graceful Shutdown

Sempre implemente graceful shutdown para não perder requests durante deploys:

package main

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

func runServer() error {
	srv := &http.Server{
		Addr:    ":8080",
		Handler: setupRoutes(),
	}

	// Canal para sinais do sistema
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	// Iniciar servidor em goroutine
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Erro no servidor: %v", err)
		}
	}()

	// Aguardar sinal de término
	<-quit
	log.Println("Recebido sinal de shutdown...")

	// Graceful shutdown com timeout
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		return fmt.Errorf("erro no shutdown: %w", err)
	}

	log.Println("Servidor encerrado")
	return nil
}

2. Logs Estruturados

Use JSON para logs no K8s (facilita parsing):

import (
	"log/slog"
	"os"
)

func init() {
	// Configurar logger estruturado
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)
}

// Uso
slog.Info("request processed",
    "method", r.Method,
    "path", r.URL.Path,
    "status", statusCode,
    "duration", duration.Milliseconds(),
)

3. Métricas Prometheus

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	httpRequestsTotal = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "http_requests_total",
			Help: "Total de requests HTTP",
		},
		[]string{"method", "endpoint", "status"},
	)

	httpRequestDuration = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "http_request_duration_seconds",
			Help:    "Duração dos requests HTTP em segundos",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"method", "endpoint"},
	)
)

// Middleware para métricas
func metricsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		rw := &responseWriter{w, http.StatusOK}
		
		next.ServeHTTP(rw, r)
		
		duration := time.Since(start).Seconds()
		httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, fmt.Sprintf("%d", rw.statusCode)).Inc()
		httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
	})
}

// Adicionar endpoint de métricas
r.Handle("/metrics", promhttp.Handler())

4. Resource Limits

Sempre defina requests e limits apropriados:

resources:
  requests:
    memory: "64Mi"   # Go apps são leves
    cpu: "50m"
  limits:
    memory: "256Mi"
    cpu: "500m"

Dica: Aplicações Go tipicamente usam pouca memória. Comece conservador e ajuste baseado em métricas.

5. Horizontal Pod Autoscaler (HPA)

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60

6. Deploy no Cluster

Aplicando os Manifests

# Criar namespace
kubectl create namespace myapp

# Aplicar configurações
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yaml

# Verificar status
kubectl get pods -n myapp
kubectl get svc -n myapp
kubectl logs -f deployment/myapp-api -n myapp

Makefile para Automatizar

# Makefile
.PHONY: build push deploy logs

IMAGE := gcr.io/myproject/myapp
VERSION := $(shell git describe --tags --always --dirty)

build:
	docker build -t $(IMAGE):$(VERSION) .
	docker tag $(IMAGE):$(VERSION) $(IMAGE):latest

push: build
	docker push $(IMAGE):$(VERSION)
	docker push $(IMAGE):latest

deploy: push
	kubectl set image deployment/myapp-api api=$(IMAGE):$(VERSION) -n myapp
	kubectl rollout status deployment/myapp-api -n myapp

logs:
	kubectl logs -f deployment/myapp-api -n myapp

rollout-status:
	kubectl rollout status deployment/myapp-api -n myapp

rollback:
	kubectl rollout undo deployment/myapp-api -n myapp

7. Troubleshooting

Comandos Úteis

# Ver logs de um pod específico
kubectl logs -f pod/myapp-api-xxx -n myapp

# Ver logs de todos os pods do deployment
kubectl logs -f deployment/myapp-api -n myapp

# Descrever pod (útil para debugging)
kubectl describe pod myapp-api-xxx -n myapp

# Executar comando dentro do pod
kubectl exec -it myapp-api-xxx -n myapp -- /bin/sh

# Port forward para teste local
kubectl port-forward svc/myapp-api-service 8080:80 -n myapp

# Ver métricas do pod
kubectl top pod -n myapp

# Ver eventos
kubectl get events -n myapp --sort-by='.lastTimestamp'

Problemas Comuns

ProblemaCausa ProvávelSolução
CrashLoopBackOffErro na aplicaçãoVer logs: kubectl logs
ImagePullBackOffImagem não encontradaVerificar nome/tag da imagem
PendingRecursos insuficientesVerificar quotas do node
Health check falhaEndpoint não respondeVerificar porta/caminho
OOMKilledLimite de memóriaAumentar memory limit

8. CI/CD com GitHub Actions

# .github/workflows/deploy.yaml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Go
      uses: actions/setup-go@v5
      with:
        go-version: '1.21'
    
    - name: Test
      run: go test ./...
    
    - name: Build
      run: go build -v ./...

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Login to GCR
      uses: docker/login-action@v3
      with:
        registry: gcr.io
        username: _json_key
        password: ${{ secrets.GCR_SA_KEY }}
    
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: |
          gcr.io/myproject/myapp:${{ github.sha }}
          gcr.io/myproject/myapp:latest
    
    - name: Setup kubectl
      uses: google-github-actions/get-gke-credentials@v2
      with:
        cluster_name: my-cluster
        location: us-central1
    
    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/myapp-api api=gcr.io/myproject/myapp:${{ github.sha }} -n myapp
        kubectl rollout status deployment/myapp-api -n myapp

Conclusão

Deployar aplicações Go em Kubernetes oferece:

  • Escalabilidade: Scale horizontal automático baseado em demanda
  • Resiliência: Self-healing com health checks
  • Eficiência: Binários pequenos, startup rápido
  • Observabilidade: Métricas e logs estruturados

Comece com um Dockerfile multi-stage, adicione health checks adequados e use HPA para escalar automaticamente. A combinação de Go + Kubernetes é ideal para microserviços de alta performance.

Próximos Passos


Gostou deste tutorial? Compartilhe com sua equipe e deixe um comentário se tiver dúvidas sobre deploy de Go no Kubernetes!