python - barplot - Cómo establecer hosts de destino en el archivo Fabric
pandas plot (15)
Deseo utilizar Fabric para implementar mi código de aplicación web en servidores de desarrollo, producción y producción. Mi fabfile:
def deploy_2_dev():
deploy(''dev'')
def deploy_2_staging():
deploy(''staging'')
def deploy_2_prod():
deploy(''prod'')
def deploy(server):
print ''env.hosts:'', env.hosts
env.hosts = [server]
print ''env.hosts:'', env.hosts
Muestra de salida:
host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: [''dev'']
No hosts found. Please specify (single) host string for connection:
Cuando creo una tarea set_hosts()
como se muestra en los documentos Fabric , env.hosts se configura correctamente. Sin embargo, esta no es una opción viable, tampoco lo es un decorador. Pasar hosts en la línea de comando finalmente resultaría en algún tipo de script de shell que llame al fabfile, yo preferiría que una sola herramienta hiciera el trabajo correctamente.
Dice en los documentos Fabric que ''env.hosts es simplemente un objeto de lista de Python''. Según mis observaciones, esto simplemente no es cierto.
¿Alguien puede explicar lo que está pasando aquí? ¿Cómo puedo configurar el host para implementar?
Aquí hay otro patrón de "summersault" que habilita el fab my_env_1 my_command
usage:
Con este patrón, solo tenemos que definir entornos una vez usando un diccionario. env_factory
crea funciones basadas en los nombres clave de ENVS
. Puse ENVS
en su propio directorio y archivo secrets.config.py
para separar la configuración del código de la tela.
El inconveniente es que, tal como está escrito, agregar el decorador @task
lo romperá .
Notas: Utilizamos def func(k=k):
lugar de def func():
en la fábrica debido a la vinculación tardía . Obtenemos el módulo en ejecución con esta solución y lo aplicamos para definir la función.
secrets.config.py
ENVS = {
''my_env_1'': {
''HOSTS'': [
''host_1'',
''host_2'',
],
''MY_OTHER_SETTING'': ''value_1'',
},
''my_env_2'': {
''HOSTS'': [''host_3''],
''MY_OTHER_SETTING'': ''value_2''
}
}
fabfile.py
import sys
from fabric.api import env
from secrets import config
def _set_env(env_name):
# can easily customize for various use cases
selected_config = config.ENVS[env_name]
for k, v in selected_config.items():
setattr(env, k, v)
def _env_factory(env_dict):
for k in env_dict:
def func(k=k):
_set_env(k)
setattr(sys.modules[__name__], k, func)
_env_factory(config.ENVS)
def my_command():
# do work
Aquí hay una versión más simple de la respuesta de serverhorrors :
from fabric.api import settings
def mystuff():
with settings(host_string=''12.34.56.78''):
run("hostname -f")
Contrariamente a algunas otras respuestas, es posible modificar las variables de entorno env
dentro de una tarea. Sin embargo, este env
solo se usará para tareas posteriores ejecutadas con la función fabric.tasks.execute
.
from fabric.api import task, roles, run, env
from fabric.tasks import execute
# Not a task, plain old Python to dynamically retrieve list of hosts
def get_stressors():
hosts = []
# logic ...
return hosts
@task
def stress_test():
# 1) Dynamically generate hosts/roles
stressors = get_stressors()
env.roledefs[''stressors''] = map(lambda x: x.public_ip, stressors)
# 2) Wrap sub-tasks you want to execute on new env in execute(...)
execute(stress)
# 3) Note that sub-tasks not nested in execute(...) will use original env
clean_up()
@roles(''stressors'')
def stress():
# this function will see any changes to env, as it was wrapped in execute(..)
run(''echo "Running stress test..."'')
# ...
@task
def clean_up():
# this task will NOT see any dynamic changes to env
Sin envolver subtareas en execute(...)
, se env
configuraciones env
nivel de módulo o lo que sea que pase desde la CLI fab
.
Desde fab 1.5, esta es una forma documentada de establecer hosts dinámicamente.
http://docs.fabfile.org/en/1.7/usage/execution.html#dynamic-hosts
Cita del documento a continuación.
Uso de ejecutar con listas de host establecidas dinámicamente
Un caso común de uso intermedio a avanzado para Fabric es parametrizar la búsqueda de la lista de host de destino en el tiempo de ejecución (cuando el uso de Roles no es suficiente). ejecutar puede hacer que esto sea extremadamente simple, así:
from fabric.api import run, execute, task
# For example, code talking to an HTTP API, or a database, or ...
from mylib import external_datastore
# This is the actual algorithm involved. It does not care about host
# lists at all.
def do_work():
run("something interesting on a host")
# This is the user-facing task invoked on the command line.
@task
def deploy(lookup_param):
# This is the magic you don''t get with @hosts or @roles.
# Even lazy-loading roles require you to declare available roles
# beforehand. Here, the sky is the limit.
host_list = external_datastore.query(lookup_param)
# Put this dynamically generated host list together with the work to be
# done.
execute(do_work, hosts=host_list)
El uso de roles actualmente se considera la forma "correcta" y "correcta" de hacer esto y es lo que "debería" hacer.
Dicho eso, si eres como la mayoría de lo que "te gustaría" o "deseas" es la capacidad de realizar un "syster retorcido" o cambiar los sistemas de destino sobre la marcha.
Por lo tanto, solo para fines de entretenimiento (!) El siguiente ejemplo ilustra lo que muchos podrían considerar como una maniobra arriesgada, pero de alguna manera totalmente satisfactoria, que dice algo como esto:
env.remote_hosts = env.hosts = [''10.0.1.6'']
env.remote_user = env.user = ''bob''
env.remote_password = env.password = ''password1''
env.remote_host_string = env.host_string
env.local_hosts = [''127.0.0.1'']
env.local_user = ''mark''
env.local_password = ''password2''
def perform_sumersault():
env_local_host_string = env.host_string = env.local_user + ''@'' + env.local_hosts[0]
env.password = env.local_password
run("hostname -f")
env.host_string = env.remote_host_string
env.remote_password = env.password
run("hostname -f")
Luego corriendo:
fab perform_sumersault
Es muy sencillo. Simplemente inicialice la variable env.host_string y todos los siguientes comandos se ejecutarán en este host.
from fabric.api import env, run
env.host_string = ''[email protected]''
def foo:
run("hostname -f")
Estaba atrapado en esto yo mismo, pero finalmente lo descubrí. Simplemente no puede establecer la configuración env.hosts desde dentro de una tarea. Cada tarea se ejecuta N veces, una por cada Host especificado, por lo que la configuración está fundamentalmente fuera del alcance de la tarea.
Mirando su código anterior, simplemente podría hacer esto:
@hosts(''dev'')
def deploy_dev():
deploy()
@hosts(''staging'')
def deploy_staging():
deploy()
def deploy():
# do stuff...
Que parece que haría lo que estás pensando.
O puede escribir algún código personalizado en el ámbito global que analiza los argumentos de forma manual y establece env.hosts antes de que se defina su función. Por algunas razones, así es como configuré la mía.
Lo hago al declarar una función real para cada entorno. Por ejemplo:
def test():
env.user = ''testuser''
env.hosts = [''test.server.com'']
def prod():
env.user = ''produser''
env.hosts = [''prod.server.com'']
def deploy():
...
Usando las funciones anteriores, escribiría lo siguiente para implementar en mi entorno de prueba:
fab test deploy
... y lo siguiente para implementar en producción:
fab prod deploy
Lo bueno de hacerlo de esta manera es que las funciones de test
y prod
se pueden utilizar antes que cualquier función fab, no solo implementar. Es increíblemente útil.
Necesita establecer host_string
un ejemplo sería:
from fabric.context_managers import settings as _settings
def _get_hardware_node(virtualized):
return "localhost"
def mystuff(virtualized):
real_host = _get_hardware_node(virtualized)
with _settings(
host_string=real_host):
run("echo I run on the host %s :: `hostname -f`" % (real_host, ))
Necesita modificar env.hosts a nivel de módulo, no dentro de una función de tarea. Yo cometí el mismo error.
from fabric.api import *
def _get_hosts():
hosts = []
... populate ''hosts'' list ...
return hosts
env.hosts = _get_hosts()
def your_task():
... your task ...
Para explicar por qué es incluso un problema. El comando fab está aprovechando el tejido de la biblioteca para ejecutar las tareas en las listas de host. Si intenta cambiar la lista de host dentro de una tarea, está intentando esencialmente cambiar una lista mientras la itera. O en el caso de que no tenga hosts definidos, recorra una lista vacía donde nunca se ejecuta el código donde estableció la lista para que se repita.
El uso de env.host_string es una alternativa para este comportamiento solo porque especifica directamente las funciones con qué hosts conectarse. Esto ocasiona algunos problemas en el sentido de que estará rehaciendo el bucle de ejecución si desea tener varios hosts para ejecutar.
La forma más sencilla en que las personas tienen la capacidad de establecer hosts en tiempo de ejecución es mantener el env populatiing como una tarea distinta, que configura todas las cadenas de host, usuarios, etc. Luego ejecutan la tarea de implementación. Se parece a esto:
fab production deploy
o
fab staging deploy
Donde la puesta en escena y la producción son como las tareas que has dado, pero ellas mismas no convocan la siguiente tarea. La razón por la que tiene que funcionar así, es que la tarea tiene que terminar y salir del ciclo (de hosts, en el caso env Ninguno, pero es un ciclo de uno en ese punto), y luego tener el ciclo terminado los hosts (ahora definidos por la tarea anterior) de nuevo.
Por lo tanto, para configurar los hosts y ejecutar los comandos en todos los hosts, debe comenzar por:
def PROD():
env.hosts = [''10.0.0.1'', ''10.0.0.2'']
def deploy(version=''0.0''):
sudo(''deploy %s'' % version)
Una vez que se definen, ejecuta el comando en la línea de comando:
fab PROD deploy:1.5
Lo que ejecutará la tarea de implementación en todos los servidores enumerados en la función PROD, ya que establece los entornos env.antes antes de ejecutar la tarea.
Puede asignar a env.hoststring
antes de ejecutar una subtarea. Asigne a esta variable global en un bucle si desea iterar en varios hosts.
Desafortunadamente para ti y para mí, la tela no está diseñada para este caso de uso. Consulte la función main
en http://github.com/bitprophet/fabric/blob/master/fabric/main.py para ver cómo funciona.
Soy totalmente nuevo en el tejido, pero para hacer que Fabric ejecute los mismos comandos en varios hosts (por ejemplo, para implementar en varios servidores, en un solo comando), puede ejecutar:
fab -H staging-server,production-server deploy
donde staging-server y production-server son 2 servidores con los que desea ejecutar la acción de implementación. Aquí hay un fabfile.py simple que mostrará el nombre del sistema operativo. Tenga en cuenta que fabfile.py debe estar en el mismo directorio donde ejecuta el comando fab.
from fabric.api import *
def deploy():
run(''uname -s'')
Esto funciona con fabric 1.8.1 al menos.
Usa roledefs
from fabric.api import env, run
env.roledefs = {
''test'': [''localhost''],
''dev'': [''[email protected]''],
''staging'': [''[email protected]''],
''production'': [''[email protected]'']
}
def deploy():
run(''echo test'')
Elija el rol con -R:
$ fab -R test deploy
[localhost] Executing task ''deploy''
...