Manual de Rust para DevOps en Linux

Guía completa para implementar Rust en pipelines de CI/CD, automatización de infraestructura y herramientas DevOps

Tabla de Contenidos

1. Introducción a Rust para DevOps

Rust es un lenguaje ideal para DevOps por su combinación única de:

¿Por qué Rust para DevOps? Herramientas críticas como Docker, Kubernetes, Terraform y otros están adoptando Rust para componentes donde el rendimiento y la seguridad son cruciales.

1.1. Casos de uso en DevOps

Área Aplicación Ventaja de Rust
Herramientas CLI Automation scripts, custom tools Rápido, binarios autocontenidos
Procesamiento de datos Log analysis, metrics processing Rendimiento, concurrencia segura
Infraestructura Custom controllers, plugins Seguridad, interoperabilidad
Contenedores Runtime tools, WASM Pequeño footprint, seguridad
Networking Proxies, service mesh Bajo latency, sin GC pauses

2. Instalación y Entorno DevOps

2.1. Instalación en Linux para entornos de producción

# Instalar Rust y herramientas básicas
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal

# Configurar entorno (para todos los usuarios)
echo 'export PATH="$HOME/.cargo/bin:$PATH"' | sudo tee /etc/profile.d/rust.sh
source /etc/profile.d/rust.sh

# Verificar instalación
rustc --version
cargo --version

2.2. Configuración avanzada para CI/CD

Dockerfile para builds Rust
FROM rust:1.60 as builder

# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \
    cmake \
    libssl-dev \
    pkg-config \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

# Build de release optimizado
RUN cargo build --release

# Runtime image minimal
FROM debian:buster-slim
RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/my-tool /usr/local/bin/my-tool

CMD ["my-tool"]

2.3. Configuración de Cargo para DevOps

# ~/.cargo/config.toml
[build]
# Para builds más rápidos en CI (usa más memoria)
incremental = true
codegen-units = 1

[target.x86_64-unknown-linux-gnu]
# Linker optimizado
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

[install]
# Instalar en /opt/rust/bin para sistemas
prefix = "/opt/rust"

3. Herramientas CLI para DevOps

3.1. Creación de herramientas CLI profesionales

CLI para gestión de infraestructura
use clap::{App, Arg, SubCommand};
use serde_json::Value;
use reqwest::blocking::Client;
use std::process;

fn main() {
    let matches = App::new("InfraCLI")
        .version("1.0")
        .about("Herramienta DevOps para gestión de infraestructura")
        .subcommand(SubCommand::with_name("deploy")
            .about("Despliega una aplicación")
            .arg(Arg::with_name("app")
                .help("Nombre de la aplicación")
                .required(true))
        .subcommand(SubCommand::with_name("status")
            .about("Verifica el estado de los servicios")
            .arg(Arg::with_name("service")
                .help("Nombre del servicio")))
        .get_matches();

    match matches.subcommand() {
        ("deploy", Some(sub_m)) => {
            let app = sub_m.value_of("app").unwrap();
            deploy_app(app);
        },
        ("status", Some(sub_m)) => {
            let service = sub_m.value_of("service");
            check_status(service);
        },
        _ => {
            eprintln!("Comando no reconocido. Use --help para ayuda");
            process::exit(1);
        }
    }
}

fn deploy_app(app: &str) {
    let client = Client::new();
    let response = client.post("https://api.infra.example.com/deploy")
        .json(&serde_json::json!({ "app": app }))
        .send();

    match response {
        Ok(res) if res.status().is_success() => {
            println!("✅ Aplicación {} desplegada con éxito", app);
        },
        Ok(res) => {
            eprintln!("❌ Error al desplegar: {}", res.status());
            process::exit(1);
        },
        Err(e) => {
            eprintln!("❌ Error de red: {}", e);
            process::exit(1);
        }
    }
}

3.2. Dependencias recomendadas para CLI

Crate Uso Ejemplo
clap Parseo de argumentos CLIs complejas con subcomandos
structopt CLI declarativa Derive-based argument parsing
indicatif Progress bars Feedback visual en operaciones largas
console Styling terminal Colores, estilos, emojis
anyhow Manejo de errores Errores fáciles de usar/debug
reqwest HTTP client APIs REST/HTTP

4. Automatización de Infraestructura

4.1. Interacción con APIs de cloud

Gestión de recursos AWS
use rusoto_core::{Region, HttpClient};
use rusoto_credential::{StaticProvider, ProvideAwsCredentials};
use rusoto_ec2::{Ec2, Ec2Client, DescribeInstancesRequest};

#[tokio::main]
async fn main() {
    // Configurar credenciales (mejor usar variables de entorno en producción)
    let creds = StaticProvider::new_minimal(
        "AKIAIOSFODNN7EXAMPLE".to_string(),
        "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
    );
    
    let client = Ec2Client::new_with(
        HttpClient::new().expect("Error creando HTTP client"),
        creds,
        Region::UsEast1,
    );
    
    // Listar instancias EC2
    let request = DescribeInstancesRequest::default();
    match client.describe_instances(request).await {
        Ok(output) => {
            if let Some(reservations) = output.reservations {
                println!("Instancias EC2 encontradas:");
                for res in reservations {
                    if let Some(instances) = res.instances {
                        for instance in instances {
                            println!(
                                "ID: {}, Estado: {:?}, Tipo: {}",
                                instance.instance_id.unwrap_or_default(),
                                instance.state.map(|s| s.name.unwrap_or_default()),
                                instance.instance_type.unwrap_or_default()
                            );
                        }
                    }
                }
            }
        },
        Err(e) => eprintln!("Error al listar instancias: {}", e),
    }
}

4.2. Automatización con Terraform

Ejecutar Terraform desde Rust
use std::process::{Command, Stdio};
use std::io::{self, Write};
use std::path::Path;

fn run_terraform(path: &str, command: &str, args: &[&str]) -> io::Result<()> {
    if !Path::new(path).join("terraform.tf").exists() {
        return Err(io::Error::new(
            io::ErrorKind::NotFound,
            "No se encontró terraform.tf",
        ));
    }

    let status = Command::new("terraform")
        .current_dir(path)
        .arg(command)
        .args(args)
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()?;

    if !status.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!("Terraform {} falló", command),
        ));
    }

    Ok(())
}

fn main() -> io::Result<()> {
    println!("Inicializando Terraform...");
    run_terraform("./infra", "init", &[])?;

    println!("Aplicando cambios...");
    run_terraform("./infra", "apply", &["-auto-approve"])?;

    println!("Infraestructura desplegada con éxito");
    Ok(())
}

5. Integración con CI/CD

5.1. Configuración de GitHub Actions

GitHub Actions para proyecto Rust
name: Rust CI/CD Pipeline

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

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        profile: minimal
        override: true
    
    - name: Cache cargo
      uses: actions/cache@v2
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
    
    - name: Build
      run: cargo build --release
    
    - name: Run tests
      run: cargo test --release
    
    - name: Lint
      run: cargo clippy --all-targets --all-features -- -D warnings
    
    - name: Upload artifact
      uses: actions/upload-artifact@v2
      if: success()
      with:
        name: release-binary
        path: target/release/my-tool
    
    - name: Deploy to staging
      if: github.ref == 'refs/heads/main'
      run: |
        scp target/release/my-tool user@staging-server:/opt/my-tool
        ssh user@staging-server "systemctl restart my-tool"

5.2. Pruebas de integración para infraestructura

Test de infraestructura con Rust
use reqwest::blocking::Client;
use std::time::Duration;

#[test]
fn test_api_endpoint() {
    let client = Client::builder()
        .timeout(Duration::from_secs(5))
        .build()
        .expect("Error creando cliente HTTP");
    
    let response = client.get("http://staging.example.com/health")
        .send()
        .expect("Error en la petición HTTP");
    
    assert!(response.status().is_success(), "El endpoint de health check falló");
    
    let health: serde_json::Value = response.json()
        .expect("Error parseando JSON");
    
    assert_eq!(
        health["status"].as_str(),
        Some("ok"),
        "El estado no es 'ok'"
    );
}

#[test]
fn test_database_connection() {
    let conn = postgres::Client::connect(
        "host=staging-db.example.com user=admin dbname=test",
        postgres::NoTls,
    ).expect("Error conectando a la base de datos");
    
    let rows = conn.query("SELECT 1 + 1 as result", &[])
        .expect("Error en la consulta");
    
    let sum: i32 = rows[0].get("result");
    assert_eq!(sum, 2, "La consulta básica falló");
}

6. Trabajo con Contenedores

6.1. Interacción con Docker API

Listar y gestionar contenedores
use bollard::Docker;
use bollard::container::{ListContainersOptions, StopContainerOptions};
use futures_util::stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box> {
    // Conectar al socket de Docker
    let docker = Docker::connect_with_unix_defaults()?;
    
    // Listar contenedores
    let containers = docker.list_containers(Some(ListContainersOptions:: {
        all: true,
        ..Default::default()
    })).await?;
    
    println!("Contenedores en ejecución:");
    for container in containers {
        println!(
            "ID: {}, Imagen: {}, Estado: {}",
            container.id.unwrap_or_default(),
            container.image.unwrap_or_default(),
            container.status.unwrap_or_default()
        );
    }
    
    // Detener un contenedor por ID
    let container_id = "mi_contenedor";
    docker.stop_container(
        container_id,
        Some(StopContainerOptions { t: 10 }),
    ).await?;
    
    println!("Contenedor {} detenido", container_id);
    Ok(())
}

6.2. Creación de imágenes minimalistas

Dockerfile optimizado para Rust
# Stage 1: Builder
FROM rust:1.60 as builder

WORKDIR /app
COPY . .

# Build estático con musl para imagen minimal
RUN apt-get update && apt-get install -y musl-tools && \
    rustup target add x86_64-unknown-linux-musl && \
    cargo build --target x86_64-unknown-linux-musl --release

# Stage 2: Runtime image
FROM scratch
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-app /my-app

# Certificados CA para conexiones TLS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ENTRYPOINT ["/my-app"]

Tamaño de imagen resultante: ~5MB (vs ~2GB para imagen completa)

7. Monitoreo y Logging

7.1. Exportación de métricas para Prometheus

Servidor de métricas
use actix_web::{web, App, HttpServer, Responder};
use prometheus::{opts, IntCounter, Registry, TextEncoder};
use std::sync::Mutex;

lazy_static::lazy_static! {
    static ref REQUEST_COUNTER: IntCounter = IntCounter::new(
        "http_requests_total",
        "Total HTTP requests"
    ).unwrap();
}

async fn metrics(registry: web::Data>) -> impl Responder {
    let encoder = TextEncoder::new();
    let metric_families = registry.lock().unwrap().gather();
    encoder.encode_to_string(&metric_families).unwrap()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let registry = Registry::new();
    registry.register(Box::new(REQUEST_COUNTER.clone())).unwrap();
    
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(Mutex::new(registry.clone())))
            .route("/metrics", web::get().to(metrics))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

7.2. Logging estructurado

Configuración profesional de logs
use log::{info, error, warn};
use serde_json::json;
use syslog::{Facility, Formatter3164};

fn setup_logging() {
    let formatter = Formatter3164 {
        facility: Facility::LOG_DAEMON,
        hostname: None,
        process: "my-devops-tool".into(),
        pid: std::process::id(),
    };

    // Log a syslog
    syslog::init(formatter, log::LevelFilter::Info, None)
        .expect("Error inicializando syslog");

    // También a archivo con JSON estructurado
    let file = std::fs::OpenOptions::new()
        .append(true)
        .create(true)
        .open("/var/log/my-devops-tool.log")
        .expect("Error abriendo archivo de log");

    let logger = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}",
                json!({
                    "timestamp": chrono::Local::now().to_rfc3339(),
                    "level": record.level().to_string(),
                    "message": message.to_string(),
                    "target": record.target(),
                    "module": record.module_path().unwrap_or_default(),
                    "line": record.line().unwrap_or_default(),
                })
            ))
        })
        .chain(file)
        .apply();

    logger.expect("Error configurando logger");
}

fn main() {
    setup_logging();
    
    info!("Iniciando herramienta DevOps");
    warn!("Configuración no óptima detectada");
    error!("Error crítico en módulo X");
}

8. Seguridad DevOps

8.1. Escaneo de vulnerabilidades

Integración con cargo-audit
name: Security Audit

on:
  schedule:
    - cron: '0 0 * * *'  # Diario a medianoche
  pull_request:
    branches: [ main ]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        profile: minimal
        override: true
    
    - name: Install cargo-audit
      run: cargo install cargo-audit
    
    - name: Run security audit
      run: cargo audit

Para ejecutar localmente:

cargo install cargo-audit
cargo audit

8.2. Gestión de secretos

Manejo seguro de credenciales
use secrecy::{Secret, ExposeSecret};
use dotenv::dotenv;
use std::env;

struct DatabaseConfig {
    host: String,
    username: String,
    password: Secret,
}

impl DatabaseConfig {
    fn from_env() -> Result {
        dotenv().ok();  // Cargar .env
        
        Ok(Self {
            host: env::var("DB_HOST")?,
            username: env::var("DB_USER")?,
            password: Secret::new(env::var("DB_PASS")?),
        })
    }
}

fn connect_to_db(config: &DatabaseConfig) {
    // Nunca imprimas el password directamente!
    println!("Conectando a {} como {}", config.host, config.username);
    
    // Acceso seguro al secreto
    let password = config.password.expose_secret();
    // ... lógica de conexión ...
    
    // El secreto se borrará de memoria cuando salga del ámbito
}

fn main() {
    let db_config = DatabaseConfig::from_env()
        .expect("Error cargando configuración de DB");
    
    connect_to_db(&db_config);
}

Archivo .env:

DB_HOST=db.example.com
DB_USER=admin
DB_PASS=supersecret123

9. Casos de Uso Reales

9.1. Herramientas DevOps populares en Rust

Herramienta Descripción Enlace
exa Reemplazo moderno para ls GitHub
bat Clone de cat con syntax highlighting GitHub
starship Prompt de terminal personalizable GitHub
bottom Monitor de sistema alternativo a top GitHub
dust Alternativa más intuitiva a du GitHub

9.2. Proyecto completo: Monitor de recursos

Monitor de sistema con API web
use actix_web::{get, App, HttpServer, Responder};
use sysinfo::{System, SystemExt, CpuExt};

#[get("/metrics")]
async fn metrics() -> impl Responder {
    let mut sys = System::new_all();
    sys.refresh_all();
    
    let cpu_usage = sys.global_cpu_info().cpu_usage();
    let used_memory = sys.used_memory();
    let total_memory = sys.total_memory();
    
    format!(
        "cpu_usage {}\nmemory_used {}\nmemory_total {}",
        cpu_usage, used_memory, total_memory
    )
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(metrics)
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Configuración de Prometheus para scrapear esta aplicación:

scrape_configs:
  - job_name: 'rust_monitor'
    static_configs:
      - targets: ['localhost:8080']

10. Recursos Adicionales

10.1. Libros y tutoriales

10.2. Crates esenciales para DevOps

Crate Descripción
tokio Runtime async para aplicaciones de red
reqwest Cliente HTTP async/sync
serde Serialización/deserialización
anyhow Manejo simple de errores
thiserror Errores personalizados
clap Parseo de argumentos CLI
sysinfo Información del sistema
bollard API Docker

Consejo final: Comienza integrando Rust en pequeñas partes de tu workflow DevOps, como scripts de automatización o herramientas CLI. A medida que ganes confianza, podrás usarlo para componentes más críticos.