2014年10月26日日曜日

Fabricはexecute APIで自由度の高いデプロイツールに

FabricはPythonの関数を直接呼び出す機能とSSH経由のコマンドを複数のホストに対して楽に発行する機能を持つライブラリです。
簡単な使い方はこちらのページで確認できます。(最近はFabricで検索すると日本語の紹介記事もヒットするようになりました。)
http://docs.fabfile.org/en/latest/tutorial.html

初歩的な使い方だとすぐに使えるようになるのがFabricのメリットです。
また結局はPythonスクリプトなので、サーバー障害時のサービスアウトからパッケージ更新等のデプロイまで幅広く使えます。

しかしある程度大規模で複雑なシステムが対象になると初歩的な使い方では間に合わなくなります。
たとえば、複数のDBサーバをサービスアウトするためには複数のAPサーバーの設定ファイルを書き換える必要がある時はどうすれば良いでしょうか?
あるいは、サービスアウト、デプロイ、サービスインの一連の手順を1台ずつ行いたいという場合はどう記述すれば良いでしょうか?

これらはfab -H [ホスト名] [タスク名]という単純なfab実行方法では不可能で、env.hostsを書き換えるのもタスク実行前に行わなければならないので難しいです。
そこで、fabric.apiのexecuteを使用します。
公式ドキュメントにはこのようなコードが載っています。

from fabric.api import run, execute, task

from mylib import external_datastore

def do_work():
    run("something interesting on a host")

@task
def deploy(lookup_param):
    host_list = external_datastore.query(lookup_param)
    execute(do_work, hosts=host_list)

http://docs.fabfile.org/en/latest/usage/execution.html#intelligently-executing-tasks-with-execute
Using execute with dynamically-set host listsより

host_listに対象となるホストのリストを与えてexecuteすることで、タスクごとに異なるホスト群を指定できることに注目してください。
また、タスク内でこのexecuteは何度も実行できますので、サービスイン・デプロイ・サービスアウトを順に行うタスクも記述可能です。(後述)
これを使えば、1つのタスク内で異なるホスト群に対していろいろなコマンドを発行することができるようになります。
例として、複数台のWebサーバ、Applicationサーバ、Databaseサーバで構成されるシステムについて以下の様なFabricスクリプトを書いてみました。(あくまでFabricのために書いたもので、サーバに対して発行するコマンドはechoのみにしてます。)
ファイル構成は以下の通り(fabfileディレクトリと__init__.pyはデフォルト設定では必須です。)

--fabfile
  |-- __init__.py
  |-- web.py
  |-- ap.py
  |-- db.py
  |-- common.py
  |-- conf.py

この例では各コンポーネントに発行するコマンドがだいたい同じだと想定してComponentという抽象クラスを用意し、各コンポーネントクラスはComponentを継承してservice_in, service_outなどのメソッドをオーバーライドしています。

実行例は以下の通り。(実行結果は長いので省きます)

# webサーバ1台ずつサービスアウト・パッケージ更新・サービスインの一連のタスクを行います
$ fab release:web,update_pkgs
# 障害が起きたwebサーバを1台指定してサービスアウトします
$ fab service_out:web01.example.com
# DBサーバそれぞれに対してバックアップを取ります
$ fab backup_db

__init__.py

# -*- coding: utf-8 -*-

import web, ap, db
from common import get_hosts, get_role
from fabric.api import sudo, execute
from fabric.decorators import task
from functools import partial

components = {
    'web': web.Web(),
    'ap': ap.Application(),
    'db': db.Database()
}

# デプロイ名を指定して各種デプロイを行う関数
def deploy(name):
    if name == 'update_pkgs':
        sudo('echo "Update packages"')
    else:
        raise Exception('Unknown deploy command %s' % name)

# 任意個のデプロイをFabricタスクとして呼び出す関数
def do_deploys(deploys, hosts):
    for d in deploys:
        execute(partial(deploy, d), hosts=hosts)

# 障害時などにホスト名を指定してサービスアウトを行うタスク
@task
def service_out(hostname):
    components[get_role(hostname)].service_out(hostname)

# 障害復旧時などにホスト名を指定してサービスインを行うタスク
@task
def service_in(hostname):
    c = components[get_role(hostname)]
    c.restart(hostname)
    c.service_in(hostname)

# パッケージ更新などのデプロイをコンポーネント指定でまとめて行うタスク
# サービスアウト、デプロイ、プロセス再起動、サービスインを順に行います
@task
def release(target, *deploys):
    c = components[target]
    # 一連のプロセスはサーバ1台毎に行います
    for host in get_hosts(target):
        c.service_out(host)
        do_deploys(deploys, host)
        c.restart(host)
        c.service_in(host)

@task
def backup_db():
    db.Database().backup(get_hosts('db'))

db.py

# -*- coding: utf-8 -*-

from common import Component, get_hosts
from fabric.api import run, execute
from functools import partial

def service_out(hostname):
    run('echo "service out %s"' % hostname)

def service_in(hostname):
    run('echo "service in %s"' % hostname)

def restart():
    run('echo "restart"')

def backup():
    run('echo "Backup database"')

class Database(Component):
    def service_out(self, *hosts):
        for host in hosts:
            execute(partial(service_out, host), hosts=get_hosts('ap'))
    def service_in(self, *hosts):
        for host in hosts:
            execute(partial(service_in, host), hosts=get_hosts('ap'))
    def restart(self, hosts):
        execute(restart, hosts=hosts)
    def backup(self, hosts):
        execute(backup, hosts=hosts)

web.pyとap.pyはdb.pyと同じような内容なので下記を参照
https://github.com/muumu/fabric-sample/blob/master/fabfile/web.py
https://github.com/muumu/fabric-sample/blob/master/fabfile/ap.py

common.py

# -*- coding: utf-8 -*-

import conf
from fabric.api import env
from abc import ABCMeta, abstractmethod

# 各コンポーネントが持っているべきメソッドを宣言します
# 実際のシステムでは全プロセスを止めるstopとかもあるべきかと思います
class Component:
    __metaclass__ = ABCMeta
    @abstractmethod
    def service_out(self, hosts): pass

    @abstractmethod
    def service_in(self, hosts): pass

    @abstractmethod
    def restart(self, hosts): pass

# 大規模システムだとサーバー情報を入れたDBからホスト名を引いてくるかと思いますが
# ここでは簡単のため@rolesデコレータとして利用可能なenv.roledefsを使用します
def get_hosts(role_name):
    if role_name not in env.roledefs:
        raise Exception('Invalid role name: %s' % role_name)
    return env.roledefs[role_name]

# ホスト名からロール名を取得する関数です
# これも簡単のため単にenv.roledefsから逆引き
def get_role(hostname):
    for role, hosts in env.roledefs.items():
        if hostname in hosts:
            return role
    raise Exception('Invalid hostname: %s' % hostname)

conf.py

# -*- coding: utf-8 -*-

from fabric.api import env

env.roledefs = {
    'web': ['web01.example.com', 'web02.example.com'],
    'ap': ['ap01.example.com', 'ap02.example.com'],
    'db': ['db01.example.com', 'db02.example.com']
}

# 1つのホストにタスク実行が終わる毎に切断して接続リソースを節約します
env.eagerly_disconnect = True
# ssh_configの設定内容を使用してssh接続を行います
env.use_ssh_config = True
https://github.com/muumu/fabric-sample
このようにFabricの自由度はかなり高いので、大規模で複雑なシステムに対して柔軟なコマンドを発行したい、という場合に手早く実装できてとても便利です。

2014年10月13日月曜日

Pythonのmap関数をC++で作る試み

Pythonのmapが便利なので、同じように使えそうなmapをC++14で作ってみました。
ソースはこちら。(勉強がてらなので、雑然としてます。)
サンプル: https://github.com/muumu/lib/blob/master/sample/sample_functional.cpp
ライブラリ: https://github.com/muumu/lib/blob/master/functional.h
以下、作ったfn::mapのだいたいの使用感です。

それぞれの要素に関数やメンバ関数を適用したコンテナを作る

{
    vector<string> source = {"a?", "ab?", "abc?", "abcd?"};
    auto output = fn::map(source, fn::init);
    print(output); // {"a","ab","abc","abcd"}
}
{
    list<string> source = {"", "occupied", "", "occupied"};
    auto output = fn::map(source, &string::empty);
    print(output); // {true,false,true,false}
}
{
    array<string, 4> source = {"add", "sub", "mul", "div"};
    auto output = fn::map(source, util::upper);
    print(output); // {"ADD","SUB","MUL","DIV"}
}

違うデータ型になる関数はそのデータ型のコンテナに変換

{
    set<string> source = {"tea", "wine", "milk", "coffee"};
    auto output = fn::map(source, &string::size);
    print(output); // {3,4,6}
}
{
    multiset<string> source = {"tea", "wine", "milk", "coffee"};
    auto output = fn::map(source, &string::size);
    print(output); // {3,4,4,6}
}

キーとバリューを逆転したマップの作成

{
    map<string, int> source_map = {{"RED", 0}, {"GREEN", 1}, {"BLUE", 2}};
    auto reversed_map = fn::map(source_map, fn::swap<string, int>);
    print(reversed_map); // {{0,"RED"},{1,"GREEN"},{2,"BLUE"}}
}

vectorの出力を違うコンテナで受け取る

{
    multimap<string, int> src_map = {{"RED", 0}, {"RED", 0}, {"GREEN", 1}};
    auto func = [](const pair<string, int>& pair) {
        return pair.first + ": " + util::to_string(pair.second);};
    auto result = fn::map(src_map, func);
    print(result); // {"GREEN: 1","RED: 0","RED: 0"}
    set<string> result_set = fn::map(src_map, func);
    print(result_set); // {"GREEN: 1","RED: 0"}
}

2014年8月18日月曜日

【読書ノート】Effective Modern C++ - Item 7

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 7: オブジェクトを生成する時、()と{}を区別しよう

以下の文は全てint型変数を0で初期化する

int x(0);
int y = 0;
int z{0};
int z = {0};

初期化と代入の区別

Widget w1; // デフォルトコンストラクタが呼ばれる
Widget w2 = w1; // コピーコンストラクタが呼ばれる
w1 = w2; // 代入(operator=が呼ばれる)

C++11で追加された{} (braced initialization) はuniform initializationの実装。
{}を使うと、C++11以前はできなかったコンテナの初期化が可能。

std::vector<int> v{1, 2, 3};

初期化方法に違いが生じるケース

C++11では、非staticデータメンバの初期化が可能になったが、()ではできない

class Widget {
private:
    int x{0}; // OK
    int y = 0; // OK
    int z(0); // エラー!
}

std::atomicsなどのコピー不可能なオブジェクトは=で初期化できない

std::atomic<int> ai1{0}; // OK
std::atomic<int> ai2(0); // OK
std::atomic<int> ai3 = 0; // エラー!
{}は組み込み型のnarrowing conversionを禁止する
double x, y, z;
...
int sum1{x + y + z}; // エラー! doubleからintへの変換に非対応
int sum2(x + y + z); // OK double型の値がint型に丸められる
int sum3 = x + y + z; // OK 同上
{}はmost vexing parseを起こさない
Widget w1(10); // 10を引数に取るWidgetのコンストラクタが呼ばれる
Widget w2(); // most vexing parse! Widget型を返す関数の宣言と解釈される
void f(const Widget& w = Widget()); // Widgetのデフォルトコンストラクタが呼ばれる

Widget w1{10}; //コンストラクタが呼ばれる
Widget w2{}; // デフォルトコンストラクタが呼ばれる
void f(const Widget& w = Widget{}); //デフォルトコンストラクタが呼ばれる

{}の直感に反する動作

autoは、{}で初期化されるオブジェクトをstd::initializer_list型と型推定する

auto v1 = -1; // v1はint型
auto v2(-1); // v2はint型
auto v3{-1}; // v3はstd::initializer_list<int>型
auto v4 = {-1}; // v4はstd::initializer_list<int>型

std::initializer_listの無いコンストラクタは通常の動作

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    ...
};

Widget w1(10, true); // 1番目のコンストラクタが呼ばれる
Widget w2{10, true}; // 同上
Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる
Widget w4{10, 5.0}; // 同上

しかしstd::initializer_listがある場合、{}は常にそちらを優先する

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    Widget(std::initializer_list<long double> il);
    ...
};

Widget w1(10, true); // 1番目のコンストラクタが呼ばれる
Widget w2{10, true}; // std::init_listのコンストラクタが呼ばれる
                     // 10とtrueはlong doubleに変換される
Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる
Widget w4{10, 5.0}; // std::init_listのコンストラクタが呼ばれる
                     // 10と5.0はlong doubleに変換される

narrowing conversionが生じてコンパイルエラーになってもstd::initializer_listを優先する

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    Widget(std::initializer_list<bool> il);
    ...
};

Widget w{10, 5.0}; // narrowing conversionによるコンパイルエラー!

しかし変換が存在しない場合は他の候補を読みに行く

class Widget {
public:
    Widget(int i, bool b);
    Widget(int i, double d);
    Widget(std::initializer_list<std::string> il);
    ...
};

Widget w1(10, true); // 1番目のコンストラクタが呼ばれる
Widget w2{10, true}; // 1番目のコンストラクタが呼ばれる
Widget w3(10, 5.0); // 2番目のコンストラクタが呼ばれる
Widget w4{10, 5.0}; // 2番目のコンストラクタが呼ばれる

空の{}は空のstd::initializer_listではなく引数が無いことを表す

class Widget {
public:
    Widget();
    Widget(std::initializer_list<int> il);
    ...
};

Widget w1; // デフォルトコンストラクタが呼ばれる
Widget w2{}; // デフォルトコンストラクタが呼ばれる
Widget w3(); // most vexing parse! 関数が宣言される
Widget w4({}); // std::init_listのコンストラクタが呼ばれる(リストは空)
Widget w5{{}}; // 同上

コピー・ムーブコンストラクタは通常通り呼ばれる
(Widget型であれば、たとえint型への変換が定義されていても、コピーコンストラクタ・ムーブコンストラクタが呼ばれる)

class Widget {
public:
    Widget(const Widget& rhs);
    Widget(Widget&& rhs);
    Widget(std::initializer_list<int> il);
    operator int() const;
    ...
};

auto w6{w5}; // コピーコンストラクタが呼ばれる
auto w7{std::move(w5)}; // ムーブコンストラクタが呼ばれる

これらのような難解なルールは、std::vectorという身近な例にも影響を与える。

std::vector<int> v1(10, 20); // 20を値とする要素を10個持つvector
std::vector<int> v2{10, 20}; // 10と20の2つの要素を持つvector

クラスデザインのガイドライン

ユーザーが()を使っても{}を使っても同じコンストラクタが呼ばれるデザインが良い。(その点においてstd::vectorは悪い例。)

std::initializer_listを引数に取るコンストラクターが無い状態からそれを新たに加える場合、既存のコードがその新しいコンストラクターを呼び出すように変化する危険性が生じる。
そのような改変は、十分に熟考してから行うべきである。

()と{}の利用ガイドライン

選択肢1: 原則{}を使う

{}は最も広範囲に適用でき、narrowing conversionを防ぎ、C++のmost vexing parseを起こさないという理由で{}を使用する。 ただし、先述のように{}を使用できないいくつかの場合において()を使用する。

選択肢2: 原則()を使う

()はC++98の文法と互換性があり、autoによるstd::initializer_list型推定の問題を回避し、std::initializer_listコンストラクタが意図せず呼び出されることが無いということを重んじて、()を使用する。 ただし、vectorの初期化などのように{}が必要な場面では{}を使用する。

片方の選択肢がもう片方より優れているということはない。
どちらかを選び、一貫した姿勢でコードを書くと良いだろう。

template関数やクラスを設計する場合はさらに事情が複雑になる。

template<typename T, typename... Args>
void doSomeWork(const T& obj, Args&&.. args)
{
    T localObject(std::forward<Args>(args)...);
    T localObject{std::forward<Args>(args)...};
}

これにたとえばユーザーがvectorを入れると、先述のように異なる結果になってしまう。

std::vector<int> v;
...
doSomeWork(v, 10, 20);

std::make_uniqueとstd::make_sharedはこの問題に直面し、()を採用してその旨をインターフェースに記述することで解決した。 他には、タグディスパッチによる解決方法もある。(Item 29)

Things to Remember

  • {}による初期化は最も広く適用可能な初期化であり、narrowing conversionを防ぎ、most vexing parseを起こさない
  • Item 2の通り、autoは{}で初期化されたオブジェクトをstd::initializer_list型と型推定する
  • {}による初期化は、std::initializer_listをパラメータに持つコンストラクタがある場合、たとえ他のコンストラクタの方がマッチしててもそちらが呼ばれる。
  • ()と{}のどちらで初期化するかで違いが生じる例はstd::vectorの2引数コンストラクタ
  • template内のオブジェクト生成において()と{}のどちらを選ぶかという決断は困難になりうる

2014年8月9日土曜日

【読書ノート】Effective Modern C++ - Item 6

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 6はproxy classに対して使用するtyped initializerイディオムが紹介されていました。 どちらも初見の用語でした。 このイディオムを使用するのは一見無駄に見えましたが、確かにコードに意図を込めるには必要なのかなと思いました。

Item 6: typed initializerイディオムに気をつけよう

autoが意図しない型推定をしてしまうケース

std::vector<bool> features(const Widget& w);

// OK
auto highPriority = features(w)[5];
processWidget(w, highPriority);

// 未定義動作に!
auto highPriority = features(w)[5];
processWidget(w, highPriority);

autoにするとhighPriorityはboolではなくstd::vector<bool>::reference型になる。 bool&型にならない理由は、std::vector<bool>はビットで真偽値を格納するように特殊化されており、bool&のように振る舞う型を返すため。 features(w)はstd::vector<bool>の一時オブジェクトを返すことに注意。 この一時オブジェクトはhighPriorityの初期化の命令文が終わった時点で破棄され、std::vector<bool>::referenceは無効なポインタとなってしまう。

proxy class

std::vector<bool>::referenceは、他の型をエミュレートしたり増強したりする"proxy class"の例。 スマートポインタもproxy class。 proxy classの有用性はデザインパターンの"Proxy"パターンに由来する。 スマートポインタはstd::shared_ptrのように目に見えるproxy、std::vector<bool>::referenceは目に見えないproxy。

以下はExpression templatesもproxy classを使用した例。

Matrix sum = m1 + m2 + m3 + m4;

operator+がMatrixオブジェクトではなくSum<Matrix, Matrix>というproxy classを返すことで無駄な処理を省いて高速化する。 結果として右辺はSum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>型となり、それがMatrix型に暗黙の型変換がされる。

一般的に、目に見えないproxy classはautoと相性が悪い。 proxy classのオブジェクトはたいてい一時オブジェクトで、文が終わる時に寿命も尽きてしまうためだ。

目に見えないproxy classが使われているかどうかは、そのライブラリのドキュメントやヘッダーファイルを読めば分かる。

template <class Allocator>
class vector<bool, Allocator> {
    public:
    class reference{ ... };

    reference operator[](size_type n);
    ...
};

typed initializerイディオム

proxy classに対してはtyped initializerイディオムを使うと良い。

auto highPriority = static_cast<bool>(features(w)[5]);
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);

このメリットは以下の例のように、戻り値と異なる型を指定しているのが意図的であることをはっきり示す点である。

double calcEpsilon();
float ep = calcEpsilon(); // 誤ってfloatを指定したのかと疑われる
auto ep = static_cast<float>(calcEpsilon()); //意図的にfloatにしたのだと分かる

// dは0.0と1.0の間の小数だとする
int index = d * c.size(); // doubleからintへの変換の意図が弱い
auto index = static_cast<int>(d * c.size()); // こちらの方が意図が伝わる

Things to Remember

  • 目に見えないproxy classはautoに「誤った」型推定をさせてしまう
  • typed initializerイディオムでautoに意図した型を推定させることができる