Jak ustawić hosty docelowe w pliku Fabric


107

Chcę używać Fabric do wdrażania kodu mojej aplikacji internetowej na serwerach deweloperskich, pomostowych i produkcyjnych. Mój plik fab:

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

Przykładowe dane wyjściowe:

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

Kiedy tworzę set_hosts()zadanie, jak pokazano w dokumentacji Fabric , env.hosts jest ustawiany poprawnie. Jednak nie jest to opłacalna opcja, podobnie jak dekorator. Przekazywanie hostów w wierszu poleceń ostatecznie doprowadziłoby do powstania jakiegoś skryptu powłoki, który wywołuje plik fabfile, wolałbym, aby jedno narzędzie wykonało zadanie poprawnie.

W dokumentacji Fabric jest napisane, że „env.hosts jest po prostu obiektem listy Pythona”. Z moich obserwacji wynika, że ​​to po prostu nieprawda.

Czy ktoś może wyjaśnić, co się tutaj dzieje? Jak ustawić hosta do wdrożenia?


Mam ten sam problem, czy znalazłeś jakieś rozwiązanie?
— Martin M.

aby uruchomić to samo zadanie na wielu serwerach, użyj opcji „fab -H staging-server, production-server deploy” ... więcej w mojej odpowiedzi poniżej: stackoverflow.com/a/21458231/26510
— Brad Parks,


Ta odpowiedź nie dotyczy tkaniny 2+. Jeśli ktoś bardziej zaznajomiony z konwencjami Stackoverflow mógłby edytować pytanie lub tytuł pytania, aby odnosić się do tkaniny 1, może to być pomocne.
— Jonathan Berger

Odpowiedzi:


128

Robię to, deklarując rzeczywistą funkcję dla każdego środowiska. Na przykład:

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

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

def deploy():
    ...

Korzystając z powyższych funkcji, wpisałbym następujące polecenie, aby wdrożyć w moim środowisku testowym:

fab test deploy

... i następujące elementy do wdrożenia do produkcji:

fab prod deploy

Zaletą robienia tego w ten sposób jest to, że funkcji testi prodmożna używać przed każdą funkcją fab, a nie tylko wdrażaniem. Jest niesamowicie przydatny.


10
Ze względu na błąd w materiale ( code.fabfile.org/issues/show/138#change-1497 ) lepiej jest uwzględnić użytkownika w ciągu znaków hosta (np. Produser@prod.server.com) zamiast ustawiać env.user.
— Michaił Korobow

1
Miałem ten sam problem i to wydaje się najlepszym rozwiązaniem. Definiuję hostów, użytkownika i wiele innych ustawień w pliku YAML, który jest ładowany przez funkcje dev () i prod (). (Aby móc ponownie użyć tego samego skryptu Fabric do podobnych projektów.)
— Christian Davén

@MikhailKorobov: Kiedy kliknąłem twój link, zobaczyłem „ Witamy w nginx! ”. Wszystkie żądania do code.fabfile.orgdomeny mają podobne odpowiedzi.
— Tadeck

Tak, wygląda na to, że wszystkie błędy zostały przeniesione na github.
— Michaił Korobow

2
Niestety wygląda na to, że to już nie działa - Fabric nie będzie uruchamiał zadań bez już zdefiniowanych env.hosts i nie będzie uruchamiał funkcji w fab A B Cstylu bez zdefiniowania ich jako zadań.
— DNelson

77

Użyj 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')

Wybierz rolę za pomocą -R:

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

7
Lub jeśli zadanie jest zawsze uruchamiane w tej samej roli, możesz użyć dekoratora @roles () w zadaniu.
— Tom

2
Wygląda na to, że roledefs jest lepszym rozwiązaniem niż definiowanie ich w oddzielnych zadaniach.
— Ehtesh Choudhury

Czy ktoś wie, jak mogę dołączyć hasło do podanej nazwy użytkownika w roledef? Kolejny wpis w słowniku 'password': 'some_password'wydaje się być ignorowany i prowadzi do zachęty w czasie wykonywania.
— Dirk

@Dirk możesz użyć env.passwords, który jest słownikiem zawierającym użytkownik + host + port jako klucz i hasło jako wartość. Np. Env.passwords = {'user @ host: 22': 'password'}
— Jonathan

49

Oto prostsza wersja odpowiedzi serverhorror :

from fabric.api import settings

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

2
Zgodnie z dokumentacją menedżer kontekstu ustawień służy do zastępowania envzmiennych, a nie do ich początkowego ustawiania. Myślę, że używanie roledefs , jak sugeruje thomie, jest bardziej odpowiednie do definiowania hostów, takich jak stage, dev i test.
— Tony

21

Utknąłem na tym sam, ale w końcu to rozgryzłem. Po prostu nie możesz ustawić konfiguracji env.hosts z poziomu zadania. Każde zadanie jest wykonywane N razy, raz dla każdego określonego hosta, więc ustawienie zasadniczo wykracza poza zakres zadania.

Patrząc na swój kod powyżej, możesz po prostu zrobić to:

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

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

def deploy():
    # do stuff...

Co wydaje się, że zrobi to, co zamierzasz.

Możesz też napisać niestandardowy kod w zakresie globalnym, który ręcznie analizuje argumenty i ustawia env.hosts przed zdefiniowaniem funkcji zadania. Z kilku powodów tak właśnie skonfigurowałem mój.


Znalazłem sposób from fabric.api import env:; env.host_string = "dev"
— Roman

18

Od fab 1.5 jest to udokumentowany sposób na dynamiczne ustawianie hostów.

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

Cytat z poniższego dokumentu.

Korzystanie z funkcji wykonaj z dynamicznie ustawianymi listami hostów

Typowym przypadkiem użycia średnio-zaawansowanego Fabric dla Fabric jest parametryzacja wyszukiwania listy hostów docelowych w czasie wykonywania (gdy użycie ról nie wystarcza). execute może uczynić to niezwykle prostym, na przykład:

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. Wiele naprawdę dobrych odpowiedzi na dole strony.
— Matt Montag,

10

W przeciwieństwie do niektórych innych odpowiedzi, to jest możliwe, aby zmodyfikować envzmienne środowiskowe w ramach zadania. Jednak envbędzie to używane tylko do kolejnych zadań wykonywanych przy użyciu fabric.tasks.executefunkcji.

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

Bez zawijania pod-zadań execute(...), zostaną użyte envustawienia na poziomie modułu lub cokolwiek zostanie przekazane z fabCLI.


To najlepsza odpowiedź, jeśli chcesz dynamicznie ustawiać env.hosts.
— JahMyst

9

Musisz host_stringpodać przykład:

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łodkie. W innej odpowiedzi zamieściłem prostszą wersję kodu.
— tobych

9

Aby wyjaśnić, dlaczego to w ogóle problem. Polecenie fab wykorzystuje strukturę biblioteki do wykonywania zadań na listach hostów. Jeśli spróbujesz zmienić listę hostów w zadaniu, zasadniczo próbujesz zmienić listę podczas iteracji po niej. Lub w przypadku, gdy nie masz zdefiniowanych żadnych hostów, zapętlaj pustą listę, w której kod, w którym ustawiono pętlę listy, nigdy nie jest wykonywany.

Użycie env.host_string jest obejściem tego zachowania tylko dlatego, że określa bezpośrednio w funkcjach, z którymi hostami mają się łączyć. Powoduje to pewne problemy polegające na tym, że przerobisz pętlę wykonywania, jeśli chcesz mieć kilka hostów do wykonania.

Najprostszym sposobem, w jaki ludzie udostępniają możliwość ustawiania hostów w czasie wykonywania, jest utrzymywanie zapełniania środowiska środowiska jako odrębnego zadania, które konfiguruje wszystkie ciągi znaków hosta, użytkowników itp. Następnie uruchamiają zadanie wdrażania. To wygląda tak:

fab production deploy

lub

fab staging deploy

Tam, gdzie inscenizacja i produkcja są podobne do zadań, które powierzyłeś, ale same nie nazywają następnego zadania. Powodem, dla którego musi tak działać, jest to, że zadanie musi zakończyć się i wyjść z pętli (hostów, w przypadku env Brak, ale w tym momencie jest to pętla jednego), a następnie pętla hosty (teraz zdefiniowane przez poprzednie zadanie) na nowo.


3

Musisz zmodyfikować env.hosts na poziomie modułu, a nie w ramach funkcji zadania. Popełniłem ten sam błąd.

from fabric.api import *

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

env.hosts = _get_hosts()

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

3

To jest bardzo proste. Wystarczy zainicjować zmienną env.host_string i wszystkie poniższe polecenia zostaną wykonane na tym hoście.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

Jestem zupełnie nowy w strukturze, ale aby uzyskać Fabric do uruchamiania tych samych poleceń na wielu hostach (np. Aby wdrożyć na wielu serwerach, w jednym poleceniu), możesz uruchomić:

fab -H staging-server,production-server deploy 

gdzie staging-serwer i produkcyjno-serwer są 2 serwery chcesz uruchomić akcję wdrożyć przeciwko. Oto prosty plik fabfile.py, który wyświetli nazwę systemu operacyjnego. Zauważ, że plik fabfile.py powinien znajdować się w tym samym katalogu, w którym uruchamiasz polecenie fab.

from fabric.api import *

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

Działa to przynajmniej z tkaniną 1.8.1.


3

Tak więc, aby ustawić hosty i uruchomić polecenia na wszystkich hostach, musisz zacząć od:

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

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

Po ich zdefiniowaniu uruchom polecenie w wierszu poleceń:

fab PROD deploy:1.5

Co spowoduje uruchomienie zadania wdrażania na wszystkich serwerach wymienionych w funkcji PROD, ponieważ ustawia env.hosts przed uruchomieniem zadania.


Załóżmy, że wdrożenie na pierwszym hoście zadziałało, ale jedno na drugim nie powiodło się, jak mam to zrobić ponownie tylko na drugim?
— nr


2

Oto kolejny wzorzec „summersault”, który umożliwia rozszerzenie fab my_env_1 my_command użycie:

W przypadku tego wzorca wystarczy, że raz zdefiniujemy środowiska za pomocą słownika. env_factorytworzy funkcje na podstawie nazw kluczy ENVS. Umieściłem ENVSwe własnym katalogu i plikusecrets.config.py aby oddzielić konfigurację od kodu tkaniny.

Wadą jest to, że jak napisano, dodanie @taskdekoratora spowoduje przerwanie .

Uwagi: Używamy def func(k=k):zamiast def func():w fabryce z powodu późnego wiązania . Otrzymujemy działający moduł z tym rozwiązaniem i poprawiamy go, aby zdefiniować funkcję.

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

Używanie ról jest obecnie uważane za „właściwy” i „prawidłowy” sposób robienia tego i jest tym, co „powinieneś” robić.

To powiedziawszy, jeśli jesteś podobny do większości tego, co „chciałbyś” lub „pragniesz”, to umiejętność wykonywania „skręconej potyczki” lub przełączania systemów docelowych w locie.

Tak więc, wyłącznie dla celów rozrywkowych (!), Poniższy przykład ilustruje, co wielu może uznać za ryzykowny, a jednocześnie w pełni satysfakcjonujący manewr, który wygląda mniej więcej tak:

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

Następnie bieganie:

fab perform_sumersault
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.