Ir para o conteúdo
Linguagem para Dominio1436 palavras

Python para DevOps: Guia Pratico

Introdução

Se você trabalha com desenvolvimento há mais de dois anos, provavelmente já escreve scripts em Bash para automatizar tarefas repetitivas. E provavelmente já sentiu a dor de manter um script de 300 linhas cheio de grep, awk e sed encadeados. Python resolve esse problema de forma elegante, e é exatamente por isso que se tornou a linguagem dominante no ecossistema DevOps.

Ferramentas como Ansible, SaltStack, AWS CLI e até partes do Kubernetes foram escritas em Python. Não é coincidência. A linguagem oferece uma combinação rara de legibilidade, ecossistema maduro e velocidade de desenvolvimento que encaixa perfeitamente nas demandas de quem opera infraestrutura.

Neste guia, vamos explorar como usar Python no domínio de DevOps de forma prática: quais ferramentas usar, como estruturar seus projetos de automação, e exemplos reais de código que você pode adaptar para o seu dia a dia. Se você já leu o post DevOps para Devs: Docker, CI/CD, Kubernetes e AWS em Produção, considere este artigo como o próximo passo natural.

Por que Python é a escolha certa para DevOps

Legibilidade como requisito de operações

Scripts de infraestrutura são frequentemente escritos às pressas durante um incidente e revisados meses depois por outra pessoa. A sintaxe limpa do Python reduz drasticamente o tempo necessário para entender o que um script faz. Compare um parser de logs em Bash com um equivalente em Python e a diferença fica evidente.

Ecossistema maduro de bibliotecas

Python tem bibliotecas para praticamente tudo que você precisa em DevOps:

  • Boto3: SDK oficial da AWS
  • Paramiko e Fabric: automação via SSH
  • Requests e httpx: interação com APIs REST
  • Click e Typer: criação de CLIs robustas
  • Jinja2: templates de configuração
  • PyYAML: manipulação de arquivos YAML (Kubernetes manifests, Ansible playbooks)
  • Docker SDK for Python: gerenciamento de containers programático

Integração nativa com ferramentas de CI/CD

GitHub Actions, GitLab CI, Jenkins e CircleCI oferecem suporte de primeira classe para Python. Isso significa que seus scripts de automação rodam nativamente nos pipelines sem gambiarras.

Casos de uso reais onde Python brilha

Na prática, equipes de DevOps usam Python para cenários como:

  1. Automação de provisionamento: scripts que criam e configuram instâncias EC2, bancos RDS e filas SQS usando Boto3
  2. Monitoramento customizado: coletores de métricas que alimentam Prometheus ou Datadog
  3. Rotação de secrets: automação de rotação de credenciais em Vault ou AWS Secrets Manager
  4. Limpeza de recursos: scripts agendados que removem recursos órfãos na cloud (aquelas instâncias de dev que ninguém desligou)
  5. Validação de configuração: ferramentas que verificam compliance de infraestrutura antes do deploy

Setup do ambiente Python para DevOps

Gerenciamento de versões e dependências

O primeiro passo é configurar um ambiente isolado e reproduzível. Evite usar o Python do sistema operacional.

TYPESCRIPT
// Exemplo de configuração de um projeto de automação DevOps
// usando TypeScript para orquestrar scripts Python via child_process
 
import { execSync, ExecSyncOptions } from "child_process";
import * as path from "path";
import * as fs from "fs";
 
interface PythonEnvironment {
  pythonVersion: string;
  venvPath: string;
  requirementsFile: string;
}
 
function setupPythonDevOpsEnv(config: PythonEnvironment): void {
  const execOptions: ExecSyncOptions = {
    stdio: "inherit",
    cwd: process.cwd(),
  };
 
  // Verifica se a versão correta do Python está disponível
  try {
    const version = execSync(`python3 --version`, { encoding: "utf-8" });
    console.log(`Python detectado: ${version.trim()}`);
  } catch {
    throw new Error(
      "Python3 não encontrado. Instale via pyenv ou gerenciador de pacotes."
    );
  }
 
  // Cria virtual environment isolado
  if (!fs.existsSync(config.venvPath)) {
    console.log(`Criando virtual environment em ${config.venvPath}...`);
    execSync(`python3 -m venv ${config.venvPath}`, execOptions);
  }
 
  // Instala dependências de DevOps
  const pipPath = path.join(config.venvPath, "bin", "pip");
  console.log("Instalando dependências DevOps...");
  execSync(`${pipPath} install --upgrade pip`, execOptions);
  execSync(
    `${pipPath} install -r ${config.requirementsFile}`,
    execOptions
  );
 
  console.log("Ambiente Python para DevOps configurado com sucesso.");
}
 
// Uso prático
const devopsEnv: PythonEnvironment = {
  pythonVersion: "3.12",
  venvPath: ".venv",
  requirementsFile: "requirements-devops.txt",
};
 
setupPythonDevOpsEnv(devopsEnv);

Para o arquivo requirements-devops.txt, um bom ponto de partida é:

Text
boto3==1.34.0
fabric==3.2.2
click==8.1.7
pyyaml==6.0.1
jinja2==3.1.3
requests==2.31.0
python-dotenv==1.0.0
docker==7.0.0

Estrutura de projeto recomendada

Organize seus scripts de DevOps como um projeto Python adequado, não como uma pasta solta de .py:

Text
devops-automation/
    pyproject.toml
    requirements-devops.txt
    src/
        infra/
            __init__.py
            aws_manager.py
            docker_cleanup.py
            health_checker.py
        utils/
            __init__.py
            logger.py
            config.py
        cli.py
    tests/
        test_aws_manager.py
        test_health_checker.py
    scripts/
        daily_cleanup.sh
        rotate_secrets.sh

Essa estrutura permite que você importe módulos entre si, escreva testes automatizados e distribua o pacote internamente se necessário.

Exemplo prático: automação de limpeza de recursos AWS

Vamos construir um script real que identifica e remove instâncias EC2 paradas há mais de 7 dias. Esse é um dos cenários mais comuns em equipes que usam AWS para desenvolvimento.

Python
import boto3
from datetime import datetime, timezone, timedelta
from dataclasses import dataclass
import click
import logging
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ec2-cleanup")
 
 
@dataclass
class StaleInstance:
    instance_id: str
    name: str
    stopped_since: datetime
    days_stopped: int
    estimated_monthly_cost: float
 
 
def get_instance_name(instance: dict) -> str:
    tags = instance.get("Tags", [])
    for tag in tags:
        if tag["Key"] == "Name":
            return tag["Value"]
    return "sem-nome"
 
 
def find_stale_instances(
    region: str, max_days_stopped: int = 7
) -> list[StaleInstance]:
    ec2 = boto3.client("ec2", region_name=region)
    now = datetime.now(timezone.utc)
    cutoff = now - timedelta(days=max_days_stopped)
 
    response = ec2.describe_instances(
        Filters=[{"Name": "instance-state-name", "Values": ["stopped"]}]
    )
 
    stale: list[StaleInstance] = []
 
    for reservation in response["Reservations"]:
        for instance in reservation["Instances"]:
            state_transition = instance.get("StateTransitionReason", "")
            # O formato típico é "User initiated (2024-01-15 10:30:00 GMT)"
            try:
                date_str = state_transition.split("(")[1].split(")")[0]
                stopped_at = datetime.strptime(
                    date_str, "%Y-%m-%d %H:%M:%S %Z"
                ).replace(tzinfo=timezone.utc)
            except (IndexError, ValueError):
                continue
 
            if stopped_at < cutoff:
                days = (now - stopped_at).days
                stale.append(
                    StaleInstance(
                        instance_id=instance["InstanceId"],
                        name=get_instance_name(instance),
                        stopped_since=stopped_at,
                        days_stopped=days,
                        estimated_monthly_cost=calculate_ebs_cost(
                            ec2, instance["InstanceId"]
                        ),
                    )
                )
 
    return stale
 
 
def calculate_ebs_cost(ec2_client, instance_id: str) -> float:
    volumes = ec2_client.describe_volumes(
        Filters=[
            {"Name": "attachment.instance-id", "Values": [instance_id]}
        ]
    )
    total_gb = sum(v["Size"] for v in volumes["Volumes"])
    # Custo aproximado de EBS gp3 por GB/mês
    return total_gb * 0.08
 
 
def terminate_instances(
    region: str, instance_ids: list[str], dry_run: bool = True
) -> None:
    ec2 = boto3.client("ec2", region_name=region)
    action = "SIMULAÇÃO" if dry_run else "EXECUÇÃO"
    logger.info(f"[{action}] Terminando {len(instance_ids)} instâncias...")
 
    try:
        ec2.terminate_instances(
            InstanceIds=instance_ids, DryRun=dry_run
        )
    except ec2.exceptions.ClientError as e:
        if "DryRunOperation" in str(e):
            logger.info("Dry run bem-sucedido. Permissões OK.")
        else:
            raise
 
 
@click.command()
@click.option("--region", default="us-east-1", help="Região AWS")
@click.option("--max-days", default=7, help="Dias máximos parado")
@click.option("--execute", is_flag=True, help="Executar de fato (sem dry run)")
def cleanup(region: str, max_days: int, execute: bool) -> None:
    """Remove instâncias EC2 paradas há mais de N dias."""
    logger.info(f"Buscando instâncias paradas há mais de {max_days} dias...")
    stale = find_stale_instances(region, max_days)
 
    if not stale:
        logger.info("Nenhuma instância obsoleta encontrada.")
        return
 
    logger.info(f"Encontradas {len(stale)} instâncias obsoletas:")
    total_savings = 0.0
    for inst in stale:
        logger.info(
            f"  {inst.instance_id} ({inst.name}): "
            f"parada há {inst.days_stopped} dias, "
            f"custo EBS estimado: ${inst.estimated_monthly_cost:.2f}/mês"
        )
        total_savings += inst.estimated_monthly_cost
 
    logger.info(f"Economia mensal estimada: ${total_savings:.2f}")
 
    ids = [inst.instance_id for inst in stale]
    terminate_instances(region, ids, dry_run=not execute)
 
 
if __name__ == "__main__":
    cleanup()

Esse script faz quatro coisas importantes: identifica recursos desperdiçados, calcula o impacto financeiro, opera em modo dry run por padrão (segurança), e usa uma CLI bem definida com Click.

Exemplo prático: orquestração TypeScript que invoca automações Python

Em muitas equipes, a camada de orquestração dos pipelines é escrita em TypeScript (especialmente com ferramentas como Pulumi ou CDK), enquanto os scripts de automação são Python. Veja como integrar os dois mundos:

TYPESCRIPT
import { spawn } from "child_process";
import * as path from "path";
 
interface AutomationResult {
  success: boolean;
  stdout: string;
  stderr: string;
  exitCode: number;
  executionTimeMs: number;
}
 
interface DevOpsTask {
  name: string;
  scriptPath: string;
  args: string[];
  timeoutMs: number;
  critical: boolean;
}
 
async function runPythonAutomation(task: DevOpsTask): Promise<AutomationResult> {
  const startTime = Date.now();
  const venvPython = path.join(process.cwd(), ".venv", "bin", "python3");
 
  return new Promise((resolve, reject) => {
    const process_child = spawn(venvPython, [task.scriptPath, ...task.args], {
      env: {
        ...process.env,
        PYTHONUNBUFFERED: "1",
        AWS_DEFAULT_REGION: "us-east-1",
      },
    });
 
    let stdout = "";
    let stderr = "";
 
    process_child.stdout.on("data", (data: Buffer) => {
      const line = data.toString();
      stdout += line;
      console.log(`[${task.name}] ${line.trim()}`);
    });
 
    process_child.stderr.on("data", (data: Buffer) => {
      stderr += data.toString();
    });
 
    const timeout = setTimeout(() => {
      process_child.kill("SIGTERM");
      reject(
        new Error(`Task "${task.name}" excedeu timeout de ${task.timeoutMs}ms`)
      );
    }, task.timeoutMs);
 
    process_child.on("close", (code: number | null) => {
      clearTimeout(timeout);
      resolve({
        success: code === 0,
        stdout,
        stderr,
        exitCode: code ?? 1,
        executionTimeMs: Date.now() - startTime,
      });
    });
  });
}
 
async function runDevOpsPipeline(): Promise<void> {
  const tasks: DevOpsTask[] = [
    {
      name: "ec2-cleanup",
      scriptPath: "src/infra/aws_manager.py",
      args: ["--region", "us-east-1", "--max-days", "7"],
      timeoutMs: 120_000,
      critical: false,
    },
    {
      name: "health-check",
      scriptPath: "src/infra/health_checker.py",
      args: ["--env", "production", "--notify-slack"],
      timeoutMs: 60_000,
      critical: true,
    },
    {
      name: "docker-prune",
      scriptPath: "src/infra/docker_cleanup.py",
      args: ["--older-than", "48h"],
      timeoutMs: 90_000,
      critical: false,
    },
  ];
 
  console.log(`Iniciando pipeline DevOps com ${tasks.length} tarefas...\n`);
  const results: Map<string, AutomationResult> = new Map();
 
  for (const task of tasks) {
    console.log(`Executando: ${task.name}`);
    console.log("=".repeat(50));
 
    try {
      const result = await runPythonAutomation(task);
      results.set(task.name, result);
 
      if (!result.success) {
        console.error(
          `FALHA em ${task.name} (exit code: ${result.exitCode})`
        );
        if (task.critical

Guias Relacionados

Explore mais guias técnicos

Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.

Ver todos os guias