Как установить целевые узлы в файле Fabric

Я хочу использовать Fabric для развертывания кода веб-приложения на серверах разработки, промежуточного уровня и производства. Мой файл:

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 docs, env.hosts устанавливается правильно. Однако это не жизнеспособный вариант, и не декоратор. Передача хостов в командной строке в конечном итоге приведет к какой-то оболочке script, которая вызывает fabfile, я бы предпочел, чтобы один инструмент правильно выполнял работу.

В документах Fabric говорится, что "env.hosts - это просто объект списка Python". Из моих наблюдений это просто неверно.

Может ли кто-нибудь объяснить, что здесь происходит? Как установить хост для развертывания?

Ответ 1

Я делаю это, объявляя фактическую функцию для каждой среды. Например:

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, а не только для развертывания. Это невероятно полезно.

Ответ 2

Используйте roledefs

from fabric.api import env, run

env.roledefs = {
    'test': ['localhost'],
    'dev': ['[email protected]'],
    'staging': ['[email protected]'],
    'production': ['[email protected]']
} 

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

Выберите роль с -R:

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

Ответ 3

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

from fabric.api import settings

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

Ответ 4

Застрял на этом сам, но, наконец, понял это. Вы просто не можете установить конфигурацию env.hosts из задачи. Каждая задача выполняется N раз, один раз для каждого указанного узла, поэтому настройка в основном за пределами области действия.

Посмотрев на свой код выше, вы можете просто сделать это:

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

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

def deploy():
    # do stuff...

Кажется, что он будет делать то, что вы намереваетесь.

Или вы можете написать какой-то пользовательский код в глобальной области, который анализирует аргументы вручную и устанавливает env.hosts до того, как будет определена ваша функция задачи. По нескольким причинам, на самом деле, как я установил мой.

Ответ 5

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

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

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

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

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

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)

Ответ 6

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

Ответ 7

Вам нужно установить 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, ))

Ответ 8

Объяснить, почему это даже проблема. Команда fab использует структуру библиотеки для запуска задач в списках хостов. Если вы попытаетесь изменить список хостов внутри задачи, вы эссенциально пытаетесь изменить список во время итерации по нему. Или в случае, если у вас нет определенных хостов, выполните цикл над пустым списком, где код, в который вы устанавливаете цикл, не выполняется.

Использование env.host_string - это работа для этого поведения только в том случае, если он напрямую указывает на функции, с которыми соединяются хосты. Это вызывает некоторые проблемы в том, что вы будете перерабатывать цикл выполнения, если хотите, чтобы количество хостов выполнялось.

Самый простой способ заставить людей устанавливать хосты во время выполнения, заключается в том, чтобы сохранить env populatiing как отдельную задачу, которая устанавливает все строки хоста, пользователей и т.д. Затем они запускают задачу развертывания. Это выглядит так:

fab production deploy

или

fab staging deploy

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

Ответ 9

Вам нужно изменить env.hosts на уровне модуля, а не в функции задачи. Я сделал ту же ошибку.

from fabric.api import *

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

env.hosts = _get_hosts()

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

Ответ 10

Это очень просто. Просто инициализируйте переменную env.host_string, и на этом хосте будут выполняться все следующие команды.

from fabric.api import env, run

env.host_string = '[email protected]'

def foo:
    run("hostname -f")

Ответ 11

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

fab -H staging-server,production-server deploy 

где сервер-сервер и производственный сервер - это 2 сервера, на которых вы хотите запустить действие развертывания. Вот простой файл fabfile.py, который отобразит имя ОС. Обратите внимание, что файл fabfile.py должен находиться в том же каталоге, где вы запускаете команду fab.

from fabric.api import *

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

Это работает с тканью 1.8.1, по крайней мере.

Ответ 12

Итак, чтобы установить хосты и выполнить команды на всех хостах, вы должны начать с:

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 перед запуском задачи.

Ответ 13

Вы можете назначить env.hoststring перед выполнением подзадачи. Назначьте эту глобальную переменную в цикле, если вы хотите перебирать несколько хостов.

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

Ответ 14

Вот еще один шаблон "summersault", который позволяет использовать fab my_env_1 my_command:

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

Недостатком является то, что, как написано, добавление декоратора @task будет сломать его.

Примечания. Мы используем def func(k=k): вместо def func(): в factory из-за позднего связывания. Мы получаем текущий модуль с этим решением и исправляем его для определения функции.

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

Ответ 15

Использование ролей в настоящее время считается "правильным" и "правильным" способом сделать это, и это то, что вы "должны" делать.

Тем не менее, если вы похожи на большинство того, что вам "хотелось бы" или "желание", это способность выполнять "скрученную систему" ​​или переключение целевых систем "на лету".

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

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