So legen Sie Zielhosts in der Fabric-Datei fest


107

Ich möchte Fabric verwenden, um meinen Web-App-Code auf Entwicklungs-, Staging- und Produktionsservern bereitzustellen. Mein 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

Beispielausgabe:

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

Wenn ich eine set_hosts()Aufgabe erstelle, wie in den Fabric-Dokumenten gezeigt , ist env.hosts richtig eingestellt. Dies ist jedoch keine praktikable Option, ebenso wenig wie ein Dekorateur. Das Übergeben von Hosts über die Befehlszeile würde letztendlich zu einer Art Shell-Skript führen, das die Fabfile aufruft. Ich würde es vorziehen, wenn ein einziges Tool die Aufgabe ordnungsgemäß erledigt.

In den Fabric-Dokumenten heißt es, dass 'env.hosts einfach ein Python-Listenobjekt ist'. Nach meinen Beobachtungen ist dies einfach nicht wahr.

Kann jemand erklären, was hier los ist? Wie kann ich den Host für die Bereitstellung festlegen?


Ich habe das gleiche Problem, haben Sie eine Lösung dafür gefunden?
Martin M.



Diese Antwort gilt nicht für Stoff 2+. Wenn jemand, der mit Stackoverflow-Konventionen besser vertraut ist, die Frage oder den Fragentitel bearbeiten kann, um auf Fabric 1 zu verweisen, kann dies hilfreich sein.
Jonathan Berger

Antworten:


128

Ich mache das, indem ich eine tatsächliche Funktion für jede Umgebung deklariere. Beispielsweise:

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

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

def deploy():
    ...

Mit den oben genannten Funktionen würde ich Folgendes eingeben, um es in meiner Testumgebung bereitzustellen:

fab test deploy

... und Folgendes für die Bereitstellung in der Produktion:

fab prod deploy

Das Schöne an es auf diese Weise tun , ist , dass die testund prodFunktionen können , bevor sie verwendet werden , jede fab Funktion, nicht nur bereitstellen. Es ist unglaublich nützlich.


10
Aufgrund eines Fehlers in Fabric ( code.fabfile.org/issues/show/138#change-1497 ) ist es besser, Benutzer in die Host-Zeichenfolge (wie produser@prod.server.com) aufzunehmen, anstatt env.user festzulegen.
Mikhail Korobov

1
Ich hatte das gleiche Problem, und dies scheint die beste Lösung zu sein. Ich definiere die Hosts, Benutzer und viele andere Einstellungen in einer YAML-Datei, die von den Funktionen dev () und prod () geladen wird. (Damit ich das gleiche Fabric-Skript für ähnliche Projekte wiederverwenden kann.)
Christian Davén

@MikhailKorobov: Als ich Ihrem Link folgte, sah ich " Willkommen bei Nginx! ". Alle Anfragen an die code.fabfile.orgDomain haben solche Antworten.
Tadeck

Ja, es scheint, dass alle Fehler auf Github migriert wurden.
Mikhail Korobov

2
Leider sieht es so aus, als würde dies nicht mehr funktionieren - Fabric führt keine Aufgaben ohne bereits definierte env.hosts aus und führt keine Funktionen im fab A B CStil aus, ohne dass sie als Aufgaben definiert sind.
DNelson

77

Verwenden Sie roledefs

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

Wählen Sie die Rolle mit -R:

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

7
Wenn die Aufgabe immer in derselben Rolle ausgeführt wird, können Sie den Dekorator @roles () für die Aufgabe verwenden.
Tom

2
Klingt so, als wäre Roledefs eine bessere Lösung, als sie in separaten Aufgaben zu definieren.
Ehtesh Choudhury

Weiß jemand, wie ich ein Passwort für den angegebenen Benutzernamen in ein einfügen kann roledef? Ein weiterer Wörterbucheintrag 'password': 'some_password'scheint ignoriert zu werden und führt zur Laufzeit zu einer Eingabeaufforderung.
Dirk

@Dirk Sie können env.passwords verwenden, ein Wörterbuch, das Benutzer + Host + Port als Schlüssel und Kennwort als Wert enthält. ZB env.passwords = {'user @ host: 22': 'password'}
Jonathan

49

Hier ist eine einfachere Version der Antwort von Serverhorror :

from fabric.api import settings

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

2
Gemäß den Dokumenten dient der Einstellungskontextmanager zum Überschreiben von envVariablen und nicht zum anfänglichen Festlegen. Ich denke, die Verwendung von Roledefs ist , wie von Thomie vorgeschlagen, besser geeignet, um Hosts wie Stage, Dev und Test zu definieren.
Tony

21

War selbst dabei festgefahren, habe es aber endlich herausgefunden. Sie können die env.hosts-Konfiguration einfach nicht von innen festlegen einer Aufgabe . Jede Aufgabe wird N-mal ausgeführt, einmal für jeden angegebenen Host, sodass die Einstellung grundsätzlich außerhalb des Aufgabenbereichs liegt.

Wenn Sie sich Ihren Code oben ansehen, können Sie einfach Folgendes tun:

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

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

def deploy():
    # do stuff...

Was scheint, als würde es tun, was Sie beabsichtigen.

Oder Sie können benutzerdefinierten Code in den globalen Bereich schreiben, der die Argumente manuell analysiert und env.hosts festlegt, bevor Ihre Aufgabenfunktion definiert wird. Aus ein paar Gründen habe ich meine so eingerichtet.


Einen Weg gefunden : from fabric.api import env; env.host_string = "dev"
Roman

18

Seit Fab 1.5 ist dies eine dokumentierte Möglichkeit, Hosts dynamisch festzulegen.

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

Zitat aus dem Dokument unten.

Verwenden von Ausführen mit dynamisch festgelegten Hostlisten

Ein häufiger Anwendungsfall für Fabric für Fortgeschrittene ist die Parametrisierung der Suche nach der Zielhostliste zur Laufzeit (wenn die Verwendung von Rollen nicht ausreicht). Ausführen kann dies extrem einfach machen, wie folgt:

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. Viele wirklich gute Antworten hier unten auf der Seite.
Matt Montag

10

Im Gegensatz zu einigen anderen Antworten, es ist möglich , die zu modifizieren envUmgebungsvariablen innerhalb einer Aufgabe. Dies envwird jedoch nur für nachfolgende Aufgaben verwendet, die mit der fabric.tasks.executeFunktion ausgeführt werden .

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

Ohne Unteraufgaben einzuschließen execute(...), werden Ihre envEinstellungen auf Modulebene oder was auch immer von der fabCLI übergeben wird, verwendet.


Dies ist die beste Antwort, wenn Sie env.hosts dynamisch festlegen möchten.
JahMyst

9

Sie müssen host_stringein Beispiel geben, wäre:

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

Süss. Ich habe eine einfachere Version des Codes in einer anderen Antwort hier veröffentlicht.
tobych

9

Um zu erklären, warum es überhaupt ein Problem ist. Der Befehl fab Stoff die Bibliothek nutzt die Aufgaben auf den Host - Listen laufen. Wenn Sie versuchen, die Hostliste innerhalb einer Aufgabe zu ändern, versuchen Sie im Wesentlichen, eine Liste zu ändern, während Sie darüber iterieren. Wenn Sie keine Hosts definiert haben, durchlaufen Sie eine leere Liste, in der der Code, in dem Sie die Liste für die Schleife festgelegt haben, niemals ausgeführt wird.

Die Verwendung von env.host_string ist eine Problemumgehung für dieses Verhalten, da nur direkt für die Funktionen angegeben wird, mit welchen Hosts eine Verbindung hergestellt werden soll. Dies führt zu einigen Problemen, da Sie die Ausführungsschleife neu erstellen, wenn Sie mehrere Hosts ausführen möchten.

Die einfachste Möglichkeit für die Benutzer, Hosts zur Laufzeit festzulegen, besteht darin, die Env-Auffüllung als eigenständige Aufgabe beizubehalten, die alle Host-Zeichenfolgen, Benutzer usw. einrichtet. Anschließend führen sie die Bereitstellungsaufgabe aus. Es sieht aus wie das:

fab production deploy

oder

fab staging deploy

Wo Inszenierung und Produktion wie die Aufgaben sind, die Sie gegeben haben, aber nicht die nächste Aufgabe selbst aufrufen. Der Grund, warum es so funktionieren muss, ist, dass die Aufgabe beendet werden und aus der Schleife ausbrechen muss (von Hosts, im Env-Fall None, aber es ist zu diesem Zeitpunkt eine Schleife von eins), und dann die Schleife beendet haben muss die Hosts (jetzt durch die vorhergehende Aufgabe definiert) neu.


3

Sie müssen env.hosts auf Modulebene ändern, nicht innerhalb einer Taskfunktion. Ich habe den gleichen Fehler gemacht.

from fabric.api import *

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

env.hosts = _get_hosts()

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

3

Es ist sehr einfach. Initialisieren Sie einfach die Variable env.host_string und alle folgenden Befehle werden auf diesem Host ausgeführt.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

Fabric ist für mich völlig neu, aber um Fabric dazu zu bringen, dieselben Befehle auf mehreren Hosts auszuführen (z. B. auf mehreren Servern in einem Befehl bereitzustellen), können Sie Folgendes ausführen:

fab -H staging-server,production-server deploy 

Dabei sind Staging-Server und Produktionsserver zwei Server, für die Sie die Bereitstellungsaktion ausführen möchten. Hier ist eine einfache fabfile.py, die den Namen des Betriebssystems anzeigt. Beachten Sie, dass sich die Datei fabfile.py in demselben Verzeichnis befinden sollte, in dem Sie den Befehl fab ausführen.

from fabric.api import *

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

Dies funktioniert mindestens mit Stoff 1.8.1.


3

Um die Hosts festzulegen und die Befehle auf allen Hosts ausführen zu lassen, müssen Sie zunächst Folgendes ausführen:

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

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

Sobald diese definiert sind, führen Sie den Befehl in der Befehlszeile aus:

fab PROD deploy:1.5

Was führt die Bereitstellungsaufgabe auf allen in der PROD-Funktion aufgelisteten Servern aus, da die env.hosts vor dem Ausführen der Aufgabe festgelegt werden?


Angenommen, die Bereitstellung auf dem ersten Host hat funktioniert, die auf dem zweiten ist jedoch fehlgeschlagen. Wie kann ich sie nur auf dem zweiten Host erneut ausführen?
Nr.

2

Sie können zuweisen env.hoststring vor dem Ausführen einer Unteraufgabe zuweisen. Weisen Sie dieser globalen Variablen in einer Schleife zu, wenn Sie über mehrere Hosts iterieren möchten.

Leider ist Stoff für Sie und mich nicht für diesen Anwendungsfall ausgelegt. Überprüfen Sie die mainFunktion unter http://github.com/bitprophet/fabric/blob/master/fabric/main.py, um zu sehen, wie sie funktioniert.


2

Hier ist ein weiteres "Summersault" -Muster, das die fab my_env_1 my_commandVerwendung ermöglicht :

Mit diesem Muster müssen wir Umgebungen nur einmal mithilfe eines Wörterbuchs definieren. env_factoryErstellt Funktionen basierend auf den Schlüsselnamen von ENVS. Ich habe ENVSein eigenes Verzeichnis und eine eigene Datei erstellt secrets.config.py, um die Konfiguration vom Fabric-Code zu trennen.

Der Nachteil ist, dass, wie geschrieben, das Hinzufügen des @taskDekorateurs es brechen wird .

Hinweise: Wir verwenden def func(k=k):anstelle def func():der Fabrik wegen verspäteter Bindung . Wir erhalten das laufende Modul mit dieser Lösung und patchen es, um die Funktion zu definieren.

secret.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

Die Verwendung von Rollen wird derzeit als die "richtige" und "richtige" Methode angesehen und ist das, was Sie tun sollten.

Das heißt, wenn Sie wie die meisten von dem sind, was Sie "möchten" oder "wünschen", ist die Fähigkeit, eine "verdrehte Syster" durchzuführen oder Zielsysteme im laufenden Betrieb zu wechseln.

Nur zu Unterhaltungszwecken (!) Veranschaulicht das folgende Beispiel, was viele für ein riskantes und dennoch irgendwie durchaus befriedigendes Manöver halten, das ungefähr so ​​aussieht:

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

Dann laufen:

fab perform_sumersault
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.