概要とチュートリアル

Fabric へようこそ !

このドキュメントは Fabric の機能を紹介する駆け足のツアーであり、使い方のクイックガイドでもあります。さらに詳しいドキュメント(全体にリンクされています)は 使用方法 にあります。ぜひご覧になってください。

Fabricとは ?

README によると:

Fabricは、アプリケーションのデプロイやシステム管理のタスクのためにSSHの利用を簡素化するためのPython(2.5-2.7)のライブラリとコマンドラインのツールです。

もっと具体的に言うと、Fabricとは:

  • コマンドライン 経由で 任意の Python 関数 を実行するツールです。
  • (低レベルライブラリの上に構築された)サブルーチンのライブラリで、SSH経由で 簡単に かつ Python風に シェルコマンドを実行します。

当然、たいていのユーザーはこの2つを組み合わせます。Fabricを使ってPythonの関数もしくは タスク を作成し、実行し、リモートサーバとのやりとりを自動化します。ではちょっと見てみましょう。

Hello, fab

"いつもの"がないと正しいチュートリアルではないですよね:

def hello():
    print("Hello world!")

上のコードを fabfile.py という名前のPythonモジュールファイルとしてカレントのワーキングディレクトリに置くと、 fab ツール(Fabricのパーツとしてインストールされています)で hello 関数を実行することができ、期待した通りに動きます:

$ fab hello
Hello world!

Done.

どうってことはありませんね。この機能性により、自身のAPIを何もインポートしなくても(とても)ベーシックなビルドツールとしてFabricを利用することができます。

注釈

fab ツールは単にあなたのfabfileをインポートしてその指示にしたがい、ひとつもしくは複数の関数を実行します。何かマジックがあるわけではありません。通常のPythonスクリプトで可能なすべてのことがfabfile内でも可能なのです !

タスク引数

Fabricでは実行時引数をタスクに渡せるので便利なことも多いです。ちょうと通常のPythonプログラミングのようなものです。Fabricはこの基本的なサポートを持っていて、シェル互換ノーテーションを使っています: <task name>:<arg>,<kwarg>=<value>,... 不自然な感じがするかもしれませんが、上の例を拡張してあなたに say hello と言うようにしてみましょう:

def hello(name="world"):
    print("Hello %s!" % name)

デフォルトでは、 fab hello を呼び出しても以前と同じ動きをします。今度はこれをパーソナライズしてみましょう:

$ fab hello:name=Jeff
Hello Jeff!

Done.

Pythonプログラミングに慣れた方なら、この呼び出しでもまったく同じ挙動をすることが想像できると思います:

$ fab hello:Jeff
Hello Jeff!

Done.

差し当たりは、引数の値は常に文字列としてPythonに現れ、リストなどの複雑な型では少し文字列操作が必要になります。将来のバージョンではこれをより簡単にするため、型キャストシステムが追加されるかもしれません。

ローカルコマンド

上の例では、 fabif __name__ == "__main__" の定型文の何行かを省略できるに過ぎません。たいていはFabricのAPIと利用するためにデザインされます。APIにはシェルコマンドの実行、ファイルの転送などの関数(もしくは 操作)が含まれます。

では、仮定のウェブアプリケーションのfabfileを作ってみましょう。この例のシナリオは次のようなものです: このウェブアプリケーションはリモートホスト vcshost 上にGit経由で管理されています。 localhost 上ではこのウェブアプリケーションのローカルクローンがあります。 vcshost に変更をプッシュすると、すぐに、そして自動的にリモートホスト my_server に変更を反映させたいと思います。これを、ローカルとリモートのGitコマンドを自動化することによって実施させてみましょう。

通常は、fabfileはプロジェクトのルートに置くといいでしょう:

.
|-- __init__.py
|-- app.wsgi
|-- fabfile.py <-- our fabfile!
|-- manage.py
`-- my_app
    |-- __init__.py
    |-- models.py
    |-- templates
    |   `-- index.html
    |-- tests.py
    |-- urls.py
    `-- views.py

注釈

ここではDjangoアプリケーションを使用していますが、単に例として用いているだけです。Fabricは、SSHライブラリは別として、どんな外部のコードベースにもひも付けられていません。

まず第一にこのテストを実行し、VCSにコミットしてみましょう。そしてデプロイを準備をします:

from fabric.api import local

def prepare_deploy():
    local("./manage.py test my_app")
    local("git add -p && git commit")
    local("git push")

出力はだいたい次のようになるでしょう:

$ fab prepare_deploy
[localhost] run: ./manage.py test my_app
Creating test database...
Creating tables
Creating indexes
..........................................
----------------------------------------------------------------------
Ran 42 tests in 9.138s

OK
Destroying test database...

[localhost] run: git add -p && git commit

<interactive Git add / git commit edit message session>

[localhost] run: git push

<git push session, possibly merging conflicts interactively>

Done.

このコード自身は単純です。FabricのAPI関数 local をインポートし、それを利用してローカルのシェルコマンドを実行し、やりとりを行います。他のFabricのAPIも似ていて、すべてただのPythonです。

好きなように構造化する

Fabricは"ただのPython"なので、fabfileは好きなように自由に構造化できます。例えば、サブタスクに分けることから始めると便利でしょう:

from fabric.api import local

def test():
    local("./manage.py test my_app")

def commit():
    local("git add -p && git commit")

def push():
    local("git push")

def prepare_deploy():
    test()
    commit()
    push()

prepare_deploy タスクは以前と同じように呼び出すことができますが、今回は必要であればサブタスクの一つとしてより粒度を細かくして呼び出しをすることができます。

失敗

基本的な動きは問題ないですが、もしテストに失敗したらどうなるでしょうか? デプロイの前にブレーキをかけて修正する機会があります。

Fabricは操作経由で呼び出されたプログラムの返り値をチェックして、正常に終了しなかった場合には停止します。テストのひとつがエラーに出くわしたときにどうなるか見てみましょう:

$ fab prepare_deploy
[localhost] run: ./manage.py test my_app
Creating test database...
Creating tables
Creating indexes
.............E............................
======================================================================
ERROR: testSomething (my_project.my_app.tests.MainTests)
----------------------------------------------------------------------
Traceback (most recent call last):
[...]

----------------------------------------------------------------------
Ran 42 tests in 9.138s

FAILED (errors=1)
Destroying test database...

Fatal error: local() encountered an error (return code 2) while executing './manage.py test my_app'

Aborting.

素晴らしい! 私たち自身では何もする必要がありませんでした。Fabricが失敗を検知して停止し、commit タスクは決して実行されることはありません。

失敗の扱い

さて、これを柔軟にしてユーザーに選択をさせるにはどうすれいいでしょう? warn_only と呼ばれる設定(もしくは environment variable、通常は短く env var)が停止を警告に変え、柔軟なエラーの扱いを可能にします。

test 関数でこの設定を有効にして、local 呼び出しの結果を調べて見ましょう:

from __future__ import with_statement
from fabric.api import local, settings, abort
from fabric.contrib.console import confirm

def test():
    with settings(warn_only=True):
        result = local('./manage.py test my_app', capture=True)
    if result.failed and not confirm("Tests failed. Continue anyway?"):
        abort("Aborting at user request.")

[...]

この新しい機能を追加するにあたり、新しいことをたくさん導入しました:

  • Python 2.5 では with: を使うために __future__ のインポートが必要です。
  • Fabricの contrib.console サブモジュールは confirm 関数を含んでいて、簡単なイエス/ノープロンプトに使われます。
  • settings コンテキストマネージャーはコードの特定のブロックに設定を適用するのに使われます。
  • local のようなコマンドランニング操作は、その結果(.failed.return_code など) に関する情報を含むオブジェクトを返すことができます。
  • そして abort 関数は手動で停止を実行するために使われます。

とは言え、この追加的な複雑性を別にすれば、理解するのは依然としてとても簡単で、さらに柔軟になりました。

接続する

では今度は、肝心な部分を入れてfabfileを仕上げましょう。 deploy タスクは一つもしくは複数のリモートサーバーで実行され、コードが確実に最新になるにようにします:

def deploy():
    code_dir = '/srv/django/myproject'
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

今回もまた、たくさんの新しいコンセプトが導入されています:

  • FabricはただのPythonです。したがって、変数や文字列の操作などの通常のPythonコードの概念を自由に利用することができます。
  • cd はコマンドに``cd /どこ/かの/ディレクトリ`` 呼び出しを追加する簡単な方法です。これは同じことをローカルで実行する lcd と似ています。
  • runlocal に似ていますが、ローカルではなく リモートで 動作します。

また、ファイルの一番上で新しい関数を確実にインポートするようにします:

from __future__ import with_statement
from fabric.api import local, settings, abort, run, cd
from fabric.contrib.console import confirm

これらを変更したら、デプロイしてみましょう:

$ fab deploy
No hosts found. Please specify (single) host string for connection: my_server
[my_server] run: git pull
[my_server] out: Already up-to-date.
[my_server] out:
[my_server] run: touch app.wsgi

Done.

このfabfileでは接続情報は指定していません。したがって、Fabricはどのホスト(複数可)でこのリモートコマンドが実行されるべきなのかが分かりません。このようなとき、Fabricは起動時に入力を促します。接続定義はSSHのような "ホスト文字列" (例えば user@host:port)を使い、デフォルトではローカルのユーザー名が使われます。そのため、この例では単にホスト名 my_server だけを指定しています。

リモートとの双方向性

チェックアウトしたソースコードがすでにあるのなら git pull で問題ないでしょう。しかし最初のデプロイだったらどうでしょう? そうしたケースも扱えて、最初の git clone も実行するようにするといいでしょう:

def deploy():
    code_dir = '/srv/django/myproject'
    with settings(warn_only=True):
        if run("test -d %s" % code_dir).failed:
            run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir)
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

上の local との場合と同じように run もまた、シェルコマンドの実行をベースにきれいなPythonレベルのロジックを組み立てることができます。しかし、ここでの興味深い部分は git clone 呼び出しで、Gitサーバ上のリポジトリへのアクセスにGitのSSHメソッドを利用します。つまりリモートの run 呼び出しは、自身の認証を必要とするのです。

Fabricの以前のバージョン(と、同じようなハイレベルなSSHライブラリ)では、リモートプログラムのの実行は中途半端な状態で、ローカル側からは触れませんでした。これはパスワードの入力が本当に必要な場合やリモートプログラムとの情報のやりとりが必要な場合に解決が難しい問題でした。

Fabric 1.0以降ではこの問題を解決し、リモート側と常にやりとりできることを確保しています。では、Gitチェックアウトがないときに新しいサーバー上でアップデートした deploy タスクを実行したときに何が起こるか見てみましょう:

$ fab deploy
No hosts found. Please specify (single) host string for connection: my_server
[my_server] run: test -d /srv/django/myproject

Warning: run() encountered an error (return code 1) while executing 'test -d /srv/django/myproject'

[my_server] run: git clone user@vcshost:/path/to/repo/.git /srv/django/myproject
[my_server] out: Cloning into /srv/django/myproject...
[my_server] out: Password: <enter password>
[my_server] out: remote: Counting objects: 6698, done.
[my_server] out: remote: Compressing objects: 100% (2237/2237), done.
[my_server] out: remote: Total 6698 (delta 4633), reused 6414 (delta 4412)
[my_server] out: Receiving objects: 100% (6698/6698), 1.28 MiB, done.
[my_server] out: Resolving deltas: 100% (4633/4633), done.
[my_server] out:
[my_server] run: git pull
[my_server] out: Already up-to-date.
[my_server] out:
[my_server] run: touch app.wsgi

Done.

Password: プロンプトは、ウェブサーバ上のリモートの git 呼び出しで、Gitサーバーへのパスワードへの問い合わせであることに留意してください。パスワードをここで入力することができ、クローンは通常のように継続されます。

予め接続を定義する

起動時に接続情報を指定するのはすぐにうんざりしてくると思います。そのためFabricでは、fabfile内やコマンドライン上でこれを行うためのたくさんの手段を提供しています。ここではすべてをカバーしませんが、もっともよくある手段、グローバルなホストリストの設定 env.hosts をお見せしましょう。

env はFabricのたくさんの設定を操作するグローバルな辞書のようなオブジェクトで、さらに属性とともに書くことも可能です。(実際のところ、上にみられるように settings はこれの単なるラッパーです)したがって、モジュールレベルで、自分のfabfileの一番上に近いところで次のように変更が可能です:

from __future__ import with_statement
from fabric.api import *
from fabric.contrib.console import confirm

env.hosts = ['my_server']

def test():
    do_test_stuff()

fab がfabfileを読み込むとき、今回変更した env が実行され、設定の変更を格納します。その結果は上の通りになり、deploy タスクが my_server に対して実行されます。

また、このようにして、Fabricに対して一度に複数のリモートシステム上で実行させることもできます。env.hosts はリストなので fab はこのリストを順に処理し、各接続に対して与えられたタスクを呼び出します。

まとめ

完成したfabfileは、それでもかなり短いものです。全体では以下になります:

from __future__ import with_statement
from fabric.api import *
from fabric.contrib.console import confirm

env.hosts = ['my_server']

def test():
    with settings(warn_only=True):
        result = local('./manage.py test my_app', capture=True)
    if result.failed and not confirm("Tests failed. Continue anyway?"):
        abort("Aborting at user request.")

def commit():
    local("git add -p && git commit")

def push():
    local("git push")

def prepare_deploy():
    test()
    commit()
    push()

def deploy():
    code_dir = '/srv/django/myproject'
    with settings(warn_only=True):
        if run("test -d %s" % code_dir).failed:
            run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir)
    with cd(code_dir):
        run("git pull")
        run("touch app.wsgi")

このfabfileはFabricの機能セットのうちのかなりの部分を利用しています:

  • fabfileのタスクを定義し、それを fab で実行
  • local でローカルのシェルコマンドを呼び出し
  • settings で env 変数を変更
  • コマンド失敗の扱い、ユーザーにプロンプト表示、手動アボート
  • ホストリストの定義と run のリモートコマンド実行

とは言え、ここではカバーしていないこともまだたくさんあります ! ぜひさまざまな "see also" リンクをたどってみてください。また、 the main index page のドキュメントの目次もチェックしてみてください。

読んでくれて、ありがとうございます !