Come impostare gli host di destinazione nel file Fabric


107

Desidero utilizzare Fabric per distribuire il codice della mia app Web a server di sviluppo, gestione temporanea e produzione. Il mio 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

Output di esempio:

host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: ['dev']
No hosts found. Please specify (single) host string for connection:

Quando creo set_hosts()un'attività come mostrato nella documentazione di Fabric , env.hosts è impostato correttamente. Tuttavia, questa non è un'opzione praticabile, né è un decoratore. Il passaggio di host sulla riga di comando risulterebbe infine in una sorta di script di shell che chiama il fabfile, preferirei che un unico strumento eseguisse correttamente il lavoro.

Nei documenti di Fabric si dice che "env.hosts è semplicemente un oggetto elenco Python". Dalle mie osservazioni, questo semplicemente non è vero.

Qualcuno può spiegare cosa sta succedendo qui? Come posso impostare l'host su cui eseguire la distribuzione?


Ho lo stesso problema, hai trovato qualche soluzione a questo?
Martin M.

per eseguire la stessa attività su più server, usa "fab -H staging-server, production-server deploy" ... altro nella mia risposta di seguito: stackoverflow.com/a/21458231/26510
Brad Parks


Questa risposta non si applica al tessuto 2+. Se qualcuno che ha più familiarità con le convenzioni di Stackoverflow potesse modificare la domanda o il titolo della domanda per fare riferimento al fabric 1, potrebbe essere utile.
Jonathan Berger

Risposte:


128

Lo faccio dichiarando una funzione effettiva per ogni ambiente. Per esempio:

def test():
    env.user = 'testuser'
    env.hosts = ['test.server.com']

def prod():
    env.user = 'produser'
    env.hosts = ['prod.server.com']

def deploy():
    ...

Utilizzando le funzioni di cui sopra, vorrei digitare quanto segue per eseguire la distribuzione nel mio ambiente di test:

fab test deploy

... e quanto segue da distribuire alla produzione:

fab prod deploy

La cosa bella di farlo in questo modo è che le funzioni teste prodpossono essere usate prima di qualsiasi funzione fab, non solo deploy. È incredibilmente utile.


10
A causa di un bug in fabric ( code.fabfile.org/issues/show/138#change-1497 ) è meglio includere l'utente nella stringa host (come produser@prod.server.com) invece di impostare env.user.
Mikhail Korobov

1
Ho avuto lo stesso problema e questa sembra la soluzione migliore. Definisco gli host, l'utente e molte altre impostazioni in un file YAML che viene caricato dalle funzioni dev () e prod (). (Così posso riutilizzare lo stesso copione Fabric per progetti simili.)
Christian Davén

@MikhailKorobov: Quando ho seguito il tuo link, ho visto " Benvenuto in nginx! ". Tutte le richieste al code.fabfile.orgdominio hanno risposte del genere.
Tadeck

Sì, sembra che tutti i bug siano stati migrati su GitHub.
Mikhail Korobov

2
Sfortunatamente, sembra che questo non funzioni più: fabric non eseguirà attività senza env.hosts già definito e non eseguirà funzioni nello fab A B Cstile senza che siano definite come attività.
DNelson

77

Usa i ruoli

from fabric.api import env, run

env.roledefs = {
    'test': ['localhost'],
    'dev': ['user@dev.example.com'],
    'staging': ['user@staging.example.com'],
    'production': ['user@production.example.com']
} 

def deploy():
    run('echo test')

Scegli il ruolo con -R:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...

7
Oppure, se l'attività viene sempre eseguita sullo stesso ruolo, è possibile utilizzare il decoratore @roles () sull'attività.
Tom

2
Sembra che i roledef siano una soluzione migliore rispetto alla loro definizione in attività separate.
Ehtesh Choudhury

Qualcuno sa come posso includere una password per il nome utente fornito in un roledef? Un'ulteriore voce del dizionario 'password': 'some_password'sembra essere ignorata e porta a un prompt in fase di esecuzione.
Dirk

@Dirk puoi usare env.passwords che è un dizionario che contiene user + host + port come chiave e password come valore. Ad es. Env.passwords = {'user @ host: 22': 'password'}
Jonathan

49

Ecco una versione più semplice della risposta di serverhorror :

from fabric.api import settings

def mystuff():
    with settings(host_string='192.0.2.78'):
        run("hostname -f")

2
Secondo i documenti , il gestore del contesto delle impostazioni serve per sovrascrivere le envvariabili, non per impostarle inizialmente. Penso che l'utilizzo di roledef , come suggerito da thomie, sia più appropriato per definire host come stage, dev e test.
Tony

21

Ero bloccato su questo io stesso, ma alla fine l'ho capito. Semplicemente non puoi impostare la configurazione di env.hosts dall'interno di un'attività. Ogni attività viene eseguita N volte, una volta per ogni host specificato, quindi l'impostazione è fondamentalmente al di fuori dell'ambito dell'attività.

Guardando il tuo codice sopra, potresti semplicemente fare questo:

@hosts('dev')
def deploy_dev():
    deploy()

@hosts('staging')
def deploy_staging():
    deploy()

def deploy():
    # do stuff...

Sembra che farebbe quello che intendi.

Oppure puoi scrivere del codice personalizzato nell'ambito globale che analizza gli argomenti manualmente e imposta env.hosts prima che la funzione dell'attività venga definita. Per alcuni motivi, è così che ho impostato il mio.


Trovato un modo from fabric.api import env:; env.host_string = "dev"
Roman

18

A partire da fab 1.5 questo è un modo documentato per impostare dinamicamente gli host.

http://docs.fabfile.org/en/1.7/usage/execution.html#dynamic-hosts

Citazione dal documento qui sotto.

Utilizzo di execute con elenchi di host impostati dinamicamente

Un caso d'uso comune da intermedio ad avanzato per Fabric consiste nel parametrizzare la ricerca dell'elenco di host di destinazione in fase di esecuzione (quando l'uso dei ruoli non è sufficiente). execute può renderlo estremamente semplice, in questo modo:

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)

3
+1. Molte risposte davvero buone verso la fine della pagina qui.
Matt Montag

10

Contrariamente ad altre risposte, è possibile modificare le envvariabili di ambiente all'interno di un'attività. Tuttavia, questo envverrà utilizzato solo per le attività successive eseguite utilizzando la fabric.tasks.executefunzione.

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

Senza includere le sotto-attività execute(...), verranno utilizzate le envimpostazioni a livello di modulo o qualsiasi altra cosa passata dalla fabCLI.


Questa è la risposta migliore se vuoi impostare dinamicamente env.hosts.
JahMyst

9

È necessario impostare host_stringun esempio sarebbe:

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

Dolce. Ho pubblicato una versione più semplice del codice in un'altra risposta qui.
tobych

9

Per spiegare perché è anche un problema. Il comando fab sta sfruttando fabric la libreria per eseguire le attività sugli elenchi host. Se provi a modificare l'elenco degli host all'interno di un'attività, stai essenzialmente tentando di modificare un elenco mentre lo ripeti. Oppure, nel caso in cui non siano stati definiti host, eseguire il ciclo su un elenco vuoto in cui il codice in cui si imposta l'elenco per il ciclo continuo non viene mai eseguito.

L'uso di env.host_string è una soluzione a questo comportamento solo in quanto specifica direttamente alle funzioni con quali host connettersi. Ciò causa alcuni problemi in quanto si rifarà il ciclo di esecuzione se si desidera avere un numero di host su cui eseguire.

Il modo più semplice con cui le persone riescono a impostare gli host in fase di esecuzione è mantenere il popolamento dell'ambiente come un'attività distinta, che imposta tutte le stringhe host, gli utenti, ecc. Quindi eseguono l'attività di distribuzione. Assomiglia a questo:

fab production deploy

o

fab staging deploy

Dove la messa in scena e la produzione sono come i compiti che hai assegnato, ma non chiamano il compito successivo. Il motivo per cui deve funzionare in questo modo è che l'attività deve finire e uscire dal ciclo (degli host, nel caso env Nessuno, ma è un ciclo di uno a quel punto), e quindi avere il ciclo finito gli host (ora definiti dall'attività precedente) di nuovo.


3

È necessario modificare env.hosts a livello di modulo, non all'interno di una funzione di attività. Ho fatto lo stesso errore.

from fabric.api import *

def _get_hosts():
    hosts = []
    ... populate 'hosts' list ...
    return hosts

env.hosts = _get_hosts()

def your_task():
    ... your task ...

3

È molto semplice. Basta inizializzare la variabile env.host_string e tutti i seguenti comandi verranno eseguiti su questo host.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

Sono totalmente nuovo in fabric, ma per fare in modo che fabric esegua gli stessi comandi su più host (ad esempio per distribuire su più server, in un comando) puoi eseguire:

fab -H staging-server,production-server deploy 

dove staging server e produzione di server sono 2 server che si desidera eseguire l'azione contro Deploy. Ecco un semplice fabfile.py che mostrerà il nome del sistema operativo. Nota che fabfile.py dovrebbe essere nella stessa directory in cui esegui il comando fab.

from fabric.api import *

def deploy():
    run('uname -s')

Funziona almeno con il tessuto 1.8.1.


3

Quindi, per impostare gli host e fare in modo che i comandi vengano eseguiti su tutti gli host, devi iniziare con:

def PROD():
    env.hosts = ['10.0.0.1', '10.0.0.2']

def deploy(version='0.0'):
    sudo('deploy %s' % version)

Una volta definiti, esegui il comando dalla riga di comando:

fab PROD deploy:1.5

Cosa eseguirà l'attività di distribuzione su tutti i server elencati nella funzione PROD, poiché imposta env.hosts prima di eseguire l'attività.


Supponiamo che la distribuzione sul primo host abbia funzionato ma quella sul secondo non sia riuscita, come posso rifarla solo sul secondo?
n.


2

Ecco un altro modello "summersault" che consente l' fab my_env_1 my_commandutilizzo:

Con questo modello, dobbiamo definire gli ambienti solo una volta utilizzando un dizionario. env_factorycrea funzioni basate sui nomi chiave di ENVS. Ho messo ENVSnella sua directory e file secrets.config.pyper separare la configurazione dal codice del fabric.

Lo svantaggio è che, come scritto, l'aggiunta del @taskdecoratore lo romperà .

Note: Usiamo def func(k=k):invece che def func():in fabbrica a causa della rilegatura tardiva . Otteniamo il modulo in esecuzione con questa soluzione e lo applichiamo per definire la funzione.

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

0

L'uso dei ruoli è attualmente considerato il modo "appropriato" e "corretto" di farlo ed è ciò che "dovresti" farlo.

Detto questo, se sei come la maggior parte di ciò che "vorresti" o "desideri" è la capacità di eseguire un "twisted syster" o cambiare i sistemi di destinazione al volo.

Quindi, solo per scopi di intrattenimento (!) Il seguente esempio illustra ciò che molti potrebbero considerare come una manovra rischiosa, eppure in qualche modo completamente soddisfacente, che va più o meno così:

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

Quindi in esecuzione:

fab perform_sumersault
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.