ジョギング
- 9 キロ位(多分、45 分くらい)
Amazon Prime で
- 深夜食堂シーズン 2 を一気見した
- シーズン 2 は第一話が神回
どうも、郷です。
そして、ちゃんと前後の処理をハンドリングしつつ、処理の流れをコード化(可視化)出来ればなあと黄昏れておりましたら…山の向こうから「AWS Step Functions というツールがあるばってん、こんツールはくさ、Activity を使えばくさ、EC2 やオンプレのタスクも制御出来るちゃんねー」という声を聞くのであった…
ということで、EC2 で動かしているバッチ処理を Activity で制御することを想定して、Golang で Worker を実装して Acitivity の挙動についてチュートリアルしてみたメモでござる。
Step Functions の詳しい説明については、以下の資料がとても参考になりました。
www.slideshare.net
有難うございます!!
Activities are an AWS Step Functions concept that refers to a task to be performed by a worker that can be hosted on EC2, ECS, mobile devices—basically anywhere.
超ざっくりだけど…
Activity は AWS CLI では以下のように作成します。
$ aws \ --profile oreno-profile --region ap-northeast-1 \ stepfunctions create-activity \ --name=OrenoActivity
以下のように出力されます。
{ "creationDate": 1500076193.843, "activityArn": "arn:aws:states:ap-northeast-1:012345678912:activity:OrenoActivity" }
State Machine には以下のように指定します。
{ "Comment": "Golang Demo", "StartAt": "Godesu1", "States": { "Godesu1": { "Type": "Task", "Resource": "arn:aws:states:ap-northeast-1:012345678912:activity:OrenoActivity", "Next": "wait_using_seconds" } } }
こちらやこちらを目一杯参考にさせて頂きました。有難うございます!!
./taskRunner -arn=${Activity ARN} -command=${実行したいコマンド}
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G1421 $ go version go version go1.8.3 darwin/amd64 $ go build taskRunner.go
State Machine とは State をつなげたフローのことで、以下のようなフローで動かしてみます。
State Name | State Type | State で実行したいコマンド |
---|---|---|
Godesu1 | Task | demo01.sh |
wait_using_seconds | Wait | 10 秒待ってから State: Godesu2 を実行 |
Godesu2 | Task | demo02.sh |
{ "Comment": "Golang Demo", "StartAt": "Godesu1", "States": { "Godesu1": { "Type": "Task", "Resource": "arn:aws:states:ap-northeast-1:123456789012:activity:OrenoFirstActivity", "Next": "wait_using_seconds" }, "wait_using_seconds": { "Type": "Wait", "Seconds": 10, "Next": "Godesu2" }, "Godesu2": { "Type": "Task", "Resource": "arn:aws:states:ap-northeast-1:123456789012:activity:OrenoSecondActivity", "End": true } } }
$ cat demo01.sh #!/usr/bin/env bash for i in $(seq 1 2 10); do echo $i done
Activity Worker を以下のように実行して待機させておきます。
./taskRunner -arn=arn:aws:states:ap-northeast-1:123456789012:activity:OrenoFirstActivity -command="./demo01.sh"
以下のように出力されます。
$ ./taskRunner -arn=arn:aws:states:ap-northeast-1:123456789012:activity:OrenoFirstActivity -command="./demo01.sh" 2017/07/15 08:00:49 taskRunner Started.
$ cat demo02.sh #!/usr/bin/env bash for i in $(seq 2 2 10); do echo $i done
Activity Worker を以下のように実行して待機させておきます。
./taskRunner -arn=arn:aws:states:ap-northeast-1:123456789012:activity:OrenoSecondActivity -command="./demo02.sh"
以下のように出力されます。
$ ./taskRunner -arn=arn:aws:states:ap-northeast-1:123456789012:activity:OrenoSecondActivity -command="./demo02.sh" 2017/07/15 08:00:55 taskRunner Started.
$ aws \ --profile oreno-profile --region ap-northeast-1 \ stepfunctions start-execution \ --state-machine-arn=arn:aws:states:ap-northeast-1:012345678912:stateMachine:DemoStateMachine-Go-3
以下のようにレスポンスが返ってきます。
{ "startDate": 1500075053.349, "executionArn": "arn:aws:states:ap-northeast-1:012345678912:execution:DemoStateMachine-Go-3:859949c4-b3a4-4b7c-84ed-60f555e859e6" }
そして、それぞれの Activity Worker では以下のように出力されています。
それぞれ別の Activity を利用しているので、順番も制御出来ている(Godesu1 → Godesu2 の順番)ことが判ります。
マネジメントコンソールでも上図のように State Machine の処理が正常に終了していることが判ります。
このフロー図を見ているだけでウキウキしますね。
Step Functions = Lambda という印象がありましたが、Activity を使うことで従来のバッチ処理等について変更を最小限に処理フロー(State Machine)に組み込めるのはとても嬉しいです。しかし、こちらでも言及されている通り、Activity は Worker を実装する必要があったり、Worker の運用管理が必要になる点は注意が必要です。
今回、禿兵衛の目の前にあるシチュエーションの場合、Activity Worker は EC2 が起動したタイミングで必ず起動することを保証する必要があったりしますが、それ自体も State の一つとして Lambda ファンクションに組み込んでしまうのもありかなと思ったりしています。
Step Functions とても面白いサービスで好きになっちゃいました。
Golang で実装された Datadog API のラッパーです。
$GOPATH
等は設定済みの状態です。
go get gopkg.in/zorkian/go-datadog-api.v2
GetMonitors() 関数を利用すれば良いようです。
package main import ( "log" "gopkg.in/zorkian/go-datadog-api.v2" ) func main() { client := datadog.NewClient("api key", "application key") mons, err := client.GetMonitors() if err != nil { log.Fatalf("fatal: %s\n", err) } for _, mon := range mons { log.Printf("Monitor %d: %s\n", mon.GetId(), mon.GetName()) } }
monitor の各要素は GetId()
や GetName()
等のメソッドを利用すれば取得出来るようです。
以下のようなエラーが出ちゃいます。
$ go run dd-monitors.go 2017/07/09 17:22:03 fatal: json: cannot unmarshal string into Go struct field Options.evaluation_delay of type int exit status 1
取得した monitor の evaluation_delay
というキーの値について、意図した値(型は int
)が入っていない為、JSON の解析に失敗しているようです。この事象については、以下の issue でやりとりされていました。
evaluation_delay
に値が入っていない場合の処理については、こちらのプルリクエストで Merge はされているので、近日中には Merge された内容でリリースされるんぢゃないかなーと期待しております。
もう少々待ちたいと思います。当然、evaluation_delay
に値が設定されている場合や evaluation_delay
がそもそも設定されていない monitor の場合には、以下のように monitor の各要素を取得することは出来ています。
$ cat dd-monitors.go package main import ( "log" "gopkg.in/zorkian/go-datadog-api.v2" ) func main() { client := datadog.NewClient("api key", "application key") mon, err := client.GetMonitor(12345678) if err != nil { log.Fatalf("fatal: %s\n", err) } log.Printf("Monitor %d: %s\n", mon.GetId(), mon.GetName()) } $ go run dd-monitors.go 2017/07/09 17:51:12 Monitor 12345678: Apache Log Monitor 40x Error Test
Datadog API ドキュメントには以下のように書かれています。
evaluation_delay
Time (in seconds) to delay evaluation, as a non-negative integer. For example, if the value is set to 300 (5min), the timeframe is set to last_5m and the time is 7:00, the monitor will evaluate data from 6:50 to 6:55. This is useful for AWS CloudWatch and other backfilled metrics to ensure the monitor will always have data during evaluation.
ざっくりと意訳すると…
CloudWatch のメトリクスを取得する際に NO DATA になってしまうことを防げるのかもしれないですが、使ったことが無いのでなんとも言えません。すいません。
Golang で JSON パースについてはちゃんと書こうとすると 1 つの記事になってしまうんだろうけど、ひとまず encoding/json パッケージを使ったチュートリアルをしてみます。
シンプルに纏まっていて解りやすかったです。有難うございましたmm
1つもしくは複数の値をまとめて格納できる型。それぞれのメンバ(フィールド)は型が異なっていてもよい点が配列と異なる。
なるほど。(今更感)
以下のサンプルで言うと、
type SelfIntroduction struct { Name string `json:"name"` Options []Options `json:"options"` }
これが構造体。
参考サイトのサンプルスクリプトを拝借させて頂いて自己紹介スクリプトを self-introduction.go として保存しておきます。
package main import ( "encoding/json" "fmt" ) type SelfIntroduction struct { Name string `json:"name"` Options []Options `json:"options"` } type Options struct { Name string `json:"name"` Value json.Number `json:"value"` } func main() { json := ` { "name": "ハゲ", "options": [ { "name": "髪", "value": "つるっぱげ" }, { "name": "age", "value": 40 } ] } ` jsonBytes := ([]byte)(json) data := new(SelfIntroduction) if err := json.Unmarshal(jsonBytes, data); err != nil { fmt.Println("JSON Unmarshal error:", err) return } fmt.Println("Name: " + data.Name) for _, option := range data.Options { fmt.Printf("%s: %s\n", option.Name, option.Value) } }
実行してみます。
$ go run self-introduction.go
Name: ハゲ
髪: つるっぱげ
age: 40
おお、ちゃんと解析出来ています。
まだまだ Golang で JSON を扱う上で色々と学ぶ必要が有りそうなので、追々チュートリアルしていきます。
go-datadog-api から始まって、evaluation_delay
を経由して Golang の JSON パースまで薄く触れてみました。引き続き、チュートリアルな夏は続きます。