ファブリックファイルでターゲットホストを設定する方法


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 docsにset_hosts()示されているようにタスクを作成すると、env.hostsが正しく設定されます。ただし、これは実行可能なオプションではなく、デコレータでもありません。コマンドラインでホストを渡すと、最終的にはfabfileを呼び出すある種のシェルスクリプトが生成されます。1つのツールで適切に処理を行うことをお勧めします。

それはファブリックのドキュメントで「env.hostsは単なるPythonリストオブジェクト」であると述べています。私の観察から、これは単に真実ではありません。

誰かがここで何が起こっているのか説明できますか?デプロイするホストを設定するにはどうすればよいですか?


私も同じ問題を抱えていますが、これに対する解決策はありますか?
マーティンM.10年

複数のサーバーに対して同じタスクを実行するには、「fab -H staging-server、production-server deploy」を使用してください...以下の私の回答の詳細:stackoverflow.com/a/21458231/26510
Brad Parks


この回答は、ファブリック2+には適用されません。Stackoverflowの慣例に詳しい人が質問や質問のタイトルを編集してファブリック1を参照できるようになっていると便利です。
Jonathan Berger

回答:


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の機能が前に使用することができます任意のファブ機能だけでなく、展開します。それは信じられないほど便利です。


10
ファブリック(code.fabfile.org/issues/show/138#change-1497)のバグのため、env.userを設定する代わりに、ホスト文字列(produser@prod.server.comなど)にユーザーを含めることをお勧めします。
ミハイルコロボフ2011

1
私も同じ問題を抱えていましたが、これが最善の解決策のようです。ホスト、ユーザー、およびその他の多くの設定を、dev()関数とprod()関数によって読み込まれるYAMLファイルで定義します。(同様のプロジェクトで同じファブリックスクリプトを再利用できるようにするため)
クリスチャンダベン

@MikhailKorobov:私があなたのリンクをたどったとき、「nginxへようこそ!」を見ました。code.fabfile.orgドメインへのすべてのリクエストには、そのような応答があります。
Tadeck

ええ、すべてのバグがgithubに移行されたようです。
ミハイルコロボフ2012

2
残念ながら、これは機能してfab A B Cいないようです。ファブリックは、env.hostsが定義されていないとタスクを実行せず、タスクとして定義されていないスタイルでは関数を実行しません。
DNelson

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()デコレータを使用できます。
トム

2
roledefのように聞こえるのは、別のタスクで定義するよりも優れたソリューションです。
Ehtesh Choudhury 2014

指定されたユーザー名のパスワードをどのように含めることができるか知っていroledefますか?それ以上の辞書エントリ'password': 'some_password'は無視されるようで、実行時にプロンプ​​トが表示されます。
Dirk

@Dirk env.passwordsを使用できます。env.passwordsは、キーとしてユーザー+ホスト+ポートを、値としてパスワードを含む辞書です。例: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変数は、ない最初にそれらを設定するため。thomieが示唆したように、roledefs を使用することは、ステージ、開発、テストなどのホストを定義するのにより適切であると思います。
トニー・

21

私はこれにこだわっていましたが、ようやくそれを理解しました。あなたは、単にすることができないからenv.hosts構成を設定内のタスク。各タスクは、指定された各ホストに対して1回、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。ここのページの下の方に、本当に良い答えがたくさんあります。
Matt Montag、2014

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 2016年

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ですが、その時点では1つのループ)を抜けて、ループを終了する必要があるためです。ホスト(前のタスクで定義したもの)を新たに追加。


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

私はまったく新しいファブリックですが、ファブリックで複数のホストで同じコマンドを実行するには(たとえば、1つのコマンドで複数のサーバーにデプロイする場合)、次のコマンドを実行できます。

fab -H staging-server,production-server deploy 

どこをステージング・サーバーおよびプロダクション・サーバーは、あなたが反対配備アクションを実行したい2台のサーバです。OS名を表示する単純な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

タスクを実行する前にenv.hostsを設定するため、PROD関数にリストされているすべてのサーバーにわたってデプロイタスクを実行するもの。


最初のホストでの展開は機能したが、2番目のホストでの展開が失敗した場合、2番目のホストでのみもう一度実行するにはどうすればよいですか?
番号

2

env.hoststringサブタスクを実行する前にに割り当てることができます。複数のホストを反復処理する場合は、このグローバル変数にループで割り当てます。

残念ながら、あなたと私にとって、ファブリックはこの使用例のために設計されていません。このmain関数の動作を確認するには、http://github.com/bitprophet/fabric/blob/master/fabric/main.pyで関数を確認してください


2

次に、fab my_env_1 my_command使用を可能にする別の「サマーサルト」パターンを示します。

このパターンでは、辞書を使用して一度だけ環境を定義する必要があります。env_factoryのキー名に基づいて関数を作成しますENVSENVS独自のディレクトリとファイルに配置して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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.