Go Admin

Como Usar Deploy de APIs Go em Produção: VPS, Kubernetes e GitHub Actions em Produção Já leu

Introdução: Por Que Go é Ideal para APIs em Produção Go é uma linguagem compilada, estaticamente tipada e com foco em concorrência. Quando falamos de deploy em produção, você precisa de uma linguagem que seja rápida, confiável e fácil de operar. Go atende todos esses requisitos. Um único binário, sem dependências externas de runtime, torna a implantação em VPS extremamente simples. Além disso, a comunidade Go tem um ecossistema maduro com frameworks como Gin, Echo e Chi que facilitam a construção de APIs robustas. O objetivo deste artigo é guiá-lo através de três cenários reais de deploy: em uma VPS tradicional, em um cluster Kubernetes e automatizando tudo com GitHub Actions. Você aprenderá não apenas como fazer, mas por que cada abordagem faz sentido em diferentes contextos. Entendendo a API Go que Vamos Deployar Estrutura Básica de uma API Go Antes de falar de deploy, você precisa entender o que estamos implantando. Vou usar o framework Echo, que é lightweight

Introdução: Por Que Go é Ideal para APIs em Produção

Go é uma linguagem compilada, estaticamente tipada e com foco em concorrência. Quando falamos de deploy em produção, você precisa de uma linguagem que seja rápida, confiável e fácil de operar. Go atende todos esses requisitos. Um único binário, sem dependências externas de runtime, torna a implantação em VPS extremamente simples. Além disso, a comunidade Go tem um ecossistema maduro com frameworks como Gin, Echo e Chi que facilitam a construção de APIs robustas.

O objetivo deste artigo é guiá-lo através de três cenários reais de deploy: em uma VPS tradicional, em um cluster Kubernetes e automatizando tudo com GitHub Actions. Você aprenderá não apenas como fazer, mas por que cada abordagem faz sentido em diferentes contextos.

Entendendo a API Go que Vamos Deployar

Estrutura Básica de uma API Go

Antes de falar de deploy, você precisa entender o que estamos implantando. Vou usar o framework Echo, que é lightweight e production-ready. Uma API simples com autenticação básica, logging e tratamento de erros será nosso exemplo.

package main

import (
    "log"
    "net/http"
    "os"
    "time"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

type Product struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Price float64 `json:"price"`
}

func main() {
    // Inicializar Echo
    e := echo.New()

    // Middleware de logging
    e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
        Format: "[${time_rfc3339}] ${method} ${uri} ${status} ${latency_human}\n",
    }))

    // Middleware de recuperação de panics
    e.Use(middleware.Recover())

    // Rota de saúde (importante para healthchecks)
    e.GET("/health", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "status": "ok",
            "time":   time.Now().UTC().String(),
        })
    })

    // Rota de produtos
    e.GET("/api/products", getProducts)
    e.POST("/api/products", createProduct)

    // Obter porta da variável de ambiente ou usar padrão
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    log.Printf("Server starting on port %s\n", port)
    if err := e.Start(":" + port); err != nil {
        log.Fatalf("Server error: %v\n", err)
    }
}

func getProducts(c echo.Context) error {
    products := []Product{
        {ID: 1, Name: "Notebook", Price: 2500.00},
        {ID: 2, Name: "Mouse", Price: 50.00},
    }
    return c.JSON(http.StatusOK, products)
}

func createProduct(c echo.Context) error {
    p := new(Product)
    if err := c.BindJSON(&p); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{
            "error": "Invalid JSON",
        })
    }
    return c.JSON(http.StatusCreated, p)
}

Este código é minimalista, mas representa uma API real: tem rota de health check (essencial para monitoramento), logging estruturado, middleware de recuperação, e usa variáveis de ambiente. Essas práticas são fundamentais quando você vai para produção.

Preparando o Projeto para Produção

Você precisa de um arquivo go.mod e go.sum versionados. Crie um Dockerfile para containerizar a aplicação, pois isso facilita deploy tanto em VPS quanto em Kubernetes:

# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

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

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o api-go .

# Runtime stage
FROM alpine:3.18

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /app/api-go .

EXPOSE 8080

ENV PORT=8080

CMD ["./api-go"]

Essa abordagem multi-stage reduz o tamanho da imagem final. O primeiro stage compila o Go (que tira o binário), o segundo stage usa apenas Alpine Linux com o binário pronto. Isso resulta em imagens de ~20MB em vez de 800MB.

Deploy em VPS: Simplicidade e Controle Total

Configuração do Servidor

Um deploy em VPS é a abordagem mais simples. Você terá controle total, menos abstrações, e custos previsíveis. A maioria das VPS oferece Ubuntu 22.04 LTS, que é estável e bem suportada. O fluxo é: build local → upload binário → execute com um gerenciador de processo.

Primeiro, provisionando a VPS, você precisa de SSH configurado, firewall adequado e um usuário não-root. Instale Go ou Docker (se optar por container):

#!/bin/bash
# Script de setup inicial da VPS

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git ca-certificates

# Instalar Docker (opcional mas recomendado)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER

# Criar diretório para a aplicação
sudo mkdir -p /opt/api-go
sudo chown $USER:$USER /opt/api-go

Build e Deploy Manual

Localmente, compile seu binário Go para Linux:

# No seu computador ou no runner
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o api-go-linux .

# Upload para VPS via SCP
scp api-go-linux user@seu-vps.com:/opt/api-go/

# Conectar via SSH e executar
ssh user@seu-vps.com
cd /opt/api-go
chmod +x api-go-linux
./api-go-linux

Mas executar manualmente não é prático. Use Systemd para gerenciar o processo:

# /etc/systemd/system/api-go.service
[Unit]
Description=Go API Service
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/api-go
ExecStart=/opt/api-go/api-go-linux
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

# Limites de recursos
LimitNOFILE=65536
LimitNPROC=65536

# Variáveis de ambiente
Environment="PORT=8080"
Environment="ENV=production"

[Install]
WantedBy=multi-user.target

Ativar e iniciar:

sudo systemctl daemon-reload
sudo systemctl enable api-go
sudo systemctl start api-go
sudo systemctl status api-go

Reverse Proxy com Nginx

Nunca exponha sua aplicação Go diretamente na porta 80/443. Use um reverse proxy. Nginx é leve e eficiente:

# /etc/nginx/sites-available/api-go
server {
    listen 80;
    server_name seu-dominio.com;

    # Redirecionar HTTP para HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name seu-dominio.com;

    ssl_certificate /etc/letsencrypt/live/seu-dominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/seu-dominio.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Logs
    access_log /var/log/nginx/api-go-access.log;
    error_log /var/log/nginx/api-go-error.log;

    # Proxy para sua API Go
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }

    # Health check endpoint
    location /health {
        proxy_pass http://127.0.0.1:8080/health;
        access_log off;
    }
}

Use Certbot para SSL gratuito com Let's Encrypt:

sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d seu-dominio.com

Por que essa abordagem? VPS é ideal para aplicações pequenas e médias. Você tem controle total, debugar é simples (logs em /var/log ou via journalctl), e não há overhead de orquestração. O custo é entre $5-20/mês.

Deploy em Kubernetes: Escalabilidade e Automação

Quando Usar Kubernetes

Kubernetes é complexo, mas resolve problemas reais em larga escala: orquestração automática de containers, escalabilidade horizontal, service discovery, rolling updates sem downtime. Se sua API precisa servir milhões de requisições e lidar com falhas automaticamente, Kubernetes é o caminho.

Vou assumir um cluster Kubernetes gerenciado (como GKE, EKS ou DigitalOcean Kubernetes). Você precisará de kubectl instalado e acesso ao cluster.

Containerizar a Aplicação

Você já tem o Dockerfile. Agora build e push para um registry:

# Build da imagem
docker build -t seu-usuario/api-go:1.0.0 .

# Login no Docker Hub (ou seu registry)
docker login

# Push
docker push seu-usuario/api-go:1.0.0

Manifest Kubernetes

Crie arquivos de configuração. Primeiro, um Deployment:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-go
  labels:
    app: api-go
spec:
  replicas: 3  # 3 instâncias sempre rodando
  selector:
    matchLabels:
      app: api-go
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # Zero downtime
  template:
    metadata:
      labels:
        app: api-go
    spec:
      containers:
      - name: api-go
        image: seu-usuario/api-go:1.0.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: PORT
          value: "8080"
        - name: ENV
          value: "production"

        # Health checks
        livenessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 10
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3

        readinessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 5
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3

        # Limites de recursos
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi

        # Graceful shutdown
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]

Depois, um Service para expor a aplicação:

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-go
  labels:
    app: api-go
spec:
  type: ClusterIP  # Interno, exposto via Ingress
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: api-go

E um Ingress para roteamento HTTP/HTTPS:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-go
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - seu-dominio.com
    secretName: api-go-tls
  rules:
  - host: seu-dominio.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-go
            port:
              number: 80

Aplicando no Cluster

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

# Verificar status
kubectl get pods
kubectl get svc
kubectl get ingress

# Ver logs de um pod
kubectl logs -f deployment/api-go

# Acessar shell de um pod (para debug)
kubectl exec -it <pod-name> -- sh

Atualizando a Aplicação

Quando você tem uma nova versão, atualize a tag da imagem no Deployment:

kubectl set image deployment/api-go api-go=seu-usuario/api-go:1.0.1 --record

# Ou edite manualmente
kubectl edit deployment api-go

Kubernetes fará um rolling update: novos pods com a nova versão são criados enquanto os antigos são gracefully desligados. Zero downtime.

Por que essa abordagem? Kubernetes é ideal quando você tem equipes maiores, múltiplas aplicações, e precisa de escalabilidade automática. Mas tem custo de complexidade: você precisa entender networking, storage, RBAC, etc.

CI/CD com GitHub Actions: Automação Completa

Por Que Automatizar?

Deploy manual é propenso a erros. GitHub Actions permite que você defina um pipeline: quando faz push para main, o código é testado, builtado, deployado automaticamente. Você dorme tranquilo.

Estrutura do Workflow

Crie um arquivo .github/workflows/deploy.yaml:

name: Build and Deploy

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

env:
  REGISTRY: docker.io
  IMAGE_NAME: seu-usuario/api-go

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'

    - name: Run Tests
      run: go test -v ./...

    - name: Run Linter
      uses: golangci/golangci-lint-action@v3
      with:
        version: latest

    - name: Build Binary
      run: |
        GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
        go build -o api-go-linux .

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}
          type=sha,prefix={{branch}}-

    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

  deploy-vps:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v4

    - name: Deploy to VPS
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.VPS_HOST }}
        username: ${{ secrets.VPS_USER }}
        key: ${{ secrets.VPS_SSH_KEY }}
        script: |
          cd /opt/api-go
          docker pull seu-usuario/api-go:main
          docker stop api-go || true
          docker rm api-go || true
          docker run -d \
            --name api-go \
            --restart unless-stopped \
            -p 127.0.0.1:8080:8080 \
            -e PORT=8080 \
            -e ENV=production \
            seu-usuario/api-go:main

          echo "Waiting for health check..."
          sleep 5
          curl -f http://localhost:8080/health || exit 1
          echo "Deployment successful!"

  deploy-kubernetes:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v4

    - name: Set up kubectl
      uses: azure/setup-kubectl@v3
      with:
        version: 'latest'

    - name: Configure kubectl
      run: |
        mkdir -p $HOME/.kube
        echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
        chmod 600 $HOME/.kube/config

    - name: Update image in Deployment
      run: |
        kubectl set image deployment/api-go \
          api-go=seu-usuario/api-go:main \
          --record

    - name: Wait for rollout
      run: kubectl rollout status deployment/api-go --timeout=5m

    - name: Health check
      run: |
        POD=$(kubectl get pods -l app=api-go -o jsonpath='{.items[0].metadata.name}')
        kubectl exec $POD -- wget -qO- http://localhost:8080/health

Configurar Secrets

No repositório GitHub, vá em Settings → Secrets and Variables → Actions e adicione:

  • DOCKER_USERNAME: seu usuário Docker Hub
  • DOCKER_PASSWORD: seu token Docker Hub
  • VPS_HOST: IP ou domínio da VPS
  • VPS_USER: usuário SSH
  • VPS_SSH_KEY: chave privada SSH (sem passphrase)
  • KUBE_CONFIG: arquivo kubeconfig em base64 (cat ~/.kube/config | base64)

Análise do Pipeline

O workflow acima faz:

  1. test: A cada push, testa o código com go test, roda linter (golangci-lint detecta bugs e estilo ruim), builda o binário.
  2. build: Se tudo passou e é push em main, constrói a imagem Docker e faz push ao registry.
  3. deploy-vps: Conecta via SSH, faz pull da nova imagem, reinicia o container com docker run, valida com health check.
  4. deploy-kubernetes: Atualiza o Deployment no cluster, aguarda o rollout ser concluído.

Ambos os deploys rodam em paralelo (needs: build), economizando tempo.

Tagging Automático

Para versionamento semântico, crie tags Git e o GitHub Actions pega automaticamente:

git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

O workflow de build está configurado para criar tags Docker automáticas baseadas em tags Git.

Por que essa abordagem? GitHub Actions reduz erros humanos, garante que apenas código testado é deployado, e permite que você desligue e o sistema continue funcionando. É essencial em qualquer operação seria.

Boas Práticas e Considerações Operacionais

Monitoramento e Logging

Sua API precisa ser observável. Implemente logging estruturado:

import "github.com/sirupsen/logrus"

var log = logrus.New()

func main() {
    log.SetFormatter(&logrus.JSONFormatter{})
    log.SetLevel(logrus.InfoLevel)
    log.Info("Application started")
}

Em produção, agregue logs em um serviço como ELK Stack, DataDog ou CloudWatch. Seu /health endpoint é fundamental para alertas.

Versionamento e Rollback

Sempre tagueie releases. Se algo quebrar:

# VPS
docker run -d seu-usuario/api-go:v1.0.0

# Kubernetes
kubectl rollout undo deployment/api-go

Secrets e Configuração

Nunca committe secrets. Use variáveis de ambiente ou gerenciadores como:

  • VPS: /etc/api-go/.env (arquivo local, não versionado)
  • Kubernetes: Secret resources
  • GitHub Actions: Secrets
# Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
  name: api-go-secrets
type: Opaque
stringData:
  DATABASE_URL: postgresql://user:pass@db:5432/api
  API_KEY: seu-chave-secreta
# Usar no Deployment
env:
- name: DATABASE_URL
  valueFrom:
    secretKeyRef:
      name: api-go-secrets
      key: DATABASE_URL

Testes em Produção

Sempre teste localmente antes de fazer push:

# Build e test local
go build
go test -v ./...

# Ou com Docker
docker build -t api-go:test .
docker run -p 8080:8080 api-go:test
curl http://localhost:8080/health

Conclusão

Você aprendeu três paradigmas complementares: VPS é simplicidade e controle, ideal para projetos menores onde você gerencia manualmente. Kubernetes é complexidade e escalabilidade, necessário quando você tem múltiplas instâncias e precisa de automação sofisticada. GitHub Actions é o glue que une tudo, garantindo que código testado é deployado automaticamente.

A realidade profissional é que você frequentemente usa os três: develop e test localmente, push para GitHub Actions que testa e builda, depois choose entre deployar em VPS (para staging) ou Kubernetes (para produção em escala). O importante é automatizar o máximo possível e ter um rollback plan.

Pratique: crie uma API simples, faça deploy em uma VPS trial (DigitalOcean oferece $200 de crédito), configure GitHub Actions, e integre com Kubernetes se seu cluster oferece trial (GKE, EKS têm tiers free). Erros em teste são ouro; erros em produção são disaster.

Referências


Artigos relacionados