mackerel-plugin-rack-stats を例にした mackerel agent plugin の作り方
この記事は、Mackerel Advent Calendar 2015 16日目の記事です。
mackerel-agent-plugins には様々な plugin がありますが、無い場合は自作する必要がありますよね。
ということで、mackerel-plugin-rack-stats を例に mackerel agent plugin の作り方を紹介します。
mackerel-plugin-rack-stats
raindrops の stats を収集する plugin です。
raindrops が Linux でしか動かないようですので、
必然的にこのプラグインも Linux でしか利用できません。
mackerel agent plugin の作り方
go-mackerel-plugin-helper を使うと簡単に実装することができます。
mackerel-agent-plugins の中には go-mackerel-plugin を使っているものもありますが、
1年近く更新されていないようですので、go-mackerel-plugin-helper を使うのが良さそうです。
それでは、順に説明していきます。
func main()
// mackerel-plugin-rack-stats package main import ( "flag" "fmt" ... mp "github.com/mackerelio/go-mackerel-plugin-helper" ) func main() { optAddress := flag.String("address", "http://localhost:8080", "URL or Unix Domain Socket") optPath := flag.String("path", "/_raindrops", "Path") optMetricKey := flag.String("metric-key", "", "Metric Key") optVersion := flag.Bool("version", false, "Version") optTempfile := flag.String("tempfile", "", "Temp file name") flag.Parse() if *optVersion { fmt.Println("0.2") os.Exit(0) } var rack RackStatsPlugin rack.Address = *optAddress rack.Path = *optPath rack.MetricKey = *optMetricKey helper := mp.NewMackerelPlugin(rack) if *optTempfile != "" { helper.Tempfile = *optTempfile } else { helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-rack-stats") } helper.Run() }
オプション解析に標準モジュールの flag を使っていますが、
機能が充実したライブラリを使用しても良いと思います。
以降は、処理ごとに分割して説明します。
MackerelPlugin
の生成
// mackerel-plugin-rack-stats var rack RackStatsPlugin rack.Address = *optAddress rack.Path = *optPath rack.MetricKey = *optMetricKey helper := mp.NewMackerelPlugin(rack)
go-mackerel-plugin-helper.NewMackerelPlugin
で MackerelPlugin
構造体を生成します。
MackerelPlugin
は Plugin
を変数に持っており、
Plugin
は FetchMetrics()
と GraphDefinition()
を実装する必要があります。
それぞれの定義は以下のとおりです。
// go-mackerel-plugin-helper type Plugin interface { FetchMetrics() (map[string]interface{}, error) GraphDefinition() map[string]Graphs } type MackerelPlugin struct { Plugin Tempfile string } func NewMackerelPlugin(plugin Plugin) MackerelPlugin { mp := MackerelPlugin{plugin, "/tmp/mackerel-plugin-default"} return mp }
FetchMetrics()
と GraphDefinition()
の説明は後ほど行います。
GraphDefinition()
の返り値である Graphs
と、
その引数である Metrics
の定義は以下のとおりです。
// go-mackerel-plugin-helper type Graphs struct { Label string `json:"label"` Unit string `json:"unit"` Metrics []Metrics `json:"metrics"` } type Metrics struct { Name string `json:"name"` Label string `json:"label"` Diff bool `json:"diff"` Type string `json:"type"` Stacked bool `json:"stacked"` Scale float64 `json:"scale"` }
Graphs
と Metrics
の詳細にも触れておきましょう。
Graphs
Label
赤枠で囲った memory
の部分の設定です。
Unit
float
, integer
, percentage
, bytes
, bytes/sec
, iops
のいずれかを指定します。
Metrics
Name
カスタムメトリック名です。
ワイルドカードとして、*
, #
を使うことができます。
Label
赤枠で囲った used
, buffers
などの設定です。
API仕様(v0) の /api/v0/graph-defs/create
に書いてありますが、
%1、%2.. のようにしてメトリック名の1つ目、2つ目.. のワイルドカードにマッチした部分を利用することができます。
とあるように、%N
を指定することもできます。
Diff
グラフを差分値で出力する設定です(デフォルト false
)。
Type
データの型です。
float64
, uint32
, uint64
のいずれかを指定します(デフォルト float64
)。
Stacked
積み重ねグラフにする設定です(デフォルト false
= 線グラフ)。
Scale
文字通り目盛りで、Mackerel に送る値を調整してくれます。
例えば、free -k
の実行結果を parse して、Mackerel に送る plugin があったとします。
free -k
の実行結果は単位が KB なので、Bytes にする場合 1024 を乗算する必要があります。
Scale
を 1024 とした場合、1024 を乗算して Mackerel にデータを送ってくれます。
Tempfile
if *optTempfile != "" { helper.Tempfile = *optTempfile } else { helper.Tempfile = fmt.Sprintf("/tmp/mackerel-plugin-rack-stats") }
差分値の出力に使います。
Tempfile
は差分値の出力以外では使っていなさそうでした。
mp := MackerelPlugin{plugin, "/tmp/mackerel-plugin-default"}
となっているので、
Tempfile
の設定をしないと/tmp/mackerel-plugin-default
に実行結果のJSONが出力されます。
plugin 実行
helper.Run()
でグラフの設定とメトリック収集処理を実行します。
他のプラグインを見ると、ほとんどが以下のような実装になっています。
if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" { helper.OutputDefinitions() } else { helper.OutputValues() }
しかし、MackerelPlugin.Run()
は内部で同じことを行っているため、
Run()
を使うのが良いでしょう。
なぜ、2015-12-16 現在、ほとんどのプラグインで使われていないかのかというと、
Run()
が実装されたのが da2aa407cda88bca4a1159f32bfdcee078ad20fc のコミットで、
2015-12-03 に実装されたばかりだからですね。
mackerel-agent が、環境変数 MACKEREL_AGENT_PLUGIN_META
をつけて実行した場合は、
OutputDefinitions()
が実行され、グラフ定義の json が出力されます。
この時、GraphDefinition()
が実行されます。
環境変数がついていない場合は、OutputValues()
が実行され、
メトリックが出力されます。
こちらは、FetchMetrics()
と GraphDefinition()
の両方が実行されます。
FetchMetrics() (map[string]interface{}, error)
メトリックの収集処理です。
mackerel-plugin-rack-stats では、 GET /_raindrops
のレスポンスを parse して map[string]interface{}
を生成します。
細かい処理は省略しますが、map[string]interface{}
は以下のようになっています。
map[string]interface{ "active": N.nnnnnn, "queued": N.nnnnnn, "calling": N.nnnnnn, "writing": N.nnnnnn, }
このとき、map[string]interface{}
のキーが、
GraphDefinition()
の map[string](mp.Graphs)
の Metrics.Name
と
対応している必要があります(この例では、active
, queued
, calling
, writing
)。
GraphDefinition() map[string]Graphs
func (u RackStatsPlugin) GraphDefinition() map[string](mp.Graphs) { scheme, path, err := parseAddress(u.Address) if err != nil { log.Fatal(err) } var label string if u.MetricKey == "" { switch scheme { case "http": _, port, _ := net.SplitHostPort(path) u.MetricKey = port label = fmt.Sprintf("Rack Port %s Stats", port) case "unix": u.MetricKey = strings.Replace(strings.Replace(path, "/", "_", -1), ".", "_", -1) label = fmt.Sprintf("Rack %s Stats", path) } } else { label = fmt.Sprintf("Rack %s Stats", u.MetricKey) } return map[string](mp.Graphs){ fmt.Sprintf("%s.rack.stats", u.MetricKey): mp.Graphs{ Label: label, Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, }, }, } }
1 ホストに複数設定できるように、map[string](mp.Graphs)
のキーを変更できるようになっているため処理が複雑になっていますが、
スルーして return している map[string](mp.Graphs)
に注目してください。
わかりやすくするために、fmt.Sprintf
で生成しているキーや label
を展開した状態の map[string](mp.Graphs)
を以下に示します。
map[string](mp.Graphs){ "8080.rack.stats": mp.Graphs{ Label: "Rack Port 8080 Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, }, }, }
前述のとおり、Metrics.Name
の値が、
FetchMetrics()
が返す map[string]interface{}
のキーと
対応している必要があります(この例では、active
, queued
, calling
, writing
)。
Metrics.Name
にワイルドカードを使う場合
Metrics.Name
にワイルドカードとして、*
と #
を使うことができます。
ワイルドカードを使う場合、GraphDefinition()
の map[string](mp.Graphs)
のキーと
FetchMetrics()
が返す map[string]interface{}
のキーを
連結したものにしなくてはなりません。
map[string](mp.Graphs)
のキーが 8080.rack.stats
だった場合、
map[string]interface{}
は
// FetchMetrics() map[string]interface{ "8080.rack.stats.active": N.nnnnnn, "8080.rack.stats.queued": N.nnnnnn, "8080.rack.stats.calling": N.nnnnnn, "8080.rack.stats.writing": N.nnnnnn, }
となります。
ワイルドカードを使うと、GraphDefinition()
の map[string](mp.Graphs)
を
以下のように書き換えることができます。
// GraphDefinition() return map[string](mp.Graphs){ "8080.rack.stats" : mp.Graphs{ Label: "Rack Port 8080 Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "*", Label: "%1", Diff: false}, }, }, }
前述したとおり、ワイルドカードにマッチした部分を Label でつかうことができます。
map[string](mp.Graphs)
のキー にワイルドカードを使う場合
ワイルドカードは、map[string](mp.Graphs)
のキーにも使うことができます。
こちらは、グラフの凡例をグループ化する場合に使います。
mackerel-plugin-rack-stats には当てはまりませんが、
FetchMetrics()
が以下のようなデータを返すとし、
// FetchMetrics() map[string]interface{ "rack.foo.stats.active": 10.000000, "rack.foo.stats.queued": 10.000000, "rack.foo.stats.calling": 10.000000, "rack.foo.stats.writing": 10.000000, "rack.hoge.stats.active": 5.000000, "rack.hoge.stats.queued": 5.000000, "rack.hoge.stats.calling": 5.000000, "rack.hoge.stats.writing": 5.000000, }
GraphDefinition()
の map[string](mp.Graphs)
が以下のような実装になっているとします。
// GraphDefinition() map[string](mp.Graphs){ "rack.#.stats": mp.Graphs{ Label: "Rack test Stats", Unit: "integer", Metrics: [](mp.Metrics){ mp.Metrics{Name: "active", Label: "Active", Diff: false}, mp.Metrics{Name: "queued", Label: "Queued", Diff: false}, mp.Metrics{Name: "calling", Label: "Calling", Diff: false}, mp.Metrics{Name: "writing", Label: "Writing", Diff: false}, }, }, }
その場合、グラフは
となります。
foo
と hoge
でグループ化されていますね。
API仕様にも書いてありますが、
またグラフ定義のメトリック名にはワイルドカード(* または #)を 2つのドット(.)の間、または最後のドット(.)の後ろに単独で使用することができます。
とあるので、#.rack.stats
のようには定義できないようです。
ちなみに、#
と *
の使い分けについては、
ワイルドカード # を使った場合はメトリック名の# にマッチした部分でグラフの凡例がグループ化されます。
とありますし、色々なプラグインを見たところ、map[string](mp.Graphs)
のキーは #
、
Metrics.Name
では *
が使われていましたので、慣例に従うと良いと思います。
またワイルドカード # は一つまでしか使えません。メトリック名全体は ^custom(.[-a-zA-Z0-9_]+|[*#])+ のようになります。
という制約もありますのでご注意ください。
以上が mackerel agent plugin の作り方でした。
長々と説明しましたが、
- 自作の
Plugin
構造体 Plugin
構造体にFetchMetrics() (map[string]interface{}, error)
Plugin
構造体にGraphDefinition() map[string]Graphs
を実装するだけですのでお手軽ですね!
mackerel-plugin-rack-stats の使い方
mackerel-plugin-rack-stats の説明があまりできていなかったので軽く紹介します。
-address
で指定した Port または Unix Domain Socket に、
-path
で指定した(デフォルト /_raindrops
) に GET リクエストを送ります。
1ホストに Rack server が複数起動している場合にも対応できるように、
-metric-key
を指定すると、別のメトリックとして登録することができます。
-metric-key
を指定しない場合は、
PORT.rack.stats
や _path_to_unicorn_sock.rack.stats
のようなメトリック名になります。
config の例は以下のとおりです。
[plugin.metrics.rack_stats] command = "/path/to/mackerel-plugin-rack-stats -address=unix:/path/to/unicorn.sock"
[plugin.metrics.rack_stats] command = "/path/to/mackerel-plugin-rack-stats -address=http://localhost:8080"
他にも Rack の stats を収集する gem がありましたら教えて下さい...
まとめ
mackerel-plugin-rack-stats を例に mackerel agent plugin の作り方を紹介しました。
go-mackerel-plugin-helper を使うと簡単ですので、皆さんも実装してみてください!
17日目の担当は、@shiozaki さんです!
Mackerel のホスト名を ssh の補完候補リストに出力する
この記事は、Mackerel Advent Calendar 2015 8日目の記事です。
Mackerel に登録しているホストに ssh するとき、
補完できたら楽かもしれないと思い、その実現方法を模索してみました。
補完機能を一から書くのは大変なので、
bash-completion
を使います。
動作環境は Linux です。
※紹介する方法は、ローカルマシンからの ssh で補完する方法ではなく、
リモートマシンからリモートマシンへ ssh する際に補完する方法です。
bash-completion
bash-completion
は、bash の補完機能を拡張するものです。
細かく説明されている記事がいくつもあると思いますので説明は省きます。
以下のようにインストールできます。
yum
yum install -y epel-release yum install -y --enablerepo=epel bash-completion
apt-get
apt-get install -y bash-completion
補完
consul membersの結果でbash補完する - Qiita を参考に、
_known_hosts_real()
を上書きして補完できるようにします。
consul
の場合は DNS の機能があるので、
dnsmasq などと組み合わせることで、ホスト名で名前解決できます。
そのため、consul members
でホスト名一覧を取ることができれば、
ssh に使うことができます。
しかし、今回のケースでは、ホストの一覧を取るだけでは、
ssh することができません。
bash-completion
の補完は、
/etc/hosts
, ~/.ssh/config
, ~/.ssh/known_hosts
を使うようですので、
/etc/hosts
, ~/.ssh/config
に自動で設定を追加することで、
補完できるようにする方法を考えます。
Mackerel の /api/v0/hosts.json
を parse するのに jq
を使いますので、
yum
, apt-get
もしくは source build してインストールしてください。
/etc/hosts
を使う
_known_hosts_real()
を使うと書いておきながら、
/etc/hosts
に設定を追加する場合、
root 以外は書き込めないので使えなさそうです。
仕方がないので cron で定期的に更新します。
以下のようなスクリプトになりました。
ssh で入れるホストには mackerel-agent
が入っていると思いますので、
mackerel-agent.conf から apikey を取得して使っています。
curl で https に対して Request を送ると dentry_cache が肥大化してしまうので、
export NSS_SDB_USE_CACHE=yes
することをおすすめします。
cron の場合、普通に使えば毎分より短い間隔では実行できないので、
Mackerel のサーバへの負荷もそれほど高くないと思いますが、
台数に応じて実行間隔をばらつかせる、実行間隔を伸ばすなどしましょう。
実行例
Mackerel に、
- server01 192.168.1.100
- server02 192.168.1.110
- server03 192.168.1.120
- server04 192.168.1.130
というホストが登録されていると仮定します。
$ ssh s[Tab] $ ssh server0[Tab] server01 server02 server03 server04
といった具合に補完されます。
/etc/hosts
には以下のように設定が追記されます。
192.168.1.100 server01 # mackerel 192.168.1.110 server02 # mackerel 192.168.1.120 server03 # mackerel 192.168.1.130 server04 # mackerel
# mackerel
を検索して追記に利用していますので、
事前に /etc/hosts
に色々な設定が書いてあっても、
影響することはありません。
また、追記する前に古い設定を削除していますので、
同じような設定が無限に追記されていくことはありません。
~/.ssh/config
を使う
/etc/hosts
の場合、root 以外は書き込めないので
_known_hosts_real()
を使えませんでしたが、
~/.ssh/config
であれば、自分の権限で書き込むことができます。
/etc/bash_completion
の _known_hosts_real()
を、
以下のコードで上書きすると ~/.ssh/config
に動的に設定を追加することができます。
ファイルの更新間隔をチェックして、
interval
で設定した秒数経過するまで api を叩かないようにしています。
こちらも同時に使用するユーザ数に応じてばらつかせたり、
間隔を伸ばしたりしましょう。
実行例
補完のされ方は /etc/hosts
のときと同じです。
~/.ssh/config
には以下のように追記されます。
Host server01 # mackerel Hostname 192.168.1.100 Host server02 # mackerel Hostname 192.168.1.110 Host server03 # mackerel Hostname 192.168.1.120 Host server04 # mackerel Hostname 192.168.1.130
こちらも # mackerel
を検索して追記に利用していますので、
~/.ssh/config
に色々設定していた場合でも影響はありませんし、
設定が無限に追記されていくこともありません。
まとめ
ssh する際に Mackerel のホストを補完する方法を紹介しました。
かなり無理やりな方法ですので、もっと良い方法をご存知でしたら教えて下さい...
また、ローカルホストから ssh するときにも使えれば良いと思ったのですが、
複数オーガニゼーションで運用している場合、
apikey の切り替えが難しそうでしたので諦めてしまいました。
9日目の担当は、@norisu0313 さんの、
http://qiita.com/norisu0313/items/8e66f6b6adae60279d5f です!
mackerel-agent を root 以外で動かす
執筆時の mackerel-agent
の version は、
mackerel-agent version 0.25.0 (rev 0ce0115) [linux 386 go1.4.2]
です。
RHEL 系の Linux で mackerel-agent
を root
以外で動かす方法です。
mackerel-agent
を動かすユーザは mackerel
とします。
設定
以下のコマンドを実行してユーザとディレクトリを作成します。
useradd -r mackerel mkdir /var/log/mackerel-agent /var/run/mackerel-agent chown mackerel: /var/log/mackerel-agent /var/run/mackerel-agent
/etc/init.d/mackerel-agent
を以下のように変更します。
24,25c24,25 < LOGFILE=${LOGILE:="/var/log/$prog.log"} < PIDFILE=${PIDFILE:="/var/run/$prog.pid"} --- > LOGFILE=${LOGILE:="/var/log/mackerel-agent/$prog.log"} > PIDFILE=${PIDFILE:="/var/run/mackerel-agent/$prog.pid"} 26a27 > USER="mackerel" 41c42 < $BIN ${APIBASE:+--apibase=$APIBASE} ${APIKEY:+--apikey=$APIKEY} --pidfile=$PIDFILE --root=$ROOT $OTHER_OPTS >>$LOGFILE 2>&1 & --- > runuser -c "$BIN ${APIBASE:+--apibase=$APIBASE} ${APIKEY:+--apikey=$APIKEY} --pidfile=$PIDFILE --root=$ROOT $OTHER_OPTS >>$LOGFILE 2>&1" $USER &
変更したら、 service mackerel-agent restart
で mackerel
ユーザで動作させることができます。
解説
軽く何をしているか書くと、
mackerel
ユーザを作成mackerel
ユーザで pid と log を書き込めるようにしrunuser
でmackerel
ユーザでmackerel-agent
を実行
しています。
runuser
の代わりに su
でも動作すると思います。
runuser
は入っていない環境もあるようですので、適宜使い分けると良いです。
まとめ
mackerel-agent
を root
以外で動かす方法を紹介しました。
CPU や Memory Usage のメトリクス収集は問題なく動作していましたが、
正常に動作しないことも考えられますのでご注意ください。
RHEL 以外のディストリビューションについても、
pid と log を書き込めるようにして、 runuser
(su
) 経由で実行すれば動作すると思います
(useradd
が adduser
などの細かい違いもあるかもしれません)。
他には、systemd
や supervisord
で起動するという方法もありそうです。
Deploy ツール Stretcher で Rollback する
Stretcher を使うと、Consul と連携して、所謂 Pull 型の Deploy ができるようになります。
Consul と連携させる場合は、
$ consul event -name deploy s3://xxx-stretcher-files/deploy-20151112-193139.yml
のように、consul event
で Manifest の path を指定してイベントを送ると、
YAML に書いてある path(s3, http, file) から tar.gz を取得して展開してくれます。
Stretcher は Rollback するのも簡単で、
Rollback したいバージョンの Manifest の path を指定して consul event
を実行するだけです。
S3 に置いている場合、都度 aws s3 ls
などして Manifest の path を調べて consul event
を実行するのは面倒です。
そこで、以下の様なスクリプトを用意しておくと Rollback が楽になります。
aws s3 ls
の結果から最新の Manifest を除いて、
bash の select で番号で選択できるようにしています。
実行すると以下のようになります。
$ ./rollback_stretcher.sh 1) deploy-20151112-191452.yml 14) deploy-20151029-215027.yml 2) deploy-20151112-191236.yml 15) deploy-20151028-182546.yml 3) deploy-20151112-190934.yml 16) deploy-20151028-175655.yml 4) deploy-20151112-190820.yml 17) deploy-20151028-175201.yml 5) deploy-20151112-190421.yml 18) deploy-20151028-174909.yml 6) deploy-20151112-190254.yml 19) deploy-20151028-174155.yml 7) deploy-20151112-190127.yml 20) deploy-20151028-171838.yml 8) deploy-20151112-190018.yml 21) deploy-20151022-191616.yml 9) deploy-20151112-185832.yml 22) deploy-20151022-143736.yml 10) deploy-20151112-185441.yml 23) deploy-20151022-143011.yml 11) deploy-20151112-185341.yml 24) deploy-20151022-142645.yml 12) deploy-20151112-185242.yml 25) deploy-20151022-141728.yml 13) deploy-20151110-162450.yml 26) deploy-20151022-140614.yml Select manifest (quit q or Ctrl-C) : 1 Event ID: 7a5e80c5-27aa-973e-3328-b7093a91439f
Rollback するスクリプトを用意しておくと、
障害発生時にも落ち着いて作業ができるので良いですね。
※前提条件として、Manifest のファイル名に timestamp を含めて sort できるようにしておく必要があります。
AWS Lambda のスケジュールイベントで定期的に RDS の Snapshot を作成する
AWS Lambda でスケジュールイベントの設定ができるようになりました。
- Amazon Web Services ブログ: 【AWS発表】AWS Lambdaのアップデート – Python, VPC, 実行時間の延長, スケジュールなど
- http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-scheduled-events.html
これにより cron で実行していたスクリプトを Lambda に移行できます。
cron の冗長化は面倒ですし、cron のためだけに EC2 を起動しなくて良いので便利ですね。
そこで(?)、Lambda から AWS の API を叩いて定期的に RDS の Snapshot を作成してみます。
Create Lambda Function
- 「Get started now」か 「Create Lambda Function」を押下
Select blueprint
Lambda のテンプレートを選択します。
- Python 2.7 を選択
- 「lambda-canary」か「hello-world-python」を選択
- (一通り設定した後にスケジュールイベントの設定をしたかったので「hello-world-python」を選択)
Configure function
Lambda の設定をします。
- Name, Description を入力
Lambda function code
Lambda のコードを作成します。
- 以下のコードを入力する
lambda_handler
の以下の変数を適宜修正delete_days
: 指定日経過したら削除する- 削除する必要がなければ
delete_snapshots
ごと削除
- 削除する必要がなければ
snapshot_prefix
: Snapshot 名の prefixinstances
: RDS の Instance 名を配列で指定
- Snapshot 名の時刻は、JST に変換する必要がなければ
tz=JST()
を削除
追記 2017-01-11 14:09
Aurora の場合は以下のコードで snapshot を取ることができます。
Lambda function handler and role
Lambda function handler はソースコード中の関数を指定します。
handler を変更すれば lambda_handler
以外の関数を実行することも可能です。
また、Lambda 実行に必要な IAM role も設定します。
こちらもテンプレートがありますが、
RDS の Snapshot を操作するものは用意されていないので、
IAM policy を修正します。
- 「Basic execution role」を押下
- IAM role 設定画面に遷移する
- ポップアップブロックしていると設定できないようです
- IAM role 設定画面に遷移する
- 「編集」を押下
- (ドキュメントを読んで)「OK」を押下
- 以下の json を入力する
Advanced settings
メモリやタイムアウトの時間を設定します。
数台分の Snapshot を作るだけの想定ですので、
タイムアウトは 10 秒で設定します。
Review
- 確認して「Create function」を押下
スケジュールイベントの設定
スケジュールイベントの設定を行います。
cron の書式が若干違うので、
詳しくは以下のドキュメントを参照してください。
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-scheduled-events.html
(執筆時は、)設定する時間は UTC ですので、
日本時間で設定しないように注意してください。
- 「Event sources」タブの「Add event source」を押下
- 「Scheduled Event」を押下
- Name, Description を入力
- Schedule expression を入力
- (cron(0 4 * ? * *))
- (例は日本時間で毎13:00 に実行)
- (cron(0 4 * ? * *))
これで、スケジュールイベントの設定完了です。
あとは、時間になれば対象 Instance の Snapshot が作成されます。
CloudWatch
CloudWatch に実行ログが出力されます。
- ログを選択
print などで出力した結果やエラーログが出力されます。
まとめ
AWS Lambda で RDS の Snapshot を作成する方法を紹介しました。
EC2を使わないようにできるので、
可能な範囲で Lambda で実行させるようにすると運用が楽になりそうです。
また、紹介したコードでは省略しましたが、
実際に使う場合は Snapshot 作成に成功・失敗したかを、
チャットなどに通知するようにしたほうが良いですね。
Nginx で query string を見て動的にファイルを配信する
server { listen 80; server_name localhost; rewrite_log on; error_log /var/log/nginx/rewrite.log notice; location ~ ^/weather+\.json { rewrite ^ /weather/$arg_date.json; } location ~ ^/weather/.*\.json { default_type application/json; root /var/www/html; try_files $uri =404; error_page 404 = @info; } location @info { more_set_headers -s 404 "Cache-Control: no-cache, no-store"; return 404 " "; } }
という設定ファイルで、
$ cat /var/www/html/weather/20141001.json {"weather":"sunny"}
を置いている状態で、http request を送ると、
$ curl -s "http://localhost/weather.json?date=20141001" {"weather":"sunny"} $ curl -I "http://localhost/weather.json?date=20141001" HTTP/1.1 200 OK Server: ngx_openresty/1.4.3.6 Date: Fri, 10 Oct 2014 07:25:05 GMT Content-Type: application/json Content-Length: 20 Last-Modified: Fri, 10 Oct 2014 07:02:02 GMT Connection: keep-alive ETag: "5437846a-14" Accept-Ranges: bytes
のようになります。
以下は、rewrite log です。
2014/10/10 16:08:03 [notice] 21659#0: *1 "^" matches "/weather.json", client: 127.0.0.1, server: localhost, request: "GET /weather.json?date=20141001 HTTP/1.1", host: "localhost" 2014/10/10 16:08:03 [notice] 21659#0: *1 rewritten data: "/weather/20141001.json", args: "date=20141001", client: 127.0.0.1, server: localhost, request: "GET /weather.json?date=20141001 HTTP/1.1", host: "localhost"
ファイルが存在しない場合は以下のように 404 を返すことが確認できます。
$ curl -I "htp://localhost/weather.json?date=20141002" HTTP/1.1 404 Not Found Server: ngx_openresty/1.4.3.6 Date: Fri, 10 Oct 2014 07:25:32 GMT Content-Type: application/octet-stream Content-Length: 1 Connection: keep-alive Cache-Control: no-cache, no-store
application で認証した後に nginx で static file を配信する
application で認証している場合のみファイルをダウンロードさせたい場合があると思いますが、
そんなときの設定例です。
以下に簡単に試す方法を記載しています。
tkuchiki/nginx-direct-data-transfer-sample · GitHub
例では Basic 認証ですが、実際はちゃんとした認証がいいでしょう。
application 側で http headr に、
X-Accel-Redirect としてファイルのパスを追加すると、
nginx から static file を配信することができます。
X-Accel-Redirect を使用しなくても、
nginx の proxy cache がききますが、
大容量ファイルの配信を行う場合、
proxy cache の作成に Disk I/O、CPU リソースを大量に使ってしまう可能性があります。
sinatra の場合、以下のようになります。
content_type "image/png" headers "X-Accel-Redirect" => "/img/black.png" status 200
これで負荷を低減することができます。