Hola comunidad, les comparto una solución que he implementado para mantener sincronizados dos servidores Proxmox (o más) con control dinámico de dirección, ancho de banda y parada/arranque sin detener el proceso principal. Está pensada para entornos donde se necesita un servidor en espejo (réplica) con posibilidad de invertir el sentido de la sincronización en caliente.
Pega el siguiente contenido (ajusta scriptname si quieres otro nombre):
Dale permisos de ejecución:
Contenido para este ejemplo (sincronización desde ordenadorB hacia ordenadorA):
Explicación de data:
FROM:192.168.1.245:100:ServidorApp significa que el script, ejecutándose en ordenadorA (192.168.1.240), tomará la VM con ID 100 desde el servidor remoto 192.168.1.245 y la sincronizará al pool local zpool1, con el nombre de snapshot ServidorApp.
Si quisieras sincronizar hacia ordenadorB, cambiarías a TO:192.168.1.245:100:ServidorApp (entonces la VM local ID 100 se enviaría al remoto).
Añade la línea:
De esta forma, si el script se detiene (por ejemplo tras cambiar command=Stop), el próximo minuto volverá a arrancar respetando la nueva configuración.
En menos de un minuto el script comenzará a sincronizar las VM una tras otra en un bucle continuo. Para detenerlo, vuelve a poner command=Stop.
Sincronizar una VM detendia hacia una VM encendida en el destino, puede corromper los datos. La réplica debe hacerse siempre desde una VM apagada o desde un snapshot consistente.
Saludos cordiales.
¿Qué hace el script?
- Lee un archivo de configuración (/etc/script_<nombre>.command) donde se definen:
- data= : lista de máquinas virtuales a sincronizar, cada una con formato FROM|TO:IP:VMID:NOMBRE.
- FROM = sincroniza desde esa IP remota hacia el servidor local.
- TO = sincroniza desde el servidor local hacia esa IP remota.
- command=Play|Stop : arranca o detiene el bucle de sincronización.
- limit= : límite de ancho de banda en KB/s (ej. 62500 ≈ 50% de una red 1 Gbps).
- zpoolname= : nombre del pool ZFS (debe ser el mismo en ambos lados).
- debug=1|0 : activa logs detallados.
- data= : lista de máquinas virtuales a sincronizar, cada una con formato FROM|TO:IP:VMID:NOMBRE.
- Ejecuta pve-zsync sync para cada VM, respetando el sentido indicado.
- Corre en un bucle infinito controlado por el archivo de comandos, de modo que se puede modificar data o pasar de Play a Stop en caliente.
- Rotación de logs diaria (/tmp/script_<nombre>.output y .debug).
- Usa un archivo de bloqueo (/tmp/script_<nombre>.lck) para evitar múltiples instancias.
Ejemplo práctico: ordenadorA (local, IP 192.168.1.240) ←→ ordenadorB (PVE remoto, IP 192.168.1.245)
En este ejemplo, el script se aloja en ordenadorA (192.168.1.240) y sincronizará desde ordenadorB hacia ordenadorA (copia de seguridad en caliente). Las VM involucradas son las IDs 100, 101 y 102.1. Crear el script binario en ordenadorA
Code:
mkdir -p /root/bin
nano /root/bin/sync20260129.sh
Bash:
#!/bin/bash
source /etc/profile
scriptname="sync20260129"
outputfile="/tmp/script_${scriptname}.output"
debugfile="/tmp/script_${scriptname}.debug"
commandfile="/etc/script_${scriptname}.command"
lckfile="/tmp/script_${scriptname}.lck"
function output_debugfile_rotation(){
if [[ -f $outputfile ]];then
if [[ "$(grep "Start Datetime:" ${outputfile} | wc -l)" -eq "1" ]];then
fechaoutputfile=$(grep "Start Datetime:" ${outputfile} | tail -1 | awk -F"Start Datetime: " '{print $2}')
if [[ "$fechaoutputfile" -ne "$(date +%Y%m%d)" ]];then
mv ${outputfile} ${outputfile}_yesterday
> ${outputfile}
fi
fi
fi
if [[ -f $debugfile ]];then
if [[ "$(grep "Start Datetime:" ${debugfile} | wc -l)" -eq "1" ]];then
fechadebugfile=$(grep "Start Datetime:" ${debugfile} | tail -1 | awk -F"Start Datetime: " '{print $2}')
if [[ "$fechadebugfile" -ne "$(date +%Y%m%d)" ]];then
mv ${debugfile} ${debugfile}_yesterday
> ${debugfile}
fi
fi
fi
}
function write_debugfile(){
output_debugfile_rotation
if [[ "$(grep -v "^#" $commandfile | grep "debug=")" == "debug=1" ]];then
echo "$(date +"%Y%m%d %H:%M:%S") - $@" >> $debugfile
fi
}
function write_outputfile(){
output_debugfile_rotation
echo "$(date +"%Y%m%d %H:%M:%S") - $@" >> $outputfile
}
function zfsSyncMigration(){
p1_source=$1
p2_destination=$2
p3_name=$3
write_debugfile "VM ${p3_name}"
date1=$(date +"%Y%m%d%H%M%S")
if [[ "$(grep -v "^#" $commandfile | grep "limit=" | wc -l)" == "1" ]];then
limit=$(grep -v "^#" $commandfile | grep "limit=" | awk -F"=" '{print $2}')
write_debugfile "Limit=$limit"
/usr/sbin/pve-zsync sync --source ${p1_source} --dest ${p2_destination} --name ${p3_name} --verbose --limit ${limit} >> ${debugfile} 2>&1
else
write_debugfile "ERROR on file ${commandfile}, command limit missed!"
fi
date2=$(date +"%Y%m%d%H%M%S")
write_debugfile "Start: ${date1} - END: ${date2} - LAPSE: $((date2-date1))"
}
if [[ ! -f $lckfile ]];then
write_debugfile "==================================================================================================="
write_debugfile "Archivo $lckfile no existe"
touch $lckfile
if [[ ! -f $commandfile ]];then
echo "#data=FROM:IP:ID:NAME;FROM:IP:ID:NAME
data=FROM:192.168.1.245:100:ServidorApp;FROM:192.168.1.245:101:ServidorTest;FROM:192.168.1.245:102:ServidorBD
#command=Stop o command=Play
command=Stop
debug=1
# 25% = 31250 | 50% = 62500 | 75% = 93750 | 90% = 112500 | 95% = 118750
limit=62500
zpoolname=zpool1" > $commandfile
fi
write_debugfile "[[ Command=$(grep -v "^#" $commandfile | grep "command=") ; Debug=$(grep -v "^#" $commandfile | grep "debug=") ; Limit=$(grep -v "^#" $commandfile | grep "limit=" | awk -F"=" '{print $2}') ]]"
write_debugfile "Se ha creado el archivo $lckfile"
write_debugfile "Start Datetime: $(date +"%Y%m%d")"
write_outputfile "Start Datetime: $(date +"%Y%m%d")"
zpoolname=$(grep -v "^#" $commandfile | grep "zpoolname=" | awk -F"=" '{print $2}')
while true;do
data=$(grep -v "^#" $commandfile | grep "data=" | awk -F"=" '{print $2}')
write_debugfile "** Data: ${data} **"
for item in $(echo ${data} | sed "s/;/ /g");do
write_debugfile "** Line: ${item} **"
if [[ "$(grep -v "^#" ${commandfile} | grep "command=")" == "command=Play" ]];then
IFS=":" read -r fromto ip vm name <<< ${item}
write_debugfile "** Syncing VM ${vm} **"
write_outputfile "** Start of Syncing VM ${vm} **"
if [[ "${fromto}" == "FROM" ]];then
zfsSyncMigration ${ip}:${vm} ${zpoolname} ${name}
elif [[ "${fromto}" == "TO" ]];then
zfsSyncMigration ${vm} ${ip}:${zpoolname} ${name}
fi
wait
write_outputfile "** End of Syncing VM ${vm} **"
write_debugfile "==================================================================================================="
elif [[ "$(grep -v "^#" $commandfile | grep "command=")" == "command=Stop" ]];then
write_debugfile "Comando STOP"
write_outputfile "Comando STOP"
break
fi
done
if [[ "$(grep -v "^#" $commandfile | grep "command=")" == "command=Stop" ]];then
write_debugfile "Comando STOP"
write_outputfile "Comando STOP"
break
fi
done
rm $lckfile
# Probar: trap 'rm -f $lckfile; exit' INT TERM EXIT
write_debugfile "Se ha borrado el archivo $lckfile"
write_debugfile "==================================================================================================="
#else
# write_debugfile "No es posible ejecutar por la existencia del archivo $lckfile"
fi
chmod +x /root/bin/sync20260129.sh2. Crear el archivo de configuración (se generará automáticamente la primera vez, pero es mejor crearlo manualmente)
vim /etc/script_sync20260129.commandContenido para este ejemplo (sincronización desde ordenadorB hacia ordenadorA):
Bash:
# Dirección: FROM = desde el IP indicado hacia el local
# TO = desde el local hacia el IP indicado
data=FROM:192.168.1.245:100:ServidorApp;FROM:192.168.1.245:101:ServidorTest;FROM:192.168.1.245:102:ServidorBD
# command=Play o Stop
command=Stop
debug=1
# 25% = 31250 | 50% = 62500 | 75% = 93750 | 90% = 112500 | 95% = 118750
limit=62500
zpoolname=zpool1
Explicación de data:
FROM:192.168.1.245:100:ServidorApp significa que el script, ejecutándose en ordenadorA (192.168.1.240), tomará la VM con ID 100 desde el servidor remoto 192.168.1.245 y la sincronizará al pool local zpool1, con el nombre de snapshot ServidorApp.
Si quisieras sincronizar hacia ordenadorB, cambiarías a TO:192.168.1.245:100:ServidorApp (entonces la VM local ID 100 se enviaría al remoto).
3. Configurar crontab para lanzar el script cada minuto
crontab -eAñade la línea:
Bash:
* * * * * /root/bin/sync20260129.sh >/dev/null 2>&1
4. Poner en marcha la sincronización
Edita /etc/script_sync20260129.command y cambia command=Stop por command=Play.En menos de un minuto el script comenzará a sincronizar las VM una tras otra en un bucle continuo. Para detenerlo, vuelve a poner command=Stop.
ADVERTENCIA IMPORTANTE
Antes de modificar el sentido (FROM ↔ TO) o añadir/eliminar VM en la línea data=, debes:- Primero asegurarse que el nombre del zpool donde se van a transferir o mantener sincronizadas las VM posee el mismo nombre (Pueden probar con nombres distintos, pero creo que da error)
- Detener la VM.
- Esperar a que termine la iteración actual (puedes vigilar el log /tmp/script_sync20260129.output).
- Realizar los cambios en data=.
- Vigila el log.
- Iniciar la VM en el servidor donde en el sentido de la sincronizacion si el sentido es FROM LOCAL TO REMOTE, entonces la VM debe estar ejecutada en el servidor LOCAL, pero si es FROM REMOTE TO LOCAL, entonces la VM debe iniciarse en el servidor REMOTO
Sincronizar una VM detendia hacia una VM encendida en el destino, puede corromper los datos. La réplica debe hacerse siempre desde una VM apagada o desde un snapshot consistente.
Observaciones sobre el rendimiento
- La primera sincronización puede durar horas o días según el tamaño de las VM, ya que transfiere todos los datos.
- Las siguientes ejecuciones solo envían los cambios (snapshots incrementales), por lo que el tiempo se reduce drásticamente (menos de 15 minutos en entornos típicos).
- El parámetro limit permite ajustar el ancho de banda para no saturar el enlace. Valores orientativos para red de 1 Gbps:
- 25% → 31250 KB/s
- 50% → 62500 KB/s
- 75% → 93750 KB/s
- 90% → 112500 KB/s
Requisitos
- Ambos servidores Proxmox con ZFS y el mismo nombre de pool (por ejemplo zpool1).
- Comunicación SSH sin contraseña entre los nodos (root o usuario con privilegios), puedes crear una llave sin password etre ambos servidores.
- El comando pve-zsync instalado (no viene por defecto en Proxmox VE).
- El script debe ejecutarse como root.
NOTA:
Les recomiendo hacer un laboratorio primero, aunque es sencillo de entender, les sugiero entiendan lo que hace antes de ponerlo en producción, en nuestro caso, cuando el cliente desea hacer el cambio por motivos de mantenimiento, el cambio de dirección es sencillo, se les pide primero detener las VM y se espera a que termine de sincronizarse, esto se detectua cuando la sincronización ya es casi de 12kb por ejemplo en cambios y los tiempos ya son casi de 0 segundos y ya no ha variacion, al estar apagadas las vm en ambos lados, se hace el cambio de sentido en el data, y se monitoreo el log, una vez detectado el cambio de sincronización, entonces se da inicio en el nuevo destino.Saludos cordiales.