Python+SSHな自動化・デプロイメントツールFabricを活用するTips

こんにちは。CTOの馬場です。

みんな大好きFabricのTipsです。

Welcome to Fabric! -- Fabric documentation

よくデプロイツールとして紹介されますが、 自動化のためのPython+SSH+コマンド実行フレームワークとして柔軟に使えて超便利です。

基本的には

  • 手元でのコマンド実行
  • SSHごしのリモートサーバでのコマンド実行
  • SSHごしのリモートサーバでsudoしてコマンド実行

ができるツールなのですが、使い方の例を紹介します。

間違いなどあればお近くのハートビーツ社員か @netmarkjp に教えていただけると嬉しいです。

Python 2.7.10 + Fabric 1.10.2 + Paramiko 1.15.2で動作確認しました。

複数のサーバに対して同じユーザ・パスワードでログインする

ユーザ名やパスワードを一括指定できます。

鍵認証の場合は keykey_filename を利用しましょう。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"


def go_fab():
    run("hostname")
    run("whoami")

複数のサーバに対してそれぞれ異なるユーザ・パスワードでログインする

env.passwords のキーに USER@HOST:PORT を全部書くのがミソです。

鍵認証の場合は keykey_filename を利用しましょう。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["user1@192.168.56.102", "user2@192.168.56.103"]
env.passwords = {"user1@192.168.56.102:22": "password1",
                 "user2@192.168.56.103:22": "password2", }


def go_fab():
    run("hostname")
    run("whoami")

コマンドのリターンコードが成功じゃなくても無視して進める

warn_only を使います。

ただし特定コマンド実行のみの場合は次項の一部のみこの設定を適用する方法を参照してください。 またコマンド実行成功と判断するリターンコードを変更することもできるので次々項を参照してください。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.warn_only = True


def go_fab():
    run("ls /none")
    run("hostname")
    run("whoami")

コマンドのリターンコードが成功じゃなくても無視して進める(特定コマンド実行のみ)

# coding: utf-8

from fabric.api import env
from fabric.api import run
from fabric.api import settings

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"


def go_fab():
    with settings(warn_only=True):
        run("ls /none")
    run("hostname")
    run("whoami")

コマンド実行成功と判断するコマンドのリターンコードを指定する

rsyncとかで使いますね。

# coding: utf-8

from fabric.api import env
from fabric.api import run
from fabric.api import settings

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"


def go_fab():
    with settings(ok_ret_codes=[0, 24]):
        run("exit 0")
        run("exit 24")
    run("hostname")
    run("whoami")

必ず並列実行する

parallel を使います。

なお一部タスクだけ並列実行する/しない場合は @parallel@serial デコレータを使います。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.parallel = True


def go_fab():
    run("hostname")
    run("whoami")

sshのconfigを使う

use_ssh_configTrue にすると使えます。

configはデフォルトで $HOME/.ssh/config ですが、 ssh_config_path で変更できます。

今のところ Use , Port , HostName , IdentityFile , ForwardAgent , ProxyCommand に対応しているようです。

ただし ProxyCommand については fabric側の gateway ( env.gateway )を使ったほうが楽そうです(次項参照)。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.password = "password0"
env.ssh_config_path = "config.local"
env.use_ssh_config = True


def go_fab():
    run("hostname")
    run("whoami")

踏み台経由でSSHする

fabric側の gateway ( env.gateway )を使います。

# coding: utf-8

from fabric.api import run
from fabric.api import env

# [gateway:192.168.56.102] -> [dest:192.168.56.102]
env.hosts = ["192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.gateway = "192.168.56.102"


def go_fab():
    run("hostname")
    run("whoami")
    run("w")

SSHでつながらないホストは無視する

SSH接続タイムアウトも指定できます

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.101", "192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.skip_bad_hosts = True
env.timeout = 3


def go_fab():
    run("hostname")
    run("whoami")

コマンド実行のタイムアウトを指定する

command_timeout で指定します。 デフォルトは無限に待ちます。 長く待つ場合は次項のSSH接続キープアライブも設定しましょう。

# coding: utf-8

from fabric.api import run
from fabric.api import env
from fabric.api import settings

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.command_timeout = 1


def go_fab():
    with settings(command_timeout=3):
        run("sleep 2")
        run("echo sleep 2")
        run("sleep 4")
        run("echo sleep 4")
    run("hostname")
    run("whoami")

SSH接続キープアライブを設定する

デフォルトは0(no keepalive)。

sshで言うところの ServerAliveInterval=60 にするなら env.keepalive = 60 です。

# coding: utf-8

from fabric.api import run
from fabric.api import env

env.hosts = ["192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.keepalive = 60


def go_fab():
    run("hostname")
    run("whoami")

並列実行した結果を接続先ホストごとにまとめる

ちょっとややこしいですが execute@runs_once デコレータ、 @parallel デコレータを使います。

execute() の戻り値はdictになります。キーがホスト名、returnされたものが値です。

# coding: utf-8

from fabric.api import run
from fabric.api import env
from fabric.api import execute
from fabric.decorators import parallel
from fabric.decorators import runs_once

env.hosts = ["192.168.56.101", "192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.command_timeout = 3600
env.keepalive = 60
env.skip_bad_hosts = True
env.timeout = 3


@runs_once
def go_fab():
    result = execute(_go_fab)
    for k, v in sorted(result.iteritems()):
        print "result:", k, v


@parallel
def _go_fab():
    result = []
    result.append(run("hostname"))
    result.append(run("whoami"))
    return result

リモートで実行した大量の結果を接続先ホストごとにまとめて手元に持ってくる

リモートサーバでfindしたりして出力された大量の結果を手元にもってくる方法です。 runやsudoの戻り値を文字列で保持するのもちょっとな...と思った時にどうぞ。

# coding: utf-8

from fabric.api import run
from fabric.api import env
from fabric.api import get

env.hosts = ["192.168.56.101", "192.168.56.102", "192.168.56.103"]
env.user = "user0"
env.password = "password0"
env.command_timeout = 3600
env.keepalive = 60
env.skip_bad_hosts = True
env.timeout = 3
env.parallel = True


def go_fab():
    temp = run("mktemp")
    run("for i in {1..10000}; do echo $RANDOM >>%s ; done" % (temp))
    run("hostname >>%s" % (temp))
    run("whoami >>%s" % (temp))

    # 取得後のファイルパスを指定する場合は第二引数を指定。
    # 例:           get(temp, "/tmp/%s.log" % (env.host_string))
    # デフォルト:   env.host_string/取得元と同じファイル名 (ディレクトリは自動作成される)
    get(temp)

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