Je souhaite utiliser Fabric pour déployer mon code d'application Web sur des serveurs de développement, de stockage intermédiaire et de production. Mon 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
Exemple de sortie:
Host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: ['dev']
No hosts found. Please specify (single) Host string for connection:
Lorsque je crée une tâche set_hosts()
, comme indiqué dans le docs Fabric , env.hosts est défini correctement. Cependant, ce n'est pas une option viable, ni un décorateur. Passer des hôtes sur la ligne de commande aboutirait finalement à une sorte de script Shell qui appelle la fabfile. Je préférerais qu'un seul outil fasse le travail correctement.
Dans la documentation Fabric, il est indiqué que "env.hosts est simplement un Python objet liste"). D'après mes observations, ce n'est tout simplement pas vrai.
Quelqu'un peut-il expliquer ce qui se passe ici? Comment définir l'hôte sur lequel déployer?
Je le fais en déclarant une fonction réelle pour chaque environnement. Par exemple:
def test():
env.user = 'testuser'
env.hosts = ['test.server.com']
def prod():
env.user = 'produser'
env.hosts = ['prod.server.com']
def deploy():
...
En utilisant les fonctions ci-dessus, je taperais les éléments suivants à déployer dans mon environnement de test:
fab test deploy
... et les suivants à déployer en production:
fab prod deploy
La bonne chose à faire de cette façon est que les fonctions test
et prod
peuvent être utilisées avant la fonction toute fab, pas seulement la déployer. C'est incroyablement utile.
Utilisez 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')
Choisissez le rôle avec -R:
$ fab -R test deploy
[localhost] Executing task 'deploy'
...
Voici une version plus simple de serverhorrors answer :
from fabric.api import settings
def mystuff():
with settings(Host_string='192.0.2.78'):
run("hostname -f")
Était coincé sur moi-même, mais a finalement compris. Vous simplement ne pouvez pas définir la configuration env.hosts à partir de dans une tâche. Chaque tâche est exécutée N fois, une fois pour chaque hôte spécifié, de sorte que le paramètre est en dehors de la portée de la tâche.
En regardant votre code ci-dessus, vous pouvez simplement faire ceci:
@hosts('dev')
def deploy_dev():
deploy()
@hosts('staging')
def deploy_staging():
deploy()
def deploy():
# do stuff...
Ce qui semble vouloir faire ce que vous avez l'intention de faire.
Ou vous pouvez écrire du code personnalisé dans la portée globale qui analyse manuellement les arguments et définit env.hosts avant que votre fonction de tâche ne soit définie. Pour quelques raisons, c'est en fait ainsi que j'ai créé la mienne.
Depuis la fab 1.5, il s'agit d'une méthode documentée pour définir de manière dynamique les hôtes.
http://docs.fabfile.org/fr/1.7/usage/execution.html#dynamic-hosts
Citation de la doc ci-dessous.
Utilisation de execute avec des listes d'hôtes définies de manière dynamique
Un cas d'utilisation courant intermédiaire à avancé pour Fabric consiste à paramétrer la recherche de la liste d'hôtes cible au moment de l'exécution (lorsque l'utilisation de rôles ne suffit pas). execute peut rendre cela extrêmement simple, comme ceci:
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)
Contrairement à d'autres réponses, il est possible de modifier les variables d'environnement env
d'une tâche. Cependant, ce env
ne sera utilisé que pour les tâches ultérieures exécutées à l'aide de la fonction 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
Sans inclure les sous-tâches dans execute(...)
, vos paramètres env
de niveau module ou tout ce qui est passé à partir de la fab
CLI seront utilisés.
Pour expliquer pourquoi c'est même un problème. La commande fab exploite la bibliothèque pour exécuter les tâches sur les listes d'hôtes. Si vous essayez de modifier la liste des hôtes dans une tâche, vous tentez essentiellement de modifier une liste tout en effectuant une itération. Ou, dans le cas où aucun hôte n'est défini, bouclez sur une liste vide dans laquelle le code sur lequel vous définissez la liste en boucle n'est jamais exécuté.
L'utilisation de env.Host_string est un moyen de contourner ce problème uniquement dans le sens où elle spécifie directement aux fonctions les hôtes avec lesquels se connecter. Cela pose certains problèmes en ce sens que vous allez refaire la boucle d'exécution si vous voulez avoir plusieurs hôtes sur lesquels exécuter.
Le moyen le plus simple de configurer les hôtes au moment de l’exécution est de laisser l’environnement peupler en tant que tâche distincte, qui configure toutes les chaînes de l’hôte, les utilisateurs, etc. Ensuite, ils exécutent la tâche de déploiement. Cela ressemble à ceci:
fab production deploy
ou
fab staging deploy
Où la mise en scène et la production sont similaires aux tâches que vous avez confiées, mais elles n'appellent pas la tâche suivante elles-mêmes. La raison pour laquelle il doit fonctionner comme ceci est que la tâche doit être terminée et sortir de la boucle (des hôtes, dans le cas env. None, mais c'est une boucle de un à ce moment-là), puis laisser la boucle passer. les hôtes (maintenant définis par la tâche précédente) à nouveau.
Vous devez définir Host_string
un exemple serait:
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, ))
Je suis totalement nouveau dans la structure, mais pour que la structure exécute les mêmes commandes sur plusieurs hôtes (par exemple, pour déployer sur plusieurs serveurs, en une seule commande), vous pouvez exécuter:
fab -H staging-server,production-server deploy
où serveur de stockage intermédiaire et serveur de production sont deux serveurs pour lesquels vous souhaitez exécuter l'action de déploiement. Voici un fichier fabfile.py simple qui affichera le nom du système d'exploitation. Notez que fabfile.py doit se trouver dans le même répertoire que celui où vous exécutez la commande fab.
from fabric.api import *
def deploy():
run('uname -s')
Cela fonctionne avec le tissu 1.8.1 au moins.
Vous devez modifier env.hosts au niveau du module, pas dans une fonction de tâche. J'ai fait la même erreur.
from fabric.api import *
def _get_hosts():
hosts = []
... populate 'hosts' list ...
return hosts
env.hosts = _get_hosts()
def your_task():
... your task ...
Donc, pour définir les hôtes et faire en sorte que les commandes soient exécutées sur tous les hôtes, vous devez commencer par:
def PROD():
env.hosts = ['10.0.0.1', '10.0.0.2']
def deploy(version='0.0'):
Sudo('deploy %s' % version)
Une fois ceux-ci définis, exécutez la commande sur la ligne de commande:
fab PROD deploy:1.5
Ce qui exécutera la tâche de déploiement sur tous les serveurs répertoriés dans la fonction PROD, car il définit env.hosts avant d'exécuter la tâche.
C'est très simple. Il suffit d'initialiser la variable env.Host_string et toutes les commandes suivantes seront exécutées sur cet hôte.
from fabric.api import env, run
env.Host_string = '[email protected]'
def foo:
run("hostname -f")
Vous pouvez affecter à env.hoststring
avant d'exécuter une sous-tâche. Affectez cette variable globale dans une boucle si vous souhaitez effectuer une itération sur plusieurs hôtes.
Malheureusement pour vous et moi, le tissu n'est pas conçu pour ce cas d'utilisation. Découvrez la fonction main
sur http://github.com/bitprophet/fabric/blob/master/fabric/main.py pour voir comment cela fonctionne.
Voici un autre modèle "summersault" qui permet l'utilisation de fab my_env_1 my_command
:
Avec ce modèle, il suffit de définir les environnements une seule fois à l’aide d’un dictionnaire. env_factory
Crée des fonctions basées sur les noms de clé de ENVS
. J'ai mis ENVS
dans son propre répertoire et le fichier secrets.config.py
Pour séparer la configuration du code de structure.
L’inconvénient est que, comme il est écrit, l’ajout du décorateur @task
Aura le casser .
Notes: Nous utilisons def func(k=k):
au lieu de def func():
en usine à cause de liaison tardive . Nous obtenons le module en cours d'exécution avec cette solution et le corrigeons pour définir la fonction.
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
L'utilisation de rôles est actuellement considérée comme la manière "correcte" et "correcte" de le faire et c'est ce que vous "devriez" faire.
Cela dit, si vous êtes comme la plupart de ce que vous "voulez" ou "désirez", c'est la possibilité d'effectuer un "système tordu" ou de changer de système cible à la volée.
Ainsi, à des fins de divertissement uniquement (!), L'exemple suivant illustre ce que beaucoup pourraient considérer comme une manœuvre risquée, et pourtant tout à fait satisfaisante, qui ressemble à ceci:
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")
Puis en cours d'exécution:
fab perform_sumersault