Rust para DevOps: Ferramentas e CLI | Rust Brasil

Rust para DevOps: ferramentas CLI, containers, infrastructure as code e automação. Guia prático em português.

Introdução

O mundo DevOps está sendo silenciosamente transformado por Rust. Enquanto Go dominou a primeira onda de ferramentas cloud-native (Docker, Kubernetes, Terraform), uma nova geração de ferramentas de infraestrutura está sendo construída em Rust, aproveitando sua performance superior, consumo mínimo de memória e segurança de memória. Projetos como Firecracker (microVMs da AWS Lambda), Bottlerocket (OS para containers da Amazon), Vector (pipeline de observabilidade) e Turbopack (bundler da Vercel) demonstram que Rust é a escolha ideal para software de infraestrutura que precisa ser rápido, confiável e eficiente.

Para engenheiros DevOps e SRE, Rust também oferece vantagens práticas: binários estáticos sem dependências de runtime facilitam deployment, imagens Docker minúsculas reduzem tempo de pull e consumo de storage, e a ausência de garbage collector garante latência previsível — essencial para proxies, load balancers e agentes de monitoramento.

Ferramentas de Infraestrutura Escritas em Rust

FerramentaCategoriaDescrição
FirecrackerVirtualizaçãoMicroVMs da AWS — motor por trás do Lambda e Fargate
BottlerocketSistema OperacionalOS minimalista para containers, mantido pela Amazon
VectorObservabilidadePipeline de dados unificado (logs, métricas, traces)
TikvBanco de dadosKey-value store distribuído (usado pelo PingCAP/TiDB)
Linkerd2-proxyService MeshData plane do Linkerd, proxy ultrarrápido
nushellShellShell moderno com output estruturado
justBuildTask runner (alternativa a Makefile)
watchexecAutomaçãoExecutor de comandos baseado em file watchers
deltaGitVisualizador de diffs com syntax highlighting
gitoxideGitImplementação de Git em Rust (usado pelo cargo)

Otimização de Imagens Docker para Rust

Uma das maiores vantagens de Rust para DevOps é a capacidade de criar imagens Docker extremamente pequenas. Veja como otimizar:

Dockerfile Multi-Stage Otimizado

# === Stage 1: Build ===
FROM rust:1.85-bookworm AS builder

# Criar projeto vazio para cache de dependências
WORKDIR /app
RUN cargo init --name meu-servico
COPY Cargo.toml Cargo.lock ./
RUN cargo build --release && rm -rf src

# Copiar código real e compilar
COPY src ./src
RUN touch src/main.rs && cargo build --release

# === Stage 2: Runtime ===
FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Criar usuário não-root
RUN useradd --create-home --shell /bin/bash app
USER app

COPY --from=builder /app/target/release/meu-servico /usr/local/bin/

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

ENTRYPOINT ["meu-servico"]

Build Estático com musl (Imagem Scratch)

Para imagens ainda menores, compile com musl e use uma imagem scratch (vazia):

FROM rust:1.85-bookworm AS builder
RUN rustup target add x86_64-unknown-linux-musl
RUN apt-get update && apt-get install -y musl-tools

WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

# Imagem final: apenas o binário, sem sistema operacional
FROM scratch
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/meu-servico /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/meu-servico"]

Comparação de Tamanhos

AbordagemTamanho da Imagem
rust:1.85 (sem multi-stage)~1.5 GB
Debian slim + binário~80 MB
Alpine + binário musl~15 MB
Scratch + binário musl~5-10 MB
Distroless + binário~20 MB

Compare com uma imagem Node.js típica (~300MB) ou Python (~400MB). Rust permite reduzir o tamanho em 95%.

GitHub Actions para Projetos Rust

Pipeline CI/CD Completo

name: CI/CD Rust

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-Dwarnings"

jobs:
  check:
    name: Verificação de Qualidade
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - uses: Swatinem/rust-cache@v2

      - name: Verificar formatação
        run: cargo fmt --all -- --check

      - name: Executar Clippy (linter)
        run: cargo clippy --all-targets --all-features

  test:
    name: Testes
    runs-on: ubuntu-latest
    needs: check
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: teste
          POSTGRES_DB: app_teste
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Executar testes
        run: cargo test --all-features
        env:
          DATABASE_URL: postgres://postgres:teste@localhost:5432/app_teste

      - name: Testes de integração
        run: cargo test --test '*' -- --ignored
        env:
          DATABASE_URL: postgres://postgres:teste@localhost:5432/app_teste

  build-and-push:
    name: Build e Push Docker
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Login no GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build e push
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Exemplo Prático: Ferramenta de Monitoramento de Infraestrutura

Vamos construir uma CLI que verifica a saúde de múltiplos serviços e exibe um dashboard no terminal.

[package]
name = "infra-monitor"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
clap = { version = "4", features = ["derive"] }
colored = "2"
anyhow = "1"
chrono = "0.4"
use anyhow::Result;
use chrono::Local;
use clap::Parser;
use colored::*;
use serde::Deserialize;
use std::time::{Duration, Instant};

#[derive(Parser)]
#[command(name = "infra-monitor")]
#[command(about = "Monitor de saúde de infraestrutura")]
struct Cli {
    /// Arquivo de configuração com endpoints
    #[arg(short, long, default_value = "endpoints.json")]
    config: String,

    /// Intervalo entre verificações (segundos)
    #[arg(short, long, default_value_t = 30)]
    intervalo: u64,

    /// Executar uma vez e sair
    #[arg(short = '1', long)]
    uma_vez: bool,
}

#[derive(Debug, Deserialize)]
struct Endpoint {
    nome: String,
    url: String,
    #[serde(default = "timeout_padrao")]
    timeout_ms: u64,
    #[serde(default)]
    esperar_status: Option<u16>,
}

fn timeout_padrao() -> u64 {
    5000
}

#[derive(Debug)]
struct ResultadoVerificacao {
    nome: String,
    url: String,
    status: StatusVerificacao,
    latencia: Duration,
}

#[derive(Debug)]
enum StatusVerificacao {
    Ok(u16),
    StatusInesperado(u16),
    Timeout,
    Erro(String),
}

async fn verificar_endpoint(
    client: &reqwest::Client,
    endpoint: &Endpoint,
) -> ResultadoVerificacao {
    let inicio = Instant::now();

    let resultado = client
        .get(&endpoint.url)
        .timeout(Duration::from_millis(endpoint.timeout_ms))
        .send()
        .await;

    let latencia = inicio.elapsed();

    let status = match resultado {
        Ok(resp) => {
            let codigo = resp.status().as_u16();
            match endpoint.esperar_status {
                Some(esperado) if codigo != esperado => {
                    StatusVerificacao::StatusInesperado(codigo)
                }
                _ if codigo >= 200 && codigo < 400 => {
                    StatusVerificacao::Ok(codigo)
                }
                _ => StatusVerificacao::StatusInesperado(codigo),
            }
        }
        Err(e) if e.is_timeout() => StatusVerificacao::Timeout,
        Err(e) => StatusVerificacao::Erro(e.to_string()),
    };

    ResultadoVerificacao {
        nome: endpoint.nome.clone(),
        url: endpoint.url.clone(),
        status,
        latencia,
    }
}

fn exibir_resultados(resultados: &[ResultadoVerificacao]) {
    let agora = Local::now().format("%Y-%m-%d %H:%M:%S");
    println!("\n{}", format!("═══ Health Check — {} ═══", agora).bold().cyan());

    let mut ok_count = 0;
    let mut fail_count = 0;

    for r in resultados {
        let (icone, status_texto) = match &r.status {
            StatusVerificacao::Ok(codigo) => {
                ok_count += 1;
                ("OK".green().bold(), format!("{}", codigo).green())
            }
            StatusVerificacao::StatusInesperado(codigo) => {
                fail_count += 1;
                ("WARN".yellow().bold(), format!("{}", codigo).yellow())
            }
            StatusVerificacao::Timeout => {
                fail_count += 1;
                ("TIMEOUT".red().bold(), "timeout".red())
            }
            StatusVerificacao::Erro(msg) => {
                fail_count += 1;
                ("ERRO".red().bold(), msg.as_str().red())
            }
        };

        let latencia_texto = if r.latencia.as_millis() > 1000 {
            format!("{:.1}s", r.latencia.as_secs_f64()).red()
        } else if r.latencia.as_millis() > 300 {
            format!("{}ms", r.latencia.as_millis()).yellow()
        } else {
            format!("{}ms", r.latencia.as_millis()).green()
        };

        println!(
            "  [{}] {}{} ({})",
            icone,
            r.nome.white().bold(),
            status_texto,
            latencia_texto
        );
    }

    println!(
        "\n  Resumo: {} {} | {} {}",
        ok_count.to_string().green().bold(),
        "saudáveis".green(),
        fail_count.to_string().red().bold(),
        "com problemas".red()
    );
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    let config_conteudo = std::fs::read_to_string(&cli.config)?;
    let endpoints: Vec<Endpoint> = serde_json::from_str(&config_conteudo)?;

    let client = reqwest::Client::builder()
        .user_agent("infra-monitor/0.1.0")
        .build()?;

    loop {
        let mut handles = Vec::new();
        for endpoint in &endpoints {
            let client = client.clone();
            let endpoint_clone = Endpoint {
                nome: endpoint.nome.clone(),
                url: endpoint.url.clone(),
                timeout_ms: endpoint.timeout_ms,
                esperar_status: endpoint.esperar_status,
            };
            handles.push(tokio::spawn(async move {
                verificar_endpoint(&client, &endpoint_clone).await
            }));
        }

        let mut resultados = Vec::new();
        for handle in handles {
            resultados.push(handle.await?);
        }

        exibir_resultados(&resultados);

        if cli.uma_vez {
            break;
        }

        tokio::time::sleep(Duration::from_secs(cli.intervalo)).await;
    }

    Ok(())
}

Arquivo de Configuração (endpoints.json)

[
    {
        "nome": "API Principal",
        "url": "https://api.meusite.com.br/health",
        "timeout_ms": 3000,
        "esperar_status": 200
    },
    {
        "nome": "Frontend",
        "url": "https://meusite.com.br",
        "timeout_ms": 5000
    },
    {
        "nome": "Banco de Dados (proxy)",
        "url": "http://db-proxy:8080/health",
        "timeout_ms": 2000,
        "esperar_status": 200
    }
]

Empresas Usando Rust em DevOps e Infraestrutura

  • Amazon Web Services: Firecracker (Lambda, Fargate), Bottlerocket OS, S3 components, IAM services
  • Cloudflare: Pingora (proxy HTTP que substitui o Nginx), Workers runtime, ferramentas de rede
  • Datadog: Agent components para alta performance em coleta de métricas
  • Timber/Vector: Pipeline unificado de observabilidade usado por milhares de empresas
  • Vercel: Turbopack (bundler), turborepo components
  • 1Password: Infraestrutura de backend e criptografia
  • Fly.io: Components do runtime de microVMs
  • Linkerd: Data plane proxy do service mesh

Como Começar

  1. Aprenda Rust: Tutorial de primeiros passos e tratamento de erros
  2. Configure Docker: Siga nosso guia de Docker para Rust para builds otimizados
  3. Configure CI/CD: Use nosso guia de GitHub Actions para pipelines Rust
  4. Construa CLIs: Muitas ferramentas DevOps começam como CLIs — veja Rust para CLIs
  5. Async Rust: Essencial para ferramentas de networking — receita de async/await
  6. HTTP requests: Aprenda a fazer requisições HTTP com reqwest

Conclusão

Rust está se consolidando como a linguagem de escolha para a próxima geração de ferramentas de infraestrutura. Se Go trouxe Docker e Kubernetes, Rust está trazendo microVMs, proxies de alta performance, pipelines de observabilidade e sistemas operacionais para containers. Para engenheiros DevOps e SRE, aprender Rust é um investimento que se paga tanto em ferramentas melhores quanto em oportunidades profissionais crescentes.


Veja Também