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
| Componente | Descrição | Uso para Go |
|---|---|---|
| Pod | Menor unidade deployável | 1 container Go por pod |
| Deployment | Gerencia réplicas dos pods | Escalar horizontalmente |
| Service | Expõe pods para acesso | Balancear carga entre pods |
| ConfigMap | Configurações não-sensíveis | Variáveis de ambiente |
| Secret | Dados sensíveis | Senhas, tokens API |
| Ingress | Roteamento HTTP | Expor 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
| Problema | Causa Provável | Solução |
|---|---|---|
CrashLoopBackOff | Erro na aplicação | Ver logs: kubectl logs |
ImagePullBackOff | Imagem não encontrada | Verificar nome/tag da imagem |
Pending | Recursos insuficientes | Verificar quotas do node |
| Health check falha | Endpoint não responde | Verificar porta/caminho |
| OOMKilled | Limite de memória | Aumentar 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
- Go Observability: Logs, Métricas e Traces
- Go e Docker: Containerização de Aplicações
- Go para Microserviços: Arquitetura e Práticas
- Go e gRPC: Comunicação entre Serviços
Gostou deste tutorial? Compartilhe com sua equipe e deixe um comentário se tiver dúvidas sobre deploy de Go no Kubernetes!