遺伝的アルゴリズム(GA)によるサーバの自動チューニング

[遺伝的アルゴリズム(GA)](http://ja.wikipedia.org/wiki/%E9%81%BA%E4%BC%9D%E7%9A%84%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0)でサーバの自動チューニングをします。

GAを機械学習を一つと書いてしまいましたが違うようなのでタイトルを変更させて頂きました。

遺伝的アルゴリズムについては↓の動画が分かりやすいです

> http://www.youtube.com/watch?v=yZJ1V-zv_gU

まずは通常の負荷テストができるところまで準備する必要があります。攻撃用のサーバをターゲットと(ネットワーク的に)近い場所に用意してください。負荷を掛ける側(Attacker)にも相応のスペックは必要です。

ストレスツールはコマンドラインから利用出来るものでしたらなんでもかまいません。`ab`(Apache Bench)などは最初から入っているので手軽ですが、今回は「[グリーン破壊](https://github.com/KLab/green-hakai/)」というソフトを利用しました（グリーン破壊のインストール方法は本家サイトに譲ります）

自動チューニングを行うにあたり、ターゲット（=チューニング対象のサーバ群）がChefで管理されていて、パラメータが属性になっている必要があります。例えば、`httpd.conf`テンプレート`httpd.conf.erb`が以下のようになっているとき、

```erb:httpd.conf.erb
...
<IfModule prefork.c>
StartServers     8
MinSpareServers  10
MaxSpareServers  20
ServerLimit      255
MaxClients       255
MaxRequestsPerChild  1000
</IfModule>
...
```
この`MaxClients`をChefの属性(attribute)にするなら以下のようにします。

```erb:httpd.conf.erb
...
<IfModule prefork.c>
StartServers     8
MinSpareServers  10
MaxSpareServers  20
ServerLimit      255
MaxClients       <%= node["httpd"]["max_clients"] %>
MaxRequestsPerChild  1000
</IfModule>
...
```
この状態で、`nodes/www-1.example.json`に以下のように書くことで、`www-1.example`サーバの`MaxClients`を制御することができます。

```json:
{
    "httpd" :{
        "max_clients" :255
    },
    ...
}
```

この`JSON`を書き換え、Chefを使ってターゲットにデプロイし、負荷テストを掛けてその結果から新しい試行パラメータをGAを用いて探索していくのが拙作の[gargor](https://github.com/tumf/gargor)です。

gargorをインストールします。(Ruby 1.9〜が必要です)

```bash:
gem install gargor
```

Chefレポジトリの下に`gargor.rb`を置きます。内容を適宜書き換えてください。

```rb:gargor.rb
# 世代数: 1以上を指定してください
max_generations 10

# 個体数: 1世代あたりの個体数
population 10

# エリート: 世代交代の時に適応値の高い個体をそのまま次世代に引き継ぐ数
elite 1

# 突然変異の確率: "0.01"で"1%"
mutation 0.01

# ターゲットをChefで料理するコマンド %sには、ノード名が入る
target_cooking_cmd "knife solo cook %s"

# ターゲットのノード
#   攻撃前に以下のノードすべてに対してtarget_cooking_cmdが実施される
target_nodes ["www-1.example"]

# 攻撃コマンド
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"

# 攻撃結果の評価
# code: attack_cmdのプロセス終了コード(普通は0:成功)
# out:  attack_cmdの標準出力
# time: attack_cmdの実行時間
evaluate do |code,out,time|
  puts out
  fitness = 0

  # 攻撃コマンドで使っている、グリーン破壊の標準出力から
  # FAILEDの値を抜き出し0以外は適応値0としている
  if time > 0 && code == 0 && /^FAILED (\d+)/ =~ out && $1 == "0"
    # 攻撃コマンドで使っている、グリーン破壊の標準出力から
    # 適応値に代用できそうなものを正規表現で取得する
    # request count:200, concurrenry:20, 45.060816 req/s
    if /, ([\.\d]+) req\/s/ =~ out
      fitness = $1.to_f
    end
    # 単純に実行時間で適応値を設定したいなら以下のようにしても良い
    # fitness = 1/time
  end
  # このブロックは必ず適応値を返すこと(整数or浮動小数)
  fitness
end

# パラメータ定義
# GAにより変動されるパラメータをここで定義する
# 
# param 名前 do
# json_file: 値を上書くJSONファイル nodes/*.jsonやroles/*.json
#            (注意!) gargorはこのjsonファイルを容赦なく書き換える
# json_path: json_fileの中から書変える値の場所をJSONPath形式で指定する
# mutaion:   突然変異時の値の設定
param "max_clients" do
  json_file "roles/www.json"
  json_path '$.httpd.max_clients'
  mutation rand(500)+10
end
```

グリーン破壊以外を使うときには、evaluateをカスタマイズする必要がありますが

```rb:
# fitness = 1/time
```

この部分をコメントアウトを外すことで計測時間（の逆数）でざっくりスコアを決めることができます。

gargorは最大で`世代数×個体数`の数の負荷試験を行い良さそうなパラメータを採用していきます。ものすごく時間がかかるので、帰り際に仕掛けて翌朝、結果を確認するような感じがよいかと。

もちろんパラメータを複数設定することもできます。

GAなので気まぐれの要素があり良い結果が出るかはやってみないとわかりません。個体数や世代数を増やすと時間がかかりますが精度が上がります。また、`mutation`の書き方で精度も変わりますので、是非いろいろやってみてください。

gargorはできたてホヤホヤのソフトウェアなので、不具合や改良がありましたらgithubにてお願いします。pull-requestお待ちしています。

> https://github.com/tumf/gargor