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”
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”
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”
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”
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”
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)