Category Archives: Apache

Sencillo parser para Apache mod_status en Python

Cuánto hace que no actualizo este blog!! Hoy estuve jugando un poco con Python y me puse a hacer un pequeño script que supongo que a otros les podrá servir como base para hacer algo más interesante. Precisamente, tengo planes para hacer algo más interesante, pero como recién están en veremos, les voy pasando esta pequeña base a ver si alguien me gana de mano :-)

Como muchos sabrán, Apache (y otros web servers) tiene un módulo llamado mod_status que nos permite ver en tiempo real el estado del servidor (los requests que se están procesando, el uso del CPU, el estado de las conexiones, etc.). Esta información puede ser muy útil para hacer diagnósticos cuando está habiendo algún tipo de problema, para hacer monitoreo, etc. El módulo nos muestra una página web con la información y tiene dos interfaces: una está pensada para ser vista por seres humanos y otra para ser consultada por scripts (para ver esta segunda sólo hace falta pasarle el parámetro “?auto” en la URL). El problema es que esta segunda interfaz no nos muestra a qué dominios (virtual hosts) están dirigidos los requests que se están procesando (o si hay forma de que lo muestre yo no la encontré). Entonces me puse a hacer un sencillo script en Python para parsear la página “para seres humanos”.

Por suerte Python nos ofrece muchas herramientas muy útiles para este tipo de cosas, así que no me tuve que esforzar demasiado. En este caso utilicé urllib2 para acceder a la página de mod_status y BeautifulSoup para parsear el HTML. Por si no lo conocen, BeautifulSoup es un muy poderoso parser para XML/HTML hecho en Python. No viene con el código fuente sino que hay que instalarlo, pero hacerlo es muy fácil:

En Debian/Ubuntu:

apt-get install python-beautifulsoup

En CentOS/RedHat:

yum install python-BeautifulSoup

Con easy_install:

easy_install BeautifulSoup

El código tiene una clase Status que es la que hace la magia y una función main() que utiliza la clase para parsear una URL e imprimir algunos datos a modo de ejemplo.

import sys
import urllib2
from operator import itemgetter
from BeautifulSoup import BeautifulSoup

class Status (object):
    _url = None

    def __init__ (self, url):
        self._url = url

    def fetch (self):
        return urllib2.urlopen(self._url).read()

    def parse (self):
        html = self.fetch()
        soup = BeautifulSoup(html)
        status = {}
        status[‘server_info’] = [i.string.strip() for i in soup.findAll(‘dt’)]
        status[‘requests’] = []
        requests = soup.find(‘table’).findAll(‘tr’)
        keys = [i.string for i in requests.pop(0)]
        for tr in requests:
            req = {}
            for n, td in enumerate(tr):
                req[keys[n]] = td.string
            status[‘requests’].append(req)
        return status

def main (argv):
    if len(argv) < 2:
        print "Usage %s "%argv[0]
        return 1

    status = Status(argv[1])
    data = status.parse()
    print "SERVER INFORMATION"
    print "=================="
    for v in data[‘server_info’]:
        print v

    print "REQUESTS BY VHOST"
    print "================="
    entries = [i[‘VHost’] for i in data[‘requests’]]
    requests = sorted([(entries.count(i), i) for i in list(set(entries))], reverse=True)
    print "\n".join(["%d: %s"%(a,b) for a,b in requests])

if __name__ == "__main__":
    sys.exit(main(sys.argv))

La forma de uso es:

python status.py "http://localhost/server-status"

Y en este caso la salida del script es algo así:

SERVER INFORMATION
==================
Server Version: Apache/2.2.19 (Unix) mod_ssl/2.2.19 OpenSSL/0.9.8e-fips-rhel5 DAV/2
Server Built: May 26 2011 15:14:47
Current Time: Sunday, 31-Jul-2011 00:53:59 ART
Restart Time: Saturday, 30-Jul-2011 12:07:12 ART
Parent Server Generation: 0
Server uptime:  12 hours 46 minutes 47 seconds
Total accesses: 407813 - Total Traffic: 3.7 GB
CPU Usage: u2.05 s3.23 cu52 cs0 - .125% CPU load
8.86 requests/sec - 85.0 kB/second - 9.6 kB/request
10 requests currently being processed, 8 idle workers
REQUESTS BY VHOST
=================
9: www.tail-f.com.ar
5: www.otro-dominio.com.ar
4: www.not-a-domain.com.ar
3: www.algunlado.com.ar
2: subdominio.dominio.com.ar
1: www.pepe.com.ar
1: www.no-votes-a-macri.com.ar
1: www.asd.com.ar
1: localhost

Obviamente esto no es una aplicación funcional, sino un ejemplo que espero que les sirva para hacer algo más copado. Yo seguiré jugando y si hago algo un poco más interesante, ya se los mostraré.

Python: Ver dominios de Apache que más transferencia consumen

Hace un rato publiqué un post con un script para ver qué dominios de Apache consumen mayor transferencia. El script en BASH toma unos 7 segundos en procesar 750 archivos de uno de mis servidores. Ahora hice una versión en Python, para la que me dieron algunos consejos los chicos de PyAr, y toma aproximadamente 4 segundos en procesar los mismos archivos. Aquí les dejo el código:

#!/usr/bin/env python
""" Access Log Parser

Parses all the files in a directory
treating them as access_log files
and outputs the list of files sorted
by transfered megabytes. Useful for
identifying heavy users.

Usage:
./access_log_parser.py <base_dir>
base_dir = directory where the access_log files are
"""
import sys
import os
import time
from operator import itemgetter

def main (args):
    """Main

    Main function of the script.
    This is where the magic happens.
    It takes the script arguments
    and returns an exit code.
    """

    # Parameter checking
    if len(args) < 2:
        print "Usage: %s <base_dir>" % args[0]
        return 1

    if os.path.isdir(args[1]):
        base_dir = args[1]
    else:
         print "%s is not a directory" % args[1]
         return 2

    # Init vars
    t1 = time.time()
    mbyte = 1048576.0
    domains = []

    # Start processing files
    for item in os.listdir(args[1]):
        path = os.path.join(base_dir, item)
        if os.path.isfile(path) and \
           os.path.getsize(path) > 0:

            bytes = 0
            init_date = None
            data = None

            # Process file lines
            for line in open(path, ‘r’):
                data = line.split(" ")
                if init_date is None:
                    init_date = data[3][1:]
                try:
                    bytes += long(data[9])
                except ValueError:
                    pass
            domains.append({‘domain’: item, \
                            ‘mbytes’: bytes / mbyte, \
                            ‘init_date’: init_date, \
                            ‘end_date’: data[3][1:] })

    # Print out sorted information
    for domain in sorted(domains, key=itemgetter(‘mbytes’)):
        print "%.2f MB | From: %s | To: %s | %s" % \
              (domain[‘mbytes’], \
               domain[‘init_date’], \
               domain[‘end_date’], \
               domain[‘domain’])

    print "Generated in %d seconds" % (time.time() – t1)
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

Para ejecutarlo:

./access_log_parser.py /tmp/domain_logs
...
544.30 MB | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:18:13:25 | dominio1.com.ar.log
602.34 MB | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:23:39:45 | dominio2.com.ar.log
944.03 MB | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:23:39:57 | dominio3.com.ar.log
Generated in 3 seconds

Ver dominios de Apache que más transferencia consumen

Frecuentemente me encuentro ante la necesidad de saber qué dominios de los que tengo alojados en mis servidores consumen más tráfico HTTP. Como éste es el tipo de tráfico más importante y que más incide en el desempeño general de mis servidores, saber qué dominios transfieren más datos por HTTP me orienta en la búsqueda de heavy users. Para ello hice un script hace algún tiempo, y ahora lo estuve revisando, retocando y simplificando, y decidí compartirlo.

En el entorno de Directadmin, los logs de Apache se encuentran en /var/log/httpd. Allí hay una carpeta “domains” que guarda los logs de cada dominio. Por cada dominio hay tres logs: dominio.com.log, dominio.com.error.log y dominio.com.bytes. El primero es el access_log, el segundo el error_log y el tercero solamente guarda la cantidad de bytes de cada request. La opción más sencilla es trabajar con este último tipo de logs, pero el problema que tienen es que no indican la fecha. Si bien se supone que los logs de Directadmin rotan una vez por día, alguna vez me ha pasado que por razones que nunca pude descubrir, algunos logs no rotaban y generaban ciertas confusiones. Por lo tanto, el script que dejo usa los access_logs. Además esta opción es reutilizable en otras implementaciones de Apache que no tengan archivos de bytes.

El código:

# Access Log Parser
#
# Parses all the files in a directory
# treating them as access_log files
# and outputs the list of files sorted
# by transfered megabytes. Useful for
# identifying heavy users.
#
# Usage:
# ./access_log_parser.sh <base_dir>
# base_dir = directory where the access_log files are

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        from=`head -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        to=`tail -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        bytes=`cat $file | awk ‘{a+=$10}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) | From: $from | To: $to | ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`

Un detalle importante: el script levanta todos los archivos de un directorio que se especifica por parámetro. Esto es así porque yo lo invoco desde otro script:

#!/bin/bash
LOG_DIR=/var/log/httpd/domains/
TMP=/tmp/domain_logs
if [ ! -d $TMP ]; then
    mkdir -p $TMP
fi

rm -f $TMP/*

cd $LOG_DIR
for i in `ls *.log |grep -v error`; do
    ln $i $TMP/$i
done
/root/access_log_parser.sh $TMP

Este último script lo que hace es crear un directorio temporal (si no existe). Vaciar su contenido. Y luego generar hardlinks a todos los access_logs de /var/log/httpd/domains. De esta forma, no me tengo que preocupar por si los logs son modificados mientras yo estoy ejecutando el script. Por último, ejecuta mi primer script (access_log_parser.sh) y le pasa como parámetro el directorio temporal donde están los access_logs.

Por último, la salida del script sería algo así:

...
186.995 MB (196078184 bytes) | From: 10/Mar/2010:00:27:47 | To: 10/Mar/2010:16:51:09 | dominio1.com.ar.log
187.096 MB (196184596 bytes) | From: 10/Mar/2010:03:24:55 | To: 10/Mar/2010:16:51:24 | dominio2.com.ar.log
245.692 MB (257626221 bytes) | From: 10/Mar/2010:02:53:50 | To: 10/Mar/2010:16:39:54 | dominio3.com.log
273.46 MB (286743390 bytes) | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:14:59:29 | dominio4.com.ar.log
306.344 MB (321224473 bytes) | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:16:51:20 | dominio5.com.ar.log
444.097 MB (465669066 bytes) | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:16:51:20 | dominio6.com.ar.log
Generated in 6 seconds

Les dejo, de yapa, la opción del script utilizando los logs de bytes. Como dije, esta opción es un poco más sencilla y veloz, pero no nos dice las fechas de los logs.

#!/bin/bash

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*.bytes;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        bytes=`cat $file | awk ‘{a+=$1}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`
echo "Generated in $(($T-$T1)) seconds"

Y la salida sería así:

# ./parser.sh /var/log/httpd/domains
...
181.716 MB (190543470 bytes) dominio1.com.bytes
187.115 MB (196203973 bytes) dominio2.com.ar.bytes
189.093 MB (198278283 bytes) dominio3.com.ar.bytes
245.692 MB (257626221 bytes) dominio4.com.bytes
273.472 MB (286756234 bytes) dominio5.com.ar.bytes
314.32 MB (329588717 bytes) dominio6.com.ar.bytes
444.516 MB (466108759 bytes) dominio7.com.ar.bytes
Generated in 2 seconds

Grave exploit para Apache descubierto

Leo en Slashdot que se descubrió un grave exploit para Apache. De acuerdo con la nota de ZDNet, la compañía de seguridad “Sense of Security” descubrió un serio bug en el servidor web Apache que podría permitir a un atacante remoto obtener control sobre una base de datos. Una vulnerabilidad existente en el módulo “mod_isapi” del core de Apache podría permitirle a un atacante obtener privilegios de administrador, comprometiendo seriamente la seguridad de la información.

La nota de ZDNet no da muchos detalles del bug, pero indica que comprometería especialmente a los servidores Windows. Por otra parte, el reporte de Sense of Security es más detallado e incluye una “prueba de concepto” acerca de cómo explotar el bug.

Se recomienda actualizar a la versión 2.2.15 de Apache que corrige el bug.

Directadmin ya ha hecho los cambios correspondientes, así que con Custombuild podemos actualizar rápida y fácilmente a la nueva versión siguiendo el procedimiento que explique recientemente.

Tips Apache: ver configuración desde la línea de comandos

Apache Web Server

A veces queremos saber algunas cosas de la configuración de Apache, pero no queremos revisar el httpd.conf ni seguir sus includes. El binario httpd acepta algunos parámetros que nos pueden servir, proveyéndonos esa información. Dejo un par de ejemplos.

Verificar sintaxis de los archivos de configuración

Muy facilmente podemos pedirle al Apache que verifique la sintaxis de los archivos de configuración. Esto es útil por si hicimos una modificación de la que no estamos seguros y queremos verificarla antes de reiniciar el servicio (y dejar a los clientes sin servicio hasta que lo resolvamos).

Comando:

httpd -t

Salida:

Syntax OK

Obtener versión de Apache

La más sencilla y obvia, para ver la versión de Apache sólo basta con:

httpd -v

Y la salida es algo así:

Server version: Apache/2.2.14 (Unix)
Server built:   Jan 28 2010 12:43:06

Ver información de compilación

Eventualmente podemos querer tener algunos datos sobre cómo fue compilado Apache.
Esta es una versión más completa de lo que ofrece httpd -v.
Entre otros datos, nos sirve para saber qué módulo de MPM está usando Apache.

Comando:

httpd -V

Salida:

Server version: Apache/2.2.14 (Unix)
Server built:   Jan 28 2010 12:43:06
Server's Module Magic Number: 20051115:23
Server loaded:  APR 1.3.9, APR-Util 1.3.9
Compiled using: APR 1.3.9, APR-Util 1.3.9
Architecture:   32-bit
Server MPM:     Prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/prefork"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_PIDLOG="/var/logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_LOCKFILE="/var/logs/accept.lock"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

Ver módulos

Hay dos alternativas diferentes para ver los módulos. Por un lado podemos ver todos los módulos que están compilados con Apache. Por otro lado, podemos ver todos los módulos que carga Apache (esto incluye módulos compilados aparte).

Módulos compilados

httpd -l

Esto no muestra el listado de módulos compilados en el binario de Apache:

Compiled in modules:
  core.c
  mod_authn_file.c
  mod_authn_default.c
  mod_authz_host.c
  mod_authz_groupfile.c
  mod_authz_user.c
  mod_authz_default.c
  mod_auth_basic.c
  mod_include.c
  mod_filter.c
  mod_deflate.c
  mod_log_config.c
  mod_logio.c
  mod_env.c
  mod_headers.c
  mod_unique_id.c
  mod_setenvif.c
  mod_version.c
  mod_proxy.c
  mod_proxy_connect.c
  mod_proxy_ftp.c
  mod_proxy_http.c
  mod_proxy_scgi.c
  mod_proxy_ajp.c
  mod_proxy_balancer.c
  mod_ssl.c
  prefork.c
  http_core.c
  mod_mime.c
  mod_dav.c
  mod_status.c
  mod_autoindex.c
  mod_asis.c
  mod_suexec.c
  mod_cgi.c
  mod_dav_fs.c
  mod_dav_lock.c
  mod_negotiation.c
  mod_dir.c
  mod_actions.c
  mod_userdir.c
  mod_alias.c
  mod_rewrite.c
  mod_so.c

Módulos cargados:

httpd -M

Así veremos todos los módulos que carga Apache:

Loaded Modules:
 core_module (static)
 authn_file_module (static)
 authn_default_module (static)
 authz_host_module (static)
 authz_groupfile_module (static)
 authz_user_module (static)
 authz_default_module (static)
 auth_basic_module (static)
 include_module (static)
 filter_module (static)
 deflate_module (static)
 log_config_module (static)
 logio_module (static)
 env_module (static)
 headers_module (static)
 unique_id_module (static)
 setenvif_module (static)
 version_module (static)
 proxy_module (static)
 proxy_connect_module (static)
 proxy_ftp_module (static)
 proxy_http_module (static)
 proxy_scgi_module (static)
 proxy_ajp_module (static)
 proxy_balancer_module (static)
 ssl_module (static)
 mpm_prefork_module (static)
 http_module (static)
 mime_module (static)
 dav_module (static)
 status_module (static)
 autoindex_module (static)
 asis_module (static)
 suexec_module (static)
 cgi_module (static)
 dav_fs_module (static)
 dav_lock_module (static)
 negotiation_module (static)
 dir_module (static)
 actions_module (static)
 userdir_module (static)
 alias_module (static)
 rewrite_module (static)
 so_module (static)
 python_module (shared)
 evasive20_module (shared)
 qos_module (shared)
 php5_module (shared)
Syntax OK

Aquí se puede notar la diferencia entre los módulos compilados con el Apache (linkeados como ‘static’) y los compilados como objetos compartidos que se cargan en tiempo de ejecución (‘shared’).

Ver virtual hosts

También podemos ver todos los virtual hosts que están configurados en el Apache.

httpd -S

Esto nos va a mostrar una lista como esta:

VirtualHost configuration:
1.1.1.1:80       is a NameVirtualHost
         default server localhost (/etc/httpd/conf/extra/httpd-vhosts.conf:29)
         port 80 namevhost localhost (/etc/httpd/conf/extra/httpd-vhosts.conf:29)
         port 80 namevhost www.midominio.com (/usr/local/directadmin/data/users/usuario/httpd.conf:11)
1.1.1.1:443      is a NameVirtualHost
         default server localhost (/etc/httpd/conf/extra/httpd-vhosts.conf:38)
         port 443 namevhost localhost (/etc/httpd/conf/extra/httpd-vhosts.conf:38)
         port 443 namevhost www.midominio.com (/usr/local/directadmin/data/users/usuario/httpd.conf:48)

En el ejemplo vemos un hipotético servidor (usando Directadmin) con IP 1.1.1.1, y los virtual hosts para el puerto 80 y el 443, tanto los default como los que corresponden al dominio “www.midominio.com” del usuario llamado “usuario”.

Nota sobre diferentes distribuciones

El artículo lo escribí pensando en CentOS y otras distros donde el binario de Apache se llama httpd. En Debian y sus derivados, el binario es apache o apache2. Todos los comandos que comenté acá se pueden correr invocando esos binarios o apachectl (que en algunos casos puede ser apache2ctl).