Archivo de la etiqueta: deploy

Node.js

Desplegar una aplicación Node.js como servicio Upstart (demonio)

Cuando quise desplegar mi app, imagine que iba a ser fácil, que lanzaba el comando “node miapp.js” y punto, pero aprendí a palos que así no va la cosa.

Para desplegar una aplicación web, necesitas que funcione como apache por lo menos: que puedas arrancarla, pararla, reiniciarla, ver su estado, acceder a su log, que se reinicie si se produce un error, etc etc etc.

Busque por Internet y leí un montón de artículos y foros que te dicen como montartelo: haciendo scripts en /etc/init.d y combinándolo con monit para reiniciarlo si se cae, Usando Upstart, lanzando la aplicación con “node miapp.js &” (proceso en background pero es la peor solución que leí en años), y alguna que otra mas.

La mas razonable fue la de Upstart, aunque ninguna de las soluciones fue bastante clara o completa como para copiar, pegar y arrancar. Por eso hice este script que toma en cuenta todo lo que hace falta para montar tu app como un servicio:

description "Demonio para mi aplicación en Node.js"
author "Capy - http://ecapy.com"
env LOG_FILE=/var/log/node/miapp.log
env APP_DIR=/var/node/miapp
env APP=app.js
env PID_NAME=miapp.pid
env USER=www-data
env GROUP=www-data
env POST_START_MESSAGE_TO_LOG="miapp ha sido iniciada."
env NODE_BIN=/usr/local/bin/node
env PID_PATH=/var/opt/node/run
env SERVER_ENV="production"
######################################################
# A partir de aquí no debería hacer falta tocar nada #
######################################################
#Nuestro proceso espera hasta que el sistema este cargado
start on runlevel [2345]
#Y se detiene cuando el sistema se apague
stop on runlevel [016]
#Levanta el proceso automáticamente si se muere:
respawn
#limitamos el "respawn" a 99 veces con un timeout de 5s para que no intente levantar el proceso infinitamente.
respawn limit 99 5
pre-start script
    #Necesitamos asegurarnos que el path del pid exista antes de arrancar el proceso.
    mkdir -p $PID_PATH
    mkdir -p /var/log/node
end script
script
	#Seteamos NODE_ENV para que nuestra app se ejecute en modo production, development, test, etc.
	export NODE_ENV=$SERVER_ENV
	#El siguiente comando es el que realmente arranca el proceso:
	#Utiliza el usuario y grupo www-data (--chuid)
	#Asigna un pid y lo crea si no existe (--make-pidfile y --pidfile)
	#Cambia el directorio de ejecución a donde esté nuestra app de node (--chdir)
	#Ejecuta la app (--exec)
	#Envía cualquier output que genere nuestra app al log (>> $LOG_FILE 2>&1)
    exec start-stop-daemon --start --chuid $USER:$GROUP --make-pidfile --pidfile $PID_PATH/$PID_NAME --chdir $APP_DIR --exec $NODE_BIN -- $APP >> $LOG_FILE 2>&1
end script
post-start script
	echo $POST_START_MESSAGE_TO_LOG >> $LOG_FILE
end script

Puede parecer un poco largo, pero si lo lees detenidamente vas a ver que es bastante concreto:

  1. Autor y descripción: son parte básica de un script Upstart
  2. Variables de configuración para que no tengas que meterte entre el código para ajustar el script para cada nueva app que crees.
  3. Cuerpo del script.

Acá esta el mismo script sin comentarios:

description "Demonio para mi aplicación en Node.js"
author "Capy - http://ecapy.com"
env LOG_FILE=/var/log/node/miapp.log
env APP_DIR=/var/node/miapp
env APP=app.js
env PID_NAME=miapp.pid
env USER=www-data
env GROUP=www-data
env POST_START_MESSAGE_TO_LOG="miapp ha sido iniciada."
env NODE_BIN=/usr/local/bin/node
env PID_PATH=/var/opt/node/run
env SERVER_ENV="production"
######################################################
start on runlevel [2345]
stop on runlevel [016]
respawn
respawn limit 99 5
pre-start script
    mkdir -p $PID_PATH
    mkdir -p /var/log/node
end script
script
    export NODE_ENV=$SERVER_ENV
    exec start-stop-daemon --start --chuid $USER:$GROUP --make-pidfile --pidfile $PID_PATH/$PID_NAME --chdir $APP_DIR --exec $NODE_BIN -- $APP >> $LOG_FILE 2>&1
end script
post-start script
	echo $POST_START_MESSAGE_TO_LOG >> $LOG_FILE
end script

Uso:

Por cada aplicación en nodejs que quieras montar tenes que crear un archivo en /etc/init/. El nombre del archivo tiene que terminar en .conf. por ejemplo miapp-service.conf. Una cosa importante es que el servicio que estas creando se llama como el archivo (sin el .conf), por lo que en nuestro ejemplo se va a llamar miapp-service.

Dentro de /etc/init/miapp-service.conf pegas el script y personalizas todo lo que haya hasta la linea divisoria.

Un par de aclaraciones:
env APP=app.js define el nombre del “index” de tu aplicación, y por lo general se usan o app.js o server.js. Pon el nombre de tu app sin el path hasta ella. Si tu aplicación lleva parámetros podes encerrar en comillas dobles algo asi como “env APP=app.js -extrasettings ../settrings.json”
env APP_DIR=/var/node/miapp el path hasta tu aplicación
env PID_NAME=miapp.pid pon el nombre que quieras, pero que sea único. por ejemplo, si tu app es una pagina web pon el nombre de tu pagina “mipaginaweb_com.pid” o algo así.
env USER=www-data y env GROUP=www-data no sería muy responsable usar root para ejecutar tu aplicación salvo que esta si que necesite estar en root, pero en el caso de que sea una pagina web, usa el usuario y grupo www-data así podes unificar criterios. Es solo una sugerencia, yo para mi app uso www-data aunque podes usar el grupo y usuario que te parezca mejor.
POST_START_MESSAGE_TO_LOG es solo un mensaje que se envía al log de la app cuando esta arranca.
NODE_BIN indica donde esta ubicado node, por lo general está en /usr/local/bin/node aunque si no estuviera allí podes cambiar este parámetro.
env SERVER_ENV=”production” Posiblemente tu aplicación web utilice entornos de “development“, “test” y “production“. Bien, especificalo acá.

El resto del script está comentado en código así que no añado nada.

Cuando hayas acabado ya vas a disponer de tu servicio y tratarlo como cualquier otro:

start miapp-service
stop miapp-service
restart miapp-service
status miapp-service

Espero que les sirva.

Chau!