Gestionar nodos blockchain de forma manual es insostenible. Los clientes de Ethereum requieren actualizaciones frecuentes, las bases de datos crecen continuamente y necesitan pruning, los backups deben ejecutarse sin interrumpir el servicio, y los fallos deben detectarse y resolverse antes de que generen penalties economicas. La automatización de todas estas operaciones mediante herramientas de Infrastructure as Code, pipelines de CI/CD y scripts de mantenimiento es lo que distingue una operación profesional de una artesanal.

Infrastructure as Code para nodos blockchain

Terraform para la infraestructura base

Terraform permite definir la infraestructura de compute, networking y storage donde correran los nodos:

# main.tf - Infraestructura para nodos Ethereum en AWS
resource "aws_instance" "ethereum_node" {
  count         = var.node_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  root_block_device {
    volume_size = 50
    volume_type = "gp3"
  }

  ebs_block_device {
    device_name = "/dev/sdf"
    volume_size = var.data_volume_size
    volume_type = "gp3"
    iops        = 10000
    throughput  = 500
    encrypted   = true
  }

  vpc_security_group_ids = [aws_security_group.ethereum_node.id]
  subnet_id              = var.subnet_ids[count.index % length(var.subnet_ids)]

  tags = {
    Name        = "ethereum-node-${count.index + 1}"
    Environment = var.environment
    Client_EL   = var.execution_clients[count.index % length(var.execution_clients)]
    Client_CL   = var.consensus_clients[count.index % length(var.consensus_clients)]
    ManagedBy   = "terraform"
  }
}

resource "aws_security_group" "ethereum_node" {
  name_prefix = "ethereum-node-"
  vpc_id      = var.vpc_id

  # P2P execution layer
  ingress {
    from_port   = 30303
    to_port     = 30303
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 30303
    to_port     = 30303
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # P2P consensus layer
  ingress {
    from_port   = 9000
    to_port     = 9000
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 9000
    to_port     = 9000
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Bloquear RPC y metricas desde internet
  # Solo accesibles via VPN o red interna

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name      = "ethereum-node-sg"
    ManagedBy = "terraform"
  }
}

# Variables para client diversity
variable "execution_clients" {
  type    = list(string)
  default = ["geth", "nethermind", "besu", "erigon"]
}

variable "consensus_clients" {
  type    = list(string)
  default = ["lighthouse", "teku", "lodestar", "nimbus"]
}

Ansible para la configuración de nodos

Una vez que Terraform crea la infraestructura, Ansible configura el software:

# playbooks/setup-ethereum-node.yml
---
- name: Configurar nodo Ethereum
  hosts: ethereum_nodes
  become: true
  vars:
    jwt_secret_path: /etc/ethereum/jwt.hex
    data_dir: /data/ethereum

  tasks:
    - name: Crear directorios
      file:
        path: "{{ item }}"
        state: directory
        owner: ethereum
        group: ethereum
        mode: '0750'
      loop:
        - "{{ data_dir }}/execution"
        - "{{ data_dir }}/consensus"
        - /etc/ethereum

    - name: Generar JWT secret
      command: openssl rand -hex 32
      register: jwt_output
      args:
        creates: "{{ jwt_secret_path }}"

    - name: Guardar JWT secret
      copy:
        content: "{{ jwt_output.stdout }}"
        dest: "{{ jwt_secret_path }}"
        owner: ethereum
        group: ethereum
        mode: '0640'
      when: jwt_output.changed

    - name: Instalar cliente de ejecucion
      include_role:
        name: "ethereum-{{ execution_client }}"
      vars:
        client_data_dir: "{{ data_dir }}/execution"

    - name: Instalar cliente de consenso
      include_role:
        name: "ethereum-{{ consensus_client }}"
      vars:
        client_data_dir: "{{ data_dir }}/consensus"

    - name: Configurar servicios systemd
      template:
        src: "templates/{{ item }}.service.j2"
        dest: "/etc/systemd/system/{{ item }}.service"
      loop:
        - "ethereum-execution"
        - "ethereum-consensus"
      notify: Reload systemd

    - name: Habilitar e iniciar servicios
      systemd:
        name: "{{ item }}"
        state: started
        enabled: true
      loop:
        - ethereum-execution
        - ethereum-consensus

  handlers:
    - name: Reload systemd
      systemd:
        daemon_reload: true
# templates/ethereum-execution.service.j2
[Unit]
Description=Ethereum Execution Client ({{ execution_client }})
After=network.target
Wants=network-online.target

[Service]
User=ethereum
Group=ethereum
Type=simple
Restart=always
RestartSec=10
ExecStart={{ execution_client_binary }} {{ execution_client_flags }}
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

Pipelines de actualización automatizada

Las actualizaciones de clientes de Ethereum son frecuentes y a veces obligatorias (hard forks). Automatizar este proceso es crítico:

# .github/workflows/update-ethereum-clients.yml
name: Update Ethereum Clients

on:
  schedule:
    - cron: '0 6 * * 1'  # Lunes a las 6 AM
  workflow_dispatch:
    inputs:
      client:
        description: 'Cliente a actualizar'
        required: true
        type: choice
        options:
          - all
          - geth
          - nethermind
          - lighthouse
          - teku
      environment:
        description: 'Entorno'
        required: true
        type: choice
        options:
          - staging
          - production

jobs:
  check-updates:
    runs-on: ubuntu-latest
    outputs:
      updates_available: ${{ steps.check.outputs.available }}
      versions: ${{ steps.check.outputs.versions }}
    steps:
      - name: Verificar nuevas versiones
        id: check
        run: |
          # Consultar ultimas versiones via GitHub API
          GETH_LATEST=$(curl -s https://api.github.com/repos/ethereum/go-ethereum/releases/latest | jq -r .tag_name)
          LIGHTHOUSE_LATEST=$(curl -s https://api.github.com/repos/sigp/lighthouse/releases/latest | jq -r .tag_name)
          echo "versions={\"geth\":\"$GETH_LATEST\",\"lighthouse\":\"$LIGHTHOUSE_LATEST\"}" >> $GITHUB_OUTPUT

  update-staging:
    needs: check-updates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Actualizar en staging
        run: |
          ansible-playbook playbooks/update-clients.yml \
            -i inventory/staging \
            --extra-vars "client_versions=${{ needs.check-updates.outputs.versions }}" \
            --limit staging

      - name: Ejecutar tests de integracion
        run: |
          ./scripts/verify-node-health.sh staging
          ./scripts/verify-sync-status.sh staging

  update-production:
    needs: update-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Rolling update en produccion
        run: |
          # Actualizar nodo por nodo con verificacion entre cada uno
          for node in $(ansible-inventory -i inventory/production --list | jq -r '.ethereum_nodes.hosts[]'); do
            echo "Actualizando $node..."
            ansible-playbook playbooks/update-clients.yml \
              -i inventory/production \
              --limit "$node"

            echo "Verificando salud de $node..."
            ./scripts/verify-node-health.sh "$node"

            echo "Esperando sincronizacion de $node..."
            ./scripts/wait-for-sync.sh "$node" --timeout 600
          done

Automatización de pruning

Los clientes de ejecución acumulan datos historicos que consumen disco. El pruning debe automatizarse:

#!/bin/bash
# scripts/auto-prune.sh
# Ejecutar pruning cuando el disco alcanza un umbral

DATA_DIR="/data/ethereum/execution"
THRESHOLD=85  # Porcentaje de uso de disco
SERVICE_NAME="ethereum-execution"

DISK_USAGE=$(df "$DATA_DIR" --output=pcent | tail -1 | tr -d ' %')

if [ "$DISK_USAGE" -ge "$THRESHOLD" ]; then
  echo "$(date) - Disco al ${DISK_USAGE}%. Iniciando pruning..."

  # Detener el servicio de ejecucion
  systemctl stop "$SERVICE_NAME"

  # Ejecutar pruning offline (para Geth)
  geth --datadir "$DATA_DIR" snapshot prune-state

  # Reiniciar el servicio
  systemctl start "$SERVICE_NAME"

  # Verificar que el nodo reinicio correctamente
  sleep 30
  if systemctl is-active --quiet "$SERVICE_NAME"; then
    echo "$(date) - Pruning completado. Servicio activo."
  else
    echo "$(date) - ERROR: Servicio no inicio despues del pruning."
    exit 1
  fi
else
  echo "$(date) - Disco al ${DISK_USAGE}%. No requiere pruning."
fi
# /etc/systemd/system/ethereum-prune.timer
[Unit]
Description=Verificacion periodica de pruning de Ethereum

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=3600

[Install]
WantedBy=timers.target

Estrategias de backup

Los backups de nodos blockchain son complejos porque las bases de datos son grandes y estan en constante cambio:

Backup de la base de datos de slashing protection

Este es el backup mas crítico. Sin el, no se puede migrar un validador de forma segura:

#!/bin/bash
# scripts/backup-slashing-db.sh
BACKUP_DIR="/backups/slashing-protection"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Exportar base de datos de slashing protection
lighthouse account validator slashing-protection export \
  --datadir /data/ethereum/consensus \
  "${BACKUP_DIR}/slashing_protection_${TIMESTAMP}.json"

# Subir a S3
aws s3 cp "${BACKUP_DIR}/slashing_protection_${TIMESTAMP}.json" \
  "s3://ethereum-backups/slashing-protection/" \
  --storage-class STANDARD_IA

# Limpiar backups locales mas antiguos de 7 dias
find "$BACKUP_DIR" -name "*.json" -mtime +7 -delete

echo "$(date) - Backup de slashing protection completado."

Backup de la base de datos del nodo

Para nodos de ejecución, el enfoque mas eficiente es usar snapshots de volumen:

#!/bin/bash
# scripts/backup-node-snapshot.sh
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
VOLUME_ID=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[0].Instances[0].BlockDeviceMappings[?DeviceName==`/dev/sdf`].Ebs.VolumeId' \
  --output text)

# Crear snapshot de EBS
SNAPSHOT_ID=$(aws ec2 create-snapshot \
  --volume-id "$VOLUME_ID" \
  --description "Ethereum node backup $(date +%Y-%m-%d)" \
  --tag-specifications "ResourceType=snapshot,Tags=[{Key=Name,Value=eth-node-backup},{Key=AutoDelete,Value=true}]" \
  --query 'SnapshotId' \
  --output text)

echo "$(date) - Snapshot creado: $SNAPSHOT_ID"

# Eliminar snapshots mas antiguos de 30 dias
aws ec2 describe-snapshots \
  --filters "Name=tag:Name,Values=eth-node-backup" "Name=tag:AutoDelete,Values=true" \
  --query 'Snapshots[?StartTime<`'"$(date -d '-30 days' --iso-8601)"'`].SnapshotId' \
  --output text | tr '\t' '\n' | while read snap; do
    aws ec2 delete-snapshot --snapshot-id "$snap"
    echo "Eliminado snapshot antiguo: $snap"
done

Health checks y auto-restart

Systemd proporciona mecanismos nativos de restart, pero los health checks específicos de blockchain requieren lógica adicional:

#!/bin/bash
# scripts/health-check.sh
# Health check avanzado para nodos Ethereum

GETH_RPC="http://localhost:8545"
BEACON_API="http://localhost:5052"
MAX_SYNC_DIFF=30
MIN_PEERS=3

check_execution() {
  # Verificar que el RPC responde
  RESPONSE=$(curl -s -m 5 -X POST "$GETH_RPC" \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}')

  if [ -z "$RESPONSE" ]; then
    echo "CRITICAL: Cliente de ejecucion no responde"
    return 1
  fi

  # Verificar peers
  PEERS=$(curl -s -m 5 -X POST "$GETH_RPC" \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' \
    | jq -r '.result' | xargs printf "%d\n")

  if [ "$PEERS" -lt "$MIN_PEERS" ]; then
    echo "WARNING: Solo $PEERS peers conectados (minimo: $MIN_PEERS)"
    return 1
  fi

  return 0
}

check_consensus() {
  # Verificar que el beacon API responde
  HEALTH=$(curl -s -m 5 -o /dev/null -w "%{http_code}" "$BEACON_API/eth/v1/node/health")

  if [ "$HEALTH" != "200" ]; then
    echo "CRITICAL: Beacon node reporta estado no saludable (HTTP $HEALTH)"
    return 1
  fi

  # Verificar sincronizacion
  SYNC=$(curl -s -m 5 "$BEACON_API/eth/v1/node/syncing" | jq -r '.data.is_syncing')
  if [ "$SYNC" = "true" ]; then
    echo "WARNING: Beacon node aun sincronizando"
    return 1
  fi

  return 0
}

# Ejecutar checks
ERRORS=0
check_execution || ((ERRORS++))
check_consensus || ((ERRORS++))

if [ "$ERRORS" -gt 0 ]; then
  echo "$(date) - Health check fallido con $ERRORS errores"
  exit 1
fi

echo "$(date) - Todos los checks pasaron"
exit 0

Gestión de flota

Para organizaciones que operan multiples nodos, la gestión de flota requiere herramientas adicionales:

Inventario dinámico con Ansible

#!/usr/bin/env python3
# inventory/dynamic_inventory.py
"""Inventario dinamico que genera la lista de nodos desde AWS."""
import json
import boto3

def get_ethereum_nodes():
    ec2 = boto3.client('ec2')
    response = ec2.describe_instances(
        Filters=[
            {'Name': 'tag:ManagedBy', 'Values': ['terraform']},
            {'Name': 'tag:Name', 'Values': ['ethereum-node-*']},
            {'Name': 'instance-state-name', 'Values': ['running']}
        ]
    )

    inventory = {'ethereum_nodes': {'hosts': [], 'vars': {}}, '_meta': {'hostvars': {}}}

    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            ip = instance['PrivateIpAddress']
            tags = {t['Key']: t['Value'] for t in instance.get('Tags', [])}

            inventory['ethereum_nodes']['hosts'].append(ip)
            inventory['_meta']['hostvars'][ip] = {
                'execution_client': tags.get('Client_EL', 'geth'),
                'consensus_client': tags.get('Client_CL', 'lighthouse'),
                'instance_id': instance['InstanceId'],
                'region': tags.get('Region', 'unknown')
            }

    return inventory

if __name__ == '__main__':
    print(json.dumps(get_ethereum_nodes(), indent=2))

Conclusion

La automatización de nodos blockchain no es un lujo sino una necesidad operativa. Terraform para provisionar infraestructura, Ansible para configurar nodos, pipelines de CI/CD para actualizaciones rolling, scripts de pruning y backup automatizados, y health checks con auto-recovery forman un sistema cohesivo que permite operar nodos blockchain de forma profesional. El objetivo es que la intervención manual sea la excepción, no la norma, liberando al equipo DevOps para enfocarse en mejoras y nuevos desafios en lugar de tareas repetitivas de mantenimiento.

Recursos