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


107

我想使用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:

当我创建Fabric文档中set_hosts()所示的任务时,env.hosts设置正确。但是,这不是一个可行的选择,装饰器也不是。在命令行上传递主机最终会导致某种形式的shell脚本调用fabfile,我更愿意使用一个工具来正确完成这项工作。

它在Fabric文档中说“ env.hosts仅仅是Python列表对象”。根据我的观察,这根本不是事实。

谁能解释这是怎么回事?如何设置要部署到的主机?


我有同样的问题,您找到任何解决方案吗?
Martin M.

要在多台服务器上运行同一任务,请使用“ fab -H分段服务器,生产服务器部署” ...更多在我下面的答案中:stackoverflow.com/a/21458231/26510
Brad Parks 2014年


此答案不适用于结构2+。如果更熟悉Stackoverflow约定的人可以编辑问题或问题标题以引用结构1,则可能会有所帮助。
乔纳森·伯格

Answers:


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

这样做的好处是,testand prod函数可以在任何 fab函数之前使用,而不仅仅是部署。这是非常有用的。


10
由于结构中的错误(code.fabfile.org/issues/show/138#change-1497),最好将用户包含在主机字符串中(例如produser@prod.server.com),而不是设置env.user。
米哈伊尔·科罗波夫

1
我遇到了同样的问题,这似乎是最好的解决方案。我在由dev()和prod()函数加载的YAML文件中定义了主机,用户和许多其他设置。(这样我就可以在相同的项目中重复使用相同的Fabric脚本。)
ChristianDavén2011年

@MikhailKorobov:当我关注您的链接时,我看到了“ 欢迎使用nginx! ”。对code.fabfile.org域的所有请求都具有类似的响应。
塔德克2012年

是的,似乎所有错误都已迁移到github。
米哈伊尔·科罗波夫

2
不幸的是,它似乎不再起作用-结构在未定义env.hosts的情况下将无法运行任务,并且在fab A B C没有将其定义为任务的情况下无法以该样式运行功能。
DNelson 2015年

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()装饰器。
汤姆(Tom)

2
听起来,与在单独的任务中定义它们相比,roledefs是更好的解决方案。
Ehtesh Choudhury 2014年

有人知道我如何才能在中包含所提供的用户名的密码roledef吗?另一个字典条目'password': 'some_password'似乎被忽略了,并在运行时提示。
德克

@Dirk可以使用env.passwords,它是一个包含user + host + port作为密钥,而password作为值的字典。例如env.passwords = {'user @ host:22':'password'}
乔纳森(Jonathan),

49

这是serverhorror答案的简单版本:

from fabric.api import settings

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

2
根据文档,设置上下文管理器用于覆盖env变量,而不是用于初始设置。我认为,如thome所建议的那样,使用roledefs更适合定义主机,如stage,dev和test。
Tony

21

自己被卡住了,但终于想通了。你根本无法从设置env.hosts配置的任务。每个任务执行N次,对指定的每个Host执行一次,因此该设置基本上不在任务范围之内。

查看上面的代码,您可以简单地执行以下操作:

@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

引用下面的文档。

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

Fabric常见的中级到高级用例是在运行时对目标主机列表的参数化(当使用Roles不足时)。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, ))

甜。我在这里的另一个答案中发布了代码的简化版本。
tobych 2011年

9

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

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

我对Fabric完全陌生,但是要使Fabric在多个主机上运行相同的命令(例如,在一个命令中部署到多个服务器),可以运行:

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。


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

这是启用fab my_env_1 my_command用法的另一个“ summersault”模式:

使用这种模式,我们只需要使用字典一次定义环境。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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.