tjinjin's blog

インフラ要素多めの個人メモ

Rundeckを社内isuconのキューとして利用した話

先日、社内でisuconを開催した際にRundeckを利用したお話です。

背景

弊社内で、isucon5の予選問題を個人戦でやってみようということになり、私が環境の準備をしました。用意されているイメージを元にインスタンスを起動し、公開されているスクリプトを使ってインスタンス内でスコアが取得できることを確認しました。 しかし、開催日前日にとある若手エンジニアに「ベンチ実行するときにもCPUとか使うし、ベンチ対象サーバとベンチ実行サーバは分けて実行しないと正確な値取れないんじゃないですか^^」と言われて急遽リモートからベンチマークを実行できる環境を作ることになりました。

戦略

困ったものの、リモートから各インスタンスベンチマークを実行するパターンをいくつか考えてみました。

isucon5の予選相当の環境を用意する

準備が大変そう。キュー保存用のRDBMS用意しないといけないし。ということで今回は諦めました。

cronを使ってやる

毎時cronでチェックを行い、特定のファイルがあればベンチマークを実行する仕組みを考えました。リモートベンチを実行したい人がリモート環境にscpなどでファイルを送信して、実行依頼するイメージでした。が、実行依頼が面倒だし、sshログインを許可してあげないといけないなど、私の美意識が許さなかったので無しに。

delayed_jobを使う

社内では使っているのですが、私が使ったことないし、今から調べるのは時間的に厳しい/(^o^)\

このように考えて困っていたのですが、そういえば以前RundeckのAPIを検証して簡単にできたので使ってみるか、ということでRundeckを採用しました。

f:id:cross_black777:20151014231544p:plain

Rundeckとは

OSSのジョブスケジューラです。開発も活発で最近v2.6.0がリリースされました。今回はジョブの登録と実行機能を使ってみたいと思います。

設定をする

利用イメージ

f:id:cross_black777:20151014230336p:plain

※ 作画能力がなかったんです…

やりたいこと

  • 参加者からジョブを登録したい
  • 参加者からジョブ(ベンチマーク)を実行したい
  • slackへジョブの実行結果を通知したい

環境準備

まずterraformでEC2インスタンスを構築します。

resource "aws_instance" "test" {
    ami = "ami-xxxxxxxx"
    instance_type = "c3.large"
    key_name = "${var.key_name}"

    # Our Security group to allow HTTP and SSH access
    security_groups = ["${aws_security_group.test.name}"]

    iam_instance_profile = "xxx"
    root_block_device {
        delete_on_termination = "true"
        volume_size = "10"
    }
    tags {
        Name = "test"
        Project = "test"
        Stage = "production"
        Roles = "web"
    }
}

resource "aws_security_group" "test" {
    name = "test"

    # SSH access from anywhere
    ingress {
        from_port = 22
        to_port = 22
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
        from_port = 4440
        to_port = 4440
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

最小限の構成で作成しました。注意点としてはRundeckはデフォルトのポートが4440なので、ポートを開放しておく必要があります。

slackへの通知pluginを導入する

導入方法は簡単で、git cloneしてgradleを使ってjarを作成、plugin導入用のディレクトリがあるのでそこに導入するだけです(再起動は必要だったかも)

$ wget https://github.com/higanworks/rundeck-slack-incoming-webhook-plugin/archive/v0.5.dev.zip
$ unzip v0.5.dev.zip
$ cd rundeck-slack-incoming-webhook-plugin-0.5.dev
$ ./gradlew
$ ls build/libs/
rundeck-slack-incoming-webhook-plugin-0.5.dev-0.5.jar

できたファイルをlibext/ディレクトリにコピーするだけでokです。

簡単にプラグインが導入できていいですね!他にもRundeckにはプラグインがあるので探してみると面白いかもしれません。ただ、実際のスコアをslack上に出力できなかったのが少し残念でした。(少し調べた限りRundeck側が用意してなさそうに見えました)

job登録APIを利用する

参加者に配布したインスタンスからjob登録APIを実行してもらいます。仕組みは簡単で、事前に作成しておいたjob定義のテンプレートがあるので、それを使ってjobを登録するだけです。

<joblist>
  <job>
    <sequence keepgoing='false' strategy='node-first'>
      <command>
        <exec>cd isucon5-benchmarker &amp;&amp; perl5.22.0 bench.pl @IP_ADDRESS@</exec>
      </command>
    </sequence>
    <notification>
      <onsuccess>
        <plugin type='SlackNotification'>
          <configuration>
            <entry key='webhook_url' value='https://hooks.slack.com/services/hogehoge' />
          </configuration>
        </plugin>
      </onsuccess>
      <onfailure>
        <plugin type='SlackNotification'>
          <configuration>
            <entry key='webhook_url' value='https://hooks.slack.com/services/hogehoge' />
          </configuration>
        </plugin>
      </onfailure>
   </notification>
    <loglevel>INFO</loglevel>
    <name>@HOST_NAME@</name>
    <context>
      <project>isucon</project>
    </context>
    <description></description>
    <id>@HOST_NAME@</id>
    <uuid>@HOST_NAME@</uuid>
  </job>
</joblist>

このテンプレートは実際にジョブを作ってからAPIを使って抜き出しています。そのうえで必要な部分だけ変数にして置き換えるようにしました。

#!/bin/bash

. ./rundeck/env.sh

sed "s/@IP_ADDRESS@/$IP_ADDRESS/g" rundeck/template_job.xml > rundeck/define_job.xml
sed -i "s/@HOST_NAME@/$HOST_NAME/g" rundeck/define_job.xml

curl -s -H "X-Rundeck-Auth-Token: $API_KEY" -F xmlBatch=@"./rundeck/define_job.xml" $END_POINT/api/9/jobs/import | grep error || echo "succeeded."

同じUUIDのジョブはrundeck側がエラーを吐くようになっているので、その点は今回は特に考慮しませんでした。ちなみにですが、すでに定義済みのジョブを抜き出し、そのままインポートすることも可能です。その場合はパラメータに?uuidOption=removeを指定することでランダムなUUIDでジョブを作成することができます。

job実行APIを利用する

jobを実行するにはjobに割り当てられたUUIDを知る必要があります。今回はjob登録時に指定したUUIDを使っているので、それを利用させました。特に指定しない場合はランダムな値になってしまいます。

#!/bin/bash

. ./rundeck/env.sh

curl -s -H "X-Rundeck-Auth-Token: $API_KEY"  $END_POINT/api/13/job/$HOST_NAME/run | grep error || echo "succeeded. look at slack!"

上記の例で言うと、 $HOST_NAMEという部分が特に指定しない場合ランダムな文字列になります。

実際に実行されると下記のようなイメージで通知されます。

f:id:cross_black777:20151014230149p:plain

実際に使ってみての使用感

1つのジョブを直列実行することはできるのですが、複数のジョブを直列に実行する方法が見つかりませんでした。ジョブ同士が干渉してリソースを食い尽くしてベンチマークに影響しないかは気になるところです。

まとめ

RundeckのAPIは以前から触っていたので、気楽に利用することができました。え?Rundeck使う必要あったのかって?試してみたかったんですごめんなさい。

ちなみに社内ISUCON大会では、無事優勝することができました。ただ結果は悲惨だったので触れないで下さい。来年ISUCON6があればぜひ参加したいと思います。それまでにしっかり勉強しておきたいと思います。

参考情報

Rundeck.org - Job Scheduler and Runbook Automation

higanworks/rundeck-slack-incoming-webhook-plugin · GitHub

ISUCON公式Blog

karupanerura/isucon5-benchmarker · GitHub

cross-black777.hatenablog.com