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

发布于 2024-08-22 18:26:07 字数 840 浏览 10 评论 0原文

我想使用 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 文档,env.hosts 设置正确。然而,这不是一个可行的选择,装饰器也不是。在命令行上传递主机最终会生成某种调用 fabfile 的 shell 脚本,我更希望使用一个工具来正确完成这项工作。

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

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

I want to use Fabric to deploy my web app code to development, staging and production servers. My 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

Sample output:

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

When I create a set_hosts() task as shown in the Fabric docs, env.hosts is set properly. However, this is not a viable option, neither is a decorator. Passing hosts on the command line would ultimately result in some kind of shell script that calls the fabfile, I would prefer having one single tool do the job properly.

It says in the Fabric docs that 'env.hosts is simply a Python list object'. From my observations, this is simply not true.

Can anyone explain what is going on here ? How can I set the host to deploy to ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

如何视而不见 2024-08-29 18:26:08

您需要设置 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, ))

You need to set host_string an example would be:

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, ))
无妨# 2024-08-29 18:26:08

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

使用 env.host_string 可以解决此行为,只是因为它直接向函数指定要连接的主机。这会导致一些问题,因为如果您想要在多个主机上执行,您将重新创建执行循环。

人们能够在运行时设置主机的最简单方法是将环境填充为一个不同的任务,该任务设置所有主机字符串、用户等。然后他们运行部署任务。它看起来像这样:

fab production deploy

fab staging deploy

其中登台和生产就像您给出的任务,但它们本身不会调用下一个任务。它必须像这样工作的原因是任务必须完成,并跳出循环(主机的循环,在 env 情况下为 None,但此时它是一个循环),然后让循环结束重新指定主机(现在由前面的任务定义)。

To explain why it's even an issue. The command fab is leveraging fabric the library to run the tasks on the host lists. If you try and change the host list inside a task, you're esentially attempting to change a list while iterating over it. Or in the case where you have no hosts defined, loop over an empty list where the code where you set the list to loop over is never executed.

The use of env.host_string is a work around for this behavior only in that it's specifying directly to the functions what hosts to connect with. This causes some issues in that you'll be remaking the execution loop if you want to have a number of hosts to execute on.

The simplest way the people make the ability to set hosts at run time, is to keep the env populatiing as a distinct task, that sets up all the host strings, users, etc. Then they run the deploy task. It looks like this:

fab production deploy

or

fab staging deploy

Where staging and production are like the tasks you have given, but they do not call the next task themselves. The reason it has to work like this, is that the task has to finish, and break out of the loop (of hosts, in the env case None, but it's a loop of one at that point), and then have the loop over the hosts (now defined by the preceding task) anew.

青芜 2024-08-29 18:26:08

您需要在模块级别修改 env.hosts,而不是在任务函数内。我也犯了同样的错误。

from fabric.api import *

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

env.hosts = _get_hosts()

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

You need to modify env.hosts at the module level, not within a task function. I made the same mistake.

from fabric.api import *

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

env.hosts = _get_hosts()

def your_task():
    ... your task ...
粉红×色少女 2024-08-29 18:26:08

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

from fabric.api import env, run

env.host_string = '[email protected]'

def foo:
    run("hostname -f")

It's very simple. Just initialize the env.host_string variable and all of the following commands will be executed on this host.

from fabric.api import env, run

env.host_string = '[email protected]'

def foo:
    run("hostname -f")
稳稳的幸福 2024-08-29 18:26:08

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

fab -H staging-server,production-server deploy 

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

from fabric.api import *

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

这至少适用于 Fabric 1.8.1。

I'm totally new to fabric, but to get fabric to run the same commands on multiple hosts (e.g. to deploy to multiple servers, in one command) you can run:

fab -H staging-server,production-server deploy 

where staging-server and production-server are 2 servers you want to run the deploy action against. Here's a simple fabfile.py that will display the OS name. Note that the fabfile.py should be in the same directory as where you run the fab command.

from fabric.api import *

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

This works with fabric 1.8.1 at least.

誰ツ都不明白 2024-08-29 18:26:08

因此,为了设置主机并在所有主机上运行命令,您必须首先:

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。

So, in order to set the hosts, and have the commands run across all the hosts, you have to start with:

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

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

Once those are defined, then run the command on the command line:

fab PROD deploy:1.5

What will run the deploy task across all of the servers listed in the PROD function, as it sets the env.hosts before running the task.

病女 2024-08-29 18:26:08

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

不幸的是,对于你我来说,fabric 并不是为这种用例而设计的。查看 http://github.com/bitprophet/fabric/main.py" 的 main 函数/github.com/bitprophet/fabric/blob/master/fabric/main.py 看看它是如何工作的。

You can assign to env.hoststring before executing a subtask. Assign to this global variable in a loop if you want to iterate over multiple hosts.

Unfortunately for you and me, fabric is not designed for this use case. Check out the main function at http://github.com/bitprophet/fabric/blob/master/fabric/main.py to see how it works.

漆黑的白昼 2024-08-29 18:26:08

这是另一种“翻跟斗”模式,可启用 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

Here's another "summersault" pattern that enables the fab my_env_1 my_command usage:

With this pattern, we only have to define environments one time using a dictionary. env_factory creates functions based on the keynames of ENVS. I put ENVS in its own directory and file secrets.config.py to separate config from the fabric code.

The drawback is that, as written, adding the @task decorator will break it.

Notes: We use def func(k=k): instead of def func(): in the factory because of late binding. We get the running module with this solution and patch it to define the function.

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
ㄖ落Θ余辉 2024-08-29 18:26:08

使用角色目前被认为是执行此操作的“适当”和“正确”方法,并且是您“应该”执行的操作。

也就是说,如果您像大多数人一样,您“想要”或“渴望”的是执行“扭曲系统”或动态切换目标系统的能力。

因此,仅出于娱乐目的(!)以下示例说明了许多人可能认为有风险但又完全令人满意的操作,如下所示:

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

Using roles is currently considered to be the "proper" and "correct" way of doing this and is what you "should" do it.

That said, if you are like most of what you "would like" or "desire" is the ability to perform a "twisted syster" or switching target systems on the fly.

So for entertainment purposes only (!) the following example illustrates what many might consider to a risky, and yet somehow thoroughly satisfying, manoeuvre that goes something like this:

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

Then running:

fab perform_sumersault
扬花落满肩 2024-08-29 18:26:07

我通过为每个环境声明一个实际函数来做到这一点。例如:

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 函数之前使用,而不仅仅是部署。它非常有用。

I do this by declaring an actual function for each environment. For example:

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

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

def deploy():
    ...

Using the above functions, I would type the following to deploy to my test environment:

fab test deploy

...and the following to deploy to production:

fab prod deploy

The nice thing about doing it this way is that the test and prod functions can be used before any fab function, not just deploy. It is incredibly useful.

东京女 2024-08-29 18:26:07

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

Use 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')

Choose role with -R:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...
哆啦不做梦 2024-08-29 18:26:07

这是 serverhorror 的答案的简单版本:

from fabric.api import settings

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

Here's a simpler version of serverhorror's answer:

from fabric.api import settings

def mystuff():
    with settings(host_string='192.0.2.78'):
        run("hostname -f")
罗罗贝儿 2024-08-29 18:26:07

我自己也被困在这个问题上,但最后想通了。您根本无法在任务内设置 env.hosts 配置。每个任务都会执行 N 次,每个指定的主机执行一次,因此该设置基本上超出了任务范围。

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

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

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

def deploy():
    # do stuff...

这似乎可以实现您的预​​期。

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

Was stuck on this myself, but finally figured it out. You simply can't set the env.hosts configuration from within a task. Each task is executed N times, once for each Host specified, so the setting is fundamentally outside of task scope.

Looking at your code above, you could simply do this:

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

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

def deploy():
    # do stuff...

Which seems like it would do what you're intending.

Or you can write some custom code in the global scope that parses the arguments manually, and sets env.hosts before your task function is defined. For a few reasons, that's actually how I've set mine up.

善良天后 2024-08-29 18:26:07

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

Since fab 1.5 this is a documented way to dynamically set hosts.

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

Quote from the doc below.

Using execute with dynamically-set host lists

A common intermediate-to-advanced use case for Fabric is to
parameterize lookup of one’s target host list at runtime (when use of
Roles does not suffice). execute can make this extremely simple, like
so:

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)
同尘 2024-08-29 18:26:07

与其他一些答案相反,可以修改任务中的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 传递的任何内容都将是用过的。

Contrary to some other answers, it is possible to modify the env environment variables within a task. However, this env will only be used for subsequent tasks executed using the fabric.tasks.execute function.

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

Without wrapping sub-tasks in execute(...), your module-level env settings or whatever is passed from the fab CLI will be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文