Як встановити цільових хостів у файлі Fabric


107

Я хочу використовувати Fabric для розгортання коду веб-додатків на серверах розробки, постановки та виробництва. Мій 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

Вибірка зразка:

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

Коли я створюю set_hosts()завдання, як показано в документах Fabric , env.hosts встановлюється правильно. Однак це не є життєздатним варіантом, і не є декоратором. Передача хостів у командному рядку в кінцевому підсумку призведе до якогось сценарію оболонки, який викликає fabfile, я вважаю за краще один інструмент виконати роботу належним чином.

У документах Fabric сказано, що "env.hosts - це просто об'єкт списку Python". З моїх спостережень, це просто неправда.

Хтось може пояснити, що тут відбувається? Як я можу налаштувати хоста для розгортання?


У мене така ж проблема, ви знайшли рішення для цього?
Мартін М.

щоб виконати одне і те ж завдання на кількох серверах, використовуйте "fab -H staging-сервер, розгортання виробництва-сервера" ... докладніше у моїй відповіді нижче: stackoverflow.com/a/21458231/26510
Бред Паркс


Ця відповідь не стосується тканин 2+. Якщо хтось більш знайомий з умовами Stackoverflow може змінити питання або назву питання, щоб посилатися на тканину 1, це може бути корисно.
Джонатан Бергер

Відповіді:


128

Я роблю це, оголошуючи фактичну функцію для кожного середовища. Наприклад:

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

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

def deploy():
    ...

Використовуючи вищезазначені функції, я б ввів наступне для розгортання у моєму тестовому середовищі:

fab test deploy

... і наступне для впровадження у виробництво:

fab prod deploy

Приємно в тому, що робити це таким чином, це те, що функції testта prodфункції можна використовувати перед будь-якою функцією Fab, а не просто розгортати. Це неймовірно корисно.


10
Через помилку в тканині ( code.fabfile.org/isissue/show/138#change-1497 ) краще включити користувача до рядка хоста (наприклад, produser@prod.server.com), а не встановлювати env.user.
Михайло Коробов

1
У мене була така ж проблема, і це здається найкращим рішенням. Я визначаю хости, користувача та безліч інших параметрів у файлі YAML, який завантажується функціями dev () та prod (). (Щоб я міг повторно використовувати той самий сценарій Fabric для подібних проектів.)
Крістіан Давен

@MikhailKorobov: Коли я перейшов за вашим посиланням, побачив " Ласкаво просимо до nginx! ". На всі запити code.fabfile.orgдомену є такі відповіді.
Тадек

Так, здається, всі помилки були перенесені на github.
Михайло Коробов

2
На жаль, схоже, що це більше не працює - тканина не виконуватиме завдання без уже визначених env.hosts, і не запускатиме функції у fab A B Cстилі, не визначаючи їх як завдання.
DNelson

77

Використовуйте 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')

Виберіть роль за допомогою -R:

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

7
Або якщо завдання завжди виконується на одній і тій же ролі, ви можете використовувати декоратор @roles ().
Том

2
Здається, що roledefs є кращим рішенням, ніж їх визначення в окремих завданнях.
Ехтеш Чудхурі

Хтось знає, як я можу включити пароль для вказаного імені користувача у roledef? Подальший запис у словнику, 'password': 'some_password'здається, ігнорується і призводить до підказу під час виконання.
Дірк

@Dirk ви можете використовувати env.passwords, що є словником, що містить користувач + хост + порт як ключ і пароль як значення. Напр. Env.passwords = {'user @ host: 22': 'password'}
Джонатан

49

Ось більш простий варіант відповіді на помилку сервера :

from fabric.api import settings

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

2
Згідно з документами , менеджер контексту налаштувань призначений для перевизначенняenv змінних, а не для їх початкового встановлення. Я думаю, що використання roledefs , як запропонував Томі, є більш підходящим для визначення хостів, таких як етап, розробник та тест.
Тоні

21

Я сам зациклювався на цьому, але нарешті зрозумів це. Ви просто не можете встановити налаштування env.hosts зсередини завдання. Кожне завдання виконується N разів, один раз для кожного заданого хоста, тому налаштування є принципово поза рамками завдання.

Переглянувши свій код вище, ви можете просто зробити це:

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

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

def deploy():
    # do stuff...

Що здається, це зробить те, що ви маєте намір.

Або ви можете написати якийсь спеціальний код у глобальній області, який аналізує аргументи вручну та встановлює env.hosts до того, як буде визначена ваша функція завдання. З кількох причин саме таким чином я налаштував своє.


Знайшов спосіб from fabric.api import env:; env.host_string = "dev"
Роман

18

З файлу 1.5 це документально підтверджений спосіб динамічного встановлення хостів.

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

Цитата з документа нижче.

Використання Execute з динамічно встановленими списками хостів

Загальний випадок використання проміжного до просуваного використання для Fabric - це параметризувати пошук цільового списку хостів під час виконання (коли використання ролей недостатньо). Execute може зробити це надзвичайно просто, наприклад:

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. Тут ви знайдете багато справді хороших відповідей внизу сторінки.
Метт Монтаг

10

В відміну від деяких інших відповідей, то є можна змінити envзмінні середовища в рамках завдання. Однак це envбуде використовуватися лише для наступних завдань, виконаних за допомогою 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

Без обгортання підзадач будуть використані execute(...)ваші envналаштування рівня модуля або все, що передано з fabCLI.


Це найкраща відповідь, якщо ви хочете динамічно задавати env.hosts.
JahMyst

9

Вам потрібно встановити host_stringприклад:

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

Солодке. Тут я розмістив простішу версію коду в іншій відповіді.
тобич

9

Щоб пояснити, чому це навіть питання. Команда fab використовує тканину бібліотеки для виконання завдань у списках хостів. Якщо ви спробуєте змінити список хостів всередині завдання, ви, по суті, намагаєтесь змінити список, переглядаючи його. Або у випадку, коли у вас не визначено хостів, переведіть цикл на порожній список, де код, на якому ви встановили список, щоб ніколи не виконуватися.

Використання env.host_string - це вирішення такої поведінки лише в тому, що воно вказує безпосередньо на функції, з якими хости можуть підключитися. Це спричиняє деякі проблеми в тому, що ви будете переробляти цикл виконання, якщо хочете виконати кілька хостів.

Найпростіший спосіб, коли люди роблять можливість встановлювати хости під час виконання, - це зберігати env-популяризацію як окрему задачу, яка встановлює всі хост-рядки, користувачів тощо. Потім вони виконують завдання розгортання. Це виглядає приблизно так:

fab production deploy

або

fab staging deploy

Де постановка та виробництво - це як завдання, яке ви поставили, але наступне завдання вони самі не називають. Причина, по якій він повинен працювати так, полягає в тому, що завдання має закінчитися і вирватися з циклу (хостів, у випадку env None, але це петля одного в цьому пункті), а потім перевести цикл хости (зараз визначені попереднім завданням) заново.


3

Вам потрібно змінити env.hosts на рівні модуля, а не в межах функції завдання. Я зробив ту саму помилку.

from fabric.api import *

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

env.hosts = _get_hosts()

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

3

Це дуже просто. Просто ініціалізуйте змінну env.host_string, і всі наступні команди будуть виконані на цьому хості.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

Я абсолютно новачок у тканині, але щоб змусити тканину виконувати однакові команди на декількох хостах (наприклад, для розгортання на декількох серверах в одній команді), ви можете запустити:

fab -H staging-server,production-server deploy 

де staging-server і server -сервер - це 2 сервери, на яких потрібно запустити дію розгортання. Ось простий fabfile.py, який відображатиме ім’я ОС. Зауважте, що fabfile.py повинен знаходитися в тому самому каталозі, що і для запуску команди fab.

from fabric.api import *

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

Це працює якнайменше з тканиною 1.8.1.


3

Отже, для того, щоб встановити хости та виконати команди за всіма хостами, потрібно почати з:

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

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

Після того, як вони визначені, запустіть команду в командному рядку:

fab PROD deploy:1.5

Що буде виконувати завдання розгортання на всіх серверах, перелічених у функції PROD, оскільки він встановлює env.hosts перед запуском завдання.


Припустимо, розгортання на першому хості спрацювало, але на другому не вдалося, як це зробити знов лише на другому?
нос

2

Ви можете призначити його env.hoststringперед виконанням підзадачі. Призначте цю глобальну змінну в циклі, якщо ви хочете перейти на кілька хостів.

На жаль для вас і мене, тканина не призначена для цього випадку використання. Перегляньте mainфункцію на веб- сторінці http://github.com/bitprophet/fabric/blob/master/fabric/main.py, щоб побачити, як вона працює.


2

Ось ще одна схема "підсумків", яка дозволяє fab my_env_1 my_commandвикористовувати:

За цією схемою нам залишається лише одночасно визначати середовища за допомогою словника. env_factoryстворює функції на основі ключових імен ENVS. Я вкладаю ENVSу свій каталог і файл, secrets.config.pyщоб відокремити конфігурацію від тканинного коду.

Недолік полягає в тому, що, як написано, додавання @taskдекоратора це порушить .

Примітки: ми використовуємо def func(k=k):замість def func():фабрики через пізнє зв’язування . З цим рішенням ми отримуємо запущений модуль і виправляємо його, щоб визначити функцію.

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

Використання ролей в даний час вважається "правильним" і "правильним" способом цього зробити, і це ви повинні "робити".

Це означає, що якщо вам подобається більшість того, що вам "хотілося б" або "бажання", це можливість виконувати "скручену систему" або перемикати цільові системи на льоту.

Тож для розважальних цілей (!) Наступний приклад ілюструє те, що багато хто може вважати ризикованим, але все-таки всебічно задовольняючим маневром, який іде приблизно так:

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

Потім працює:

fab perform_sumersault
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.