Implantação e Otimização de Projeto

Lição 28: Implantação e Otimização de Projeto

Analogia do Mundo Real

Imagine que você abriu um restaurante. As receitas estão escritas (o código está pronto), mas para realmente começar a operar, você precisa:

Conceitos Fundamentais

Conceito Descrição
Compilação cruzada Compilar executáveis para uma plataforma em outra plataforma
Build multi-stage Build Docker separando ambientes de compilação e runtime, reduzindo tamanho da imagem
Variáveis de ambiente Configurar comportamento do programa sem modificar código
Logging estruturado Usar log/slog para saída de logs em formato parseável por máquina
Profiling de desempenho Usar pprof para identificar gargalos de CPU e memória
Encerramento gracioso Após receber sinal de término, esperar requisições em andamento completarem antes de sair

Sintaxe Básica e Uso

1. Compilação Cruzada

Go suporta nativamente compilação cruzada, basta definir as variáveis de ambiente GOOS e GOARCH:

BASH
# Compilar versão Linux amd64 (executar no Windows/Mac)
GOOS=linux GOARCH=amd64 go build -o myapp-linux .

# Compilar versão Windows (executar no Linux/Mac)
GOOS=windows GOARCH=amd64 go build -o myapp.exe .

# Compilar versão macOS ARM (M1/M2)
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin .
💡 Dica: Durante a compilação cruzada, CGO é desabilitado por padrão. Se você depende de bibliotecas C, precisa instalar toolchains de compilação cruzada.

2. Build Multi-Stage Docker

DOCKERFILE
# ---- Etapa de Build ----
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 -o server .

# ---- Etapa de Runtime ----
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
💡 Dica: A imagem final contém apenas o binário compilado e ambiente mínimo de runtime, reduzindo o tamanho de 1GB para menos de 20MB.

3. Configuração por Variáveis de Ambiente

GO
package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	// Ler variáveis de ambiente com valores padrão
	port := getEnv("APP_PORT", "8080")
	dbHost := getEnv("DB_HOST", "localhost")
	debug := getEnv("DEBUG", "false")

	fmt.Printf("Configuração de inicialização: porta=%s, db=%s, debug=%s\n", port, dbHost, debug)
}

// getEnv obtém uma variável de ambiente, retorna valor padrão se não definida
func getEnv(key, defaultVal string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defaultVal
}
💡 Dica: Para produção, recomenda-se usar arquivos .env com a biblioteca godotenv, mas não commite arquivos .env no controle de versão.

4. Logging Estruturado (log/slog)

O Go 1.21 introduziu a biblioteca padrão log/slog, suportando saída em formato JSON:

GO
package main

import (
	"log/slog"
	"os"
)

func main() {
	// Criar handler em formato JSON
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))

	// Usar logging estruturado
	logger.Info("Serviço iniciado",
		"port", 8080,
		"env", "produção",
	)

	logger.Error("Falha na conexão com banco de dados",
		"host", "db.example.com",
		"error", "conexão recusada",
	)
}
💡 Dica: slog suporta definir um logger global padrão via slog.SetDefault(), que pode ser usado uniformemente em todo o projeto.

5. Profiling de Desempenho (pprof)

GO
package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof" // Import registra rotas pprof
	"time"
)

func main() {
	// Iniciar serviço pprof em uma porta separada
	go func() {
		fmt.Println("pprof escutando em :6060")
		http.ListenAndServe(":6060", nil)
	}()

	// Sua lógica de negócio principal
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(10 * time.Millisecond)
		fmt.Fprintln(w, "Hello, World!")
	})

	http.ListenAndServe(":8080", nil)
}
💡 Dica: go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 pode coletar 30 segundos de dados de CPU.

6. Encerramento Gracioso

GO
package main

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

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second) // Simular requisição longa
		fmt.Fprintln(w, "Processamento completo")
	})

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

	// Iniciar servidor em uma goroutine
	go func() {
		fmt.Println("Servidor escutando em :8080")
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			fmt.Printf("Servidor saiu anormalmente: %v\n", err)
		}
	}()

	// Esperar sinal de interrupção
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	fmt.Println("\nSinal de encerramento recebido, saindo graciosamente...")

	// Dar às requisições em andamento até 10 segundos para completar
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		fmt.Printf("Encerramento forçado: %v\n", err)
	} else {
		fmt.Println("Servidor encerrado graciosamente")
	}
}
💡 Dica: server.Shutdown() para de aceitar novas conexões e espera as conexões existentes terminarem de processar antes de retornar.


Exemplo: Script Básico de Compilação Cruzada (Dificuldade ⭐)

Crie um script que compile binários para múltiplas plataformas de uma vez:

GO
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// Lista de plataformas alvo
type Target struct {
	OS   string
	Arch string
}

func main() {
	targets := []Target{
		{"linux", "amd64"},
		{"linux", "arm64"},
		{"darwin", "amd64"},
		{"darwin", "arm64"},
		{"windows", "amd64"},
	}

	outputDir := "dist"
	os.MkdirAll(outputDir, 0755)

	for _, t := range targets {
		outputName := fmt.Sprintf("myapp-%s-%s", t.OS, t.Arch)
		if t.OS == "windows" {
			outputName += ".exe"
		}

		outputPath := filepath.Join(outputDir, outputName)
		fmt.Printf("Compilando: %s/%s -> %s\n", t.OS, t.Arch, outputPath)

		cmd := exec.Command("go", "build", "-o", outputPath, ".")
		cmd.Env = append(os.Environ(),
			"GOOS="+t.OS,
			"GOARCH="+t.Arch,
			"CGO_ENABLED=0",
		)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		if err := cmd.Run(); err != nil {
			fmt.Printf("Falha na compilação %s/%s: %v\n", t.OS, t.Arch, err)
		}
	}

	fmt.Println("Todas as compilações concluídas!")
}
▶ Experimente

Executar:

BASH
go run build.go

Estrutura do diretório de saída:

TEXT
dist/
├── myapp-darwin-amd64
├── myapp-darwin-arm64
├── myapp-linux-amd64
├── myapp-linux-arm64
└── myapp-windows-amd64.exe

Exemplo: Serviço HTTP com Configuração por Variáveis de Ambiente (Dificuldade ⭐⭐)

Demonstra um esquema completo de configuração por variáveis de ambiente:

GO
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"
	"time"
)

// Config struct de configuração da aplicação
type Config struct {
	Port        int    `json:"port"`
	Env         string `json:"env"`
	LogLevel    string `json:"log_level"`
	DBHost      string `json:"db_host"`
	DBPort      int    `json:"db_port"`
	IdleTimeout int    `json:"idle_timeout"` // segundos
}

// LoadConfig carrega configuração das variáveis de ambiente
func LoadConfig() Config {
	return Config{
		Port:        getEnvInt("APP_PORT", 8080),
		Env:         getEnvStr("APP_ENV", "development"),
		LogLevel:    getEnvStr("LOG_LEVEL", "info"),
		DBHost:      getEnvStr("DB_HOST", "localhost"),
		DBPort:      getEnvInt("DB_PORT", 5432),
		IdleTimeout: getEnvInt("IDLE_TIMEOUT", 30),
	}
}

func getEnvStr(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

func getEnvInt(key string, fallback int) int {
	if v := os.Getenv(key); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			return n
		}
	}
	return fallback
}

func (c Config) String() string {
	data, _ := json.MarshalIndent(c, "", "  ")
	return string(data)
}

func main() {
	cfg := LoadConfig()

	// Configurar nível de log baseado no ambiente
	var level slog.Level
	switch cfg.LogLevel {
	case "debug":
		level = slog.LevelDebug
	case "warn":
		level = slog.LevelWarn
	case "error":
		level = slog.LevelError
	default:
		level = slog.LevelInfo
	}

	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: level,
	}))
	slog.SetDefault(logger)

	logger.Info("Configuração da aplicação carregada", "config", cfg.String())

	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{
			"status": "ok",
			"env":    cfg.Env,
		})
	})

	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		logger.Info("Requisição recebida", "method", r.Method, "path", r.URL.Path)
		fmt.Fprintf(w, "Olá do ambiente %s!", cfg.Env)
	})

	server := &http.Server{
		Addr:        fmt.Sprintf(":%d", cfg.Port),
		Handler:     mux,
		IdleTimeout: time.Duration(cfg.IdleTimeout) * time.Second,
	}

	go func() {
		logger.Info("Serviço iniciado", "port", cfg.Port, "env", cfg.Env)
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			logger.Error("Erro no serviço", "error", err)
			os.Exit(1)
		}
	}()

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

	logger.Info("Encerrando serviço...")
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	server.Shutdown(ctx)
	logger.Info("Serviço encerrado")
}
▶ Experimente

Iniciar:

BASH
# Usar configuração padrão
go run main.go

# Configuração personalizada
APP_PORT=3000 APP_ENV=production LOG_LEVEL=debug go run main.go

Testar requisições:

BASH
# Health check
curl http://localhost:8080/health

# Página inicial
curl http://localhost:8080/

Exemplo: Solução Completa de Implantação Docker (Dificuldade ⭐⭐⭐)

Uma solução de implantação completa incluindo Dockerfile, docker-compose e Makefile:

Estrutura do projeto:

TEXT
myproject/
├── main.go
├── go.mod
├── go.sum
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── .env.example
▶ Experimente

main.go:

GO
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"syscall"
	"time"
)

type AppConfig struct {
	Port      string
	Env       string
	LogLevel  string
	PprofPort string
}

func loadConfig() AppConfig {
	return AppConfig{
		Port:      getEnv("APP_PORT", "8080"),
		Env:       getEnv("APP_ENV", "production"),
		LogLevel:  getEnv("LOG_LEVEL", "info"),
		PprofPort: getEnv("PPROF_PORT", "6060"),
	}
}

func getEnv(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func setupLogger(level string) *slog.Logger {
	var lvl slog.Level
	switch level {
	case "debug":
		lvl = slog.LevelDebug
	case "warn":
		lvl = slog.LevelWarn
	case "error":
		lvl = slog.LevelError
	default:
		lvl = slog.LevelInfo
	}
	return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl}))
}

func main() {
	cfg := loadConfig()
	logger := setupLogger(cfg.LogLevel)
	slog.SetDefault(logger)

	// Iniciar pprof (apenas em não-produção ou quando explicitamente habilitado)
	if cfg.Env != "production" || os.Getenv("ENABLE_PPROF") == "true" {
		go func() {
			logger.Info("Serviço pprof iniciado", "port", cfg.PprofPort)
			http.ListenAndServe(":"+cfg.PprofPort, nil)
		}()
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"status":    "ok",
			"env":       cfg.Env,
			"goroutine": runtime.NumGoroutine(),
			"uptime":    time.Since(startTime).String(),
		})
	})

	mux.HandleFunc("GET /api/info", func(w http.ResponseWriter, r *http.Request) {
		logger.Info("requisição de info", "remote", r.RemoteAddr)
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{
			"app":      "myproject",
			"version":  version,
			"go":       runtime.Version(),
			"env":      cfg.Env,
		})
	})

	mux.HandleFunc("POST /api/data", func(w http.ResponseWriter, r *http.Request) {
		// Simular processamento de dados
		time.Sleep(100 * time.Millisecond)
		logger.Info("Processamento de dados completo", "content_length", r.ContentLength)
		w.WriteHeader(http.StatusCreated)
		json.NewEncoder(w).Encode(map[string]string{"result": "created"})
	})

	server := &http.Server{
		Addr:         ":" + cfg.Port,
		Handler:      mux,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// Lógica de encerramento gracioso
	done := make(chan struct{})
	go func() {
		quit := make(chan os.Signal, 1)
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
		sig := <-quit
		logger.Info("Sinal de encerramento recebido", "signal", sig)

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		// Parar pprof
		// Nota: pprof da biblioteca padrão não tem Shutdown, depende da saída do processo

		if err := server.Shutdown(ctx); err != nil {
			logger.Error("Falha no encerramento do servidor", "error", err)
		}
		close(done)
	}()

	logger.Info("Serviço iniciado",
		"port", cfg.Port,
		"env", cfg.Env,
		"version", version,
	)

	if err := server.ListenAndServe(); err != http.ErrServerClosed {
		logger.Error("Servidor saiu anormalmente", "error", err)
		os.Exit(1)
	}

	<-done
	logger.Info("Serviço totalmente encerrado")
}

var (
	startTime = time.Now()
	version   = "1.0.0"
)

Dockerfile:

DOCKERFILE
# ========== Etapa de Build ==========
FROM golang:1.22-alpine AS builder

# Instalar git (go mod pode precisar)
RUN apk add --no-cache git

WORKDIR /build

# Copiar arquivos de dependência primeiro, aproveitando camadas de cache do Docker
COPY go.mod go.sum ./
RUN go mod download

# Copiar fonte e compilar
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w" -o /app/server .

# ========== Etapa de Runtime ==========
FROM alpine:3.19

# Instalar certificados CA (para requisições HTTPS) e dados de fuso horário
RUN apk --no-cache add ca-certificates tzdata

# Criar usuário não-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copiar binário da etapa de build
COPY --from=builder /app/server .
COPY --from=builder /build/.env.example .env.example

# Executar como usuário não-root
USER appuser

EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

CMD ["./server"]

docker-compose.yml:

YAML
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - APP_ENV=production
      - LOG_LEVEL=info
      - APP_PORT=8080
      - ENABLE_PPROF=false
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: '0.5'
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Makefile:

MAKEFILE
APP_NAME := myproject
VERSION  := 1.0.0

.PHONY: build run clean docker docker-run docker-stop

# Build local
build:
	CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME) .

# Executar local
run: build
	./bin/$(APP_NAME)

# Limpar
clean:
	rm -rf bin/

# Build imagem Docker
docker:
	docker build -t $(APP_NAME):$(VERSION) .

# Iniciar container Docker
docker-run:
	docker-compose up -d

# Parar container Docker
docker-stop:
	docker-compose down

# Compilar todas as plataformas
cross-build:
	GOOS=linux   GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-linux-amd64 .
	GOOS=darwin  GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-darwin-amd64 .
	GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-windows-amd64.exe .

.env.example:

TEXT
APP_ENV=production
APP_PORT=8080
LOG_LEVEL=info
ENABLE_PPROF=false
PPROF_PORT=6060

Fluxo de build e implantação:

BASH
# 1. Teste local
make run

# 2. Build imagem Docker
make docker

# 3. Iniciar container
make docker-run

# 4. Testar serviço
curl http://localhost:8080/health
curl http://localhost:8080/api/info

# 5. Ver logs
docker-compose logs -f

# 6. Parar serviço
make docker-stop

Cenário 1: Logging e Monitoramento de Produção

Em um serviço web real, os logs precisam ser coletados em um sistema centralizado de logging:

GO
package main

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

// RequestLogger middleware: registra informações detalhadas para cada requisição
func RequestLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		// Envolver ResponseWriter para capturar código de status
		rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
		next.ServeHTTP(rw, r)

		slog.Info("Requisição HTTP",
			"method", r.Method,
			"path", r.URL.Path,
			"status", rw.statusCode,
			"duration_ms", time.Since(start).Milliseconds(),
			"remote_addr", r.RemoteAddr,
			"user_agent", r.UserAgent(),
		)
	})
}

type responseWriter struct {
	http.ResponseWriter
	statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}

// RecoveryMiddleware captura panics e os registra
func RecoveryMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				slog.Error("Panic no processamento de requisição",
					"error", err,
					"path", r.URL.Path,
					"method", r.Method,
				)
				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func main() {
	// Produção usa formato JSON para facilitar parsing pelo sistema de coleta de logs
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	mux := http.NewServeMux()
	mux.HandleFunc("GET /api/users", func(w http.ResponseWriter, r *http.Request) {
		// Simular lógica de negócio
		ctx := r.Context()
		slog.DebugContext(ctx, "Consultando lista de usuários", "page", 1)
		// ...
	})

	// Cadeia de middleware: Recovery -> Logger -> Router
	handler := RecoveryMiddleware(RequestLogger(mux))

	server := &http.Server{Addr: ":8080", Handler: handler}

	slog.Info("Serviço iniciado", "addr", server.Addr)
	server.ListenAndServe()
}

Após executar, cada requisição gera logs estruturados:

JSON
{"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"Requisição HTTP","method":"GET","path":"/api/users","status":200,"duration_ms":5,"remote_addr":"127.0.0.1","user_agent":"curl/7.68.0"}

Cenário 2: Usando pprof para Localizar Gargalos de Desempenho

Quando a resposta do serviço fica lenta, use pprof para diagnóstico:

GO
package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"runtime"
	"strings"
	"time"
)

func main() {
	// Serviço principal
	mux := http.NewServeMux()

	// Simular um endpoint com problemas de desempenho
	mux.HandleFunc("GET /api/search", func(w http.ResponseWriter, r *http.Request) {
		query := r.URL.Query().Get("q")
		result := slowSearch(query) // Função intencionalmente lenta
		fmt.Fprint(w, result)
	})

	// pprof registra no http.DefaultServeMux padrão, precisa de porta separada
	go func() {
		fmt.Println("pprof: http://localhost:6060/debug/pprof/")
		http.ListenAndServe(":6060", nil)
	}()

	fmt.Println("Serviço principal: http://localhost:8080")
	http.ListenAndServe(":8080", mux)
}

// slowSearch simula uma função intensiva de CPU com problemas de alocação de memória
func slowSearch(query string) string {
	var results []string
	// Intencionalmente alocar memória repetidamente em um loop
	for i := 0; i < 100000; i++ {
		data := fmt.Sprintf("item_%d_%s", i, query) // Alocar nova string a cada iteração
		if strings.Contains(data, query) {
			results = append(results, data)
		}
	}
	// Simular atraso adicional
	time.Sleep(50 * time.Millisecond)
	return fmt.Sprintf("Encontrados %d resultados", len(results))
}

Usando pprof para análise:

BASH
# 1. Instalar ferramenta pprof (se ainda não instalada)
go install github.com/google/pprof@latest

# 2. Coletar 30 segundos de perfil de CPU
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 3. Ver na interface interativa do pprof
(pprof) top          # Ver funções mais intensivas de CPU
(pprof) list slowSearch  # Ver custo de linhas específicas de código
(pprof) web          # Gerar visualização (requer graphviz)

# 4. Ver alocação de memória
go tool pprof http://localhost:6060/debug/pprof/heap

# 5. Ver contagem de goroutines (detectar vazamentos)
go tool pprof http://localhost:6060/debug/pprof/goroutine

# 6. Usar navegador para ver todas as métricas
# Abrir http://localhost:6060/debug/pprof/

❓ Perguntas Frequentes

P1: O que fazer ao encontrar cgo: exec gcc: exec: "gcc": not found durante compilação cruzada?

Isso acontece porque o código usa CGO. Se você não precisa de dependências de bibliotecas C, defina CGO_ENABLED=0:

BASH
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp .

Se você realmente precisa de CGO, precisa instalar toolchains de compilação cruzada para a plataforma alvo (como gcc-aarch64-linux-gnu).

P2: Imagem Docker muito grande, como reduzir ainda mais o tamanho?

Use scratch como imagem base (sem ferramentas de sistema):

DOCKERFILE
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
CMD ["/server"]

Combinado com remoção de informações de debug durante a compilação: go build -ldflags="-s -w". -s remove a tabela de símbolos, -w remove informações de debug DWARF, tipicamente reduzindo o tamanho em 20-30%.

P3: Como log/slog se compara a bibliotecas de logging de terceiros (como zap, zerolog)?

A vantagem do log/slog é ser uma biblioteca padrão sem dependências adicionais, e seu desempenho é suficiente para a maioria dos cenários. Se sua aplicação tem volume extremamente alto de logs (dezenas de milhares por segundo), o design de zero-alocação do zap será mais rápido. Para novos projetos, comece com slog; considere trocar apenas se encontrar gargalos de desempenho.

P4: Durante o encerramento gracioso, como garantir que conexões WebSocket sejam tratadas corretamente?

http.Server.Shutdown() apenas lida com requisições HTTP. Para conexões de longa duração (WebSocket), você precisa gerenciar o pool de conexões você mesmo:

GO
var connections sync.Map // Armazenar conexões ativas

// Registrar quando nova conexão chega
connections.Store(conn, true)

// No encerramento, iterar todas as conexões e enviar frames de fechamento
connections.Range(func(key, value any) bool {
    wsConn := key.(*websocket.Conn)
    wsConn.WriteMessage(websocket.CloseMessage, closeMsg)
    wsConn.Close()
    return true
})

📖 Resumo

Nesta lição aprendemos o fluxo completo de desenvolvimento à implantação para projetos Go:

Após dominar essas habilidades, você pode implantar projetos Go em ambientes de produção de forma segura e eficiente.


📝 Exercícios

Exercício 1: Escrever um Script de Compilação Cruzada

Escreva um programa Go que compile automaticamente o projeto atual para plataformas linux/amd64, darwin/arm64, windows/amd64, com nomes de arquivo de saída contendo informações de versão e plataforma.

Exercício 2: Adicionar Middleware de Logging de Requisição

Adicione middleware de logging a um serviço HTTP que registre para cada requisição: método, caminho, código de status, tempo de resposta, IP do cliente. Use log/slog para saída em formato JSON.

Exercício 3: Dockerize Seu Projeto

Escreva um Dockerfile e docker-compose.yml completos para qualquer projeto que você escreveu nas lições anteriores (ex: API Todo), com os seguintes requisitos:


Próxima Lição

Após completar esta lição, você dominou as habilidades principais para implantação de projetos Go. Em seguida entraremos na fase de projeto prático: LiÇÃO 29: Projeto Prático (Parte 1)

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%