Guía Completa de Automatización de nodos blockchain
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.