1. Qué es desplegar
Desplegar es instalar una versión nueva de un programa en el lugar donde se ejecuta, típicamente un servidor, y ponerla en funcionamiento en reemplazo de la anterior. Un buen despliegue es repetible, rápido, deja el sistema siempre en un estado coherente, y permite volver a la versión anterior si la nueva falla.
La forma ingenua es copiar los archivos nuevos encima de los viejos y reiniciar. Funciona casi siempre, y por eso es tan común, pero tiene tres problemas serios:
- Durante la copia, el servidor tiene una mezcla. Mientras
rsyncreemplaza cientos de archivos, unos son de la versión nueva y otros de la vieja, y una petición que llegue en ese momento puede encontrar un programa incoherente. Si la copia se corta a mitad de camino, la mezcla queda así. - No hay vuelta atrás. Si la versión nueva tiene un error grave, los archivos de la anterior ya no existen: hay que volver a desplegarla, con prisa y desde algún otro lugar.
- Se pisan cosas que no son del programa: la configuración del servidor, los archivos que subieron los usuarios, los registros.
La técnica de este tema, que usan desde hace años herramientas como Capistrano o Deployer, resuelve los tres con una idea simple: cada versión va a su propio directorio, y un enlace simbólico decide cuál es la activa.
2. Una versión por directorio
/srv/tienda/current.releases/guarda cada versión en un directorio con la fecha y la hora del despliegue. Una vez copiada, una versión no se modifica nunca.shared/guarda lo que sobrevive a las versiones: la configuración propia de ese servidor, con sus contraseñas, los archivos que suben los usuarios, los registros. Cada versión tiene enlaces simbólicos hacia allí.currentes un enlace simbólico a la versión activa. El servicio de systemd, el servidor web y cualquier tarea programada usan siempre/srv/tienda/current/…, sin saber qué versión hay detrás.
Con esta estructura, desplegar es copiar la versión nueva a su directorio, sin tocar la que está funcionando, y al final cambiar el enlace. Volver atrás es cambiar el enlace otra vez.
3. SSH en un script
Un script de despliegue no puede detenerse a pedir una contraseña. La conexión se hace con una clave, idealmente una dedicada a los despliegues, y con un usuario del servidor que tenga solo los permisos necesarios:
$ ssh-keygen -t ed25519 -f ~/.ssh/despliegue_ed25519 -C "despliegues de ana"
$ ssh-copy-id -i ~/.ssh/despliegue_ed25519.pub deploy@web1.example.com
Los detalles de la conexión conviene escribirlos una vez en ~/.ssh/config, con un nombre corto, en lugar de repetirlos en cada comando:
Host web1
HostName web1.example.com
User deploy
IdentityFile ~/.ssh/despliegue_ed25519
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 60
Las tres últimas líneas activan la multiplexación: la primera conexión queda abierta un minuto, y las siguientes viajan por ella sin negociar de nuevo. Un despliegue hace varias llamadas a ssh y a rsync, y la diferencia se nota. En el script, además, cada llamada lleva dos opciones:
-o BatchMode=yes: nunca preguntar nada. Si la clave no funciona o el servidor es desconocido, falla enseguida en lugar de quedarse esperando una respuesta que nadie dará.-o ConnectTimeout=10: no esperar indefinidamente a un servidor que no contesta.
Y un recordatorio del tema 16: ssh lee la entrada estándar, de modo que dentro de un bucle while read se come las líneas siguientes. Ahí se usa ssh -n.
4. Los comandos remotos y las comillas
ssh servidor comando ejecuta el comando en el servidor, y es el detalle que más errores causa en los scripts de despliegue. ssh no envía los argumentos por separado: los une con espacios en una sola línea, y la shell del servidor la vuelve a dividir. Las comillas locales protegen los argumentos de la shell local, pero no de la remota:
$ archivo="informe final.txt"
$ ssh web1 ls -l "$archivo"
ls: cannot access 'informe': No such file or directory
ls: cannot access 'final.txt': No such file or directory
$ ssh web1 ls -l "$(printf '%q' "$archivo")"
-rw-r--r-- 1 deploy deploy 812 oct 15 10:02 informe final.txt
printf '%q' escribe un valor con las comillas y barras necesarias para que una shell lo lea como un solo argumento, exactamente como era. Es la forma correcta de pasar datos a la shell remota. Para algo más largo que un comando, la técnica más limpia es no armar una línea en absoluto, sino enviar un script por la entrada estándar a un bash remoto, y los datos como argumentos:
remoto() { # remoto SCRIPT ARG...: ejecuta SCRIPT con bash en el servidor
local script="$1"
shift
ssh "${ssh_opciones[@]}" "$servidor" "bash -s -- $(printf '%q ' "$@")" <<< "set -euo pipefail
shopt -s nullglob dotglob
$script"
}
remoto 'cd "$1" && ls -1 releases' /srv/tienda
bash -s lee el programa de la entrada estándar, y lo que sigue a -- son sus argumentos $1, $2…, protegidos con printf '%q'. El script se escribe entre comillas simples, de modo que $1 no lo expande la shell local sino la remota, y se le antepone el modo estricto, que así rige también en el servidor. Es la forma que usa el script de este tema para todo lo que hace allí. Una precaución: printf '%q' produce comillas de Bash, de modo que la shell del usuario remoto tiene que ser Bash, o una compatible.
5. Copiar la versión con rsync
La versión nueva se copia con rsync a su propio directorio. Como la mayoría de los archivos no cambian de una versión a otra, conviene la técnica de instantáneas del tema 37: --link-dest apuntando a la versión activa hace que lo que no cambió se enlace en lugar de transferirse, y la copia lleva segundos aunque el programa sea grande.
rsync -az --delete -e "ssh -o BatchMode=yes" \
--link-dest=/srv/tienda/releases/20261014-163210/ \
-- build/paquete/tienda/ web1:/srv/tienda/releases/20261015-103012/
-z comprime durante la transferencia, útil por la red; -e indica el comando ssh con sus opciones. La barra final del origen copia su contenido, como vimos en el tema 37. Y --link-dest lleva una ruta absoluta en el servidor. Lo que se copia debería ser el resultado de la compilación, el directorio que preparó build.sh, y no el directorio de trabajo con sus archivos temporales; si hay que copiar un directorio con cosas que no van, se excluyen con --exclude, como .git/ o node_modules/.
6. Lo compartido y la preparación
Antes de activarla, la versión nueva se completa en el servidor. Primero, se enlaza con lo compartido: por cada elemento de shared/, se borra lo que la versión traiga con ese nombre y se crea un enlace en su lugar:
cd "$1/releases/$2"
for compartido in "$1"/shared/*; do
nombre="${compartido##*/}"
rm -rf -- "$nombre"
ln -s -- "../../shared/$nombre" "$nombre"
done
Los enlaces son relativos, ../../shared/config.env, de modo que siguen funcionando aunque se mueva el directorio entero. Así, la configuración con las contraseñas nunca viaja con el código ni queda en el repositorio, y los archivos que suben los usuarios no se pierden en cada versión.
Después, si la versión trae un programa de preparación, por convención desplegar/preparar, se ejecuta: es donde cada proyecto instala sus dependencias, compila lo que haya que compilar en el servidor o prepara la base de datos. Todo esto ocurre en el directorio de la versión nueva, mientras la anterior sigue atendiendo sin enterarse. Si algo falla en este punto, el despliegue se detiene y no pasó nada visible.
Con las bases de datos hay que tener un cuidado especial. Un cambio en su estructura, como borrar una columna, no se deshace cambiando un enlace, y deja a la versión anterior sin lo que necesita. La práctica habitual es hacer los cambios en dos pasos: primero agregar lo nuevo, de forma que las dos versiones funcionen, y recién en un despliegue posterior, cuando ya no se va a volver atrás, quitar lo viejo.
7. El cambio atómico
El momento decisivo es cambiar current para que apunte a la versión nueva. Lo natural sería ln -sfn releases/NUEVA current, pero ese comando hace dos operaciones: borra el enlace viejo y crea el nuevo. Entre una y otra hay un instante, breve pero real, en el que current no existe, y una petición que llegue justo entonces falla.
La forma atómica, como la escritura de archivos del tema 24, es crear el enlace nuevo con otro nombre y renombrarlo encima del viejo. El renombrado lo hace el núcleo en una sola operación: cualquiera que mire current ve el viejo o el nuevo, nunca un hueco.
cd /srv/tienda
ln -sfn releases/20261015-103012 current.nuevo
mv -T current.nuevo current
La opción -T de mv es imprescindible. Sin ella, como current es un enlace a un directorio, mv entendería «mover current.nuevo dentro del directorio al que apunta current», y dejaría un enlace perdido dentro de la versión activa. Con -T, el destino se trata como un archivo más, que se reemplaza.
8. Reiniciar y comprobar
Cambiar el enlace no basta: el programa en ejecución sigue siendo el viejo hasta que se reinicia. Si la aplicación es un servicio de systemd cuyo ExecStart apunta a /srv/tienda/current/…, reiniciarlo carga la versión nueva. El usuario de despliegue no debería ser root, así que se le da permiso para ese único comando, con una regla de sudo:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart tienda
En el script se usa sudo -n, que falla enseguida en lugar de pedir una contraseña si la regla no existe. Después del reinicio llega la pregunta importante: ¿funciona? La respuesta más útil la da la propia aplicación, con una dirección de salud, como /salud, que responde con éxito solo si el programa arrancó y puede acceder a lo que necesita. curl la consulta, con reintentos, porque un programa recién reiniciado tarda unos segundos en estar listo, como en los bucles con espera del tema 15:
responde() { # ¿responde bien la URL? Hasta 10 intentos, cada 3 segundos
[[ -z "$url" ]] && return 0
local intento
for intento in {1..10}; do
curl -fsS --max-time 5 -o /dev/null -- "$url" 2> /dev/null && return 0
sleep 3
done
return 1
}
-f hace que curl termine con error si el servidor responde con un código de error HTTP, como 500 o 503, y no solo si no hay conexión; --max-time limita cada intento. La comprobación se hace desde la máquina que despliega, por el mismo camino que usan las personas, y no desde dentro del servidor.
9. Volver atrás
Si la versión nueva no responde, la anterior sigue intacta en su directorio, y volver a ella es repetir el cambio atómico apuntando a ella y reiniciar: unos pocos segundos. El script lo hace solo: si la comprobación falla después de activar una versión, vuelve a la anterior, comprueba que responde, y termina con error para que la persona o el sistema de integración continua que lanzó el despliegue se entere. La versión fallida queda en el servidor, para poder examinarla.
La vuelta atrás también sirve a mano, cuando el problema aparece más tarde, por ejemplo una hora después, cuando los usuarios descubren un error que la comprobación de salud no detectaba. La acción volver del script activa la versión anterior a la activa. Como cada versión está completa, se puede volver dos o tres versiones atrás, mientras se conserven.
Lo que la vuelta atrás no deshace es lo que la versión nueva haya cambiado fuera de su directorio: la estructura de la base de datos, los archivos de shared/, los mensajes que haya enviado. Por eso la sección 6 recomendaba que los cambios en la base de datos sean compatibles con la versión anterior.
10. Un despliegue a la vez, y un registro
Dos personas que despliegan a la vez en el mismo servidor, o un despliegue automático que coincide con uno manual, pueden pisarse. El script toma un candado en el servidor antes de empezar, con mkdir, que crea el directorio o falla si ya existe, en una sola operación atómica, una de las alternativas a flock que vimos en el tema 27. Un trap lo libera al terminar, haya ido bien o mal. Si el script muere de forma abrupta y el candado queda, el siguiente despliegue lo informa y una persona decide si borrarlo.
Además, cada cambio de versión queda anotado en un registro en el servidor, despliegues.log, con la fecha, la versión y quién la activó. Cuando algo deja de funcionar, la primera pregunta siempre es «¿qué cambió?», y ese archivo la responde.
Finalmente, las versiones viejas se borran, para no llenar el disco, conservando un número fijo de las más recientes, y nunca la activa. Con --link-dest, cada versión ocupa solo lo que cambió, de modo que conservar cinco o diez cuesta poco.
11. Un script completo
#!/usr/bin/env bash
#
# desplegar — instala versiones de una aplicación en un servidor por SSH,
# con cambio atómico y vuelta atrás automática si la nueva no responde.
#
# Uso: desplegar [-s SERVICIO] [-u URL] [-k N] SERVIDOR:RUTA subir DIRECTORIO
# desplegar [-s SERVICIO] [-u URL] SERVIDOR:RUTA volver
# desplegar SERVIDOR:RUTA versiones
#
# -s servicio de systemd que se reinicia después de cada cambio
# -u URL que debe responder bien después de cada cambio
# -k cuántas versiones conservar en el servidor (5)
#
# En el servidor: RUTA/releases/VERSIÓN/ cada versión instalada
# RUTA/shared/ lo que comparten todas: configuración, datos
# RUTA/current enlace simbólico a la versión activa
#
set -euo pipefail
prog="${0##*/}"
servicio="" url="" conservar=5
ssh_opciones=(-o BatchMode=yes -o ConnectTimeout=10)
uso() {
echo "Uso: $prog [-s SERVICIO] [-u URL] [-k N] SERVIDOR:RUTA subir DIRECTORIO" >&2
echo " $prog [-s SERVICIO] [-u URL] SERVIDOR:RUTA volver" >&2
echo " $prog SERVIDOR:RUTA versiones" >&2
exit 2
}
error() { echo "$prog: $*" >&2; exit 1; }
paso() { echo "==> $*"; }
remoto() { # remoto SCRIPT ARG...: ejecuta SCRIPT con bash en el servidor
local script="$1"
shift
ssh "${ssh_opciones[@]}" "$servidor" "bash -s -- $(printf '%q ' "$@")" <<< "set -euo pipefail
shopt -s nullglob dotglob
$script"
}
# Activa la versión $2 en la ruta $1 y reinicia el servicio $3, si lo hay
CAMBIAR='
cd "$1"
[[ -d "releases/$2" ]] || { echo "no existe la versión $2" >&2; exit 1; }
ln -sfn "releases/$2" current.nuevo
mv -T current.nuevo current
if [[ -n "$3" ]]; then sudo -n systemctl restart "$3"; fi'
anotar() { # una línea en el registro de despliegues del servidor
remoto 'echo "$(date "+%F %T") $2" >> "$1/despliegues.log"' "$ruta" "$1 (${USER:-$(id -un)})"
}
responde() { # ¿responde bien la URL? Hasta 10 intentos, cada 3 segundos
[[ -z "$url" ]] && return 0
local intento
for intento in {1..10}; do
curl -fsS --max-time 5 -o /dev/null -- "$url" 2> /dev/null && return 0
sleep 3
done
return 1
}
liberar() {
remoto 'rmdir -- "$1/.desplegando"' "$ruta" || true
}
subir() {
local dir="${1%/}" version anterior
[[ -d "$dir" ]] || error "no existe el directorio: $dir"
version=$(date +%Y%m%d-%H%M%S)
paso "Preparando $servidor:$ruta"
anterior=$(remoto '
mkdir -p "$1/releases" "$1/shared"
mkdir "$1/.desplegando" 2> /dev/null || { echo "ya hay un despliegue en marcha en $1" >&2; exit 1; }
if [[ -L "$1/current" ]]; then basename "$(readlink "$1/current")"; fi' "$ruta")
trap liberar EXIT
paso "Copiando $dir como versión $version${anterior:+; la activa es $anterior}"
local opciones=(-az --delete -e "ssh ${ssh_opciones[*]}")
[[ -n "$anterior" ]] && opciones+=(--link-dest="$ruta/releases/$anterior/")
rsync "${opciones[@]}" -- "$dir/" "$servidor:$ruta/releases/$version/"
paso "Enlazando lo compartido y preparando la versión"
remoto '
cd "$1/releases/$2"
for compartido in "$1"/shared/*; do
nombre="${compartido##*/}"
rm -rf -- "$nombre"
ln -s -- "../../shared/$nombre" "$nombre"
done
if [[ -x desplegar/preparar ]]; then ./desplegar/preparar; fi' "$ruta" "$version"
paso "Activando $version"
remoto "$CAMBIAR" "$ruta" "$version" "$servicio"
if responde; then
anotar "activada $version"
paso "Versión $version activa${url:+; $url responde}"
else
[[ -n "$anterior" ]] || error "$url no responde, y no hay una versión anterior a la que volver"
paso "$url no responde: volviendo a $anterior"
remoto "$CAMBIAR" "$ruta" "$anterior" "$servicio"
anotar "falló $version; se volvió a $anterior"
responde || error "tampoco responde con $anterior: hay que intervenir a mano"
error "la versión $version falló; sigue activa $anterior"
fi
remoto '
cd "$1/releases"
versiones=(*)
activa=$(basename "$(readlink ../current)")
for (( i = 0; i < ${#versiones[@]} - $2; i++ )); do
[[ "${versiones[i]}" == "$activa" ]] && continue
rm -rf -- "${versiones[i]}"
echo " borrada la versión ${versiones[i]}"
done' "$ruta" "$conservar"
}
volver() {
local destino
destino=$(remoto '
cd "$1/releases"
activa=$(basename "$(readlink ../current)")
anterior=""
for v in *; do
[[ "$v" == "$activa" ]] && break
anterior="$v"
done
[[ -n "$anterior" ]] || { echo "no hay una versión anterior a $activa" >&2; exit 1; }
echo "$anterior"' "$ruta")
paso "Volviendo a $destino"
remoto "$CAMBIAR" "$ruta" "$destino" "$servicio"
responde || error "${url} no responde con $destino"
anotar "vuelta atrás a $destino"
paso "Versión $destino activa"
}
versiones() {
remoto '
cd "$1/releases"
activa=$(readlink ../current || true)
for v in *; do
if [[ "$v" == "${activa##*/}" ]]; then echo "$v ← activa"; else echo "$v"; fi
done' "$ruta"
}
while getopts ":s:u:k:" opcion; do
case "$opcion" in
s) servicio="$OPTARG" ;;
u) url="$OPTARG" ;;
k) conservar="$OPTARG" ;;
*) uso ;;
esac
done
shift $(( OPTIND - 1 ))
(( $# >= 2 )) || uso
[[ "$1" =~ ^([^:]+):(/.+)$ ]] || error "el destino debe tener la forma SERVIDOR:/ruta/absoluta"
servidor="${BASH_REMATCH[1]}" ruta="${BASH_REMATCH[2]%/}"
[[ "$conservar" =~ ^[1-9][0-9]*$ ]] || error "-k debe ser un número mayor que cero"
[[ -z "$servicio" || "$servicio" =~ ^[A-Za-z0-9@._-]+$ ]] || error "nombre de servicio no válido: $servicio"
accion="$2"
shift 2
case "$accion" in
subir) (( $# == 1 )) || uso; subir "$1" ;;
volver) (( $# == 0 )) || uso; volver ;;
versiones) (( $# == 0 )) || uso; versiones ;;
*) uso ;;
esac
$ desplegar -s tienda -u https://tienda.example.com/salud web1:/srv/tienda subir build/paquete/tienda
==> Preparando web1:/srv/tienda
==> Copiando build/paquete/tienda como versión 20261015-103012; la activa es 20261014-163210
==> Enlazando lo compartido y preparando la versión
==> Activando 20261015-103012
==> Versión 20261015-103012 activa; https://tienda.example.com/salud responde
borrada la versión 20261009-120455
$ desplegar -s tienda -u https://tienda.example.com/salud web1:/srv/tienda subir build/paquete/tienda
==> Preparando web1:/srv/tienda
==> Copiando build/paquete/tienda como versión 20261015-114540; la activa es 20261015-103012
==> Enlazando lo compartido y preparando la versión
==> Activando 20261015-114540
==> https://tienda.example.com/salud no responde: volviendo a 20261015-103012
desplegar: la versión 20261015-114540 falló; sigue activa 20261015-103012
$ desplegar web1:/srv/tienda versiones
20261013-091500
20261014-163210
20261015-103012 ← activa
20261015-114540
$ ssh web1 cat /srv/tienda/despliegues.log
2026-10-14 16:32:31 activada 20261014-163210 (ana)
2026-10-15 10:30:44 activada 20261015-103012 (ana)
2026-10-15 11:46:19 falló 20261015-114540; se volvió a 20261015-103012 (ana)
- Todo lo remoto pasa por
remoto: cada fragmento se ejecuta conbash -sen el servidor, en modo estricto, con los datos como argumentos protegidos porprintf '%q'. Ningún valor se mezcla con el código remoto, y el error de un comando remoto detiene también el script local, porquesshdevuelve el código del comando que ejecutó. - El orden protege a la versión activa: copiar, enlazar y preparar ocurren en el directorio nuevo; si cualquiera de esos pasos falla, el script se detiene antes de tocar
current, y para los usuarios no pasó nada. - El cambio es atómico y reutilizable: el fragmento
CAMBIARhace elln -sfna un nombre provisional, elmv -Ty el reinicio, y lo usan igual el despliegue, la vuelta atrás automática y la manual. - La vuelta atrás es automática y honesta: si la URL de salud no responde, el script vuelve a la versión anterior, comprueba que esa sí responde, y termina con error de todos modos, porque el despliegue pedido no se hizo. Si tampoco responde la anterior, lo dice, porque eso ya no se arregla cambiando un enlace.
- Un candado, y siempre se libera:
mkdir .desplegandofalla si otro despliegue está en marcha, y eltrapdel tema 24 lo borra al salir. Se instala recién después de tomar el candado, para no borrar nunca el de otro. - Las copias son baratas:
--link-desthace que la versión nueva comparta con la activa, mediante enlaces duros, todos los archivos que no cambiaron. - La limpieza respeta la versión activa: con
-kse conservan las N versiones más recientes, y la activa nunca se borra, aunque sea más vieja, como ocurre después de volver atrás. Los nombres con fecha y hora hacen que el orden del comodín sea el cronológico.
El script se probó en Linux, con los enlaces, rsync y mv -T reales y ssh reemplazado por un intermediario que ejecuta los comandos en la misma máquina. Antes de usarlo con un servidor real, conviene probarlo contra uno de prueba, o una máquina virtual, con la misma estructura.
12. Errores frecuentes
| Escribiste | Qué pasa | Corrección |
|---|---|---|
| Copiar la versión nueva encima de la activa | Durante la copia, una mezcla de versiones; si falla, queda así, y no hay vuelta atrás. | Una versión por directorio y un enlace current. |
ssh servidor rm -rf "$dir" | La shell remota vuelve a dividir el valor: con espacios, borra otras cosas. | printf '%q', o un script con bash -s y argumentos. |
Comillas dobles en el script remoto: remoto "cd $1" | El $1 lo expande la shell local, no la remota. | El script remoto entre comillas simples. |
ssh sin BatchMode=yes | Si falla la clave, pide una contraseña y el despliegue automático se cuelga. | -o BatchMode=yes -o ConnectTimeout=10 |
ln -sfn releases/X current | Hay un instante sin current: peticiones que fallan. | Enlace provisional y mv -T. |
mv current.nuevo current sin -T | Se mueve el enlace dentro del directorio de la versión activa. | mv -T |
| La configuración del servidor dentro del paquete | Cada despliegue la pisa, y las contraseñas viajan con el código. | En shared/, con un enlace desde cada versión. |
| Desplegar como root | Cualquier error del script tiene todos los permisos. | Un usuario de despliegue y una regla de sudo para el reinicio. |
| Dar el despliegue por bueno porque el reinicio funcionó | El servicio arranca pero no atiende, y nadie lo nota. | Una comprobación de salud, con curl -f y reintentos. |
| Borrar una columna de la base de datos en el mismo despliegue | La vuelta atrás deja a la versión anterior sin lo que necesita. | Cambios compatibles, en dos despliegues. |
13. Resumen
- Desplegar es instalar y activar una versión nueva; copiarla encima de la activa deja mezclas, no permite volver atrás y pisa la configuración.
- Cada versión va a
releases/FECHA/, lo que persiste ashared/, y el enlacecurrentindica la activa; el servicio usa siemprecurrent. - En un script,
sshusa una clave,BatchMode=yesyConnectTimeout;~/.ssh/configguarda los detalles, y la multiplexación acelera las conexiones. - La shell remota vuelve a leer el comando: los datos se protegen con
printf '%q', y lo más limpio es enviar un script abash -scon los datos como argumentos. - La versión se copia con
rsync -az --deletey--link-desta la activa, y se enlaza con lo compartido antes de activarla. ln -sfna un nombre provisional ymv -Tcambiancurrentde forma atómica.- Después de reiniciar, una dirección de salud consultada con
curl -fy reintentos confirma que la versión funciona; si no, se vuelve a la anterior cambiando el enlace. - Un candado con
mkdirevita despliegues simultáneos, un registro anota cada cambio, y la limpieza conserva las últimas versiones y nunca la activa. - La vuelta atrás no deshace los cambios hechos fuera del directorio de la versión, como la estructura de la base de datos.
14. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué problemas tiene copiar la versión nueva encima de la que está funcionando?
Durante la copia, el servidor tiene una mezcla de archivos de las dos versiones; si la copia se interrumpe, la mezcla queda; no hay forma rápida de volver a la anterior; y se pueden pisar la configuración y los datos del servidor.
2. ¿Para qué sirven releases/, shared/ y current?
releases/ guarda cada versión completa en su propio directorio; shared/, lo que debe sobrevivir a las versiones, como la configuración y los archivos de los usuarios; current es un enlace simbólico a la versión activa, que es la ruta que usan el servicio y el servidor web.
3. ¿Por qué ssh web1 ls "$archivo" falla si el nombre tiene espacios?
Porque ssh une los argumentos en una sola línea y la shell del servidor la vuelve a dividir en palabras. Las comillas locales ya se usaron en el equipo local. Se protege con printf '%q'.
4. ¿Qué hace ssh servidor "bash -s -- arg1 arg2" <<< "$script"?
Ejecuta en el servidor un bash que lee su programa de la entrada estándar, el contenido de $script, con arg1 y arg2 como sus argumentos $1 y $2.
5. ¿Para qué sirve --link-dest en un despliegue?
Para que los archivos que no cambiaron respecto de la versión activa se enlacen con enlaces duros en lugar de transferirse. La copia es mucho más rápida, y cada versión ocupa solo lo que cambió.
6. ¿Por qué ln -sfn releases/X current no es atómico, y cómo se logra que lo sea?
Porque borra el enlace viejo y después crea el nuevo, y entre las dos operaciones current no existe. Se crea el enlace con otro nombre y se renombra encima con mv -T, que el núcleo hace en una sola operación.
7. ¿Qué pasa si se omite -T en mv -T current.nuevo current?
Como current apunta a un directorio, mv mueve current.nuevo dentro de ese directorio, y current sigue apuntando a la versión anterior.
8. ¿Por qué la comprobación de salud usa curl -f y varios intentos?
-f hace que un código de error HTTP, como 500 o 503, cuente como fallo, y no solo la falta de conexión. Los intentos dan tiempo a que el programa recién reiniciado termine de arrancar.
9. ¿Por qué el script termina con error aunque la vuelta atrás automática haya funcionado?
Porque el despliegue pedido no se hizo: la versión nueva falló. Terminar con error avisa a la persona o al sistema que lo lanzó, que tiene que investigar la versión fallida.
10. ¿Qué no deshace volver a la versión anterior?
Los cambios hechos fuera del directorio de la versión: la estructura de la base de datos, los archivos de shared/, los efectos externos como correos enviados. Por eso los cambios de base de datos se hacen compatibles con la versión anterior.
Para practicar
- Configura en
~/.ssh/configun servidor de prueba con una clave dedicada, y comprueba quessh -o BatchMode=yes servidor truefunciona sin preguntar nada. - Crea un archivo con espacios en el nombre en el servidor y comprueba la diferencia entre
ssh servidor ls "$nombre"y la versión conprintf '%q'. - En un directorio local, arma la estructura
releases/,shared/ycurrentcon dos versiones, y cambia de una a otra conln -sfnymv -T. Prueba qué pasa conmvsin-T. - Si no tienes un servidor, prueba
desplegarconlocalhostcomo servidor, con el servicio SSH local en marcha, y una ruta como/tmp/tienda. - Haz que la comprobación de salud falle, por ejemplo con una URL inexistente, y verifica que el script vuelve a la versión anterior y lo anota en
despliegues.log. - Agrega a
desplegaruna opción-nque muestre qué haría: qué versión se activaría, qué se copiaría conrsync -n -iy qué versiones se borrarían, sin hacer nada.