1 mar 2014

Python y Fabric para administrar servidores SSH

En la medida que el tamaño de una organización crece, se vuelve cada vez más evidente la necesidad de utilizar mecanismos para monitorizar y gestionar varios servidores de la forma más eficiente posible. En este sentido, existen protocolos de red como SNMP que nos permiten monitorizar los agentes que pertenecen a una comunidad determinada, sin embargo, en ocasiones se nos queda corto y a veces necesitamos realizar tareas de administración sobre múltiples servidores en la red. En tales casos, poder ejecutar uno o varios comandos de forma paralela sobre un conjunto determinado de servidores es una posibilidad muy interesante para un administrador de sistemas. 

Como seguramente el lector ya sabrá, una de las mejores formas tener acceso remoto de forma segura a un servidor, es utilizando el protocolo SSH y es en efecto, uno de los mecanismos preferidos por administradores de sistemas a la hora de controlar y gestionar tareas administrativas sobre servidores. No obstante, realizar dichas tareas sobre varios servidores, puede ser una tarea tediosa, repetitiva y propensa a error, por ese motivo existen herramientas como PDSH y FANOUT que nos permiten gestionar todo el proceso de autenticación y ejecución paralela de comandos sobre varios servidores SSH en el segmento de red.

Con esto, el problema parece resulto, pero en el caso de que queramos más control y/o crear nuestras propias herramientas para la gestión paralela de servidores por medio de SSH, podemos utilizar Python y la librería Fabric.

FABRIC

Se trata de una librería que permite controlar un grupo de servidores SSH de forma paralela. Es posible utilizar Fabric directamente desde linea de comandos ejecutando la utilidad fab o con la API que contiene todas las clases y decoradores necesarios para declarar un conjunto de servidores SSH, así como las tareas que queremos ejecutar sobre ellos.

Una de las principales dependencias que es necesario cumplir antes instalar Fabric, es tener instalada la librería Paramiko, dicha librería es la encargada de realizar las conexiones a los servidores SSH utilizando el mecanismo de autenticación adecuado según cada caso (auth. por password o auth. por clave pública)

Si las dependencias se cumplen correctamente, es posible instalar Fabric, simplemente ejecutando el script setup.py con el argumento install.
 
Nota: El listado de dependencias necesarias para instalar Fabric, así como la librería propiamente dicha, se encuentra disponible en el siguiente enlace: http://docs.fabfile.org/en/1.8/installation.html

Utilidad Fab

La forma más sencilla de utilizar Fabric es por medio del comando fab, el cual se encarga de leer el fichero fabfile.py para ejecutar las rutinas (funciones) que en dicho fichero se encuentren declaradas.
Por ejemplo, suponiendo que el fichero fabfile.py tenga el siguiente contenido:

from fabric.api import run, local
def uname():
local('ifconfig -a')
run('uname -s; id; w')

Cuando se ingresa como argumento el nombre de la función uname en la utilidad fab, se ejecuta en la máquina local el comando ifconfig -a y en la(s) máquina(s) remota(s) los comandos uname -s , id y w.

#fab uname

En este caso, dado que no se han especificado los servidores sobre los que se desea ejecutar esta función, la utilidad solicita los detalles conexión para poder ejecutar la tarea.

#fab uname --hosts 192.168.1.253, 85.34.22.10, 10.1.1.34

Con la opción --hosts o -H es posible indicar las direcciones IP o los dominios sobre los que queremos ejecutar la tarea, sin embargo, aun faltan las credenciales de acceso, las cuales serán solicitadas posteriormente por Fabric.

Por otro lado, las tareas definidas en el fichero fabfile.py también pueden recibir argumentos que serán enviados directamente desde linea de comandos.

from fabric.api import run, local
def exec(command=”uname -a”):
local('ifconfig -a')
run('uname -s; id; w')

#fab uname --hosts 192.168.1.253, 85.34.22.10, 10.1.1.34 exec:command=id

Cuando ejecutamos la tarea exec podemos dejar que el argumento command tenga el valor por omisión (“uname -a”) o podemos establecer otro diferente indicando el nombre del argumento y su correspondiente valor por linea de comandos.

Utilizando la API de Fabric

La utilidad fab es muy útil si queremos ejecutar tareas sencillas y sin demasiada lógica, pero en muchos casos, lo que nos interesa es poder integrar Fabric directamente en nuestros scripts, con lo cual no tiene mucho sentido ejecutar la utilidad fab. En tales casos, podemos utilizar la API de Fabric con el largo conjunto de funciones y decoradores disponibles.

Variables en el entorno de Fabric

Evidentemente lo primero que hay que hacer para automatizar cualquier tarea con Fabric sin ejecutar la utilidad fab, es definir el grupo de servidores y las credenciales de acceso para cada host. Para ello, usamos la estructura env, la cual define un listado de variables que permiten controlar el comportamiento general de la librería. Cuenta con una lista bastante completa de atributos que podemos controlar desde nuestros scripts (http://docs.fabfile.org/en/1.8/usage/env.html#env-vars ). De dichas variables, seguramente las que más nos interesan inicialmente son env.hosts y env.passwords las cuales como su nombre indica, nos permite definir los servidores y las credenciales de acceso para cada servidor, de esta forma, los scripts que ejecutemos utilizando Fabric no serán “interactivos” y no nos pedirán las credenciales de acceso de cada sistema cada vez que se vaya a ejecutar una tarea.

from fabric.api import *

env.hosts=[“adastra@192.168.1.244”, “petersellers@10.1.1.4”]
env.passwords[“adastra@192.168.1.244”] = “password”
env.passwords[“adastra@10.1.1.4”] = “guateque

def exec(command=”uname -a”):
run('uname -s; id; w')

if __name__ == "__main__":
'''
Start the main program.
'''
exec(“whoami, id; w; ps -fea”)

En el script anterior, los comandos whoami, id, w y ps -fea se van a ejecutar contra los servidores definidos en env.hosts y ademas, las credenciales que utilizará Fabric serán las que se han indicado en env.passwords.
Otras funciones disponibles, permiten subir y descargar ficheros de forma paralela utilizando protocolo SFTP.

from fabric.api import *

env.hosts=[“adastra@192.168.1.244”, “petersellers@10.1.1.4”]
env.passwords[“adastra@192.168.1.244”] = “password”
env.passwords[“adastra@10.1.1.4”] = “guateque

def exec():
get('/remotedir/remotefile', '/tmp')

if __name__ == "__main__":
'''
Start the main program.
'''
exec()


En este caso, cuando se ejecute el script, la función get se encargará de descargar el fichero en la ruta /remotedir/remotefile y el directorio destino en la máquina local donde se descargará el fichero es /tmp

En el siguiente script vemos el uso de la función put

from fabric.api import *

env.hosts=[“adastra@192.168.1.244”, “petersellers@10.1.1.4”]
env.passwords[“adastra@192.168.1.244”] = “password”
env.passwords[“adastra@10.1.1.4”] = “guateque

def exec():
put('/localpath/localfile.zip', '/tmp')
if __name__ == "__main__":
'''
Start the main program.
'''
exec()

El script se encargará de subir fichero local /localpath/localfile.zip a los servidores remotos en el directorio /tmp

Otra función muy interesante que nos permite tener más control sobre las tareas a ejecutar es execute

from fabric.api import *

env.hosts=[“adastra@192.168.1.244”, “petersellers@10.1.1.4”]
env.passwords[“adastra@192.168.1.244”] = “password”
env.passwords[“adastra@10.1.1.4”] = “guateque

def exec(command):
results = “”
with hide('running', 'stdout', 'stderr'):
if command.strip()[0:5] == "sudo":
results = sudo(command)
else:
results = run(command)
return results

if __name__ == "__main__":
'''
Start the main program.
'''
for host, result in execute(self.exec, “whoami”, hosts=env.hosts).iteritems():
print host, result


La función execute es muy similar a la función run, con la diferencia de que nos permite especificar una función de callback y establecer el entorno de ejecución. Como podemos apreciar, nos permite tener mucho más control y ejecutar rutinas lógicas personalizadas en la función de callback.
Finalmente, también es posible generar directamente una shell en alguno de los sistemas que controlamos con la función open_shell

from fabric.api import *

env.hosts=[“adastra@192.168.1.244”, “petersellers@10.1.1.4”]
env.passwords[“adastra@192.168.1.244”] = “password”
env.passwords[“adastra@10.1.1.4”] = “guateque

def shell(hostId):
execute(open_shell, host=env.hosts[hostId-1])
if __name__ == "__main__":
'''
Start the main program.
'''
shell(1)

Con en el script anterior, se generará una shell en el sistema 192.168.1.244 ya que la función open_shell recibe como argumento el identificador del host que se encuentre definido en la propiedad env.hosts

Para mayor información sobre otras funciones disponibles en Fabric y los parámetros admitidos, puedes leer la documentación oficial de la librería disponible en el siguiente enlace: http://docs.fabfile.org/en/1.8/index.html

Saludos y Happy Hack!

Artículo cortesía de Adastra (http://thehackerway.com)

No hay comentarios:

Publicar un comentario