fabricのroleで試行錯誤した話
普段は構築はChefで行うのですが、
コマンド実行もサポートしたい要件があったのでfabricを試してみました。
ただ、自分のやりたいことに対してroleの挙動が難しかったので
試行錯誤の様子をメモとして残します。
fabric初心者なのでこれが良い方法かどうかはわかりません。
どんな事がしたかったか?
複数の構成要素(client、server1、server2、、、)に1コマンド実行したい。
ただし、手元のVMで動作確認する場合に一部の構成要素をひとまとめにすることがある。
(server1、server2をserverとして扱い両方の機能を1台に押し込める)
環境
今回は説明のために実際よりは簡略化し、種類としては「client」「server1」「server2」
の3つとし、更に「server1」「server2」両方の機能をもつものを「server」とします。
実施内容としては、
- file_common.binを全台に配布する。
- 各機能に合わせたファイルをそれぞれに配布する。(file_client.bin, file_server1.bin, file_server2.bin)
というわけで書いてみます。
[deploy1.py]
from fabric.api import env, put from fabric.decorators import task, roles env.user = "vagrant" env.password = "vagrant" @task def env_full(): env.roledefs = { 'client' : ['192.168.100.171'], 'server1' : ['192.168.100.172'], 'server2' : ['192.168.100.173'], } @task def env_small(): env.roledefs = { 'server' : ['192.168.100.170'], 'client' : ['192.168.100.171'], } @task def deploy(): _deploy_common() _deploy_client() _deploy_server1() _deploy_server2() @roles('server', 'client', 'server1', 'server2') def _deploy_common(): put('resources/file_common.bin', '/tmp/file_common.bin') @roles('client') def _deploy_client(): put('resources/file_client.bin', '/tmp/file_client.bin') @roles('server', 'server1') def _deploy_server1(): put('resources/file_server1.bin', '/tmp/file_server1.bin') @roles('server', 'server2') def _deploy_server2(): put('resources/file_server2.bin', '/tmp/file_server2.bin')
そして実行
vagrant@fabric:$ fab -f deploy1.py env_small deploy No hosts found. Please specify (single) host string for connection:
roleを設定したつもりでしたが、実行対象が無いと怒られました。
@roles
をつけただけでは実行対象として認識されないようです。
自分が試行錯誤した結果、「task
として実行される」「execute
を使って実行される」
ないと@roles
が認識されないようです。
excute
を使ってdeployメソッドを書き換えます。
[deploy2.py]
from fabric.api import env, put, execute ...中略 @task def deploy(): execute(_deploy_common) execute(_deploy_client) execute(_deploy_server1) execute(_deploy_server2) ...中略
vagrant@fabric:$ fab -f deploy2.py env_small deploy Fatal error: The following specified roles do not exist: server1 server2 Aborting. vagrant@fabric:$ fab -f deploy2.py env_full deploy Fatal error: The following specified roles do not exist: server Aborting.
今度はroleの一部がないと言われます。
両方実行した結果を載せたので何となく想像がつくと思いますが、
@roles
で記載したものが存在しないと言われているようです。
自分は@roles
をフィルタリング的な意味合として書きましたが、
単純に実行対象を環境変数から取得して実行するだけの仕組みということかも?
仕方が無いので@roles
を付けるメソッドは分けることにします。
構成を色々いじる必要があり、試行錯誤して以下になりました。
[deploy3.py]
from fabric.api import env, put, execute from fabric.decorators import task, roles env.user = "vagrant" env.password = "vagrant" def _env_small(): env.roledefs = { 'server' : ['192.168.100.170'], 'client' : ['192.168.100.171'], } def _env_full(): env.roledefs = { 'client' : ['192.168.100.171'], 'server1' : ['192.168.100.172'], 'server2' : ['192.168.100.173'], } @task def deploy_small(): _env_small() execute(_deploy_common_small) execute(_deploy_server) execute(_deploy_client) @task def deploy_full(): _env_full() execute(_deploy_common_full) execute(_deploy_client) execute(_deploy_server1) execute(_deploy_server2) @roles('server', 'client') def _deploy_common_small(): _deploy_common() @roles('client', 'server1', 'server2') def _deploy_common_full(): _deploy_common() def _deploy_common(): put('resources/file_common.bin', '/tmp/file_common.bin') @roles('client') def _deploy_client(): put('resources/file_client.bin', '/tmp/file_client.bin') @roles('server1') def _deploy_server1(): put('resources/file_server1.bin', '/tmp/file_server1.bin') @roles('server2') def _deploy_server2(): put('resources/file_server2.bin', '/tmp/file_server2.bin') @roles('server') def _deploy_server(): _deploy_server1() _deploy_server2()
実行部分が@roles
で分かれるのでenv.roledefs
定義も
中で呼ぶように変更しました。
_deploy_commonも分ける必要があったのは2重に呼ばないためです。
@runs_once
という1回しか呼ばないためのdecoratorもあるのですが、
これを付けたら本当に全体で1回しか呼ばれませんでした。
やりたいのは各ノードで1回にしたいということです。
vagrant@fabric:$ fab -f deploy3.py deploy_small [192.168.100.170] Executing task '_deploy_common_small' [192.168.100.170] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.171] Executing task '_deploy_common_small' [192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.170] Executing task '_deploy_server' [192.168.100.170] put: resources/file_server1.bin -> /tmp/file_server1.bin [192.168.100.170] put: resources/file_server2.bin -> /tmp/file_server2.bin [192.168.100.171] Executing task '_deploy_client' [192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin Done. Disconnecting from 192.168.100.170... done. Disconnecting from 192.168.100.171... done. vagrant@fabric:$ fab -f deploy3.py deploy_full [192.168.100.171] Executing task '_deploy_common_full' [192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.172] Executing task '_deploy_common_full' [192.168.100.172] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.173] Executing task '_deploy_common_full' [192.168.100.173] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.171] Executing task '_deploy_client' [192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin [192.168.100.172] Executing task '_deploy_server1' [192.168.100.172] put: resources/file_server1.bin -> /tmp/file_server1.bin [192.168.100.173] Executing task '_deploy_server2' [192.168.100.173] put: resources/file_server2.bin -> /tmp/file_server2.bin Done. Disconnecting from 192.168.100.173... done. Disconnecting from 192.168.100.171... done. Disconnecting from 192.168.100.172... done.
うまくいきましたね!
しかし@roles
呼び分けるためにラップするだけのメソッドが増えてしまいました。
また、単にputしてるだけなのでわかりづらいのですが、この実行は直列実行です。
異なるノードに対しては並列で実行したいですよね。
並列に扱うには@parallel
を付ければいいのですが、
付けたメソッドに対して並列実行したい対象が通る形にしないと並列になりません。
例えば上記のdeploy3.pyだと_deploy_common_full()
に@parallel
を付ければ
「client」「server1」「server2」に対して並列実行されますが、
_deploy_client()
、_deploy_server1()
、_deploy_server2()
に@parallel
を付けても、
並列には実行されないのです。
というわけで大改造します。
[deploy4.py]
from fabric.api import env, put, execute from fabric.decorators import task, roles, parallel ...中略 @task def deploy_small(): _env_small() execute(_deploy_roles_small) @task def deploy_full(): _env_full() execute(_deploy_roles_full) @roles('server', 'client') @parallel def _deploy_roles_small(): _deploy_roles() @roles('client', 'server1', 'server2') @parallel def _deploy_roles_full(): _deploy_roles() def _deploy_roles(): _deploy_common() current_role = _get_current_role() if current_role == 'server': _deploy_server1() _deploy_server2() elif current_role == 'client': _deploy_client() elif current_role == 'server1': _deploy_server1() elif current_role == 'server2': _deploy_server2() else: print "invalid role: {0}".format(current_roles) def _get_current_role(): for role in env.roledefs.keys(): if env.host_string in env.roledefs[role]: return role return None def _deploy_common(): put('resources/file_common.bin', '/tmp/file_common.bin') def _deploy_client(): put('resources/file_client.bin', '/tmp/file_client.bin') def _deploy_server1(): put('resources/file_server1.bin', '/tmp/file_server1.bin') def _deploy_server2(): put('resources/file_server2.bin', '/tmp/file_server2.bin')
並列処理したい処理部をroleを判別して挙動を変えるメソッドに押し込めてます。
判別するためのroleはenv.host_string
で現在実行中のhost対象が取れるので、
それを元にenv.roledefs
から逆引きして特定しています。
このやり方はstackoverflowで見つけました。
@roles
の付け替えのためのラップメソッドはどうしても残りますが、
全体的には前よりもわかりやすくなったのではないでしょうか。
では実行してみましょう。
vagrant@fabric:$ fab -f deploy4.py deploy_small [192.168.100.170] Executing task '_deploy_roles_small' [192.168.100.171] Executing task '_deploy_roles_small' [192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.170] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin [192.168.100.170] put: resources/file_server1.bin -> /tmp/file_server1.bin [192.168.100.170] put: resources/file_server2.bin -> /tmp/file_server2.bin Done. vagrant@fabric:$ fab -f deploy4.py deploy_full [192.168.100.171] Executing task '_deploy_roles_full' [192.168.100.172] Executing task '_deploy_roles_full' [192.168.100.173] Executing task '_deploy_roles_full' [192.168.100.173] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.173] put: resources/file_server2.bin -> /tmp/file_server2.bin [192.168.100.171] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.171] put: resources/file_client.bin -> /tmp/file_client.bin [192.168.100.172] put: resources/file_common.bin -> /tmp/file_common.bin [192.168.100.172] put: resources/file_server1.bin -> /tmp/file_server1.bin Done.
できてそうですね。
ということで@roles
、@parallel
と格闘してみたメモでした。
本当は更に個別のノード指定もサポートしたいのですが、そこは徐々にということにします。
しかしメソッド名とかセンス無くて泣きそう。