Con la llegada de Amazon AWS y conceptos como el autoescalado nos surge un reto al que tenemos que enfrentarnos: como guardamos los logs de las máquinas que pueden ser creadas y borradas en cualquier momento?

Soluciones

Para solventar este problema las soluciones son varias. A continuación se enumeran las virtudes y defectos de algunas de ellas y veremos como poner en marcha la solución de rotar los logs a un storage basado en objetos como S3.

  • Servicio de logs en cloud: Hay muchos servicios de este tipo: loggly, sumologic, papertrail, …. Las ventajas de un servicio de estas características es que nos despreocupamos de un componente de nuestra arquitectura y nos ahorramos dolores de cabeza, a parte de las funcionalidades extras para la visualización y análisis de logs. El problema pero cómo podéis ver en los enlaces de tarifas de loggly y papertrail son los precios, sobretodo si tenemos un volumen considerable de logs.
  • FIleSystem Compartido: Esta solución tiene la desventaja que tenemos un punto más de fallo en nuestra arquitectura.
  • Syslog-ng: Con esta solución la ventaja es que tenemos una máquina con los logs centralizados, pero tenemos que tener un servicio mas corriendo y si no tenemos muchas máquinas corriendo no es una solución muy óptima por los costes añadidos.
  • Rotación de logs a storage basado en objetos: Las ventajas de esta solución es que es económica y sencilla, a parte de no tener dependencias de servicios que puedan fallar al tratarse de S3 el cual tiene una disponibilidad de 99,99% y una durabilidad de un 99.999999999%. Cómo desventajas destacaría su tratamiento que como veremos existe una alta fragmentación y que existe la posibilidad aunque sea poco probable  de perder logs .

Rotación de logs a S3


Para rotar los logs de apache a S3 utilizaremos la herramienta logrotate, la idea es rotar los logs cada hora y sincronizarlos con un bucket de S3 con el comando s3cmd. A parte de rotar los logs cada hora, tenemos el añadido de que si nuestra máquina está corriendo en un autoescaling group, ésta puede ser eliminada en cualquier momento. Teniendo en cuenta que primero las máquinas són paradas con un soft shutdown, podemos crear un servicio que cuando la máquina sea parada fuerce a rotar los logs para no perderlos. En caso de que la sincronización tardara demasiado Amazon haría un hard shutdown y podríamos perder los logs de las últimas dos horas.

Instalación s3cmd

En primer lugar vamos a instalar s3cmd.
El primer paso es añadir los repositorios de epel para instalar pip:

# rpm -ivh http://mirror-fpt-telecom.fpt.net/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm

Desactivamos el repositorio epel, para que sólo sea utilizado cuando lo indiquemos explícitamente en yum.

# sed -i "s/enabled=1/enabled=0/g" /etc/yum.repos.d/epel.repo

Instalamos pip  y python-magic indicando que nos active temporalmente el repositorio epel.

# yum -y install python-pip python-magic --enablerepo=epel

Instalamos s3cmd:

# pip install s3cmd

Para la configuración del s3cmd creamos el fichero de configuración correspondiente en /root/.s3cfg, sustituyendo <acces_key> y <secret_key> por las credenciales correspondientes a nuestro usuario:

[default]
access_key = <acces_key>
access_token =
add_encoding_exts =
add_headers =
bucket_location = EU
cache_file =
cloudfront_host = cloudfront.amazonaws.com
default_mime_type = binary/octet-stream
delay_updates = False
delete_after = False
delete_after_fetch = False
delete_removed = False
dry_run = False
enable_multipart = True
encoding = UTF-8
encrypt = False
follow_symlinks = False
force = False
get_continue = False
gpg_command = /usr/bin/gpg
gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
gpg_passphrase = testtt
guess_mime_type = True
host_base = s3.amazonaws.com
host_bucket = %(bucket)s.s3.amazonaws.com
human_readable_sizes = False
invalidate_default_index_on_cf = False
invalidate_default_index_root_on_cf = True
invalidate_on_cf = False
list_md5 = False
log_target_prefix =
mime_type =
multipart_chunk_size_mb = 15
preserve_attrs = True
progress_meter = True
proxy_host =
proxy_port = 0
recursive = False
recv_chunk = 4096
reduced_redundancy = False
secret_key = <secret_key>
send_chunk = 4096
simpledb_host = sdb.amazonaws.com
skip_existing = False
socket_timeout = 300
urlencoding_mode = normal
use_https = True
verbosity = WARNING
website_endpoint = http://%(bucket)s.s3-website-%(location)s.amazonaws.com/
website_error =
website_index = index.html

Para testear que funciona bien el s3cmd, vamos a sincronizar un archivo, supondremos que ya tenemos creado un bucket de nombre logs:

# mkdir test
# cd test
# echo "This is a test" > test
# s3cmd sync . s3://logs/

En este punto deberíamos ver cómo se ha sincronizado nuestro fichero con el bucket. Para verlo con el s3cmd:

# s3cmd ls s3://logs/

Configuración logrotate

Vamos a configurar el logrotate para que al rotar los logs del apache sincronice estos con nuestro bucket de S3. Sustituimos el fichero /etc/logrotate.d/httpd con el siguiente contenido:

/var/log/httpd/*log {
    daily
    rotate 4
    size 5k
    dateext
    dateformat +%Y-%m-%d.%s
    compress
    missingok
    sharedscripts
    postrotate
        /sbin/service httpd reload > /dev/null 2>/dev/null || true
        BUCKET=logs
        INSTANCE_ID=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/instance-id`
        /usr/bin/s3cmd -c /root/.s3cfg -m text/plain sync /var/log/httpd/*gz* s3://${BUCKET}/${INSTANCE_ID}/ > /dev/null
    endscript
}

Con esta configuración, los parámetros a tener en cuenta son:

  • daily: Nos rotará los logs diariamente, aunque en nuestro caso los vamos a rotar cada hora si ocupan más de 5Kbytes. Más adelante veremos cómo.
  • rotate 4: Nos borrará los logs a partir de 4 rotaciones.
  • size 5k: Éste parámetro hace que los logs siempre sean rotados, aunque tengamos la política daily, si éstos ocupan más de 5KB.
  • dateext: Hace que los logs sean guardados poniendo la fecha en el nombre, el formato es el de dateformat.
  • dateformat +%Y-%m-%d.%s: Formato de la fecha: Año, mes, día del mes y segundos des de 1970.
  • compress: Al hacer la rotación los logs antiguos son comprimidos.
  • sharedscripts: Corre la sección de postrotate una sola vez para todos los ficheros.
  • postrotate: Sección a correr una vez se han rotado los logs, en nuestro caso se hace un reload del apache, se recoge el nombre de instancia de nuestra máquina en EC2 y se sincronizan los logs que están comprimidos con el bucket logs dentro de un directorio con nuestro nombre de instancia.

Tenemos que tener en cuenta, aunque la página del man diga lo contrario el postrotate es ejecutado antes de la compresión, por lo que no nos sincronizará los logs actuales hasta la siguiente rotación de logs, que como veremos será al cabo de una hora.
Debido a que la rotacion de logs ocurre una sola vez al dia, y en nuestro caso queremos que se haga cada hora, vamos a crear el fichero /etc/cron.hourly/1logrotatehttpd con el siguiente contenido:

#!/bin/sh
/usr/sbin/logrotate  /etc/logrotate.d/httpd >/dev/null 2>&1
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
    /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0

Le damos permisos de ejecución:

# chmod +x /etc/cron.hourly/1logrotatehttpd

Rotando logs al parar la máquina

Con lo que hemos visto hasta ahora, ya tenemos preparada nuestra máquina para que sincronice los logs cada hora, con la particularidad que siempre nos sincroniza los de la última hora y no los actuales. Si la máquina es borrada de nuestro autoscaling group perderemos nuestros logs, por lo que necesitamos crear un script en el /etc/init.d que sincronice los logs antes de apagar la máquina. Creamos el script /etc/init.d/s3sync con el siguiente contenido:

#!/bin/bash
#
# s3sync     Sync httpd logs to S3
#
# chkconfig: -  86 14
# description:	s3sync logs of apache
# processname: s3sync
#
#
### BEGIN INIT INFO
# Provides: s3sync
# Required-Start: $local_fs $remote_fs $network $named
# Required-Stop: $local_fs $remote_fs $network
# Short-Description: s3sync logs of apache
# Description: s3sync logs of apache
### END INIT INFO
# Source function library.
. /etc/init.d/functions
start() {
    touch /var/lock/subsys/s3sync
    return 0
}
stop() {
    /usr/sbin/logrotate -f /etc/logrotate.d/httpd
    BUCKET=logs
    INSTANCE_ID=`/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/instance-id`
    /usr/bin/s3cmd -c /root/.s3cfg sync /var/log/httpd/*gz* s3://${BUCKET}/${INSTANCE_ID}/ > /dev/null
    return 0
}
restart() {
    stop
    start
}
case "$1" in
    start)
	start
	RETVAL=$?
	;;
    stop)
	stop
	RETVAL=$?
	;;
    restart)
	restart
	RETVAL=$?
	;;
    *)
	echo $"Usage: s3sync {start|stop|restart|condrestart|status|panic|save}"
	RETVAL=2
	;;
esac
exit $RETVAL

Hacemos ejecutable el script, lo configuramos para que arranque al arrancar la máquina y le hacemos un start:

# chmod +x /etc/init.d/s3sync
# chkconfig s3sync on
# service s3sync start

Como hemos visto mediante el logrotate no se rotan los últimos logs y en caso de que ocupen menos de 5k tampoco. Así pues necesitaremos hacer una sincronización después de rotados los logs. A parte para que los logs de menos de 5k sean también sincronizados necesitaremos ejecutar el logrotate con el flag -f (force). En CentOs para indicar que un servicio está corriendo y por lo tanto para que al parar la máquina ejecute el stop del servicio en cuestión se debe crear un fichero con el mismo nombre del fichero en /var/lock/subsys
Con esto ya tenemos nuestros logs de apache rotando a S3 y con el mínimo peligro de que se pierdan, como ya os podéis imaginar éste manual se podría aplicar a muchos otros servicios que tengan logs.
Un saludo cloudadmins !!
Oricloud