Monit como watchdog configurado con Ansible

3
4833

En este tutorial veremos Monit como herramienta para monitorizar el estado de los servicios, así como tomar las acciones pertinentes en caso de que no esté como esperábamos.

Índice de contenidos

1. Introducción

En este tutorial nos introduciremos en el apasionante mundo de la monitorización de aplicaciones, para lo que utilizaremos Monit como watchdog.

Un watchdog en informática es una herramienta que registra uno (o varios) servicios, que deben estar periódicamente enviándole información para asegurar que están disponibles. Entendemos por servicio desde nuestro servidor web, desde Apache o Nginx hasta nuestras propias aplicaciones web.

He de comentar que si queremos un sistema de monitorización más avanzado de nuestras aplicaciones, deberíamos de ver la serie de tutoriales de mi compañero David donde habla de la monitorización de logs con Elasticsearch, Logstash y Kibana. Este tipo de monitorización es complementaria y específica de nuestra aplicación, mientras que Monit actúa como watchdog de gran cantidad de servicios (de mensajes, de ficheros, de bases de datos, FTP…)

Como suele ser costumbre en mis tutoriales, probaremos esta herramienta de monitorización en una máquina virtual creada con la combinación de Vagrant y Virtual Box y aprovisionada con Ansible.

El objetivo de este tutorial es enseñarnos como funciona Monit, monitorizando un servidor web, concretamente un Nginx. Para ello, crearemos una máquina virtual donde instalaremos Monit y Nginx, y comprobaremos como cuando paramos el servicio, este vuelve a levantarse sin nuestra intervención gracias a Monit.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitán 10.11.16
  • Vagrant 1.8.1
  • Virtual Box 5.0.14
  • Ansible 2.1.0.0
  • Monit 5.6
  • Nginx 1.4.1

3. Instalación de Vagrant, Virtual Box y Ansible

Como mencionábamos en la introducción, antes de ponernos manos a la obra, necesitamos tener Vagrant, Virtual Box y Ansible instalado. En este tutorial explico cómo se instalan estas herramientas. A partir de aquí suponemos que ya tienes esas herramientas instaladas y funcionando.

4. Creación de la máquina virtual

La creación de la máquina virtual es muy sencilla. Basta con irnos al directorio dónde queramos crear la máquina virtual y configurar el Vagrantfile que usará Vagrant para crearnos nuestra máquina virtual. Nuestro Vagrantfile tendrá la siguiente estructura:

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
    config.vm.box = "ubuntu/trusty64"

    config.vm.network :forwarded_port, host:  2222, guest: 22, id: "ssh"
    config.vm.network :forwarded_port, host:  8888, guest: 80, id: "nginx"

    config.vm.network :private_network, ip: "192.168.33.25"

    config.vm.define "monit" do |monit|
        monit.vm.hostname = "monit"

        monit.vm.provision "ansible" do |ansible|
          ansible.verbose = 'vvv'
          ansible.playbook = "ansible/watchdog_nginx.yml"
        end
    end
end

En esta configuración, como siempre elegimos la «box» de Vagrant a partir de la que se va a crear la máquina virtual. Luego, se configuran los puertos para realizar la conexión con la máquina, de forma que cuando en nuestra máquina anfitrión pongamos la url localhost:8888 estaremos accediendo al puerto 80 de la máquina virtual (192.168.33.25:80).

Por último, tenemos la configuración de Ansible en la que decimos que después de crear la máquina ejecute el playbook watchdog_nginx.yml, y que la salida sea muy verbosa. Esto quiere decir que nada más crear la máquina virtual va a ejecutar los roles que, como decíamos en la introducción, se encargarán de instalarnos Nginx y Monit.

5. Creación de los roles de Ansible

A continuación nos queda crear los roles de Ansible que vamos a ejecutar cuando se cree la máquina. Para ello dentro del directorio dónde tenemos nuestro Vagrantfile, y siendo fieles a la propiedad que hemos establecido en el mismo dónde indicábamos el playbook (ansible.playbook = «ansible/watchdog_nginx.yml»), creamos el directorio «ansible» dónde guardaremos todos nuestros archivos de Ansible.

5.1. Playbook

El playbook que vamos a ejecutar tiene el siguiente formato:

ansible/watchdog_nginx.yml
---
# file: watchdog_nginx.yml

- hosts: monit
  become: yes
  gather_facts: no
  roles:
      - nginx
      - monit

Aquí le decimos que en el host Monit, nos ejecute como usuario privilegiado los roles de Nginx y de Monit. El nombre Monit se lo pusimos como hostname en la configuración de Vagrant.

Si recordáis los dos elementos imprescindibles para aprovisionar con Ansible son: Qué quiero ejecutar (esa información viene dada en el playbook) y dónde quiero ejecutarlo (información que viene dada en el inventory). Pero si nosotros no hemos definido un inventory, ¿cómo sabe la máquina que dónde tiene que ejecutar el playbook es la máquina que acabamos de crear? Esto se sabe debido a que Vagrant crea un fichero inventory auto-generado que está en la ruta .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory que es relativa al directorio dónde tenemos creado nuestro Vagrantfile.

5.2. Rol de Nginx

Este rol solo instala Nginx en la máquina y le añade una directiva para que el servidor web actúe como proxy inverso de la aplicación objetivo, en este caso del propio Monit.

Las tareas que se ejecutan en este rol son:

ansible/roles/nginx/tasks/main.yml
# file: /roles/nginx/tasks/main.yml

- name: install nginx
  apt:
    name:  nginx
    state:  present
    update_cache: yes

- name: create nginx proxy configuration
  copy:
    src: "virtualhost-conf"
    dest: "/etc/nginx/sites-available/virtualhost.conf"
  notify: restart nginx

- name: create symlink to custom nginx configuration
  file:
   src: /etc/nginx/sites-available/virtualhost.conf
   dest: /etc/nginx/sites-enabled/virtualhost.conf
   state: link
  notify: restart nginx

- name: remove default configuration
  file:
    path: /etc/nginx/{{ item }}
    state: absent
  with_items:
      - sites-available/default
      - sites-enabled/default
  notify: restart nginx

Como vemos en el rol, usamos el módulo copy por lo que en su parámetro src esperará encontrar el fichero virtualhost-conf en el directorio ansible/roles/nginx/files. También tenemos una cláusula notify, que espera que haya un fichero main.yml en el directorio ansible/roles/nginx/handlers que contenga una tarea que tenga el nombre específico «restart nginx», que se encarga de hacer reiniciar el servicio. Vamos a crearlos 😉

ansible/roles/nginx/files/virtualhost-conf
server {
    listen 80 default_server;
    server_name test;

    location /monit {
        rewrite ^/monit/(.*) /$1 break;
        proxy_ignore_client_abort on;
        proxy_pass   http://127.0.0.1:2812;
        proxy_redirect  http://127.0.0.1:2812 /monit;
    }
}
ansible/roles/nginx/handlers/main.yml
# file: /roles/nginx/handlers/main.yml

- name: restart nginx
  service:
      name: nginx
      state: restarted

Con esto ya tendríamos el rol de Nginx creado. ¡Vamos con el de Monit!

5.3. Rol de Monit

El rol de Monit se encarga de instalar Monit y de copiar un fichero de configuración que usará para saber que servicios tiene que monitorizar, definirá las reglas de como hacerlo, y avisará en función del estado del servicio, en este caso por email usando el servidor de correo de Google.

Las tareas que tiene este rol son:

ansible/roles/monit/tasks/main.yml
---
# file: /roles/monit/tasks/main.yml

- name: install monit
  apt:
      name: monit
      state: installed
      update_cache: yes

# Is located in /etc/monit/monitrc because monit expected that the properties files are there when execute as a service
- name: copy monit configuration
  template:
      src: "monitrc"
      dest: "/etc/monit/monitrc"
      mode: "0700"
      force: yes
  notify: restart monit

- name: ensure monit is monit enabled
  service:
    name: monit
    state: started

Vemos como este rol también tiene la cláusula notify, por lo que esperamos tener dentro del directorio ansible/roles/monit/handlers un fichero llamado main.yml con una tarea de nombre «restart monit»

A diferencia del rol de Nginx que usaba el módulo copy vemos que en la segunda tarea usamos el módulo template. La diferencia principal es que espera encontrar el fichero monitrc dentro del directorio ansibleroles/monit/templates y que este fichero actúa como plantilla donde puedes añadir variables.

¡Vamos a crear estos ficheros!

ansible/roles/monit/handlers/main.yml
---
# file: roles/monit/handlers/main.yml

- name: restart monit
  service:
    name: monit
    state: restarted

Y ahora la plantilla de la configuración de Monit

ansible/roles/monit/templates/monitrc
## Start Monit in the background (run as a daemon):
set daemon {{ monit_check_services_intervals }}   # check services at 1-minute intervals
    with start delay {{ monit_check_services_intervals }}   # initial delay needed for applications that restart when reboot the instance

## Set syslog logging with the 'daemon' facility. If the FACILITY option is
## omitted, Monit will use 'user' facility by default. If you want to log to
## a standalone log file instead, specify the full path to the log file
#
# set logfile syslog facility log_daemon
set logfile /var/log/monit.log

## Set the location of the Monit id file which stores the unique id for the
## Monit instance. The id is generated and stored on first Monit start. By
## default the file is placed in $HOME/.monit.id.
#
# set idfile /var/.monit.id
set idfile /var/lib/monit/id

## Set the location of the Monit state file which saves monitoring states
## on each cycle. By default the file is placed in $HOME/.Monit.state. If
## the state file is stored on a persistent filesystem, Monit will recover
## the monitoring state across reboots. If it is on temporary filesystem, the
## state will be lost on reboot which may be convenient in some situations.
#
set statefile /var/lib/monit/state

# Set mail server witch sends mail
set mailserver smtp.gmail.com port 587
    username "{{ monit_sender_account_email }}" password "{{ monit_sender_account_password }}"
    using tlsv1
    with timeout 30 seconds

# Set recipients who receive the alerts
{% for recipient in monit_alert_recipients %}
set alert {{ recipient }}
{% endfor %}

# Set mail alert format
set mail-format {
     from: monit@server.com
     subject: [ $SERVICE ] $EVENT - $DATE
     message: This is an $ACTION: $DESCRIPTION [$SERVICE] }

# Access to the Web Service
set httpd port 2812 and
    use address localhost  # only accept connection from localhost
    allow localhost        # allow localhost to connect to the server and
    allow admin:monit      # require user 'admin' with password 'monit'

{% for process in monit_process_to_check %}
check process {{ process.name }} with pidfile {{ process.pid }}
   start program = "{{ process.start_command }}" with timeout {{ process.timeout }} seconds
   stop program = "{{ process.stop_command }}" with timeout {{ process.timeout }} seconds
   {% for term in process.terms %}
   {{ term }}
   {% endfor %}

{% endfor %}

Como vemos, este fichero viene comentado (los comentarios son el texto que está después de #) explicando para que sirve cada directiva parte del fichero. De todas formas vamos a hacer un repaso rápido de por qué hemos usado esas directivas.

  • set daemon [seconds] with start delay [seconds]: Indica que Monit sea ejecutado en background, además de darle un determinado timeout antes de empezar, y también se establece el tiempo en segundos en el que el watchdog comprueba la disponibilidad de los servicios que monitoriza.
  • set logfile [logfile]: indica dónde van a estar los logs de Monit.
  • set idfile [idfile]: indica dónde se va a encontrar un id único para la instancia del Monit.
  • set statefile [statefile]: establece el fichero dónde se guardan los estados que guarda Monit de los servicios monitorizados en cada ciclo.
  • set mailserver [mail-server] port [port-number] username [username] password [pwd] using tlsv1 with timeout 30 seconds: Indica el servicio de mailing que se va a utilizar. En nuestro caso, usamos el servicio de mensajería de Google, para lo cual damos las credenciales de una cuenta de gmail que va a ser usada con para ENVIAR el mail.

    Si te preocupa el hecho de poner información sensible, como la contraseña de un correo en un fichero de Ansible, puedes mirar este tutorial sobre como cifrar información sensible con ansible-vault.

  • set alert [mail-to-alert]: Esta directiva indica los correos electrónicos que van a RECIBIR la alerta. Si no especificas nada más a la derecha de la directiva, se enviará un correo electrónico por evento diferente, aunque también es posible enviar correos solo para eventos específicos. SI quieres saber más sobre los eventos de Monit, consulta el siguiente enlace. Para este ejemplo no queremos ningún filtro por evento así que lo dejamos como está.
  • set mail-format [format]: Aquí especificamos el contenido del mensaje del mail
  • set httpd port [port-number] and use address [monit-host] allow [monit-host] allow [monit-access-user]:[monit-access-password]: Con esta propiedad permitimos el acceso al servicio web desde el puerto 2812 (puerto por defecto de Monit). El motivo por el cual hemos montado un Nginx que haga de proxy inverso es porque Monit viene con una aplicación web para ver la monitorización. Es normal añadir una capa de seguridad entre nuestra aplicación e Internet, y no exponer directamente los puertos de nuestra aplicación hacia afuera, dejando solo disponibles los puertos 80 y 443.
  • check process [unique name] [PIDFILE [path] | MATCHING [regex]: Esta directiva nos permite monitorizar un servicio en base a unas reglas específicas y realizar una acción respecto al servicio monitorizado en caso de que esté en un estado que no sea el esperado (reiniciar el servicio, ejecutar un script…).

    En nuestro caso comprobamos que el puerto 80 está disponible, y por lo tanto Nginx levantado. En caso de que nos de una respuesta negativa en dos ciclos de Monit (cada ciclo viene definido por el intervalo que pusimos en la primera directiva) se intenta arrancar el servicio.

    Como vemos en la plantilla, le indicamos como se para y como se arranca el servicio, así como el pid (el id del proceso) que estamos monitorizando. Por último, iteramos los términos que implican las acciones que va a realizar dada una precondición.

Como vemos, las plantillas de Ansible son un mecanismo muy potente que usa por debajo el sistema de plantillas de jinja2 para permitirnos iterar y poder ajustarnos a las necesidades específicas de cada servicio.

¿Dónde están definidas las variables de la plantilla? Como norma general, y dado que las variables de Ansible pueden estar definidas en varios sitios, yo os recomiendo que todas las variables que usa un rol estén definidas en el directorio defaults dentro del mismo rol. Por lo que nuestras variables estarían en la ruta: ansible/roles/monit/defaults/main.yml

ansible/roles/monit/defaults/main.yml
---
# file: roles/monit/defaults/main.yml

monit_check_services_intervals: "5"

monit_process_to_check:
    - name: nginx
      pid: /var/run/nginx.pid
      start_command: /etc/init.d/nginx start
      stop_command: /etc/init.d/nginx stop
      timeout: 10
      terms:
          - if failed port 80 for 2 cycles then restart

monit_sender_account_email: "email@gmail.com"
monit_sender_account_password: "emailpass"

monit_alert_recipients:
    - email@gmail.com

Como comprobaréis el valor de las propiedades monit_sender_account_email, monit_sender_account_password y monit_alert_recipients debe ser definido por vosotros en cada caso o no funcionará. En mi caso usaré dos correos electrónicos para que uno envíe los mensajes de Monit y otro los reciba.

6. Comprobación

Ahora que ya tenemos nuestra configuración de la máquina virtual levantada, y el aprovisionamiento que va a realizar Ansible preparado, en el directorio dónde tenemos el Vagrantfile ejecutamos el comando:

Terminal
vagrant up

Y esperamos a que suceda la magia 😀

Una vez termine el playbook, deberíamos de ser capaces de ver en el navegador con la ruta localhost:8888 la página por defecto de instalación de Nginx.

Default image from Nginx

Si ahora vamos a la ruta localhost:8888/monit tras introducir las credenciales de acceso (hemos puesto de usuario admin y de contraseña monit) accederemos a la aplicación web que nos ofrece Monit y veremos como el estado del servicio de Nginx es satisfactorio.

Monit Interface

Ahora vamos a hacer una pequeña prueba en la que paramos manualmente el servicio, y esperamos que Monit intente levantar el servicio de forma automática sin nuestra intervención. Para ello vamos a acceder a la máquina virtual por ssh y vamos a parar el servicio de Nginx, ejecutando los siguientes comandos desde el directorio en el que está nuestro Vagrantfile:

Terminal
vagrant ssh
sudo service nginx stop

Ten en cuenta que como hemos parado el servicio del Nginx, la aplicación web que veíamos tiene que dejar de ser accesible. Es decir, que si recargas la página en el instante después de haber parado el servicio ya no verás la interfaz web. Entonces, ¿Cómo sabemos si el servicio Nginx se ha levantado o sigue caído sin hacer F5 cada poco tiempo? ¿Recuerdas las notificaciones por mail que tenemos que recibir cuando se produzca un evento? Pues vamos a ver como en el correo nos llega un mensaje de que el servicio está caído, y como al cabo de un corto periodo el servicio vuelve a estar operativo.

Mail Events

Comprobamos que Nginx vuelve a estar operativo volviendo a localhost:8888

Default image from Nfinx after restart

Y como justo después de parar el servicio y esperar a que Monit lo restaure no hemos realizado ningún comando:

Auto restart from Nginx

7. Conclusión

Tener la seguridad de que tus aplicaciones son capaces de intentar recuperar su estado ideal de forma automática no tiene precio cuando estás en producción y cada instante que tu aplicación no está en el aire son potenciales clientes que estás perdiendo.

Monit te ofrece una gran potencia con una configuración muy sencilla y es rápido y fácil de instalar, por lo que si estás empezando a monitorizar tus aplicaciones, sin duda es una buena forma de comenzar. 😀

El código completo (también el rol de Nginx y de Monit que no se han mostrado) están en mi repositorio de GitHub, de forma que solo hay que bajárselo y seguir las instrucciones desde el punto 6.

8. Referencias

3 COMENTARIOS

  1. […] hemos hablado en el tutorial sobre Monit como watchdog configurado con Ansible de la monitorización de las aplicaciones con un watchdog como Monit, que era capaz de informarnos […]

  2. hola, genial artículo!
    Pero ahora me pregunto como podríamos monitorizar también las configuraciones, por ejemplo de un apache o un mysql, con Ansible al estilo de SaltStack. Si alguien cambia una configuración que se detecte este evento y se restaure de un repo GIT su contenido para asegurar el correcto servicio. Como haría un minion.
    Se puede hacer?
    Gracias

    • Hola Joan,

      En este caso, dado que tienes un playbook de ansible que aplica la configuración no necesitas ser reactivo al respecto (tener un agente monitorizando la configuración y aplicar los cambios cuando cambie) sino que como Ansible es idempotente, puedes ejecutar de forma periódica tus playbooks para garantizar que la configuración permanece como está definida en tus playbooks de Ansible.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad