sensu プラグインを作って学ぶ Go

ども、cloudpackかっぱ (@inokara) です。

はじめに

冬休みの宿題として Go を触ってみたいと思います。何か目的が無いと長続きしないと思ったので sensu プラグインを作りながら Go を学んでいきたいと思います。


Go とは

ひろみです。

Google が公開したプログラミング言語です。
Bash や Ruby とは異なりコンパイルして実行します。

Go とはについては自分のような初心者がどうこう言えませんので golang.jp をご覧ください。


sensu プラグインを作りながら

sensu プラグインについて

チェック用のプラグインについてはスクリプトに言語は問わず、終了のステータスコードを以下のように出力させることでプラグインを実装することが出来ます。(今回はメトリクスプラグインは触れません。)

ステータス コード
正常 0
Warning 1
Critical 2
上記以外 上記以外の数値

尚、Go で作るにあたって出来るだけ Sensu-Community-Plugin にアップされているスクリプトと同じ方法でチェック、結果を返すようにするのが目標です。

check-cpu

CPU の使用率をチェックするプラグインです。

参考にしたプラグインは check-cpu.rb です。このプラグインは /proc/stat の値をチェックします。

cpu  104528 0 38935 8960969 494 16 2972 0 0 0
cpu0 14094 0 13390 2244649 247 16 2571 0 0 0
cpu1 49156 0 6885 2221203 147 0 128 0 0 0
cpu2 12056 0 9542 2256779 66 0 132 0 0 0
cpu3 29222 0 9118 2238338 34 0 141 0 0 0
intr 2089129 25 10 0 0 12 0 0 0 1 0 0 0 149 0 0 0 289 0 0 84791 15 28287 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 3247041
btime 1419826317
processes 16404
procs_running 1
procs_blocked 0
softirq 2257245 0 744345 4 197647 25399 0 1 586480 4911 698458

以下のように /proc/stat の一行目を一定間隔で取得してその差分を計算して CPU 使用率を算出します。

cpu  104528 0 38935 8960969 494 16 2972 0 0 0

当初は以下のように /proc/stat を頑張って読み込んでいました。

func acquire_cpu_stats(p string)(s string) {
    // /proc/stat を読み込む     
        cpu_stats, err := os.OpenFile(p, os.O_RDONLY, 0)

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        // ファイルを閉じる(defer 関数で main 関数の処理が終わる際(最後)に実行してくれる))
        defer func() {
                cpu_stats.Close()
        }()

    // 読み込んだ /proc/stat を一行ずつ読み込んで "^cpu " な行(一行目)をだけを取得して main() に返却
        scanner := bufio.NewScanner(cpu_stats)
        for scanner.Scan() {
                // 返り値が二つあるけど一つしか使わないのでブランク識別子(_)を入れる
                matched, _ := regexp.MatchString("^cpu ", scanner.Text())
                if matched {
                        return scanner.Text()
                }
        }

        if err := scanner.Err(); err != nil {
                fmt.Fprintln(os.Stderr, "reading standard input:", err)
        }
        return s
}

ところが、ところが、以下のパッケージを利用することで cat /proc/stat の処理をパッケージにお願いすることが出来るようになり、自前で /proc/stat を読み込んで処理をする必要がなくなりました。

$GOPATH を指定した後で以下のようにパッケージを取得するだけで利用出来るようになります。

export $GOPATH=/path/to/go
go get bitbucket.org/bertimus9/systemstat

このパッケージを使うことで以下のように書けました。パッケージのおかげで基本的には取得した値を比較してそれぞれのステータスコードを返す処理を行うだけです。

package main

import (
        "bitbucket.org/bertimus9/systemstat"
        "fmt"
        "time"
        "flag"
        "os"
        "strconv"
)

var last_cpu systemstat.CPUSample
var current_cpu systemstat.CPUSample
var cpu_stat systemstat.SimpleCPUAverage
var cpu_average systemstat.CPUAverage

func main() {

...
(snip)
...
        if cpu_stat.BusyPct > critical {
                fmt.Println("critical")
                os.Exit(2)
        } else if cpu_stat.BusyPct > warning {
                fmt.Println("warning")
                os.Exit(1)
        } else {
                fmt.Println("ok")
                os.Exit(0)
        }
}

引数を扱う為には flag を利用して Warning と Critical を指定出来るようにしています。

...
(snip)
...
        var w = flag.String("warning", "80", "Warning Level(%)")
        var c = flag.String("critical", "100", "Critical Level(%)")
        //var s = flag.Int("Sleep", 1000, "Set SleepTime")
        flag.Parse()
...
(snip)
...

-h を指定すると以下のようにヘルプメッセージにも対応しています。

Usage of ./check-sysstat-cpu:
  -critical="100": Critical Level(%)
  -warning="80": Warning Level(%)

実際にプラグインをコンパイルして実行すると以下のに出力されます。

# ./check-sysstat-cpu -warning=80 -critical=100
CPUAverage={3.846153846153846 0 0.2564102564102564 95.89743589743588 0 0 0 0 0 2014-12-29 20:01:50.213357406 +0900 JST 1.001324773}
ok

CPUAvereage から始まる行はデバッグ用です。

{
  "checks": {
    "check_cpu": {
      "handlers": ["default"],
      "command": "/etc/sensu/plugins/sensu-plugins-go/check-cpu/check-cpu -warning=80 -critical=12345",
      "interval": 10,
      "standalone": true
    }
  }
}

check-mem

監視対象のメモリ空き容量をチェックするプラグインです。

このプラグインも systemstat パッケージを利用させて頂きます。

...
(snip)
...
        // Buffers Cached MemTotal MemUsed MemFree SwapTotal SwapUsed SwapFree time.Time
        // {80888 648992 2056668 1881204 175464 1438004 476 1437528 2014-12-30 09:57:09.298455307 +0900 JST}
        mem := systemstat.GetMemSample()
        // 純粋な空き容量を算出
        mem_free_uint := (mem.Buffers + mem.Cached + mem.MemFree)
        // uint64 => String => float64
        mem_free, _ := strconv.ParseFloat(strconv.FormatUint(mem_free_uint, 10), 64)
...
(snip)
...

mem 変数を宣言(省略形で)して systemstat.GetMemSample() の値を格納しています。

{80888 648992 2056668 1881204 175464 1438004 476 1437528 2014-12-30 09:57:09.298455307 +0900 JST}

mem の中身は上記のようになっており時刻以外の型は uint64 になっていますので、パーセンテージを算出する為に型の変換を行っています。(uint64 を直接 float64 に変換する良い方法が解らなかったので uint64 => String => float64 という変換をしています…)

実際にビルドしたものを実行させると以下のように出力されます。

# ./check-sysstat-memory -warning=10 -critical=5
Free Memory: 933516.00 KB
Total Memory: 2056668.00 KB
Free Percent: 45.39 %
Used Percent: 54.61 %
ok

このプラグインスクリプトは sensu のチェック設定に以下のように設定します。

{
  "checks": {
    "check_mem": {
      "handlers": ["default"],
      "command": "/etc/sensu/plugins/sensu-plugins-go/check-memory/check-sysstat-memory -warning=10 -critical=5",
      "interval": 10,
      "standalone": true
    }
  }
}

check-port

各種サービスの TCP / UDP のポートチェックするプラグインです。

check-ports.rb は nmap を利用して指定したポートが開放されているかをチェックしていますが、check-port.go は Go の net パッケージを利用してポートの開放をチェックしています。

// hoge project main.go
package main

import (
    "flag"
    "fmt"
    "net"
    "os"
)

func main() {
    //(name, default, help)
    var proto = flag.String("prot", "tcp", "Set Protocol")
    var hostname = flag.String("hostname", "www.example.com", "Set Hostname")
    var port = flag.String("port", "80", "Set Port Number")
    flag.Parse()

    fmt.Print("Checking..." + *hostname + ":" + *port + "n")
    conn, err := net.Dial(*proto, *hostname+":"+*port)

    if err != nil {
        fmt.Println(err)
        os.Exit(2)
    } else {
        fmt.Print("OK" + "n")
        os.Exit(0)
    }

    defer func() {
        conn.Close()
    }()
}

このスクリプトは「基礎からわかる Go言語」に載っていたサンプルに引数対応しただけです(汗)。このスクリプトをビルドして以下のように実行します。

# ./chek-port -hostname=www.google.com -port=80
Checking...www.google.com:80
OK

このプラグインスクリプトは sensu のチェック設定に以下のように設定します。

{
  "checks": {
    "check_port": {
      "handlers": ["default"],
      "command": "/etc/sensu/plugins/sensu-plugins-go/check-port/check-port -hostname=foo.bar -port=12345",
      "interval": 10,
      "standalone": true
    }
  }
}

ということで…

引き続き、Go を勉強していきたいと思いますが、気づいた点をいくつか。

自分はシェルスクリプトをちょっと触るくらいのレベルですが、こんなにも値の型を意識しなければいけない(異なる型同士の比較や代入等)のかと驚きました。C や Java 等を常に触っている方にとっては当たり前のことなのかもしれませんが。

代入(ブランク識別子)

人さまのソースコードを見ていて気づいたのが以下のような _ への代入です。

// baz が使われない場合にはエラーとなる
foo, baz = bar()
// 以下のように一つ目の戻り値しか利用しない場合に 2 番目の戻り値には _ を指定する
foo, _ = bar()

これはブランク識別子と呼ばれるもので関数の戻り値が複数ある場合、一つの戻り値しか利用しない場合に明示的に _ 指定してあげることでコンパイルエラーを防ぐ目的があるようです。(※ Go では利用しない変数を宣言しておくとコンパイルエラーになるようです)

変数

利用する変数はあらかじめ型や初期値について以下のように指定する必要があります。

var foo string = "bar"

ちなみに、以下のように初期値が与えられている場合には型を省略することも可能です。

var foo = "bar"

尚、上記の場合の変数 foo の型は String になるようです。さらに、以下のように var も省略することが出来るようです。

foo := "bar"

面白いですね。

引き続き…

この冬休みは Go で郷、郷していきたいと思います。

元記事はこちらです。
sensu プラグインを作って学ぶ Go

mruby + mruby-socket で Graphite にメトリクスを送るメモ

ども、cloudpackかっぱ (@inokara) です。

以前に…

ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる

上記の記事を書いた際に @matsumotory さんより以下のようなツイートでご紹介頂きました。

記事を書き終えた後で InfluxDB にメトリクスを送る際に HTTP で送るのはちょっと大袈裟でコスト高いなーと思っていたので、mruby で socket 通信出来れば Graphite にメトリクス飛ばせそうだなと思ってググったら socket 通信出来る mruby-socket があったのでこれを利用して Graphite にメトリクスを送ってみました。

mruby-graphite-client

せっかくなので

mruby-socket を利用して Graphite のクライアントを作って(と言ってもホンの数行)みたのでこれを利用してみたいと思います。

ビルド

build_config.rb に以下を追加して make するだけ。

  conf.gem :git => 'https://github.com/iij/mruby-socket.git'
  conf.gem :git => 'https://github.com/iij/mruby-io.git'
  conf.gem :git => 'https://github.com/iij/mruby-mtest.git'
  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
  conf.gem :git => 'https://github.com/inokappa/mruby-graphite-client.git'

らくちん。

Graphite で…

メトリクスデータを登録する場合には以下のようなフォーマットで carbon-cache の TCP ポート(2003)に送りつけます。

${path} ${datapoint} ${timestamp}

nc コマンドを利用すると以下のような感じで送れます。

echo "foo.bar 123456.1 1419606029" | nc localhost 2003

timestamp は UNIX タイムで指定します。

試しに

以下のようなスクリプトを書いて Graphite にメトリクスを送りましょう。

config = {
  :host  => "127.0.0.1",
  :port  => "2003",
}
g = Graphite::Client.new(config)

path = "foo.bar"
datapoint = ARGV[0].to_f

g.post(path,datapoint)

以下のように実行すると Graphite のデータベースが作成されます。

mruby graphite-client.rb

データベースファイルは Graphite のインストール環境にも依存しますが、Ubuntu 14.04 でパッケージインストールを行った場合には以下のようなパスに whisper データベースが生成されます。

/var/lib/graphite/whisper/foo/bar.wsp

このデータベースファイルは whisper-dump というコマンドで中身を見ることが可能です。

whisper-dump /var/lib/graphite/whisper/foo/bar.wsp

以下のように表示されます。

Meta data:
  aggregation method: average
  max retention: 86400
  xFilesFactor: 0.5

Archive 0 info:
  offset: 28
  seconds per point: 1
  points: 86400
  retention: 86400
  size: 1036800

Archive 0 data:
0: 1419593100,      12345
1: 0,          0
2: 0,          0

(snip)

おお。

ちょっとデモ

適当に…

Graphite に送るスクリプトをループさせて Graphite に値をぶち込んでいきましょう。

while true
do
  mruby graphite-client.rb `echo $RANDOM`
done

以下のように Graphite や Tasseo でなんちゃってリアルタイムに数値を見ることが出来ます。

Graphite で

mruby + mruby-socket で Graphite にメトリクスを送るメモ: Graphiteで動作確認

Graphite のダッシュボード設定で 1 秒毎に Refresh させてなんちゃってリアルアイムを実現です。

Tasseo で

mruby + mruby-socket で Graphite にメトリクスを送るメモ: Tasseoで動作確認

Tasseo はデフォルトは 2 秒ごとにリロードなので放っておけばこちらもなんちゃってリアルタイムを実現です。

ちなみに…

Tasseo で Graphite をバックエンドデータベースとして使う場合には CORS を設定してクロスドメイン通信を許可する必要があります。例えば、Ubuntu 14.04 の場合には以下のように設定を行います。

apache2-graphite.conf に以下を追加します。



        Header set Access-Control-Allow-Origin "*"
        Header set Access-Control-Allow-Methods "GET, OPTIONS"
        Header set Access-Control-Allow-Headers "origin, authorization, accept"

...
(snip)
...


追加した後で headers モジュールを有効にして Apache を再起動します。

a2enmod headers
service apache2 restart

dashboard の設定は以下のように書きました。

var metrics =
[
  {
    "target": "foo.bar",
    "warning": 10000,
    "critical": 50000
  }
];

ということで…

思ったよりも簡単に socket で Graphite にメトリクスを飛ばすことが出来ました。mod_mruby や ngx_mruby と組み合わせて Apache や Nginx の内部情報の可視化に使ってみたいと思います。

元記事はこちらです。
mruby + mruby-socket で Graphite にメトリクスを送るメモ

Ansible の module の作成

こんにちは、cloudpackSebastian です。

はじめに

Ansibleのmoduleは基本的にはpythonで記述を行います。
実はmodule自体は所定の形式でレスポンスを返せばpythopnで無くても記載は可能なのですが、公式が pythonで書かれている事と、ansibleがpythonの使用を前提に記載されている事からpythonを用いないと公式にcommitは出来ません。
また、pythonの方が相性は良いので今回はそのままpythonで書きます。

moduleが実行されるまで

ansibleのmoduleを書く前に、まずansibleのmoduleがどのように実行されるのかを知らねば話になりません。
そういう訳でmoduleがどの様に実行されていくのかを追いかけてみます。

ansibleの導入後、サーバーに対してpingモジュールを用いてremote serverへの疎通応答を求めて見ます。
-vvvvはデバッグ出力用のオプションです。

ansible hogehoge -m ping -vvvv

するとレスポンスが以下の様に返ります。

 ESTABLISH CONNECTION FOR USER: huga
 REMOTE_MODULE ping
 EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r', '-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o', 'PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10', 'xxx.xxx.xxx.xxx', "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'"]
 PUT /var/folders/0x/g4lkrc4528x_rqpzr47d2k0r0000gp/T/tmpxqYhf1 TO /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping
 EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r', '-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o', 'PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10', 'xxx.xxx.xxx.xxx', u"/bin/sh -c 'LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping; rm -rf /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ >/dev/null 2>&1'"]
hogehuga | success >> {
    "changed": false,
    "ping": "pong"
}

1行目の ESTABLISH CONNECTION FOR USER: hugaはxxx.xxx.xxx.xxxに接続しhugaユーザーにてログインを行う事を明示しています。
2行目の REMOTE_MODULE pingはpingモジュールを用いる事を明示しています。

問題は3行目以降です。

 EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r','-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o','PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10','xxx.xxx.xxx.xxx', "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'"]

EXECと書いてあるからには何らかのexecuteだと判断出来ます。
[]で囲まれている範囲を見てみるとsshにて何らかの実行を行っています。

見やすく整えると以下のようになります。

ssh
  -C
  -tt
  -vvv
  -o ControlMaster=auto
  -o ControlPersist=60s
  -o ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r
  -o StrictHostKeyChecking=no
  -o Port=22
  -o IdentityFile="/etc/ansible/key/bar.pem"
  -o KbdInteractiveAuthentication=no
  -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey
  -o PasswordAuthentication=no
  -o User=huga
  -o ConnectTimeout=10
  xxx.xxx.xxx.xxx
  /bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'

幾つかのsshのオプションが並び最後に/bin/shがあります。
つまりssh経由でリモートホストに対してcommandを投げている訳です。
内容は以下の様になります。

mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901
chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901
echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901

見るとユーザーホーム以下に一時directoryを作成しています。

4行目は PUT /var/folders/0x/g4lkrc4528x_rqpzr47d2k0r0000gp/T/tmpxqYhf1 TO /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/pingとなっています。
PUTと在るようにremote server内の先ほど作成した一時directory内にpingをputしています。

ここまでで分かると思いますが、ansibleのmoduleはremote serverにmodule自身が転送されてremote server上で実行されます。

実際に5行目にはEXECの指定で実行commandが記載されています。

u"/bin/sh -c 'LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping; rm -rf /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ >/dev/null 2>&1'"

内容を追うと、言語環境が英語環境で文字コードがutf-8にてpythonを用いて先ほどのpingを実行しています。
また、その後rmにてpingファイルの削除が実行されています。

最後にjsonにて文字列が返って来ています。

hogehuga | success >> {
    "changed": false,
    "ping": "pong"
}

これはansibleのmoduleが吐き出すレスポンスの内容です。

  • ansibleはmoduleをremote serverに転送して実行を行う
  • 転送先は一時directoryとして日付付きで作成される
  • us-US.UTF-8が指定されたpythonにて実行される
  • moduleは実行後、速やかに削除される
  • moduleのresponseはjsonで返却される
    ※ この為、ansibleはsimple json moduleが必須になる
補足

当然ながらこれらの挙動はansibleの設定次第で変更されるものです。

転送先の一時directoryはansible.cfgに定義されるremote_tmpによります。
EXECにて用いられるshellはexecute設定で変更されるのでzshやcshに変更する事も可能でしょう。
inventoryのhost指定でansible_*_interpreterの指定を行えばpythonでは無くruby等の実行も可能ですが基本はpythonで行うことが好ましいです。

ansible module作る

tutorial

では、ansible module実際に作ってみます。
ansibleのTutorialを見るとexampleが書いてあります。

#!/usr/bin/python

import datetime
import json

date = str(datetime.datetime.now())
print json.dumps({
    "time" : date
})

はい、かなりシンプルですね。
jsonを用いてdateを返すだけのmoduleです。
実行してみると以下の様に返ります。

ansible hogehuga -m time -v
hogehuga | success >> {
    "time": "2014-12-25 12:02:59.370913"
}

ただのpythonファイルなので単純に実行してみても構いません

chmod +x  time
./time
{"time": "2014-12-25 21:04:14.466017"}

ansible_module class library

tutorialではjsonを自分で記載していましたが、実は最初からansible用のlibraryが用意されています。
このlibraryを用いると引数を処理したりjsonにてresponseを返したりはそのまま行えます。

試しに、必須引数messageを指定すると指定した引数messageの内容をそのまま返却するだけのmoduleを作成してみます。

#!/usr/bin/python
# -*- coding: utf-8 -*-
from ansible.module_utils.basic import *
DOCUMENTATION = '''
---
module: echo
short_description: This is a sample echo module
description :
   - This module is module which just returns the message just as it is.
version_added: "1.0"
options:
  message:
    description:
      - echo message.
    required: true
    default: null
'''
EXAMPLES = '''
- action: echo message=arg1
'''
###############################################################################
#
# ansible module main function
#
###############################################################################
def main():
    module = AnsibleModule(
        argument_spec=dict(
                message=dict(required=True) ,
        ) ,
        supports_check_mode=True
    )
    try :
        #
        # 受け取ったパラメータのチェック
        msg_param = module.params['message']
        if module.check_mode:
            module.exit_json(changed=False , msg=msg_param , mode='check mode')
        else :
            module.exit_json(changed=False , msg=msg_param)
    except Exception as e:
        module.fail_json(msg=str(e))

###############################################################################
#
# call main
#
###############################################################################
main()

はい、こんな感じです。
これをechoとして保存しmoduleとして実行してみます。

ansible hogehuga -m echo -a "message='hello ansible world'"
hogehuga | success >> {
    "changed": false,
    "msg": "hello ansible world"
}
引数の処理 arguments_spec

内部でinstance化しているClass AnsibleModuleがansibleのmoduleを記載する為に用いるclassになります。

    module = AnsibleModule(
        argument_spec=dict(
                message=dict(required=True) ,
        )
    )

arguments_specにてdictにて指定されているmessageが引数名を表しています。
更に、messageにイコールで指定されるdictは引数messageに関しての詳細を示しています。
ここではrequiredTrueが指定されているため、必須引数である旨を定義しています。
以下に引数の制御に用いれる内容の一覧を示します。

指定内容 意味
require 必須指定 required=True
default 指定引数が無指定の場合に用いる値を指定します default=None
choices 配列にて指定して指定配列の中から値を指定させる様に指定します choices=[‘start’ . ‘stop’ , ‘restart’]
type 引数の型を指定します type=’int’
aliases 引数名のエイリアスを作成します aliases[‘name’]
responseの処理

AnsibleModule classはレスポンスはdict指定で連想配列を渡すと自動的にjsonで返してくれます。
echoスクリプトで指定されているmodule.exit_json(changed=False , msg=msg_param)が実際にresponseを返している行です。
changedはmoduleが実行された結果、変更されたか否かの指定を行います。
特に何も指定しない場合はFalseが返されるようになっています。

ただし、何らかの要因により処理に失敗した場合は別です。
exit_jsonは出力結果をsuccessとして返すため、failの際は別途処理が必用です。
echoスクリプト内ではmodule.fail_json(msg=str(e))がfailでの返却を行うmethodの指定です。

check modeとsupports_check_mode

実はansibleにはcheck mode、つまりdry runモードがあります。
しかし、module側でcheck modeに対応を行い、check modeが指定された際にcheckのみを行い、変更を行わない処理の実装を行わなければなりません。
check_modeに対応するためにはまず、AnsibleModuleの引数にてsupports_check_mode=Trueの指定を行うつ様はあります。

Falseが指定されていたり指定がない場合は以下の様に返ります。

ansible hogehuga -m echo -a "message='hello ansible world'" --check
hogehuga | skipped

また、実際にsupports_check_mode=Trueの指定があってもそのまま実行しては変更されてしまいます。
よって、check modeに対応する際はAnsibleModuleのcheck_mode methodにて実行時にcheck modeであるかの確認を行います。
テスト用のスクリプトではcheck modeであった場合はresponseのmodeに「check mode」と返る様にしています。

よって、check modeで実行すると以下の様に返ります。

ansible hogehuga -m echo -a "message='hello ansible world'" --check
hogehuga | success >> {
    "changed": false,
    "mode": "check mode",
    "msg": "hello ansible world"
}
documentation

ansibleにはansible-docと言うcommandがあります。
このcommandを用いるとansibleでは各moduleのdocumentが表示されるようになっています。
折角なのでdocumentも書いておきます。
因みに、ここでmoduleにdocumentが書かれていないと漏れ無く、ansible-docでエラーが吐かれます。
ansibleのdocumentationはそのままpythonのご作法に従って書かれているので
DOCUMENTATION = ''' 〜〜〜〜 '''で書けばOKです。
注意点としてはansible上でのpythonの実行環境はあくまで英語環境のutf-8が指定されているので日本語での指定はNGです。

DOCUMENTATION = '''
---
module: echo
short_description: This is a sample echo module
description :
   - This module is module which just returns the message just as it is.
version_added: "1.0"
options:
  message:
    description:
      - echo message.
    required: true
    default: null
'''

以上の様に記載しておき、ansible-docにて表示を行うと整形されたdocumentが出力されます。

ansible-doc echo
> ECHO

  This module is module which just returns the message just as it is.

Options (= is mandatory):

= message
        echo message. [Default: None]

- action: echo message=arg1

元記事はこちらです。
ansibleのmoduleの作成

俺の consul チートシート

ども、cloudpackかっぱ (@inokara) です。

consul が自分の中で熱くなってきているので Docker でコンテナ立てて色々と動かしてみたいと思います。

consul チートシート

前提

  • consul と redis 入りの Docker コンテナを利用
  • コンテナの起動の際に --dns オプション付けて名前解決はローカルホストを利用

docker コンテナを –dns オプション付きで起動

docker run -t --dns=127.0.0.1 -i inokappa/consul-redis /bin/bash

--dns= でコンテナ内が参照する DNS サーバーを指定することが出来る。なにげに嬉しい。

consul の設定(1)

mkdir /etc/consul.d/
cat << EOT >> /etc/consul.d/consul.json
{
  "ports": {
    "dns": 53
  },
  "recursor": "8.8.8.8"
}
EOT

DNS インターフェースのポートを 53 に固定(デフォルトは 8600 ポート)、consul 内で解決出来ない名前は 8.8.8.8 に問い合わせる設定。(コレ重要)

以下、参考。

consul の起動

consul agent -server -bootstrap -client=127.0.0.1 -dc=local 
             -node=consul1 -data-dir=/tmp/consul  -bind=127.0.0.1 
             -config-dir /etc/consul.d 
             -config-file /etc/consul.d/consul.json &

たくさん起動オプション付けているけど設定ファイル(/etc/consul.d/consul.json)に加えてもいい。

redis の起動

/usr/local/bin/redis-server &
echo "set monitor ok" | redis-cli

ついでに redis 監視用にデータをセットしておく。

redis 監視用スクリプトの設置

redis に事前に登録されているデータを GET させるチェック。

#!/usr/bin/env bash
# echo "set monitor ok" | redis-cli
status=`echo -n "GET monitor" | /usr/local/bin/redis-cli`
if [ ${status} = "ok" ];then
  exit 0
else
  exit 1
fi

redis のポート(6379)を直接突くチェック。

#!/usr/bin/env bash

status=`nc -z localhost 6379`
if [ $? = "0" ];then
  exit 0
else
  exit 1
fi

どっちか選びましょう。

consul にサービスを登録

cat << EOT | curl -XPUT http://127.0.0.1:8500/v1/agent/service/register -d @-
{
    "name": "redis",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
}
EOT
cat << EOT | curl -XPUT http://127.0.0.1:8500/v1/agent/service/register -d @-
{
    "name": "redis-read",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
}
EOT

もしくは、以下のように consul.d 以下に設定ファイルとして置いても良い。

cat << EOT > /etc/consul.d/redis-read.json
{
  "service":
  {
    "name": "redis-read",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
  }
}
EOT

cat << EOT > /etc/consul.d/redis.json
{
  "service":
  {
    "name": "redis",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
  }
}
EOT

登録されたサービスの確認

curl -s http://127.0.0.1:8500/v1/catalog/services

jq なんぞカマスと以下のように。

{
  "consul": [],
  "redis": [],
  "redis-read": []
}
curl -s http://127.0.0.1:8500/v1/catalog/service/redis

jq なんぞカマスと以下のように。

[
  {
    "Node": "consul1",
    "Address": "127.0.0.1",
    "ServiceID": "redis",
    "ServiceName": "redis",
    "ServiceTags": null,
    "ServicePort": 6379
  }
]
curl -s http://127.0.0.1:8500/v1/catalog/service/redis-read

jq なんぞカマスと以下のように。

[
  {
    "Node": "consul1",
    "Address": "127.0.0.1",
    "ServiceID": "redis-read",
    "ServiceName": "redis-read",
    "ServiceTags": null,
    "ServicePort": 6379
  }
]

leader ノード確認

Raft leader の確認。

curl -X GET http://localhost:8500/v1/status/leader

DC 内の peer 確認

curl -X GET http://localhost:8500/v1/status/peers

デモっぽいこと

やりたいこと

  • アプリケーションから consul に登録されたサービス名を利用する
  • Sinatra から Redis にアクセスする際に consul に登録済みのサービス名でアクセスするようにする

Ruby をソースコードからインストール on CentOS 6.x

yum -y install gcc zlib-devel openssl-devel sqlite sqlite-devel
cd /usr/local/src
wget http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz
tar zxvf ruby-2.1.5.tar.gz
cd ruby-2.1.5
./configure
make
make install

ついでに Sinatra

Sinatra から Redis を扱いたいのでついでにインストールする。

gem install sinatra --no-ri --no-rdoc -V
gem install sinatra-contrib redis hiredis --no-ri --no-rdoc -V

Ruby から redis を扱う

以下、参考。

irb で試す。

# irb
irb(main):001:0> require 'redis'
=> true
irb(main):002:0> r=Redis.new host:"redis.service.consul", port: "6379"
=> #
irb(main):003:0> r.set :foo, "bar"
=> "OK"
irb(main):004:0> r.get :foo
=> "bar"
irb(main):005:0> exit

consul に redis という名前でサービスを登録しているのでアプリケーションからも redis.service.consul という名前で参照出来る。

Ruby から redis を扱う(2)

Write と Read のエンドポイントを変える。
とりあえず、これも irb で試す。

# irb
irb(main):001:0> require 'redis'
=> true
irb(main):002:0> r=Redis.new host:"redis.service.consul", port: "6379"
=> #
irb(main):003:0> r.set :foo, "bar"
=> "OK"
irb(main):004:0> rr=Redis.new host:"redis-read.service.consul", port: "6379"
=> #
irb(main):005:0> rr.get :foo
=> "bar"
irb(main):006:0>

consul で Write 用のサービスと Read 用のサービスを別々に登録しているが、先ほどと同様にアプリケーションから参照することが可能。

Sinatra + Redis でちょっとしたアプリ

ちょっとしたアプリのバックエンドに redis のクラスタを置いて、その redis を consul で監視しつつ、ノードの一台に障害が起きてもサービスは一応継続出来るようなシチュエーションを作ってみたい。

#!/usr/bin/env ruby
#
require 'sinatra'
require "sinatra/reloader"
require 'redis'
require 'json'

r = Redis.new host:"redis.service.consul", port:"6379"
rr = Redis.new host:"redis-read.service.consul", port:"6379"

get '/value/:key' do
  value = rr.get params[:key]
  v = {
    key: params[:key],
    value: value
  }
  v.to_json
end

post '/data/:key' do
  msg = request.body.read
  r.set params[:key], msg
end

Sinatra 先生を使って consul に登録された redis サービスに対して読み書き。書き込みは 1 インスタンスの redis-server を consul に登録、読み込みは二つのホストにまたがった 2 つの redis-server インスタンスを consul に登録した。尚、redis-server はレプリケーションを設定。←重要。

consul service id consul node DNS Name 用途
redis consul1(172.17.0.5) redis.service.consul 書き込み、読み取り
redis-read consul1(172.17.0.5) / consul2(172.17.0.6) redis-read.service.consul 読み取り

アプリケーションの起動は以下のように。

ruby app.rb &

以下のようにデータをポスト。

curl -X POST localhost:4567/data/consul -d 'good'

以下のようにデータを取得。

curl -s -X GET localhost:4567/value/consul | jq .

一応、無駄に JSON で返す実装。

{
  "key": "consul",
  "value": "good"
}

例えば、一台の redis-server が死亡しても…

# dig redis-read.service.consul
    2014/12/24 15:55:57 [WARN] dns: node 'consul1' failing health check 'service:redis-read: Service 'redis-read' check', dropping from service 'redis-read'

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6 <<>> redis-read.service.consul
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33995
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;redis-read.service.consul.     IN      A

;; ANSWER SECTION:
redis-read.service.consul. 0    IN      A       172.17.0.6

;; Query time: 2 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Dec 24 15:55:57 2014
;; MSG SIZE  rcvd: 84

ちゃんと、データへのアクセス(読み取り)は可能。

# curl -s -X GET localhost:4567/value/consul | jq .
::1 - - [24/Dec/2014:15:57:29 +0000] "GET /value/consul HTTP/1.1" 200 31 0.0007
localhost - - [24/Dec/2014:15:57:29 GMT] "GET /value/consul HTTP/1.1" 200 31
- -> /value/consul
{
  "key": "consul",
  "value": "good"
}

もちろん、書き込みは残念ながらエラーになるが、redis のレプリケーションと consul のサービス監視と DNS 機能によって完全ではないもののサービスを継続することは出来そう。(読み取りだけでサービスを提供している間に死亡した redis-server を復旧する等の時間稼ぎくらいには使えそう)


ということで…

まだまだ理解出来ていない機能があったりするが consul ってどんなふうに使うのか?というのはちょっとしたアプリケーションを動かしてみてザクッと理解出来たので引き続き紐解いていきたいですなあ。

元記事はこちらです。
俺の consul チートシート

Tasseo で CloudWatch のメトリクスを表示させて自己満足したメモ

ども、cloudpackかっぱ (@inokara) です。

追記

REGION の指定が出来るようにプルリクエストしたら取り込まれたので git pull して bundle install したら以下のように環境変数に指定して Tasseo を起動します。

export AWS_ACCESS_KEY_ID=AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_REGION=ap-northeast-1
foreman start

はじめに

Tasseo というちょっと変わった(と思っている)可視化ツールがありまして…。その Tasseo は CloudWatch のメトリクスも表示することが出来るということで試してみました。

Tasseo とは

Tasseo で CloudWatch のメトリクスを表示させる: Tasseo とは

ざっくり

  • Tasseo is a lightweight, easily configurable, near-realtime dashboard for time-series metrics.
  • 時系列データベース用の軽量、簡単設定、リアルタイムっぽいダッシュボード

github

実装は

以下のようなツールで実装されています。

  • AWS SDK for JavaScript
  • Sinatra
  • D3.js
  • rickshaw
  • underscore.js

Sinatra は HTTP リクエストを処理するだけに使用されていて 100 行程度とコンパクトな作りになっているようです。コアは JavaScript で書かれているこの部分です。

選べるバックエンド

Tasseo 単体では何もできませんが、以下のような時系列データベース等をバックエンドとして利用可能です。

  • Graphite
  • Librato
  • InfluxDB
  • Amazon CloudWatch

機能としては

  • 各種バックエンドに蓄積された時系列データのメトリクスを表示
  • しきい値を設けて表示させる色を変えることも可能(デフォルトは緑)
  • 設定ファイルは /dashboard/ ディレクトリ以下に JavaScript で書かれたファイルを置くだけ
  • GitHUB のアカウント認証が利用出来る

せっかくなんで CloudWatch のメトリクスを表示させてみる

試した環境

  • Ubuntu 14.04(Docker コンテナ)
  • Ruby 2.1.5p273

インストール

適当なディレクトリに git clone して bundle install します。

git clone https://github.com/obfuscurity/tasseo.git
cd tasseo
bundle install

アクセスキーとシークレットキー

事前に IAM ユーザーを用意しておいて CloudWatch に対するリードオンリー権限を付与しておきましょう。

export AWS_ACCESS_KEY_ID=AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

環境変数にセットしておきましょう。

設定ファイルの用意

設定ファイルは JavaScript のフォーマットで記載する必要がありますが、イチイチ書くのはカッタルイので設定ファイルを生成するスクリプトを用意しました。以下はステータスが Running である EC2 インスタンスのインスタンス ID を取得して CloudWatch メトリクス用の設定ファイルを生成します。

実行すると以下のような設定ファイルが生成されます。

var period = 60;
var refresh = 1 * 60 * 1000;
var usingCloudWatch = true;
var metrics = [
{
  "target": "CPUUtilization(%)",
  "Namespace": "AWS/EC2",
  "MetricName": "CPUUtilization",
  "Statistics": [
    "Average"
  ],
  "Dimensions": [
    {
      "Name": "InstanceId",
      "Value": "${instance_id}"
    }
  ]
},
{
  "target": "DiskReadOps(Num)",
  "Namespace": "AWS/EC2",
  "MetricName": "DiskReadOps",
  "Statistics": [
    "Average"
  ],
  "Dimensions": [
    {
      "Name": "InstanceId",
      "Value": "${instance_id}"
    }
  ]
},
(略)
{
  "target": "NetworkOut(Byte)",
  "Namespace": "AWS/EC2",
  "MetricName": "NetworkOut",
  "Statistics": [
    "Average"
  ],
  "Dimensions": [
    {
      "Name": "InstanceId",
      "Value": "${instance_id}"
    }
  ]
}
]

設定ファイルは dashboards ディレクトリ以下に置くことで利用可能となります。

尚、period = 60 で 60 分毎の Statistics な値を取得します。また、Tasseo はデフォルトで 2000ms 毎にブラウザをリロードしてリアルタイムっぽさを実現していますが、refresh = 1 * 60 * 1000 で 60 秒毎にリロードして取得するようにデフォルト値を上書きしています。

ちょっと修正

実は tasseo パイセンの CloudWatch メトリクスの取得先は us-east-1 に固定されてしまっているのでちょっと修正してリージョンも環境変数から渡せるように修正しました。

修正する必要があるファイルは lib/tasseo/views/index.hamllib/tasseo/public/j/tasseo.js の二つのファイルです。

--- lib/tasseo/views/index.haml.bk      2014-12-22 21:55:25.753600656 +0000
+++ lib/tasseo/views/index.haml 2014-12-22 21:56:02.464083743 +0000
@@ -53,13 +53,14 @@
           var influxDbAuth = "#{ENV['INFLUXDB_AUTH']}";
           var awsAccessKeyId = "#{ENV['AWS_ACCESS_KEY_ID']}";
           var awsSecretAccessKey = "#{ENV['AWS_SECRET_ACCESS_KEY']}";
+          var awsRegion = "#{ENV['AWS_REGION']}";

           if (libratoAuth != "") {
             datasource = new TasseoLibratoDatasource(libratoAuth)
           } else if (influxDbUrl != "") {
             datasource = new TasseoInfluxDBDatasource(influxDbUrl, influxDbAuth)
           } else if (typeof usingCloudWatch != 'undefined' && usingCloudWatch) {
-            datasource = new TasseoAWSCloudWatchDatasource(awsAccessKeyId, awsSecretAccessKey)
+            datasource = new TasseoAWSCloudWatchDatasource(awsAccessKeyId, awsSecretAccessKey, awsRegion)
           } else {
             var graphiteOptions = {}
             if (typeof padnulls != 'undefined') graphiteOptions['padnulls'] = padnulls;

環境変数 AWS_REGION を引き回す作戦です。

--- lib/tasseo/public/j/tasseo.js.bk    2014-12-22 21:57:18.314990023 +0000
+++ lib/tasseo/public/j/tasseo.js       2014-12-22 21:58:33.934448252 +0000
@@ -357,11 +357,10 @@

 /* AWSCloudWatch datasource */

-function TasseoAWSCloudWatchDatasource(accessKeyId, secretAccessKey, options) {
+function TasseoAWSCloudWatchDatasource(accessKeyId, secretAccessKey, region, options) {
   this.options = _.extend({}, options);
   AWS.config.update({accessKeyId: accessKeyId, secretAccessKey: secretAccessKey});
-  //AWS.config.region = 'us-east-1';
+  AWS.config.region = region ;
   this.client = new AWS.CloudWatch();
 }
 

ですので、tasseo を実行する前に export AWS_REGION=ap-northeast-1 も実行しておきましょう。

export AWS_REGION=ap-northeast-1

Tasseo 起動

Tasseo を起動してみましょう。Tasseo は foreman でプロセスを管理します。

cd tasseo/
foreman start

以下のように Tasseo が起動します。
Tasseo で CloudWatch のメトリクスを表示させる: Tasseo の起動確認

Tasseo はデフォルトで起動したホストの 5000 番で Listen します。

ブラウザでアクセス

とりあえずブラウザでアクセスしてみましょう。
Tasseo で CloudWatch のメトリクスを表示させる: Tasseo の起動確認 (ブラウザ) (1)

今回は三台のインスタンスを利用しましたので三つのリンクが並んでいますのでそのうちの一つをクリックしてみます。
Tasseo で CloudWatch のメトリクスを表示させる: Tasseo の起動確認 (ブラウザ) (2)

yes コマンドで暫く負荷をかけた状態なので数値的に大きくなっていますがちゃんと値は取得出来ていますね!

ということで

Tasseo 使うと何がいいの?

ちゃんとした値を見たいのであればマネジメントコンソールから CloudWatch のコンソールを見たほうがいいかなと思いますが、見たいメトリクスのざっくりとした傾向をできるだけ一つの画面で(ほぼ)リアルタイムに見たいと思ったら Tasseo 使ってもいいのかなと。後は EC2 や RDS や ElastiCache 等の各サービスを横断したメトリクスを表示させたい時もあると思いますが、そんな時にサクッと JavaScript っぽい設定ファイルを書くだけでオリジナルのダッシュボードが作れるのも魅力だと思います。

何が出来ないのか

あくまでも API 叩いて値を取得して表示させるだけの機能(シンプルと言えばシンプル)なので CloudWatch のメトリクスを二週間以上保持したいといった用途には利用出来ませんし、もちろん通知もありません。

とは言っても

バックエンドとして CloudWatch 以外にも Graphite や InfluxDB も利用出来たりしますので引き続き触ってみたいと思いますよ!

レッツメトリクス。

元記事はこちらです。
consul のロギング

consul のロギング

ども、cloudpackかっぱ (@inokara) です。

はじめに

consul のロギングってどうなるんでしょうか…(consul 特に指定しない場合には標準出力にログが出力されてウザい)ということで調べたのでメモ。

とりあえず

ドキュメントにはちゃんと明記されています。

以下のように設定ファイルが書けます。

{
  "bootstrap": ture,
  "server": true,
  "data_dir": "/path/to/dir",
  "node_name": "${node_name}",
  "log_level": "INFO",
  "enable_syslog": true,
  "syslog_facility": "local1"
}

enable_syslogtrue で syslog への出力が有効になる。デフォルトではファシリティ local0 を使うが、ファシリティを指定する場合には syslog_facility でファシリティを指定する。

consul agent の起動は以下のように…

consul agent -config-file /path/to/consul.json

ふう、焦りました。

元記事はこちらです。
consul のロギング

AWS Lambda Meetup #0 で士気を高めてきました!

こんにちは、cloudpack@dz_ こと大平かづみです。

Prologue – はじめに

先日は、今注目の AWS Lambda の勉強会に行ってきました!

この魅力的なサービスを率先して手中に収めていくツワモノたちのセッションは、非常に意欲をそそるものばかりでしたっ!

セッション全体を通しての感想

AWS Lambda 利用のポイント

拝聴できたセッションを通して、私が大事と思ったポイントはこちらです。

利用者からの声から見えてきた課題
  • 処理結果を受け取るのが難しい
  • タイムアウトもあるので、多くのエラー処理はできない
  • スケジューリング機能はない
  • 処理結果を受け取りに難があるので、ジョブ管理には向かない

これをふまえると、シンプルに使ってあげるのが Lambda を使いこなす決め手となりそうです。

工夫・利点
  • 処理をシンプルに
    • エラー処理も減るので扱いやすい
  • 他のAWSサービスをフル活用するためのつなぎ役に

見れなかったセッション…

※ 遅れての参加でしたので、3セッションしか拝聴できませんでした…(泣) 見れなかったセッションに関してはご了承いただければ幸いです。

「LambdaとKinesisで作るど根性Tシャツ」

by 清水崇之 (@shimy_net) さん
RaspberryPi – Lambda, Kinesis, Raspberry Pi で IoT シャツを作ろう [前編] – Qiita
※ slideshare はまだ上がってないようでした。

「AWS Lambdaを紐解いてみた」

by 篠崎祐輔 (@bad_at_math) さん
※ ご本人さんの資料を探しきれなかったので、見つかり次第更新します。


セッションのご紹介

「Lambda × Mobileの可能性」

by 諏訪悠紀 (yuki0211s) さん

モバイルアプリの制作をメインにされている諏訪さんの視点でAWS Lambda を紹介くださりました。

AWS Lambda の気になるところ
  • イベントドリブン
  • サービス同士の連携が可能
  • サーバーレス
  • モバイルで行わせたくない処理も実行可能
モバイルアプリとAWS Lambda の連携の例

Cognitoでのユーザ管理からはじまり、データの保存にS3やDynamoDBを利用、Lambdaで次の処理に連携(データの保存やSNS通知、配信)という実用的な例を紹介してくれました。

肝は、ユーザ端末におきたくないデータ(ユーザ情報や大きいデータ)をAWS側で扱う際の連携に AWS Lambda が活躍しそうというお話でした。

写真管理アプリ
Cognitoでユーザを特定、S3写真を保存 → Lambda メタデータ取得 → DynamoDBに保存
アプリでLike機能
Cognitoでユーザを特定 → DynamoDBにメッセージ保存 → Lambdaで、DynamoDBから送受信先のデータ取得 → SNSで通知
キャンペーン配信
Cognitoで非登録ユーザでも個別認識 → DynamoDBにユーザ情報保存 → Lambdaで、SNS通知やコンテンツ配信
本当にサーバーレスでいけるのか?

AWS SDK for iOS ではまだ AWS Lambda が未対応だったので、リポジトリから Fork して Lambda 対応をしてしまったそうです!すごい!
(エンドポイント(?)の末尾のスラッシュの有る無しが厳密なシーンがあるらしく、はまったそうです…)

懸念点
  • 対応しているイベントがまだ少ない
  • Functionの実行結果を受け取れない
  • バッチ処理ができない
  • テストがしにくい

「AWS Lambdaで作るクローラー/スクレイピング」

by 佐々木拓郎 (@dkfj) さん

前半はクローラーやスクレイピングの基本の話で、後半に実際に AWS Lambda を用いてみたお話でした。

佐々木さんの勘所
  • 単一の処理に限定すると、エラー処理がしやすいだろう
    • → 成功/失敗のどちらかに倒す
  • Lambda はスケジュール、ジョブ管理には向かないので、別途用意する必要があるかも
  • バグって暴走したら、ファンクション消してください!
実行元のサーバについて検証してみた
  • 基本的には1日の単位内では同一のサーバで実行されてているようだ
  • 負荷をかけるとどうなるの?
    • → 同一サーバ(IP)で実行されていた
  • さらに負荷をかけると、負荷に応じて自動的にスケールされているのがわかったそう
    • → EC2のスケールより早い気がする

負荷をかける実験を経て、最後に佐々木さんは、こんなことを…(笑)

  • Lambdaは簡単に、暗黒面に陥る
  • Lambda からLambdaを呼び出すと…
    • DDoS攻撃ツールなどになり得るので、扱いには十分に注意してください

「Lambdaによるクラウド型言語の実装」

by 菅原元気(@sgwr_dts) さん

AWS Lambda はまだプレビューなのに、Lambchop など、すでに便利ツールを作るところまでに至っている菅原さんは、なんとクラウド型言語なるものまで実装してみたというお話でした!

セッション前半でのキーワード
  • 1日動かしても大した金額ではなかった
  • Lambdaはフィルタ (スケジューラやジョブキューではない)
  • 処理結果が把握しにくい
    • CloudWatch Logsで受け取れるが、グルーピングや時系列の不順で追いにくい
  • エラーハンドラがしにくい
クラウド型言語 Clala

AWS Lambda のファンクションで使う言語の Node.js なら文字列から関数実行できるらしい。
これを利用して、ローカルで定義した関数を、AWS Lambda で実行!
クラウド側の scheme (?) で関数を実行できるんだそうです。

これは、実装はできたが、まだ実用的なものではなく、さらなる進展を期待しているそうです。

デモは、私には難しくて時間内に細部まで理解できなかったので、また AWS Lambda の勉強を進める上で見ていきたいと思いました。

Epilogue – 終わりに

自力で理解して AWS Lambda を使おうと思っているところに、既に使った話を聞いてしまっては楽しみ半減してしまうと思ったのですが、全くそんなことはありませんでした!

むしろ、気をつけなければならないところがわかり、かなり有益な情報を得られた Meetup でした!

懇親会でも、Lambda をさらに使い倒す話で盛り上がったり、最近出た Amazon EC2 Container Service の話、また、Lambdaから離れても、皆持ち前の技術分野で行動分析の話などで盛り上がりました。

久々に、猛者の集まる魅力的な勉強会でした!
次回は私も利用してる側で参加できるように、精進します!

EBS スナップショットでほげほげするアレ

こんにちは、cloudpackがみさんです。

とりあえず突貫で。

  • curl 刺さった時とかどうするののアレ
  • --queryvolume_id 指定するんだけど変数食わせられないから微妙
  • mysql停止起動失敗のアレ
  • mysql起動後にレプリケーションチェックしないとアレ
#!/bin/bash

REGION="--region ap-northeast-1"

init()
{
        INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`
        BACKUP_VOLUMEID=`aws ec2 describe-instances --instance-id ${INSTANCE_ID} ${REGION} 
        --query 'Reservations[].Instances[].BlockDeviceMappings[?DeviceName==`/dev/xvdb`].Ebs[].VolumeId' --output text`
        DESCRIPTION="${INSTANCE_ID} ${BACKUP_VOLUMEID} `date +%y%m%d` snapshot"
}
mysqlctl()
{
        ACTION=$1
        sudo service mysqld ${ACTION}
        if [ ${ACTION} == "stop" ];then
                sync;sync;sync
        fi
}
backup()
{
        BACKUP_VOLUME=$1
        aws ec2 create-snapshot --volume-id ${BACKUP_VOLUMEID} --description "${DESCRIPTION}" ${REGION}
        RESULT=$?
        return ${RESULT}
}
run()
{
        init
        mysqlctl stop
        backup
        mysqlctl start
}
run

アレ

元記事はこちらです。
EBS スナップショットでほげほげするアレ

AWS SDK for Ruby でインスタンスのステータスを取得するメモ

ども、cloudpackかっぱ (@inokara) です。

メモ

うんちく

  • describe_instances メソッドの :filters を利用する
  • instance-state-code または instance-state-name にて絞り込む

ちなみに、以下は instance-state-codeinstance-state-name の比較。

instance-state-code instance-state-name
0 pending
16 running
32 shutting-down
48 terminated
64 stopping
80 stopped

コード

#!/usr/bin/env ruby

require 'aws-sdk'

ec2 = AWS::EC2.new(
  :access_key_id     => 'AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  :secret_access_key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  :ec2_endpoint      => 'ec2.ap-northeast-1.amazonaws.com',
).client

#  0 (pending), 16 (running), 32 (shutting-down), 48 (terminated), 64 (stopping), and 80 (stopped)
p ec2.describe_instances(:filters => [{ 'name' => 'instance-state-code', 'values' => ['16'] }])
#  (pending | running | shutting-down | terminated | stopping | stopped).
p ec2.describe_instances(:filters => [{ 'name' => 'instance-state-name', 'values' => ['running'] }])

さらに…

ステータスが Running な状態なインスタンスを取得したい場合には以下のように…

#!/usr/bin/env ruby
(省略)
i = ec2.describe_instances(:filters => [{ 'name' => 'instance-state-name', 'values' => ['running'] }])
p i.reservation_set[0][:instances_set][0][:instance_id]

実行すると以下のようにインスタンス ID が出力されます。

i-xxxxxxxx

取り急ぎ

メモでした。
ちなみに、describe-instances からインスタンス ID を取り出すのってこんなに大変でしたっけ?

元記事はこちらです。
AWS SDK for Ruby でインスタンスのステータスを取得するメモ

fluentd-ui を改めて使ってみる

ども、Fluentd Advent Calendar 2014 21 日目担当、初心者枠の cloudpackかっぱ (@inokara) です。

はじめに

Fluentd をブラウザで操作出来る fluentd-ui を以前に少しだけ触ったことがありましたが、しばらく間を開けている間にバージョンアップが行われているようですので、以前に書いた記事を見ながら改めて触ってみたいと思います。私自身、それほど Fluentd や td-agent をそれほど使い込んでいるわけではありませんので、そのレベルのユーザーが触ってみたレベルの内容になってしまっていることをお詫び申し上げます。

fluentd-ui とは(参考)

GitHub にて公開されております。

ざっくり、以下のような機能を提供しています。

  • fluentd / td-agent の導入
  • Web UI による fluentd.conf や td-agent.conf の設定
  • プラグイン管理(導入、アンインストール、アップデート)
  • 稼働ログの確認

尚、生い立ちについては@repeatedlyさんが書かれていた以下の記事を御覧ください。

Fluentd というと UI コマンドラインでしか使ったことがなく、当初は GUI って必要なのかなと思っていましたが、実際に触ってみると以下の図のように Fluentd を挟んで Input プラグインと Output プラグインが視覚的に把握出来たり…
fluent-ui: 画面キャプチャ

プラグインのインストールや設定の確認や修正、そして、個人的に最も嬉しかった機能として組み込みのログフォーマットであればログを自動的に認識して色分けを行ってくれたり、さらに独自のフォーマットを書く際にFluentularのように苦手な正規表現をサポートしてくれる機能がありました。

ということで、以前に触ったのが 8 月位でバージョンは 0.1.3 位だったかと思いますが、今日現在で 0.3.1 となっていて新しい機能も追加になっているようですので、今回は最新版の 0.3.1 で記事を書き進めたいと思います。


セットアップ

環境

README でもUbuntu 14.04向けに書かれていますので、Vagrant で Ubuntu 14.04 を用意しました。

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"

尚、素の状態の Vagrant であげた Ubuntu 14.04 の場合、プラグインインストールで Nokogiri をインストールしようと際に以下のようなメモリ割り当てエラーが出てしまったので仮想マシンの割り当てメモリについてそれなりに割り当てておく必要があるかと思います。

Fetching: mini_portile-0.6.1.gem (100%)
Successfully installed mini_portile-0.6.1
Fetching: nokogiri-1.6.5.gem (100%)
Building native extensions.  This could take a while...
ERROR:  Error installing fluent-plugin-s3:
ERROR: Failed to build gem native extension.

/usr/bin/ruby2.1 extconf.rb
Cannot allocate memory - /usr/bin/ruby2.1 extconf.rb 2>&1

Gem files will remain installed in /var/lib/gems/2.1.0/gems/nokogiri-1.6.5 for inspection.
Results logged to /var/lib/gems/2.1.0/extensions/x86_64-linux/2.1.0/nokogiri-1.6.5/gem_make.out

尚、Ruby の環境については下記の通りです。

ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux-gnu]

インストールとログイン

すでに Ruby はインストールされている状態で進めたいと思いますので、引き続き、以下のパッケージをインストールしましょう。

sudo apt-get -y install build-essential libssl-dev

ruby-dev に関しては ruby2.1-dev をインストール済みなので割愛します。

次に fluentd-ui を gem でインストールします。

sudo gem install fluentd-ui --no-ri --no-rdoc -V

fluentd 自体も一緒にインストールされるので、インストール後はすぐに fluentd-ui を試すことが出来るかと思います。
インストールが終わったらコマンドラインから以下のように fluentd-ui を起動します。

fluentd-ui start

すると以下のように表示されて fluentd-ui が起動します(※バックエンドで起動したい場合には & 付けて起動しましょう

Puma 2.10.2 starting...
* Min threads: 0, max threads: 16
* Environment: production
* Listening on tcp://0.0.0.0:9292

早速、ブラウザにて fluentd-ui が起動しているホストの 9292 番ポートにアクセスします。
fluentd-ui: インストール後の動作確認

初期のユーザー名は admin でパスワードは changeme となっていますので、それを入力してログインしましょう。

初回セットアップ

初回起動時には以下のような画面となり、fluentd.conf や PID ファイルのパス等を指定します。
fluentd-ui: 初回セットアップ(1)

とりあえず今回は fluentd で進めたいと思います。
fluentd-ui: 初回セットアップ(2)

上記のようにデフォルトで起動しているユーザーのホームディレクトリ以下に .fluentd-ui というディレクトリを作成して fluentd.conf や PID ファイルが置かれるようです。

fluentd の起動

セットアップが終わるとダッシュボードに移動して以下のような画面になります。
fluentd-ui: fluentdの起動(1)

「開始」をクリックすると fluentd が起動します。
fluentd-ui: fluentdの起動(2)

ダッシュボードにログが出力され、fluentd が起動していることがわかります。


機能

ソースと出力先の追加

fluentd を介した各種データの入出力設定を一つの画面で行うことが出来ます。(この画面が fluentd を介してデータがストリーム処理しているイメージが掴みやすくて、個人的にはこの画面が一番好きです。)
fluentd-ui: ソースと出力先の追加(1)

fluentd-ui 起動直後はインプットは forward や http が有効になっており、アウトプットは stdout のみが設定されています。

例えば、出力先を追加したい場合には…

  • 標準出力
  • Treasure Data
  • AWS S3
  • MongoDB
  • Elasticsearch
  • 転送

から選択することが出来ますが、プラグインがインストールされていない場合には先にプラグインをインストールしておく必要があります。
fluentd-ui: ソースと出力先の追加(2)

また、各プラグイン毎の設定の編集をこの画面から行うことも出来ます。
fluentd-ui: ソースと出力先の追加(3)

尚、ファイルの読み込みに関しても、対象のファイルを選択後に下記のようにフォーマットの指定の際には色分けしてくれます。
fluentd-ui: ソースと出力先の追加(4)

次へをクリックしてウィザードを進めると下図のように syslog を tail プラグインで読み込む設定が作成されました。
fluentd-ui: ソースと出力先の追加(5)

設定ファイルの編集

fluentd-ui の画面から設定ファイル(fluentd.conf)の設定を行うことが出来ます。
fluentd-ui: 設定ファイルの編集(1)

また、設定の履歴管理(自動バックアップ)が行われています。
fluentd-ui: 設定ファイルの編集(2)

この履歴から設定を指定して再利用することも可能です。
fluentd-ui: 設定ファイルの編集(3)

尚、この機能について Twitter でも@repeatedlyさんが以下のように言及されています。

この機能があれば、開発環境等でトライアンドエラーをしながら設定ファイルを作成することが出来ますし、運用で問題が発生した場合でも遡って検証を行うことが出来そうです。

プラグインの管理

fluentd-ui を使えばプラグインのインストールも Web 画面から行うことが出来ます。
fluentd-ui: プラグインの管理(1)

早速、fluent-plugin-s3 をインストールしてみたいと思います。
上図の「インストール」ボタンをクリックするだけでインストールが開始されます。
fluentd-ui: プラグインの管理(2)
暫くするとインストール完了です!

インストールと同様にアンインストールも fluentd-ui から行うことが可能で、上記の画面にて「アンインストール」をクリックすることでアンインストール可能です。
画面にはリアルタイムに表示されませんが、コマンドラインには以下のように表示されました。

Successfully uninstalled fluent-plugin-s3-0.5.1

また、プラグイン一覧のページをリロードすることで一覧から消えていることも確認出来ます。

その他

ログファイルの閲覧やダウンロードもサポートされているようですし、システム情報では下記のようにインストール済みの fluentd のバージョンやインストール済みの Ruby のバージョンが表示されており、さらにダウンロードが可能です。
fluentd-ui: その他の機能

syslog を S3 に保存するまでを fluentd-ui だけでやってみる

ということで…

せっかくなので syslog を Amazon S3 に保存するまでを fluentd-ui で行ってみたいと思います。
尚、事前に以下の設定は行っている前提です。

  • S3 の操作が可能は IAM ユーザーの作成とアクセスキー、シークレットアクセスキーの取得
  • バケット準備

プラグインのインストール

「おすすめプラグイン」メニューから fluent-plugin-s3 をインストールします。
syslogをS3へ格納するフローをfluent-uiで行う: プラグインのインストール(1)

「おすすめプラグイン」ページをリロードすると以下のように fluent-plugin-s3 がインストール済みとなります。
syslogをS3へ格納するフローをfluent-uiで行う: プラグインのインストール(2)

syslog の読み込み設定

「ソースと出力先の追加」から入力の「ファイル」を選択しましょう。
syslogをS3へ格納するフローをfluent-uiで行う: syslogの読み込み設定(1)

次に読み込み対象となるファイルを選択します。
syslogをS3へ格納するフローをfluent-uiで行う: syslogの読み込み設定(2)

次にフォーマット設定です。
syslogをS3へ格納するフローをfluent-uiで行う: syslogの読み込み設定(3)

最後にタグを指定します。
syslogをS3へ格納するフローをfluent-uiで行う: syslogの読み込み設定(4)

最後に次のページで「完了」をクリックし、fluentd を再起動して syslog の読み込み設定は完了です。もちろん、再起動も fluentd-ui から行います。

S3 への書き出し設定

ソースと出力先の追加」から出力の「AWS S3」を選択してあらかじめ用意したバケットの情報やクレデンシャルな情報を設定します。
syslogをS3へ格納するフローをfluent-uiで行う: S3への書き出し設定(1)

「設定」クリックすると以下のように設定ファイルが作成されました。

<source>
  type forward
  port 24224
</source>

<source>
  # http://docs.fluentd.org/articles/in_http
  type http
  port 9880
</source>

<source>
  type monitor_agent
  port 24220
</source>
<source>
  type debug_agent
  port 24230
</source>

<match debug.*>
  # http://docs.fluentd.org/articles/out_stdout
  type stdout
</match>

<source>
  type tail
  path /var/log/syslog
  tag fluentd-ui_test
  format syslog
  time_format %b %d %H:%M:%S
  pos_file /tmp/fluentd--1419068921.pos
</source>

<match fluentd-ui_test>
  type s3
  aws_key_id xxxxxxxxxxxxxxxxxxxxxxx
  aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  s3_bucket fluentd-ui-test
  s3_endpoint s3-ap-northeast-1.amazonaws.com
  path syslog
  format out_file
  include_time_key false
  add_newline false
  output_tag true
  output_time true
  store_as gzip
  use_ssl true
  buffer_type memory
</match>

※後ほど flush_interval 60s も追加しています。

ちょっとトラブル

ダッシュボードに戻り fluentd を再起動したところ以下のようにエラーが出力されました。
syslogをS3へ格納するフローをfluent-uiで行う: ちょっとトラブル

おおっ。

改めて再起動

先ほどのエラーを修正して改めて fluentd を再起動します。
syslogをS3へ格納するフローをfluent-uiで行う: 改めて起動

そして、logger コマンドでひたすら syslog にログを吐かせます。

while :;do logger -t fluentd-ui-test foo ; done &

暫くすると…
syslogをS3へ格納するフローをfluent-uiで行う: 動作確認(1)

おお、キタキター。
ダウンロードしてメモ帳で開いてみます。
syslogをS3へ格納するフローをfluent-uiで行う: 動作確認(2)

改行が解釈されていないご様子ですが syslog の内容が保存されています!
ここまで AWS のダッシュボード以外は fluentd-ui でしか操作しないで S3 へのログ転送を行うことができました!

最後に

駆け足で fluentd-ui を改めて触ってみましたが、プラグイン設定(おそらく対象のプラグインは限られている?)や管理、設定ファイルの修正、ダッシュボードからの再起動等、単体の fluentd ノードを管理する上ではコマンドラインとの違和感なく利用できました。fluentd 利用の敷居を下げるという目的を十分に達成出来ているのではないかと思います。

また、設定ファイルの履歴管理機能は今回のようにブログ記事を書く際等はもちろん、設定ファイルのリファクタリングや検証等で便利に利用出来るのではないかと思います。また、以前に触れましたが、読み込みのフォーマットを色分けしてくれるので、それだけの為だけに fluentd-ui を使ってもいいかもしれないなあと思っています!

元記事はこちらです。
fluentd-ui を改めて使ってみる

ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる

ども、mod_mruby ngx_mruby Advent Calendar 2014 19 日目担当、初心者枠の cloudpackかっぱ (@inokara) です。

はじめに

以前「Nginx で構築したリバースプロキシを ngx_mruby で細かい制御をする試み(1)」に ngx_mruby を利用して、コネクション数を監視してアクセス制限を試してみましたが、今回は Nginx 内部で保持しているコネクション数をはじめ取得可能な値を InfluxDB に放り込んで可視化してみたいと思います。

既に @matsumotory さんが mod_mruby と GrowthForecast で同様のことを実践されていらっしますのでご一読ください。

当初は同じように Growthforecast を利用しようと思いましたが、InfluxDB と Tasseo の組み合わせが面白そうなので mrbgems 作成の勉強を兼ねて InfluxDB を利用してみました。

動作イメージ

ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる: 構成図

  1. client マシンでは ab を実行して nginx マシンに ab を使ってアクセスします
  2. 定期的に(テストでは 1 秒に一回)情報取得の URL(127.0.0.1/nginx-status)にアクセスします
  3. 情報取得用の URL にアクセスすると下記の Nginx 内部情報を ngx_mruby を介して取得して InfluxDB にポストします
  4. InfluxDB に書き込んだ時系列データは Tasseo と呼ばれる可視化ツールでリアルタイムに確認します

以下は ngx_mruby を介して取得する Nginx の内部情報です。

  • connections_active
  • connections_reading
  • connections_writing
  • connections_waiting
  • connection_requests

これらの値は ngx_mruby の Nginx::Var クラスで取得することが出来ます。

尚、上記の管理用端末以外は Docker コンテナで作りました。各々の Dockerfile はそのうちにアップしたいと思います。

メモ

準備

こちら「Install · matsumoto-r/ngx_mruby Wiki」を参考に ngx_mruby をまずはセットアップしましょう。次に ngx_mruby をセットアップする際に利用した build.sh に以下を追加します。

  conf.gem :git => 'https://github.com/iij/mruby-io.git'
  conf.gem :git => 'https://github.com/iij/mruby-socket.git'
  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
  conf.gem :git => 'https://github.com/suzukaze/mruby-msgpack.git'
  conf.gem :git => 'https://github.com/mattn/mruby-http.git'
  conf.gem :git => 'https://github.com/y-ken/mruby-simplehttp-socket.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-simplehttp.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-httprequest.git'
  conf.gem :git => 'https://github.com/y-ken/fluent-logger-mruby.git'
  conf.gem :git => 'https://github.com/luisbebop/mruby-polarssl.git'
  conf.gem :git => 'https://github.com/iij/mruby-mtest.git'
  conf.gem :git => 'https://github.com/iij/mruby-digest.git'
  conf.gem :git => 'https://github.com/mattn/mruby-uv.git'
  conf.gem :git => 'https://github.com/iij/mruby-aws-s3.git'
  conf.gem :git => 'https://github.com/mattn/mruby-json.git'
  conf.gem :git => 'https://github.com/mattn/mruby-base64.git'
  conf.gem :git => 'https://github.com/inokappa/mruby-influxdb-client.git'

全てが必要ではありませんが、HTTP を処理する為の mruby-httpmruby-simplehttp は必須ですので忘れずに追加しましょう。あと、先日作った mruby-influxdb-client も追加しましょう。

追加した上で以下のように再ビルドして再インストールしましょう。

sh build.sh
make install

尚、InfluxDB のセットアップ等は割愛させて頂きますm(__)m

Nginx の内部情報を ngx_mruby から取得して InfluxDB に突っ込む hook スクリプト

先日作った mruby-influxdb-client はココで生きてきます(笑)

v = Nginx::Var.new

conn_act  = v.connections_active
conn_read = v.connections_reading
conn_wri  = v.connections_writing
conn_wait = v.connections_waiting
conn_req  = v.connection_requests

config = {
  :url  => "http://127.0.0.1:8086",
  :db   => "foo",
  :ua   => "mruby-influxdb-client",
  :user => "root",
  :pass => "root",
}

i = Influxdb::Client.new(config)
points = [ conn_act.to_f, conn_read.to_f, conn_wri.to_f, conn_wait.to_f, conn_req.to_f ]
data = [{
  :name => "Test_Metrics",
  :columns => ["conn_act", "conn_read", "conn_wri", "conn_wait", "conn_req" ],
  :points => [
  points
  ]
}]
Nginx.echo "#{points}"
Nginx.echo "#{data}"
i.post(data)
Nginx.return Nginx::HTTP_OK

特に難しいことはなく Nginx::Var を初期化して、各値はそれぞれメソッドとして取得します。あとはそれぞの値を InfluxDB に放り込んでいきます。

hook スクリプトを呼び出す Nginx の設定

以下のように nginx.conf に設定します。

        location /nginx-status {
            mruby_content_handler /usr/local/nginx/hook/nginx-status.rb;
        }

mruby_content_handler に hook スクリプトのパスを追加するだけです。
簡単ですね。

アクセスしてみる

以下は client 側(下半分)から ab を断続的に実行しつつ、nginx 側(上半分)で情報取得用の URL に断続的にアクセスしている図です。
ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる: 動作確認(1)

下半分はあまり変化はありませんが、http://172.17.0.30 に対して断続的に ab を実行しています(watch コマンドを利用しています)

この状態で InfluxDB でメトリクスを確認すると以下のように各値が登録されていることがわかります。(途中で値の登録を止めてしまった為に途中が抜けてしまっていますが見逃してください…)
ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる: 動作確認(2)

Tasseo でリアルタイムで値の変化を見る

さらに Tasseo を設定して InfluxDB のデータベースを見てみると以下のようにリアルタイムに値が変化していくのも見ることが出来ます。(Tasseo については改めて書きます)
ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる: 動作確認(3)

Tasseo 自体は自動的にリロードが行われて InfluxDB から値を取得するので上記のようにリアルタイムに値が変化して見えるようです。なんかカッコイイですね。

ということで…

あまり ngx_mruby のテクニックは書くことが出来ずお恥ずかしい限りですが、munin 等のツールを利用しなくても ngx_mruby を使うだけで Nginx の内部情報を取得することが出来るという試みでした。また、メトリクスの収集とデータストアへの登録も mruby と mrbgems を利用することでリソース消費も抑えることが出来るのではないかと思います。。

元記事はこちらです。
ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる

cloudpackブログ週刊レビュー 2014/12/22

先週公開した記事の一覧です。計 14件 です。

Serf はじめての運用ツール 〜 インストールとエージェント起動

こんにちは、cloudpack@dz_ こと大平かづみです。

Prologue – はじめに

cloudpackに在籍しておりますが、前職まではプログラマの私。
そんなインフラに憧れる一介のプログラマが、運用ツールに初挑戦のシリーズ第一弾!
(続くのでしょうか…!)

出会い

先日、「Code the Clouds Mix-up Vol.2」に参加してきました!
と言っても、駆け付けた時間は、LTの時間でした(汗)

そのLTは、「Serf / Consul 入門 ~仕事を楽しくしよう~」 by @zembutsu さん

これが運用ツールとの華麗なる出会いでありました☆

Serf と Consul

このcloudpack技術ブログでも、SerfConsul の話題をよく見かけます。

どう便利なの?

Serf や Consul などの運用ツールは、なにを便利にしてくれるのでしょうか?

答え: 既存の業務フローを変更せずに省力化

確かに、携わる環境がすべて新規で構築できるとは限りません。既存のフローは変えずに手間を減らしていく、それを助けてくれるツールなら、ぜひ試してみたい!

でも…

インフラ初心者の私でも、扱えるものなんだろうか?

さっそく、LT発表された@zenbutsuさんに聞いてみました!

Serf を試すべき理由

今回LTのテーマに挙がった 2つの運用ツール Serf と Consul のうち、小規模で手軽に使い出すには、 Serf をお勧めしてもらいました。

  • 機能が必要最小限で、シンプルに扱える
  • 管理サーバが不要で、クラスタ構成が用意

その理由も含め、SerfとConsulの特徴や違いについて、スライド資料に書かれていました。

考えてるだけではわかりません、さっそく Serf を触ってみましょう!!

まずはドキュメントからわかること

Serfの特徴

  • 主要なプラットフォームで利用可能(Linux, Mac OS X, Windows)
  • 常駐メモリが小さく、軽量
  • UDPの通信

Serfの主な機能は3つ

  • メンバーシップの管理(ノード管理)
  • 障害検知とリカバリ
  • イベントのカスタマイズ

gossipプロトコルを用いて、素早く効率的なノード管理ができます。サーバーが不要で、Serfをインストールしてエージェントを起動するだけで、クラスタの生成や参加ができます。

また、各ノードが定期的に通信して生存確認を行い続けるので、障害検知がすばやく、リカバリも早いとのこと。

ノードの参加や離脱、デプロイやプロセスのリスタートなどの任意のトリガでイベントやクエリを発行することもできます。

Serf トップページとイントロダクションについて訳したものを、こちらにまとめました。ご参考になれば幸いです。
Getting Started: Serf ~ ドキュメントを読んでみます – Qiita

Serf をはじめよう

インストールは簡単

インストールは簡単、解凍して置くだけです!
こちらの「Downloads – Serf by HashiCorp」からパッケージを入手してください。
2014.12.16 現在、Serf のバージョンは 0.6.3 です。FreeBSD, Linux, Max OS X, OpenBSD, Windows 向けにビルドされたパッケージが用意されています。これ以外のディストリビューションの場合は「コンパイルについて(Developing Serf)」を参考に環境に合わせて準備してください。

以下は、Amazon Linux でインストールした際の手順メモです。

# - パッケージを取得する
[user@ip-xx-xx-xx-xx ~]$ wget https://dl.bintray.com/mitchellh/serf/0.6.3_linux_amd64.zip
--2014-12-15 22:28:06--  https://dl.bintray.com/mitchellh/serf/0.6.3_linux_amd64.zip
Resolving dl.bintray.com (dl.bintray.com)... 108.168.194.92, 108.168.194.91
  ... # 略
Saving to: ‘0.6.3_linux_amd64.zip’

0.6.3_linux_amd64.zip         100%[=================================================>]   2.86M   702KB/s   in 4.6s

2014-12-15 22:28:12 (638 KB/s) - ‘0.6.3_linux_amd64.zip’ saved [3002701/3002701]

[user@ip-xx-xx-xx-xx ~]$ ls
0.6.3_linux_amd64.zip

# - 解凍
[user@ip-xx-xx-xx-xx ~]$ unzip 0.6.3_linux_amd64.zip
Archive:  0.6.3_linux_amd64.zip
  inflating: serf
[user@ip-xx-xx-xx-xx ~]$ ls
0.6.3_linux_amd64.zip  serf

# - PATHが通っているディレクトリへコピー
[user@ip-xx-xx-xx-xx ~]$ sudo cp serf /usr/local/bin

# - 動作確認
[user@ip-xx-xx-xx-xx ~]$ serf
usage: serf [--version] [--help]  []

Available commands are:
    agent           Runs a Serf agent
    event           Send a custom event through the Serf cluster
    force-leave     Forces a member of the cluster to enter the "left" state
    info            Provides debugging information for operators
    join            Tell Serf agent to join cluster
    keygen          Generates a new encryption key
    keys            Manipulate the internal encryption keyring used by Serf
    leave           Gracefully leaves the Serf cluster and shuts down
    members         Lists the members of a Serf cluster
    monitor         Stream logs from a Serf agent
    query           Send a query to the Serf cluster
    reachability    Test network reachability
    tags            Modify tags of a running Serf agent
    version         Prints the Serf version

ちなみに、Mac OS では、homebrew を利用できるそうです。(cask プラグインが必要)
(執筆時では試してません。)

brew cask install serf

その他インストールについては、原文「Installing Serf – Serf by HashiCorp」をご参照下さい。

クラスタ最初の一歩、エージェントを起動してみる

ドキュメントに従って、エージェントを起動してみます。

以下のように、起動したまま待機状態になります。

# - エージェント起動
[user@ip-xx-xx-xx-xx ~]$ serf agent
==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
         Node name: 'ip-xx-xx-xx-xx'
         Bind addr: '0.0.0.0:7946'
          RPC addr: '127.0.0.1:7373'
         Encrypted: false
          Snapshot: false
           Profile: lan

==> Log data will now stream in as it occurs:

    2014/12/15 23:12:11 [INFO] agent: Serf agent starting
    2014/12/15 23:12:11 [INFO] serf: EventMemberJoin: ip-xx-xx-xx-xx xx.xx.xx.xx
    2014/12/15 23:12:12 [INFO] agent: Received event: member-join

この状態で、なにかイベントがあると、ログが追記されていきます。
起動した直後では、既に、以下のようなログが表示されています。

agent: Serf agent starting
このエージェントが起動
serf: EventMemberJoin: ip-xx-xx-xx-xx xx.xx.xx.xx
Serf側でメンバーの参加イベント
agent: Received event: member-join
エージェント側でメンバー参加イベントを受信

エージェントを終了するには、Ctrl + c を押下 (割り込みシグナルを発行) してください。
すると、エージェントへ割り込みが行われ、正常に終了処理を行い終了する様子が出力されます。

# - Ctrl + C 押下でエージェント終了
==> Caught signal: interrupt
==> Gracefully shutting down agent...
    2014/12/15 23:13:23 [INFO] agent: requesting graceful leave from Serf
    2014/12/15 23:13:23 [INFO] serf: EventMemberLeave: ip-xx-xx-xx-xx xx.xx.xx.xx
    2014/12/15 23:13:23 [INFO] agent: requesting serf shutdown
    2014/12/15 23:13:23 [INFO] agent: shutdown complete

正常に終了できました!

エージェント終了の2つのケース「離脱」と「障害」

さて、この終了処理でエージェントが何をしているかというと、クラスタのほかのメンバーに自身が離脱することを通知しているのです。
仮にエージェントプロセスを強制的に終了した場合は、この通知が行われないので、クラスタのメンバーたちは、そのノードは障害が起きたのだと検知するのだそうです。

実は、この2つの動作の違いがSerfでは重要な要素なんです。
なぜかというと、Serfは、障害が起きたノードに対しては自動的に再接続を試み、復帰してくることを許します
一方で、意図的に離脱したノードに対してはもう通信はしないので、無駄な通信が抑えられます。

このことについては、次回に実際に確認してみたいです。

メンバーシップを実感してみる

さて、もっとエージェントの動きを見てみますしょう!
serf agent で再びエージェントを起動します。

[user@ip-xx-xx-xx-xx ~]$ serf agent
==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
         Node name: 'ip-172-31-1-18'
         Bind addr: '0.0.0.0:7946'
          RPC addr: '127.0.0.1:7373'
         Encrypted: false
          Snapshot: false
           Profile: lan

==> Log data will now stream in as it occurs:

    2014/12/18 13:15:05 [INFO] agent: Serf agent starting
    2014/12/18 13:15:05 [INFO] serf: EventMemberJoin: ip-xx-xx-xx-xx xx.xx.xx.xx
    2014/12/18 13:15:06 [INFO] agent: Received event: member-join

ここで、別のセッションで接続して、serf membersコマンドを実行してみます。
別セッションで接続するには、私は Windows で iceiv+putty を使ってアクセスしているので、もう一つウィンドウを立ち上げてアクセスしました。

[user@ip-xx-xx-xx-xx ~]$ serf members
ip-xx-xx-xx-xx  xx.xx.xx.xx:7946  alive

なんということでしょう!♪

[user@ip-xx-xx-xx-xx ~]$ serf agent
==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
         Node name: 'ip-xx-xx-xx-xx'
         Bind addr: '0.0.0.0:7946'
          RPC addr: '127.0.0.1:7373'
         Encrypted: false
          Snapshot: false
           Profile: lan

==> Log data will now stream in as it occurs:

    2014/12/18 13:15:05 [INFO] agent: Serf agent starting
    2014/12/18 13:15:05 [INFO] serf: EventMemberJoin: ip-xx-xx-xx-xx xx.xx.xx.xx
    2014/12/18 13:15:06 [INFO] agent: Received event: member-join
    2014/12/18 13:15:25 [INFO] agent.ipc: Accepted client: 127.0.0.1:53399    # <- serf members を実行した直後に、この1行が表示されました

最後の行が追加され、クラスタへのアクセスがちゃんと検知されたことがわかります。

ぱちぱちぱち!

複数ノードでガチャガチャしたい!

先走って「Join a Cluster (クラスタへの参加)」に突入したのですが、Vagrantを使って、複数ノードのクラスタをシミュレートしていくようです。これを進めるとまた1週間くらいを要してしまいそうなので、本記事ではいったん一区切りとします。

すぐに試していきますけど!♪

Epilogue – おわりに

さて、初めて運用ツールに触れてみた感想です。
インフラは本業でないので、かなり手探り状態ですが、Serf について読んで触ってみて、運用のことが少しわかってきたような気がして楽しいです。
まだ準備ができただけで何も運用してません。複数ノードの動作を早く動かしたくてうずうずしています。

まだまだこれからです!
よろしくお願いします!

やむを得ず AWS の Credential な情報を Ansible で管理する場合のメモ

どうも、cloudpack の やむを得ずシリーズをシリーズ化したい かっぱ (@inokara) です。

はじめに

前回、「やむを得ず AWS の Credential な情報を Chef で管理する場合のメモ」というのを書いた後、Chef と人気を二分する Ansible ではどのように書くのかしらと思って調べたら意外に簡単だったのでメモしておきます。

ちなみに、私は Chef 好き、Ansible 初心者(結局、全部初心者)です。

参考

いろいろ端折って

Chef で言うところの template について

簡単に扱うことが出来ました。
インベントリで指定する hosts ファイルに以下のように記述します。

[hoge]
node01

[hoge:vars]
key = AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sec_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

上記のように書いておいて Playbook は以下のように書いておきます。

- hosts: hoge
  tasks:
    - template: src=templates/hoge.j2 dest=/tmp/hoge.txt owner=root group=root mode=0644

そして、template ディレクトリを作成し配下に hoge.j2 というファイル名で以下のようなテンプレートファイルを起きます。

key = {{key}}
sec_key = {{sec_key}}

これで ansible-playbook コマンドを実行して収束させてみます。

% ansible-playbook -i ./hosts ./simple-playbook.yml -u root -k

以下のように出力されます。

% ansible-playbook -i ./hosts ./simple-playbook.yml -u root -k                                                                                            [~/Dropbox/dev/ansible]
SSH password:

PLAY [hoge] *******************************************************************

GATHERING FACTS ***************************************************************
ok: [node1]

TASK: [template src=templates/hoge.j2 dest=/tmp/hoge.txt owner=root group=root mode=0644] ***
changed: [node1]

PLAY RECAP ********************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0

実際にリモートホストの /tmp/hoge.txt を見ると…

% ansible -i ./hosts node1 -m command -a "cat /tmp/hoge.txt" -uroot -k
SSH password:
node1 | success | rc=0 >>
key = AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sec_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

せっかくツールでファイルを設置したのに確認する為に SSH でログインしていたら負けですので command モジュールで確認します。
ちゃんとファイルが展開されていますね。

まさに Chef で言う template 的な動きではないでしょうか。

ただ…

いかんせん

肝心のアクセスキーやシークレットアクセスキーが平文でインベントリファイルに残っていますよね….

出来るだけ暗号化

ということで、ansible-vault というツールを使ってアクセスキーやシークレットアクセスキーを暗号化して保管しておきましょう。暗号化して保管しておいてリモートのホストに適用時のみ暗号を解いて template で指定したファイルに展開出来るようにしたいと思います。

vars_files への切り出し

インベントリファイルに直書きしていた鍵の情報を key.yml というファイルに書き出します。

---
key: 'AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
sec_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

YAML の形式で書き(コピペ)だしておきます。

ansible-vault による暗号化

そして、ココから今回のキモです。
以下のように実行して作成した key.yml を暗号化してしまいます。

ansible-vault encrypt key.yml

以下のように暗号化パスワードを入力して暗号化します。

% ansible-vault encrypt key.yml
Vault password:
Confirm Vault password:
Encryption successful

暗号化したファイルの中身は下記のような中身です。

$ANSIBLE_VAULT;1.1;AES256
32353135643832303538376565366461383863363333656465633566653631383361313664646265
3539366534336663623636646364323337393661373432630a396366396565356135626564336564
65356361316661383763653939346662376465656134636139306435643038363038373338306165
6533613734366562610a373034373762656265306230376465613636643035373439666538323566
31653538366663633830373366623835313165376534376265353532396163303532383230363232
39656632636135323835626265396163636532666230626134623531363230373263633962323066
37646237373436383938623532356132396664646137326331613130343734343635666532373063
34316462633166663566623762636533333133316631343431663062303366316233623063613263
31376232613637396361366265323661383837353764303737316239363337613839

おお、ランダム(マンダム)…(ラムダコリャ)

Playbook の修正

そして、以下のように Playbook を修正しておきます。

---
- hosts: hoge
  gather_facts: no
  vars_files:
    - key.yml
  tasks:
    - template: src=templates/hoge.j2 dest=/tmp/hoge.txt owner=root group=root mode=0644

テンプレートファイルには手を触れません。

改めて適用

改めて ansible-playbook を実行して収束させます。

% ansible-playbook -i ./hosts ./simple-playbook.yml -u root -k --ask-vault-pass
SSH password:(SSH パスワード)
Vault password:(暗号化した際に利用したパスワード)

PLAY [hoge] *******************************************************************

TASK: [template src=templates/hoge.j2 dest=/tmp/hoge.txt owner=root group=root mode=0644] ***
changed: [node1]

PLAY RECAP ********************************************************************
node1                      : ok=1    changed=1    unreachable=0    failed=0

新たに --ask-vault-pass というオプションを付加しています。これが暗号化したファイルを展開する為のパスワード(暗号化時に入力したパスワード)を入力します。

確認してみます。

% ansible -i ./hosts node1 -m command -a "cat /tmp/hoge.txt" -uroot -k
SSH password:
node1 | success | rc=0 >>
key = AKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sec_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

おお、ちゃんと展開出来ているようです。

ということで…

Ansible でも Chef の Encrypted Data Bags 的なことが簡単に実現することが出来ました。これで Chef でも Ansible でもそこそこ安全に AWS の Credential な情報を扱うことが出来そうです。

ということで、そろそろ眠いので寝ます。

元記事はこちらです。
やむを得ず AWS の Credential な情報を Ansible で管理する場合のメモ

mruby で InfluxDB のクライアントっぽいのを作っているメモ

ども、cloudpackかっぱ (@inokara) です。

はじめに

時系列データベースのダークホース(自分の中では)InfluxDB って HTTP でデータ登録出来るよねって思って作ってみました。

鉄板

mruby で HTTP を扱うなら以下の mrbgems が鉄板かなと思っています。

GET や POST 等主なメソッドが使えるので大概のことはこの mrbgems で出来ると思います。

作ってます

やってること

  • post メソッドでデータを InfluxDB にポストします
  • get_metrics メソッドで InfluxDB からメトリクスを取得します

github

詳しくは README を御覧ください。

デモ

build_config.rb に以下を記述して make します。

MRuby::Build.new do |conf|
  conf.gem :git => 'https://github.com/inokappa/mruby-influxdb-client.git'
end

次に以下のようなスクリプトを用意します。

config = {
  :url  => "http://127.0.0.1:8086",
  :db   => "foo",
  :ua   => "mruby-influxdb-client",
  :user => "root",
  :pass => "root",
}

i = Influxdb::Client.new(config)

points = [ ARGV[0].to_f, ARGV[1].to_f, ARGV[2].to_f ]
query = "select * from Test_Metrics limit 1"

data = [{
  :name => "Test_Metrics",
  :columns => ["value1", "value2", "value3"],
  :points => [
  points
  ]
}]

  puts "request:  #{JSON::stringify(data)}"
  puts "response: #{i.post(data)["body"]}"

  puts "response: #{i.get_metrics(query).body}"

以下のように実行します。

/path/to/mruby /path/to/bin/test.rb `echo $RANDOM` `echo $RANDOM` `echo $RANDOM`

以下のように出力されます。

# ./mruby test.rb `echo $RANDOM` `echo $RANDOM` `echo $RANDOM`
request:  [{"name":"Test_Metrics","columns":["value1","value2","value3"],"points":[[32575.0,28913.0,17897.0]]}]
response:
response: [{"name":"Test_Metrics","columns":["time","sequence_number","value1","value2","value3"],"points":[[1418822788216,33410001,32575,28913,17897]]}]

作る過程のメモ

URL エンコード

InfluxDB でメトリクスデータを取得する際にクエリは URL エンコードする必要がありますが、mruby で URL エンコードを行う場合には mruby-http を利用しました。

mrbgems をインストールして以下のに書くだけで任意の文字列の URL エンコードした値が取得できました。

HTTP::URL::encode("hoge")

InfluxDB のインストール

インストールはとても簡単。(Ubuntu の場合)

wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb
sudo dpkg -i influxdb_latest_amd64.deb

InfluxDB のちょっとした操作

データベースの作成。

curl -X POST 'http://localhost:8086/db?u=root&p=root'   -d '{"name": "foo"}'

データの登録。

curl -X POST -d '[{"name":"foo","columns":["val"],"points":[[23]]}]' 'http://localhost:8086/db/hoge/series?u=root&p=root'

データの取得。

curl -G 'http://localhost:8086/db/foo/series?u=root&p=root' --data-urlencode "q=select * from Disk_Used limit 1"

データの型はちゃんと指定してデータベースに登録必要があるようです…。

例えば、以下のようにデータを登録した場合には一見ちゃんと登録されているように見えますが….
mruby で InfluxDB のクライアントを作る: 動作確認

points = [ ARGV[0], ARGV[1], ARGV[2] ]
query = "select * from Test_Metrics limit 1"

data = [{
  :name => "Test_Metrics",
  :columns => ["value1", "value2", "value3"],
  :points => [
  points
  ]
  }]

なんかおかしい…引数で渡す値が文字列として解釈されている模様…。ちゃんと、以下のように数値としてデータを登録する必要があるようです。

points = [ ARGV[0].to_f, ARGV[1].to_f, ARGV[2].to_f ]
query = "select * from Test_Metrics limit 1"

data = [{
  :name => "Test_Metrics",
  :columns => ["value1", "value2", "value3"],
  :points => [
  points
  ]
  }]

まあ、ハマったのは自分だけなのかもしれませんが…。

Tasseo による可視化

InfluxDB には標準で登録されているデータをグラフとして表示してくれる機能を備えていますが、若干、貧弱な感じは否めませんので Tasseo というツールを利用してみたいと思います。

mruby で InfluxDB のクライアントを作る: Tasseoによる可視化
Rails で実装されていて Ruby の環境さえあれば bundle install でセットアップした後で以下のような JSON 形式のファイルを用意すればすぐに利用することが出来ます。

# cd /path/to/tasseo
# cat << EOT > dashboards/hoge.js
var metrics =
[
  {
    target: "value1",
    series: "Test_Metrics",
    scale: true
  },
  {
    target: "value2",
    series: "Test_Metrics",
    scale: true
  },
  {
    target: "value3",
    series: "Test_Metrics",
    scale: true
  },
];
EOT
foreman start

ということで…

mruby-httprequestmruby-json を利用すれば mruby の HTTP API クライアントは簡単に実装出来る!(かも!)という一例でございました!

元記事はこちらです。
mruby で InfluxDB のクライアントっぽいのを作っているメモ

VagrantでAWSのEC2インスタンス起動

はじめに

こんにちは。cloudpack の インフラエンジニアレベル1の @f_prg (古渡晋也) です。

VagrantAWSEC2インスタンスを起動することになったので、その手順メモです。

Vagrantの初期化をする

[13:53:25][f_prg@mba:~]# cd "/Users/f_prg/Documents/project/f_prg_packer/aws"
[13:53:25][f_prg@mba:aws]# vagrant init
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Vagrantの準備をする

vagrant-awsプラグインのインストール

[13:53:31][f_prg@mba:aws]# vagrant plugin install vagrant-aws
Installing the 'vagrant-aws' plugin. This can take a few minutes...
Installed the plugin 'vagrant-aws (0.5.0)'!

VagrantのAWSのEC2インスタンスを起動するためのboxを追加する

[13:55:43][f_prg@mba:aws]# vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
==> box: Adding box 'dummy' (v0) for provider:
box: Downloading: https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
==> box: Successfully added box 'dummy' (v0) for 'aws'!

Vagrantfileの編集

VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "dummy"

  config.vm.provider :aws do |aws, override|
    aws.access_key_id     = "AAAAAAAAAAAAAAAAAAAA"
    aws.secret_access_key = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"

    aws.keypair_name = 'CCCCCCCC'
    aws.instance_type = "t2.micro"
    aws.region = "ap-northeast-1"
    aws.availability_zone =  "ap-northeast-1a"
    aws.ami = "ami-4985b048"
    aws.tags = {
      'Name' => 'vagrant'
    }

    aws.security_groups = ['sg-DDDDDDDD']
    aws.subnet_id = 'subnet-EEEEEEEE'
    aws.elastic_ip = true

    override.ssh.username = "ec2-user"
    override.ssh.private_key_path = "FFFFFFFF.pem"
  end
end

Vagrant を起動します

vagrant up --provider=aws

まとめ

久々に起動したので、まとめてみました。

参考資料・リンク

今回はございません。

元記事はこちらです。
VagrantでAWSのEC2インスタンス起動

Amazon S3のウェブサイトホスティングでリダイレクト設定を行う

はじめに

こんにちは。cloudpack のインフラエンジニアレベル1の @f_prg (古渡晋也) です。

昨年、JAWSのアドベントカレンダーに記事を投稿させていただきました。
ブログをWordPressからHexoで生成した静的サイトに移行しました為に記事のURLが変更になってしまいました。
そこで、Amazon S3のウェブサイトホスティングの設定してリダイレクト制御をしたいと思います。

S3のウェブサイトホスティングの設定をする

まず、元のURLはこちらです。

「JAWS-UGに関わってからの今まで、そしてこれから」
http://blog.star-flare.com/posts/2013-12-16/1366/

こちらの記事を下のURLに移行させます。
http://blog.star-flare.com/2013/12/16/jawsug-advent-calender/

S3のRedirection Rulesを設定します

まず、公式のドキュメントはコチラです。
Configure a Bucket for Website Hosting – Amazon Simple Storage Service

Management Consoleにて、S3のRedirect Rulesを設定します。


	
		
			posts/2013-12-16/1366/
		
		
			2013/12/16/jawsug/
		
	

ブラウザで確認します

これで完了です。
ブラウザでアクセスしたら、リダイレクトされるようになりました。

まとめ

今回は、1つのページでしたがこれが複数だったら大変ですね。
移行する際は、URLの構成はキチンとしてからしたほうがよいですね。

補足、おまけ

文字数制限があるはずなので、多く定義することは無理だと予想されます。
多分。ちゃんと調べなきゃ。。。

参考資料・リンク

Configure a Bucket for Website Hosting – Amazon Simple Storage Service

元記事はこちらです。
S3のウェブサイトホスティングでリダイレクト設定を行う | f_prgのブログです。

AWS Advent Calendar 2014 〜 Elastic IP アドレス (EIP) のお話

cloudpack古渡晋也(@f_prg)です。

今回の投稿内容は、AWS Advent Calendar 2014 になります。

AWS Advent Calendar 2014

今回の取り上げるテーマ – EIP

今回はElastic IP アドレス (EIP)を取り上げます。EIPを付ける際はどうされてますか?
私は先ほど仕事で10個EC2インスタンスにEIPをつけようとしました。
yumによるインストールするによりリポジトリと通信を行うためです。
Managment Consoleでの選択してEIPを設定するのは正直手間がかかりました。

Managment Consoleでの作業

AWS Advent Calendar 2014 〜 Elastic IP アドレス(EIP)のお話: Management Console での作業
発行されたEIPで接続するために、sshのconfigを作成したり
EC2へのインストール作業後にEIPを外す作業もなかなか手間がかかるものです。

Terraformによるeipのオーケストレーション

最近Terraformを覚えたので、もしかしたらそちらでできるのではないかと思いつきました。
すると調べたところ、作業が簡単になる見込みがありました。
HashicoprのTerraformにより、一斉設定を行います。

Terraformファイルの準備
[01:41:52][f_prg@mba:eip]# cat eip.tf
provider "aws" {
    access_key = "XXXXXXXXXXXXXXXXXXXX"
    secret_key = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
    region = "ap-northeast-1"
}

resource "aws_eip" "hoge" {
    instance = "i-AAAAAAAAA"
    vpc = true
}

resource “aws_eip”
の4行を複数記述すれば、複数のインスタンスに実行ができるようになります。

Terraformの実行
[01:41:53][f_prg@mba:eip]# terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_eip.hoge
    allocation_id:  "" => ""
    association_id: "" => ""
    domain:         "" => ""
    instance:       "" => "i-AAAAAAAAA"
    private_ip:     "" => ""
    public_ip:      "" => ""
    vpc:            "" => "1"


[01:41:58][f_prg@mba:eip]# terraform apply
aws_eip.hoge: Creating...
  allocation_id:  "" => ""
  association_id: "" => ""
  domain:         "" => ""
  instance:       "" => "i-AAAAAAAAA"
  private_ip:     "" => ""
  public_ip:      "" => ""
  vpc:            "" => "1"
aws_eip.hoge: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate
各々のインストールはchefやansibleで行います。

今回は割愛します。
EIPが付いているので、packageやyumによる
パッケージインストールができるようになります。

Terraform destroyの破棄

EIPが解放されます。

[01:48:18][f_prg@mba:eip]# terraform destroy
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_eip.hoge: Refreshing state... (ID: eipalloc-AAAAAAAAA)
aws_eip.hoge: Destroying...
aws_eip.hoge: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

まとめ

  • public ipを付与できないEC2インスタンスには有効です。
  • EC2インスタンスIDを入力するのはまだ手間がかかりました。
  • プライベートサブネット配下のEC2インスタンスにインストール作業するときに大変便利だなぁと思いました。
  • Terraformファイルを作成する工夫でもっと簡単になるのかとも思いました。

元記事はこちらです。
AWS Advent Calendar 2014 | f_prgのブログです。

やむを得ず AWS の Credential な情報を Chef で管理する場合のメモ

ども、cloudpackかっぱ (@inokara) です。

はじめに

やむを得ない理由で AWS のアクセスキーやシークレットアクセスキーを Chef で扱わないといけない時には…迷わず Encrypted Data Bag を使いましょう。ということで、Encrypted Data Bag を使って Credential な情報を扱う際のメモです。

参考

使い方

動作環境は…

  • MacOS X 10.9 Mavericks
  • Chef 11.16.0

手順(1)環境変数 EDITOR の指定

事前に環境変数の EDITOR を指定しておきましょう。

export EDITOR=/usr/bin/vim

何気に重要です。

手順(2)暗号化キーを作成

暗号化する為の鍵を作っておきます。

cd ${CHEF_REPO}
openssl rand -base64 512 > /path/to/data_bag_key

作成できたら knife.rb に以下のように指定しましょう。

(中略)
data_bag_path             './data_bags'
encrypted_data_bag_secret '/path/to/data_bag_key')

実はワタクシ、最近になって初めて encrypted_data_bag_secret の存在をしった新米板前でございます。

手順(3)Data Bag の作成

knife data bag create --secret-file data_bag_key foo bar

以下のようにエディタが起動するので access_keysec_access_key にアクセスキーとシークレットアクセスキーを記述します。

{
  "id": "bar",
  "access_key": "AKxxxxxxxxxxxxxxxxxxxx",
  "sec_access_key": "zzzzzzzzzzzzzzzzzzzzzzz"
}

尚、上記 JSON のキー部分は id 以外は任意の名前で指定が可能です。この id とキーが後々レシピから指定する重要な要素になります。

エディタを保存して閉じると以下のように出力されます。

Created data_bag[foo]
Created data_bag_item[bar]

data_bag と data_bag_item が作成されました。
内容を確認してみます。

{
  "id": "bar",
  "access_key": {
    "encrypted_data": "t7ziGTFo0Vm8V0LbkDDI3a9zFsWpgOXRpqMngDDSKp6CVu4/Pi/yqq7CnlSRn96r6n",
    "iv": "BFYJpgzX3p0wdk1YulZxVw==n",
    "version": 1,
    "cipher": "aes-256-cbc"
  },
  "sec_access_key": {
    "encrypted_data": "p2WfkB7uQn4GdXFk1YMbtBWOlC1AHtPGJxaqAk5vDIG7EJcyEciXPE/Kdk6MnYWJYn",
    "iv": "ScYPBlBP0lHZM3Y6A940hA==n",
    "version": 1,
    "cipher": "aes-256-cbc"
  }
}

上記のように肝心な部分は暗号化されています。

手順(4)レシピを書く

以下のような template リソースのレシピをご用意致しました。(お料理番組風)

credentials = Chef::EncryptedDataBagItem.load('foo', 'bar')

template "/path/to/file" do
  source "secret_file.erb"
  mode '00644'
  owner 'root'
  group 'root'
  variables({
     :key => credentials['access_key'],
     :secret_key => credentials['sec_access_key']
  })
end

尚、file.erb は以下のような内容です。

aws_key_id <%= @key %>
aws_sec_key <%= @secret_key %>

あとは --why-run で毒味するなりしてノードに対して収束をかけましょう。
デモは…ナシです。
すんません。

ということで…

今更感が否めないですが、Encrypted Data Bag を使うことでおおっぴらに出来ない情報もコードで管理することが出来ますね。(今まで使っていなくてすいませんでした!)

Ansible の場合ってどうするんだろう…。

元記事はこちらです。
やむを得ず AWS の Credential な情報を Chef で管理する場合のメモ

Amazon VPC 内に EC2 を起動して Name tag を付与するまでを一撃必殺で行うシェルスクリプトの例

ども、cloudpackかっぱ (@inokara) です。

はじめに

aws-cli を利用して
Amazon VPC 内に Amazon EC2 を起動して tag を付与するまでを一撃必殺で行うシェルスクリプトを作ってみました。タダの CLI ベタ書きだけどすいません。

シェルスクリプト

動作確認は MacOS X 10.9 Mavericks でのみ行っております。
尚、jq の代わりに –query オプションや –filter オプションでいけそうな気がしますm(__)m。

#!/usr/bin/env bash

export LC_ALL=ja_JP.UTF-8

AMI_ID="ami-xxxxxxxx"
COUNT="1"
INSTANCE_TYPE="t2.micro"
KEY_NAME="your-key-name"
SG_IDS="sg-xxxxxxx"
SUBNET_ID="subnet-xxxxxxxx"
IAM_NAME="your-iam-name"
PUBLIC_IP_FLAG="0" #=>"0 -> True or 1 -> False"
TAG_NAME_PREFIX="cli-test"

if [ ${PUBLIC_IP_FLAG} = "0" ];then
  PUBLIC_IP="--associate-public-ip-address"
else
  PUBLIC_IP="--no-associate-public-ip-address"
fi

INSTANCE_ID=`aws ec2 run-instances 
             --image-id ${AMI_ID} 
             --count ${COUNT} 
             --instance-type ${INSTANCE_TYPE} 
             --key-name ${KEY_NAME} 
             --security-group-ids ${SG_IDS} 
             --subnet-id ${SUBNET_ID}
             --iam-instance-profile Name=${IAM_NAME} 
             ${PUBLIC_IP} 
             | jq -r '.Instances[].InstanceId'`

echo ""
echo "${INSTANCE_ID} is ${TAG_NAME_PREFIX} ..."
echo ""

aws ec2 create-tags 
             --resources ${INSTANCE_ID} 
             --tags Key=Name,Value=${TAG_NAME_PREFIX}

ポイントという程ではありませんが…ドキュメントでは --iam-instance-profile に関しては以下のように指定するように書かれていますが…

--iam-instance-profile Arn=value,Name=value

又は

{
  "Arn": "string",
  "Name": "string"
}

と書かれていますが、実際にコマンドオプションとして指定して実行すると以下のようなエラーが出てしまいました。

A client error (InvalidParameterCombination) occurred when calling the RunInstances operation: The parameter 'iamInstanceProfile.name' may not be used in combination with 'iamInstanceProfile.arn'

自分の指定の仕方が可怪しいのかもしれませんがナゾですのでもう少し文献等を漁ってみたいと思います。

地味に

こんなスクリプトが欲しいと思った時になかなか見つけられないので…。
ホントにメモ程度ですのでツッコミ等ございましたら宜しくお願い致します!

元記事はこちらです。
VPC 内に EC2 を起動して Name tag を付与するまでを一撃必殺で行うシェルスクリプトの例