如何在Fabric文件中设置目标主机

时间:2010-02-24 14:45:11

标签: python host fabric

我想使用Fabric将我的Web应用程序代码部署到开发,登台和生产服务器。我的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 docs所示,env.hosts设置正确。但是,这不是一个可行的选择,也不是装饰者。在命令行上传递主机最终会导致调用fabfile的某种shell脚本,我宁愿让一个工具正常工作。

它在Fabric文档中说'env.hosts只是一个Python列表对象'。根据我的观察,这根本不是真的。

任何人都可以解释这里发生了什么吗?如何设置要部署的主机?

15 个答案:

答案 0 :(得分: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

以这种方式执行此操作的好处是testprod函数可以在任何 fab函数之前使用,而不仅仅是部署。它非常有用。

答案 1 :(得分:75)

使用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'
...

答案 2 :(得分:49)

以下是serverhorrors answer的简单版本:

from fabric.api import settings

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

答案 3 :(得分:21)

自己被困在这上面了,但终于明白了。您只需无法 任务中设置env.hosts配置。每个任务执行N次,每个指定的主机执行一次,因此设置基本上超出了任务范围。

查看上面的代码,您可以这样做:

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

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

def deploy():
    # do stuff...

看起来它会像你想要的那样。

或者您可以在全局范围内编写一些自定义代码来手动解析参数,并在定义任务函数之前设置env.hosts。出于几个原因,这实际上就是我如何设置我的。

答案 4 :(得分:18)

从fab 1.5开始,这是一种动态设置主机的文档化方法。

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

从以下文档中引用。

  

将execute与动态设置主机列表一起使用

     

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)

答案 5 :(得分: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 CLI传递的任何设置。

答案 6 :(得分: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, ))

答案 7 :(得分:9)

解释为什么它甚至是一个问题。命令 fab 正在利用结构库来运行主机列表上的任务。如果您尝试更改任务中的主机列表,那么在迭代它时,您实际上是在尝试更改列表。或者,在没有定义主机的情况下,循环遍历一个空列表,在该列表中,您设置要循环的列表的代码永远不会被执行。

使用env.host_string是解决此问题的唯一方法,因为它直接指定要连接的主机的功能。如果您想要执行多个主机,这会导致一些问题,即您将重新构建执行循环。

人们在运行时设置主机的最简单方法是将env作为一项独特的任务保存,设置所有主机字符串,用户等。然后他们运行部署任务。它看起来像这样:

fab production deploy

fab staging deploy

分段和制作就像你给出的任务一样,但他们不会自己调用下一个任务。它必须像这样工作的原因是,任务必须完成,并且突破循环(主机,在env情况下为None,但在那时它是一个循环),然后循环结束主机(现在由前面的任务定义)重新开始。

答案 8 :(得分: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 ...

答案 9 :(得分:3)

这很简单。只需初始化env.host_string变量,所有以下命令都将在此主机上执行。

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

答案 10 :(得分:3)

我对Fabric非常陌生,但是为了让Fabric能够在多个主机上运行相同的命令(例如,在一个命令中部署到多个服务器),您可以运行:

fab -H staging-server,production-server deploy 

其中登台服务器生产服务器是您要对其运行部署操作的2台服务器。这是一个简单的fabfile.py,它将显示操作系统名称。请注意,fabfile.py应该与运行fab命令的目录位于同一目录中。

from fabric.api import *

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

至少适用于Fabric 1.8.1。

答案 11 :(得分: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。

答案 12 :(得分:2)

您可以在执行子任务之前分配给env.hoststring。如果要迭代多个主机,请在循环中分配给此全局变量。

不幸的是,对于您和我来说,面料不是为这个用例而设计的。查看http://github.com/bitprophet/fabric/blob/master/fabric/main.py处的main功能,了解其工作原理。

答案 13 :(得分:2)

这是另一个“summersault”模式,支持fab my_env_1 my_command用法:

使用这种模式,我们只需要使用字典一次定义环境。 env_factory根据ENVS的键名创建函数。我将ENVS放在自己的目录中,并将secrets.config.py文件与结构代码分开配置。

缺点是,如上所述,添加@task装饰器将break it

注意:由于late binding,我们在工厂中使用def func(k=k):代替def func():。我们使用this solution获取运行模块并对其进行修补以定义函数。

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

答案 14 :(得分: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