こんにちは、滝澤です。

いくつかのプロジェクトでタスクランナーFabric 2を使う機会がありました。少しですが知見が溜まったので紹介します。 また、Fabric 1.xを利用していた方は互換性も気になると思いますでのその点についても紹介します。

記事が長くなったので3編に分けます。

本記事は後編の「Fabricの使い方」になります。

なお、執筆時点(2018年11月21日)での最新バージョンはFabric 2.4.0、Invoke 1.2.0です。 動作確認はPython 3.7.1にて行っています。

Fabricについて

Fabricについては前編で紹介したのでここでは省略します。

Fabricのインストール

pipコマンドでインストールできます。依存によりInvokeやParamikoもインストールされます。

$ pip install fabric

タスクランナーFabric

Fabricのタスクランナーとしての使い方を紹介します。

ssh_configと設定ファイル

リモートのホストにSSH接続を行うため、リモートホストへのSSH接続の情報が必要となります。 そのため、ssh_configファイルを用意するとよいでしょう。デフォルトでは~/.ssh/config/etc/ssh/ssh_configが参照されますが、このタスクのプロジェクト用として独立して配置してもよいでしょう。

Host host01
  User foo
  Identityfile ~/.ssh/host01.key
  HostName host01.example.com

後述するfabコマンドのオプション-S--ssh-configssh_configファイルを指定できます。

fab -S ssh_config タスク名

ssh_configファイルを指定するのを省略したい場合はFabricの設定ファイルを用意します。 設定ファイルの配置方法や記述方法にはいくつかあるのですが、ここでは同じ場所にfabric.yamlという名前のファイルを作成し、次のようにパラメータssh_config_pathssh_configファイルのパス名を指定します。

---
ssh_config_path: ssh_config

公式ドキュメント:

タスク実行

タスクの定義方法を紹介します。 例として、与えられたシェルコマンドを単純に実行するタスクshを定義してみます。

まず、fabric.pyという名前のファイルを作成します。 fabricパッケージからtaskモジュールをインポートします。

from fabric import task

タスク名shの関数を定義し、1つ目の引数にはFabricのコネクションの変数としてcを指定し、2つ目以降の引数にはfabコマンドで渡される引数を指定します。さらに、タスクとして認識させるためにデコレーター@taskを付けます。 run()の引数にシェルで実行するコマンドを指定します。

@task
def sh(c, command):
    """Execute a shell command."""
    c.run(command)

tasks.pyファイルと同じディレクトリにおいて、fabコマンドを実行します。fabコマンドの基本的な使い方は次の通りです。

fab -H ホスト名のカンマ区切りのリスト タスク名 [引数...]

ここで接続先のホスト名とタスク名shと引数のuname -nを指定してfabコマンドを実行してみます。 SSHのパスフレーズを入力する場合は--prompt-for-passphraseオプションを付けます。

$ fab --prompt-for-passphrase -H host01 sh 'uname -n'
host01

uname -nコマンドの実行結果が出力されます。

なお、ssh-agentを利用して予めパスフレーズを入力しておけば、--prompt-for-passphraseオプションを省略できます。

$ ssh-agent /bin/bash
$ ssh-add ~/.ssh/host01.key
Enter passphrase for /home/foo/.ssh/host01.key:
Identity added: /home/foo/.ssh/host01.key (/home/foo/.ssh/host01.key)
$ fab -H host01 sh 'uname -n'
host01

タスクの実行方法は以上の通りです。

上記の例ではタスクshを定義しましたが、実は次のように--を指定するとタスクの読み込みをせずにシェルコマンドを実行することができます。SSH接続の確認に使うとよいでしょう。

$ fab -H host01 -- uname -n
host01

前編で説明しましたが、Fabric 2はInvokeのSSHラッパーです。 そのため、Invokeで説明したrun(), sudo(), cd(), prefix()はSSH接続先で実行されます。

タスク一覧についても、Invokeと同様に--listオプションを付けてfabコマンドを実行することで表示できます。

$ fab --list
Available tasks:

  get          Execute get().
  local        Execute local().
  print-date   Print date.
  put          Execute put().
  sh           Execute a shell command.
  uname        Execute uname.

Fabric特有の機能もありますので、以降のセクションで紹介していきます。

公式ドキュメント:

local()

SSH接続先ではなく、ローカルでコマンドを実行したい場合はlocal()を使います。

@task
def local(c):
    """Execute local()."""
    c.local("uname -n")

タスクlocalを実行すると、接続先のホストではなくローカル上でコマンドが実行されます。

$ fab -H host01 local
foo-pc.local

公式ドキュメント:

put()とget()

SFTPを利用して、リモートホストにファイルを送信したり、リモートホストからファイルを取得したりすることができます。 ファイルの送信にはput()を、ファイルの取得にはget()を使います。

それぞれのラッパーのタスクを次のように作成してみます。

@task
def put(c, local, remote):
    """Execute put()."""
    c.put(local, remote)

@task
def get(c, remote, local):
    """Execute get()."""
    c.get(remote, local)

タスクputとタスクgetをそれぞれ実行してみます。

$ fab -H host01 put test.dat test.dat
$ fab -H host01 -- ls -l test.dat
-rw-r--r-- 1 foo users 5 11月 21 15:04 2018 test.dat
$ fab -H host01 get test.dat test2.dat
-rw-r--r-- 1 foo staff  5 Nov 21 15:07 test2.dat

なお、put()localおよびget()localにはfile-likeオブジェクトも利用できます。

以上でタスクランナーとしてのFabricの基本的な使い方を紹介しました。

公式ドキュメント:

Patchwork

リモートのホスト上でファイルの中身を操作しようとするとそれなりに作り込みが必要になります。

Fabric 1.xではcontribモジュールにおいて様々な便利なファイル操作の関数が提供されていました。

Fabric 2ではcontribモジュールは提供されていなく、代わりにPatchworkという関連プロジェクトのライブラリが提供されています。しかし、"still early in development"というステータスではあります。

While Patchwork has been released with a major version number to signal adherence to semantic versioning, it's still early in development and has not fully achieved its design vision yet.

公式ドキュメント:

ライブラリとしての利用例

FabricをSSH経由でリモートシェルコマンドを実行するPythonライブラリとして利用する例を紹介します。

基本的な処理の流れ

ライブラリとしての利用方法を次のスクリプトを用いて説明します。 ホストhost01にSSH接続してuname -nコマンドを実行するだけのものです。

from fabric import Config, Connection

def get_uname(hostname, config=None):
    """Execute uname."""
    c = Connection(hostname, config=config)
    result = c.run("uname -n", hide=True)
    uname = result.stdout.strip()
    return uname

def main():
    config = Config(
        runtime_ssh_path="ssh_config"
    )
    uname = get_uname("host01", config=config)
    print(uname)

if __name__ == '__main__':
    main()

処理の流れは次の通りです。なお、ssh-agentを利用してパスフレーズの入力を省略する前提での流れです。

  1. Configオブジェクトの生成
    • システムやユーザーのデフォルトのssh_configファイルではなく、独自のssh_configファイルを使いたい場合は、上記のようにruntime_ssh_pathパラメータを指定してConfigオブジェクトを生成します。
  2. Connectionオブジェクトの生成
    • 接続先のホスト名を引数としてConnectionオブジェクトを生成します。デフォルトの設定のままでよければconfigパラメータの指定は不要です。
  3. 生成したConnectionオブジェクトに対して、コマンドを実行する関数を実行します。ここではrun()を実行しています。

なお、ドキュメントによると生成したConnectionオブジェクトの明示的なクローズは必要ないそうです。

公式ドキュメント:

スクリプト内での設定

ssh_configファイルを使わずにスクリプト内でSSH接続のパラメータを設定したい場合には、次のようにoverridesパラメータを指定してConfigオブジェクトを生成します。

    user = "foo"
    port = 22
    key_filename = "/home/foo/.ssh/host01.key"
    config = Config(overrides={
        "user": user,
        "port": port,
        "connect_kwargs": {
            "key_filename": key_filename
        },
    })

overridesパラメータにはデフォルトの設定値を上書きする設定パラメータをディクショナリーとして格納します。 デフォルトの設定値の詳細は次のサイトをご覧ください。

例えば、sudoのパスワードも指定したい場合は、次のような内容をoverridesのディクショナリーに追加します。

        "sudo": {
            "password": sudo_password
        },

connect_kwargsにはParamikoのSSHClient.connect()に渡すパラメータを指定します。 ここではkey_filenameにプライベート鍵ファイルのパスを指定しています。

以上のようにして設定パラメータをカスタマイズできます。

公式ドキュメント:

最後に

本記事ではFabric 2とInvokeの基本的な使い方を紹介しました。

Fabric 1.xのスクリプトをFabric 2に移行するのに私自身が色々嵌まったので本記事が少しでも参考になれば幸いです。

目次

参考サイト

株式会社ハートビーツのインフラエンジニアから、ちょっとした情報をお届けします。