Как установить целевые хосты в файле 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-server, production-server deploy» ... подробнее в моем ответе ниже: 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функции могут быть использована до любой потрясающей функции, а не просто развернуть. Это невероятно полезно.


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

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

@MikhailKorobov: Когда я перешел по вашей ссылке, то увидел « Добро пожаловать в nginx! ». На все запросы к code.fabfile.orgдомену есть такие ответы.
Tadeck

Да, похоже, все ошибки перенесены на github.
Михаил Коробов

2
К сожалению, похоже, что это больше не работает - Fabric не будет запускать задачи без уже определенного env.hosts и не будет запускать функции в fab A B Cстиле, если они не определены как задачи.
DNelson 02

77

Использовать ролевые определения

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 () для задачи.
Tom

2
Похоже, что ролевые определения - лучшее решение, чем их определение в отдельных задачах.
Ehtesh Choudhury

Кто-нибудь знает, как я могу включить пароль для указанного имени пользователя в roledef? 'password': 'some_password'Кажется, что следующая словарная запись игнорируется и приводит к запросу во время выполнения.
Dirk

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

49

Вот более простая версия ответа serverhorror :

from fabric.api import settings

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

2
Согласно документации , диспетчер контекста настроек предназначен для переопределения envпеременных, а не для их первоначальной установки. Я думаю, что использование roledefs , как предположил Томи, более подходит для определения хостов, таких как stage, dev и test.
Тони

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

Начиная с fab 1.5 это документированный способ динамической установки хостов.

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

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

Использование выполнения с динамически настроенными списками хостов

Распространенный промежуточный и продвинутый вариант использования 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или все, что передается из fabинтерфейса командной строки.


Это лучший ответ, если вы хотите динамически устанавливать 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, ))

Сладкий. Я разместил здесь более простую версию кода в другом ответе.
tobych

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 и production-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():на заводе из-за позднего связывания . Мы получаем работающий модуль с этим решением и исправляем его, чтобы определить функцию.

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