Controlar los mongrels con monit en Slicehost

Recientemente me he estado pegando con mi mongrel cluster en un una de mis aplicaciones en Slicehost… Los mongrel comenzaban ocupando menos de 50 Mb de memoria pero conforme pasaban los días iban ganando peso y llegaban a chupar cerca de 200 Mb, acabando con la memoria disponible…

Parece que esta facilitar para ganar peso de los mongrels es en parte natural (van creciendo conforme van cargando códigos de la aplicación) y en parte debido a bugs en el propio código de mongrel, plugins de rails, etc. Sea como sea, es necesario mantenerlos a raya.

Para ello, qué mejor que monit. Esta aplicación se emplea para controlar los procesos de un servidor, de manera que podemos medir continuamente el tamaño de ram que emplean (y otras medidas como uso de cpu, carga…) y reiniciarlos en caso necesario. También puede encargarse de comprobar que los procesos están corriendo y lanzarlos en caso de que se hubieran caído. Lo podemos usar para todos los procesos del servidor, no sólo los mongrels: nginx, apache, mysqld, sendmail, etc.

Os cuento cómo lo monté en mi servidor debian en slicehost:

1.- En primer lugar, instalamos monit:

sudo apt-get install monit

2.- Una vez instalado, tenemos un fichero de configuración de monit. Está bien guardarlo como ejemplo pues vienen muchos casos útiles, pero para simplemente controlar mongrels, podéis hacer como en este ejemplo:

set daemon 60
set mailserver localhost
set mail-format { from: monit@tuaplicacion.com }
set alert tu@email.com

set logfile /var/log/monit.log

##### mongrel 12000 #####
check process mongrel-12000 with pidfile /home/usuario/apps/aplicacion/tmp/pids/mongrel.12000.pid
    start program = "/usr/bin/mongrel_rails cluster::start -C /home/usuario/apps/aplicacion/config/mongrel_cluster.yml --clean --only 12000"
    stop program  = "/usr/bin/mongrel_rails cluster::stop -C /home/usuario/apps/aplicacion/config/mongrel_cluster.yml --clean --only 12000"

    if totalmem is greater than 75.0 MB for 5 cycles then restart       # eating up memory?
    if cpu is greater than 50% for 2 cycles then alert                  # send an email to admin
    if cpu is greater than 80% for 3 cycles then restart                # hung process?
    if loadavg(5min) greater than 10 for 8 cycles then restart          # bad, bad, bad
    if 3 restarts within 5 cycles then timeout                          # something is wrong, call the sys-admin

    if failed port 12000 protocol http                   # check for response
        with timeout 10 seconds
        then restart
    group mongrel

El código es muy fácil de entender:

  • La primera línea establece cada cuántos segundos se despertará monit para hacer sus comprobaciones
  • El siguiente bloque establece la configuración para los envíos de email de alerta
  • La línea de logfile indica en qué ficheros guardar los logs
  • Y después, un bloque para cada proceso que se quiera vigilar. Para cada proceso hay que indicar el nombre que le daremos a este, dónde puede encontrar el PID del proceso, y cuál es el comando para arrancarlo y para pararlo. A continuación, se pueden poner varias directivas para las comprobaciones. En este ejemplo, si el mongrel consume más de 75 Mb de RAM durante 5 comprobaciones (cycles) seguidas, se reiniciará el proceso (vamos, se le hará un stop y un start con los comandos arriba indicados). Hay otras comprobaciones adicionales debajo. También se comprueba que el mongrel esté accesible en su puerto, y si no se reinicia, etc.

En mi caso tengo 3 mongrels… para controlar los otros dos se copia ese ultimo parrafo exactamente igual para cada uno, pero cada uno en su puerto, claro… 12001 y 12002 en mi caso.

3.- Ya casi estamos… para indicar que monit se arranque al iniciar el servidor, editaremos el fichero /etc/default/monit y lo indicaremos mediante la línea startup=1

4.- Y finalmente, para lanzar monit ahora sin tener que reiniciar el servidor…

sudo /etc/init.d/monit start

5.- Como nota final, un tema que me encontré es que cuando monit entraba en acción y reiniciaba algún mongrel, me los lanzaba como usuario root en lugar de como el usuario no-root con el que normalmente lanzo el mongrel cluster. Esto era porque me faltaba indicar en el fichero config/mongrel_cluster.yml de mi aplicación el usuario y grupo con el que se deben ejecutar los mongrels:

---
user: usuario
group: grupo
(...)

Eso es todo amigos… espero que os venga bien y os facilite la vida al tener que estar menos pendientes del servidor…

Gracias a Marze y The Robot por sugerirme usar monit, y a Guillermo por darme la pista sobre el tema del usuario no-root en la lista hacking-es.

Aplicación Rails que se autodestruye

Suicidio de amor / Love suicideTras leer el interesante artículo 21 Ruby Tricks You Should Be Using In Your Own Code sobre la posibilidad de borrar un árbol de directorios con FileUtils.rm_r ‘nombre_directorio’ no pude resistir la tentación de probar esto:

rails autodestroy
cd autodestroy
script/generate controller Suicide now

Y en app/controllers/suicide_controller.rb:

def now
FileUtils.rm_r "#{RAILS_ROOT}"
end

Listos para la autodestrucción…

script/server
Al visitar http://localhost:3000/suicide/now, la aplicación se borrará a si misma del disco duro.

Manejar con cuidado! :P

Creative Commons License photo credit: Vagamundos

Cómo recortar imágenes con attachment_fu y mini_magick

avatar_max_headroom.jpgAttachment_fu es genial para subir y redimensionar imágenes en el servidor, pero de serie viene sin soporte para recortar (crop) las fotos.

El cropping o recorte de imágenes es algo muy interesante para generar sitios de mejor aspecto: sobre todo, por ejemplo, para que los thumbnails tengan unas dimensiones fijas (digamos, 100×100px) independientemente de las dimensiones de la foto original, o de si esta era apaisada o vertical.

También viene bien en aquellos proyectos dirigidos por el diseñador, que, sobre todo si viene del mundo Flash, te impondrá su criterio estético en el que es super-importante que las imágenes vayan recortadas a las dimensiones específicas que te indique (esto me pasó en el proyecto Nurbijou, donde las imágenes de portada van a 295×240px, que es casi 320×240, pero no lo es… así que toca recortar.

Si necesitas recortar las fotos, echa un vistazo a este tutorial de Ian Drysdale para saber cómo has de parchear el attachment_fu. Para tu comodidad, adjunto los dos ficheros modificados.

mini_magick_processor.rb

geometry.rb

Pásate al desarrollo guiado por tests

Cada vez que voy a una reunión de programadores Rails suelo preguntar para ver cuántos están empleando testing automatizado en sus aplicaciones, y aunque cada vez lo va usando más gente, en la actualidad no llega a 1/3 del total el número de programadores que los ha incorporado como práctica habitual en sus desarrollos.

El desarrollo guiado por tests (TDD, Test Driven Development) es una técnica de desarrollo de software mediante la cual trabajamos en iteraciones cortas cubiertas por tests automáticos que cubren la nueva funcionalidad que queremos en nuestra aplicación. Lo interesante del tema es que primero escribimos el test y después programamos la funcionalidad, hasta que el test pase con éxito. Cuando no se nos ocurren más casos en los que los tests fallen, hemos acabado con la programación.

Un ejemplo muy sencillo: supongamos que estamos programando un comercio electrónico y queremos que el modelo Producto tenga un método precio_con_iva que nos devuelva… el precio con IVA. Lo podemos comprobar con un test unitario como el siguiente:

def test_precio_con_iva
  @producto = Producto.new(:precio_base => 100)
  assert_equal 116, @producto.precio_con_iva
end

Los tests consisten básicamente en eso… líneas tipo “assert…” que comprueban cosas. En este caso, que el precio con iva del producto recién creado cuyo precio base es 100, es igual (equal) a 116.

Creado el test, si lo pasamos fallará porque no existe ese método… lo siguiente sería definirlo en el modelo, y si lo hacemos bien (y devuelve 116 en este caso), pasará el test. Ya tendremos entonces la nueva funcionalidad programada, y un test más en nuestro saco que nos alertará en caso de que más adelante esa funcionalidad no funcione como esperamos.

Evidentemente, deberíamos escribir más casos de prueba, con distintos escenarios: productos sin precio, productos con otro tipo de IVA, etc., etc. Recordemos: cuando no se nos ocurren más casos de prueba que fallen, hemos terminado de programar.

Mediante los tests podemos comprobar muchos tipos de condiciones: comparar valores de campos, contar el total de registros de un modelo para ver si hay diferencia (antes / después) de una acción, verificar si es nil… pero incluso podemos testear los controladores para ver el tipo de respuesta (ok, redirect, etc.) e incluso el HTML generado por las vistas para verificar que existe determinada tag, etc., etc.

Otro aspecto interesante es intentar seguir la ley del mínimo esfuerzo, que es una de las cosas que nos enseña nuestra madre Naturaleza (las rocas ruedan cuesta abajo, no cuesta arriba) y una vez se rompen los tests, programar lo mínimo que necesitemos para que todo vuelva a funcionar. Primero, haz que vuelva a funcionar. Después, si quieres ya te podrás entretener en refactorizar tu código para que esté más optimizado. Si se te ocurren nuevos casos, recuerda primero escribir el test y después programar.

Entre las muchas ventajas de Ruby on Rails hay que destacar que viene muy bien preparado para crear los tests: conforme vamos creando modelos y controladores, se van creando los tests correspondientes dentro de la carpeta test… Echa un vistazo ahí dentro y verás algunas cosas:

  • Fixtures. Para tus datos de prueba. Ahí definirás unos pocos datos de prueba para realizar los tests.
  • Unit. Los tests unitarios, que prueban la funcionalidad de los modelos.
  • Functional. Los tests funcionales, para testear controladores.
  • Integration. Para los tests de integración, con lo que puedes simular una sesión de usuario que se pasea por varios controladores y cómo se relacionan unos con otros.
  • Mocks. Nos permite simular el funcionamiento de otras partes de la aplicación, normalmente para conseguir una respuesta más rápida y controlada. Por ejemplo en mi aplicación Pagerankalert.com la uso para comprobar el PageRank que me devuelve Google… en lugar de consultar directamente a Google, en algunos de mis tests establezco el PR esperado. Así puedo testear incluso si no tengo conexión a Internet pq estoy trabajando en modo offline.

Desde nuestra aplicación Rails podemos ejecutar tests puntuales, o toda la batería de tests… y mediante la utilidad autotest que proporciona la gema ZenTest, podemos tener un demonio que estará vigilando constantemente nuestra aplicación en desarrollo. Cada vez que modifiquemos un archivo, ejecutará los tests asociados a él para comprobar si nuestros cambios han hecho que deje de funcionar algún test. En ese caso nos informará del error, y una vez que lo corrijamos volverá a pasar toda la batería de tests completa y seguirá vigilando nuestros pasos.

Por último, mencionar otras herramientas de test externas a Rails pero también muy interesantes y necesarias:

  • Watir / Firewatir. Permite testear desde dentro del navegador, esto es, lanzar una instancia del navegador mediante un script, rellenar campos, pulsar botones, enviar formularios y comprobar los textos de la página. Interesante para poder probar eventos Javascript, AJAX…
  • Mechanize. Simula navegación por páginas desde consola, mediante scripts de Ruby podemos indicar que visite tal página, rellene formularios, los envíe, pulse enlaces, compruebe textos… Es interesante pq una de las limitaciones de Watir es que sólo puede abrir un navegador a la vez; mientras que con mechanize puedes lanzar el mismo script en paralelo y por ejemplo simular 50 usuarios subiendo fotos a una aplicación…

En resumen, para mí la diferencia fundamental entre seguir el desarrollo guiado por tests y no hacerlo es la paz mental que consigues, la confianza en la calidad de tu programación. Cuando ves que se han ejecutado 500, 1000… tests y todos han ido bien, sientes que lo tienes todo bajo control.

Cuando algo falla, si antes de corregirlo lo cubres con un test, ya sabes que para la siguiente ocasión eso no fallará. Y cuando tocas un código en un sitio y eso hace que falle en otro que no recordabas que estaba relacionado, das las gracias al TDD por haberte avisado de algo que no se te había ocurrido que podía fallar.

¡Animate y pásate al TDD!

Cómo programar una araña web con Rails, el vídeo

Gracias a Fernando Padrón, que vino desde México para participar como ponente en la Conferencia Rails 2007 y usó su videocámara para grabar mi taller, ahora está disponible en Google Video mi taller sobre arañas web con Ruby on Rails.

Tutorial: recursos anidados con REST y Rails 2

Aunque el tema de REST y ActiveResource está disponible en Rails 1.2, hasta ahora ha sido un tema que he ido posponiendo en mi lista de cosas por aprender.

La introducción de REST por defecto en la generación de scaffolds de Rails 2 ha cambiado las cosas, y me ha hecho ver que no puedo dejar para más adelante el actualizarme a la filosofía REST.

He estado haciendo algunas pruebas para aclarar las ideas, así que aquí os dejo los códigos para que podáis echarle un vistazo, aprender conmigo y dejarme comentarios… seguro que he pasado por alto algunas cosas.

El objetivo de este tutorial es practicar los recursos anidados. Queremos construir una aplicación típica, un blog, pero en este caso, multiusuario. Tendremos N usuarios, cada uno de los cuales tiene M posts, y cada post tiene Z comentarios. Lo que buscamos es poder definir rutas REST anidadas, de manera que cada recurso (usuario, post, comentario) sólo tenga sentido dentro del contexto de su padre. En concreto, queremos rutas como estas:

# Listado de usuarios
/users

# Un usuario determinado
/users/:user_id

# Posts de un usuario determinado
/users/:user_id/posts

# Un post determinado de un usuario determinado
/users/:user_id/posts/:post_id

# Comentarios de un post y usuario determinados
/users/:user_id/posts/:post_id/comments

# Comentario determinado de un post y usuario determinados
/users/:user_id/posts/:post_id/comments/:comment_id

Como se ve, son rutas muy limpias y autoexplicativas.

Os explico en rasgos generales lo que hay que hacer para conseguir esto. Tras crear nuestra aplicación y la base de datos, generaremos los 3 scaffolds para controlar nuestros 3 recursos (users, posts y comments):

rails blog
cd blog
rake db:create
script/generate scaffold User name:string
script/generate scaffold Post title:string body:text user_id:integer
script/generate scaffold Comment body:text post_id:integer
rake db:migrate

Los scaffolds generarán los modelos y migraciones, controladores y vistas, etc. Además, introducirán rutas REST por defecto para cada modelo, pero las tenemos que cambiar porque no queremos que sean independientes, sino anidadas. Esto es, no queremos gestionar un post fuera del contexto de su usuario, ni un comentario fuera del contexto de su post y del usuario de este post.

Para ello, edita config/routes.rb, y elimina estas 3 líneas:

map.resources :comments
map.resources :posts
map.resources :users

Y pon en su lugar esta ruta anidada:

map.resources :users do |user|
   user.resources :posts do |post|
     post.resources :comments
   end
 end

Ahora, edita los modelos e introduce la relación entre ellos como de costumbre en Rails:

class User < ActiveRecord::Base
   has_many :posts, :dependent => :destroy
 end

class Post < ActiveRecord::Base
   belongs_to :user
   has_many :comments, :dependent => :destroy
 end

class Comment < ActiveRecord::Base
   belongs_to :post
end

Ahora, tendremos que modificar los controladores y vistas, porque fueron generados sin tener en cuenta que estarían anidados, así que hemos de actualizarlos. El controlador de usuarios (user_controller.rb) no necesita cambios porque es independiente de los de posts y comentarios.

Donde sí necesitamos cambios es en el controlador de posts. En primer lugar, necesitamos asegurar que un post sólo tiene sentido dentro del contexto de un usuario determinado, que se habrá pasado en la URL. Así que usaremos un filtro before_filter para que, antes de realizar cualquier acción, coja este usuario:

  before_filter(:get_user)

El método get_user lo definiremos así, dentro del apartado de métodos privados ya que no se usa más que desde dentro de este controlador:

private

def get_user
   @user = User.find(params[:user_id])
end

Ahora, modificaremos el resto de los métodos para que no se busquen los posts en general, sino dentro del contexto del usuario. Por ejemplo, en el método index, que lista los posts, en lugar de mostrarlos todos con:

@posts = Post.find(:all)

Mostraremos sólo los de ese usuario:

@posts = @user.posts.find(:all)

Modificaremos de manera similar el resto de acciones, para buscar el post a mostrar, crear, modificar o eliminar dentro de la colección de posts del usuario. En lugar de:

@post = Post.find(params[:id])

Usaremos:

@post = @user.posts.find(params[:id])

Esto nos añade seguridad ya que por ejemplo para editar un post, hay que indicar no sólo su id sino también su user_id, y si no son válidos, no lo encontrará.

Dentro del controlador también hay que cambiar las redirecciones, ya que por ejemplo tras crear un post, para redirigir al “show” de este post venía así recién creado el scaffold:

redirect_to(@post)

Ahora, como se trata de un recurso anidado dentro del usuario, hay que indicarle la ruta completa así:

redirect_to(user_post_url(@user, @post))

Echa un vistazo a los códigos de ejemplo que adjunto en este tutorial para ver el resto de modificaciones en este controlador.

El controlador de comentarios (comment_controller.rb) necesitará modificaciones similares, pero esta vez además del usuario hemos de recuperar el post, para tener el contexto completo. Es muy similar al ejemplo anterior, primero usamos los siguientes before_filter:

before_filter(:get_user)
before_filter(:get_post)

Y los definimos así:

private

def get_user
   @user = User.find(params[:user_id])
end

def get_post
   @post = User.find(params[:user_id]).posts.find(params[:post_id])
end

Fíjate en que podría haber cogido el post directamente pero para mayor seguridad lo estoy cogiendo a través del usuario.

Las redirecciones también serán similares. Por ejemplo, tras crear un comentario y redirigir al comentario, sería así:

redirect_to(user_post_comment_url(@user, @post, @comment))

Como ves, es similar pero indicando el usuario y post y comentario a que quieres redirigir.

También será necesario que edites todas las vistas de posts y comments, ya que los helpers generados por el scaffold no te valen, y has de indicar el contexto en todos. Por ejemplo, el enlace para crear un nuevo comentario será así ahora:

link_to 'New comment', new_user_post_comment_path(@user, @post)

Eso es todo por mi parte… Os dejo a continuación los códigos de este ejemplo para que podáis ver cómo he construido el resto de rutas con la ayuda de los helpers de Rails 2.0.2.

Códigos del blog rest con rutas anidadas

Seguro que he cometido unas cuantas correcciones, por lo que os agradecería que me lo indicarais con vuestros comentarios.