1. tkit

    Posted

    tkit
Changes in title
+イベントドリブンな自動化ツール StackStormを試す
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,863 @@
+イベントドリブンな自動化ツール、StackStormを試してみました。
+
+# TL;DR
+
+Jenkinsと似たようなツールであり、それでいてAPIを通じてより細かく、しかも外部から管理ができるというところがメリットです。
+Jenkinsだと、実質JenkinsのGUIで殆どを管理することが前提であり、かつワークフロー定義をJenkinsに任せるしかなく、ちょっとなぁ…という課題を持っている方は、検討してみる価値はありそうです[^1]。
+
+https://stackstorm.com/
+
+## メリット
+
+* イベント(ジョブ実行のトリガー)をRESTful APIにできる
+* GUIではなくコマンドで(も)管理できる
+ * コマンドで全てを管理できるので、独自のDashboardを作ることも簡単にできそうです。
+* ワークフローやアクションをテキストで構成管理できる
+
+## デメリット
+
+* 構成要素が多い
+ * JenkinsみたいにJDKとJenkinsのパッケージで一発、とかではなく、ものすごくたくさんのパッケージを入れる必要があります。
+ * 従って、障害時はどこで問題が起こっていて、どのデータがどういう状態なのかを、アーキテクチャを理解してちゃんと調べる必要があります。
+
+## このような用途に向く
+
+* ワークフローが多くて複雑、ワークフローを構成したい
+* RESTful APIなどで外部から管理したい
+* 監視アラートが出たら何らかのアクションを起こす、とか、ChatOpsでSlackに発言したら何かを発動させる、とかもっと多くのトリガーと連携したい
+
+## このような用途には向かない
+
+* ビルドパイプライン
+
+アプリケーションをビルドするとか、デプロイするとか、テストするとかの用途には向きません。
+当然実行するだけならできますが、例えばJenkinsだとJUnitテスト結果の集計とかで綺麗に表示することもできますが、そんな機能はありません。
+ビルドパイプラインならJenkinsの方が適切でしょう。
+
+* 小規模サービス
+
+そもそもコンポーネントが多すぎるので、機能的に多分オーバースペックでしょう。
+
+* GUIで全てを管理する
+
+GUIはおまけです。そもそもコマンドとかテキストで構成管理とかPull Requestとかで開発フローを回す気がないのであれば、ほとんどメリットはないでしょう。チームの技術的スキルにもよると思います。
+何かあった時にポチポチ画面から情報を見ることの方が気楽だというのであれば、StackStormは適さないように思います。
+(※ もちろん、別途管理画面を作りこむのであれば話は別です)
+
+## 不明
+
+* スケーラビリティ/可用性
+
+これだけコンポーネントが分かれているのですし、Workerコンポーネントすらいるのですから、多分問題なさそうな気がしていますが、未調査です。
+
+# StackStormとは
+
+非常に乱暴な言い方をするならば、「Jenkinsのジョブ管理、ワークフロー管理を外部から行い易くしたもの」という理解です。
+ワークフローや個別のアクションを自由に定義でき、それらをテキストファイルで管理することができます。
+また、トリガをコマンドやAPI、時間など様々なものに定義することもできます。
+
+無償版のCommunity Editionと、有償版のEnterprise Editionがあります。
+Enterpriseでは、ワークフローをGUIで作ることができるみたいです。
+
+## 特徴
+
+公式[^2]によると、以下の3つだそうです。
+
+* 障害対応の円滑化
+* 改善の自動化
+* 継続的開発
+
+自分が触ってみた所感ですが、Jenkinsの機能をより外部から扱いやすくしたイメージです。
+Jenkinsは素晴らしいツールですが、それ故に「Jenkinsから処理を扱う」ことが必須であり、「Jenkinsを"通して"処理を扱う」ことには若干限界を感じていました。
+つまり、外部からRESTful APIなどを叩いて、Jenkinsの持つワークフローなどを実行したり管理したりしたかったのです。
+
+以下、自分が感じるメリットです。
+
+* GUIも持っていますが、それはむしろおまけで、APIエンドポイントをトリガーにして様々な処理を行うことができます。
+つまり、昨今のマイクロサービスアーキテクチャにも親和性が高く、全てRESTful APIとして処理を扱うことができます。
+* ワークフロー(つまり◯◯だったら××を実行する、次に△△を実行する)ということもできます。
+* ワークフローやトリガーをファイルで管理できます。つまり昨今のInfrastructure as Codeとの親和性が高いのです。
+
+# StackStormのアーキテクチャ
+
+公式ドキュメント[^2]に書いてあります。
+さすが、様々な処理を行うだけあって、コンポーネントは盛りだくさんです。
+![architecture_diagram.jpg](https://qiita-image-store.s3.amazonaws.com/0/16458/7ee9dae1-aeb9-e2ae-499c-e4745b45cca5.jpeg)
+
+* 様々なイベントをきっかけにSensorがそれを受け付ける
+* SensorがTriggerへ渡し、Ruleに基づいてActionまたはWorkflowに処理を渡す。渡した処理はMessage Queueに貯める。
+* Actionは様々な処理を実行する。外部サービスへ飛ばすことも可能。OpenStackのWorkflowプロジェクトであるMistralを利用することも可能。
+* 実行履歴や監査はMongoDBに格納される。
+
+という感じでしょうか。
+
+インフラの構成もこちら[^3]に紹介されています。
+
+![st2-deployment-big-picture.png](https://qiita-image-store.s3.amazonaws.com/0/16458/a40cb5fb-3259-2cc1-d074-bdb9df137aa8.png)
+
+さっとインストールログを見た感じ、これくらいは入ってます。
+
+* RabbitMQ
+* MongoDB
+* Python
+* erlang (RabbitMQのため)
+* Node.js
+* hubot
+* nginx
+* PostgreSQL
+
+今回は試しに使ってみるので1ノードに全部入れていますが、本格的に運用するならそれなりにサーバを分けるなりしないと大変そうです。(そう考えると、むしろJenkinsは(pluginの力もありますが)コンパクトにあれだけの機能が詰まっていて凄い…)
+
+# StackStormをインストールする
+
+ インストール方法も公式[^4]にまとまっています。今回はVagrant上にCentOS7を立てて、その中にインストールするため、以下のコマンドで一発でした。
+
+```bash
+curl -sSL https://stackstorm.com/packages/install.sh | bash -s -- --user=st2admin --password=<CHANGEME>
+```
+
+`<CHANGEME>`には好きなパスワードを入れてください。
+様々なパッケージをインストールするため、それなりに時間がかかります。
+
+`st2ctl` コマンドで、StackStormの各コンポーネントの起動状況を調べることができます。
+
+```bash
+# st2ctl status
+##### st2 components status #####
+st2actionrunner PID: 6690
+st2actionrunner PID: 6692
+st2actionrunner PID: 6694
+st2actionrunner PID: 6696
+st2actionrunner PID: 6698
+st2actionrunner PID: 6700
+st2actionrunner PID: 6702
+st2actionrunner PID: 6704
+st2actionrunner PID: 6706
+st2actionrunner PID: 6708
+st2api PID: 6715
+st2api PID: 7409
+st2stream PID: 6721
+st2stream PID: 7412
+st2auth PID: 6727
+st2auth PID: 7456
+st2garbagecollector PID: 6733
+st2notifier PID: 6739
+st2resultstracker PID: 6745
+st2rulesengine PID: 6751
+st2sensorcontainer PID: 6757
+st2chatops is not running.
+mistral-server PID: 6786
+mistral-api PID: 6784
+mistral-api PID: 7399
+mistral-api PID: 7510
+```
+
+`st2ctl restart` コマンドで一斉に再起動できますので、もしGUIなどに繋がらない場合は試してみてください。
+
+`st2chatops`が起動していませんが、これは内部で動いているhubotの環境設定を行っていないためなので無視します。
+
+インストール後、80番ポートにアクセスすると、Webの管理画面にアクセスすることができます。
+
+<img width="317" alt="スクリーンショット 2016-08-21 19.08.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/16458/c8ff3a2f-3522-2931-aa4a-f844ca23cbbc.png">
+
+インストール時に`st2admin`ユーザ、パスワードは自分が指定したものがありますので、それでログインしてみてください。ログインすると、以下のような画面になるはずです。
+
+<img width="1204" alt="スクリーンショット 2016-08-21 19.09.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/16458/c6a070c0-be30-bf32-0b5a-7b81fbcd9c66.png">
+
+`HISTORY` はこれまでの実行履歴、 `ACTIONS`は個々のアクション、`RULES`は何をきっかけ(Trigger)にして何を行うかという定義が表示されます。GUI上で`ACTION`や`RULES`をいじることも可能です。
+
+# `st2`コマンドで様々な動きを確認する
+
+GUI上でもいろいろできるのですが、最初にお伝えした通り、GUIはおまけだと思っています。本質はコマンドやAPI経由でのアクセスでいろいろとコントロールできることだと思うので、その方向でいろいろ試してみましょう。
+
+ここからは若干リファレンス的になるので、結局どうだったの?ということが気になる方は一番最後をご覧ください。
+
+## Action
+
+個々の処理を定義しています。
+
+### Actionの一覧
+
+```bash
+# st2 action list
++---------------------------------+---------+----------------------------------------+
+| ref | pack | description |
++---------------------------------+---------+----------------------------------------+
+| chatops.format_execution_result | chatops | Format an execution result for chatops |
+| chatops.post_message | chatops | Post a message to stream for chatops |
+| chatops.post_result | chatops | Post an execution result to stream for |
+| | | chatops |
+| core.announcement | core | Action that broadcasts the |
+| | | announcement to all stream consumers. |
+| core.http | core | Action that performs an http request. |
+| core.local | core | Action that executes an arbitrary |
+(snip)
+| st2.rules.list | st2 | Retrieve a list of available |
+| | | StackStorm rules |
+| st2.sensors.list | st2 | Retrieve a list of available |
+| | | StackStorm sensors. |
+| st2.upload_to_s3 | st2 | Sends collected data to write-only |
+| | | StackStorm S3 bucket |
++---------------------------------+---------+----------------------------------------+
+```
+
+様々なものがあります。アクションの詳細を見たいときは、 `st2 action get <ACTION_NAME>`で確認することができます。
+
+### Actionの詳細
+
+```bash
+# st2 action get linux.file_touch
++-------------+---------------------------------------------------------+
+| Property | Value |
++-------------+---------------------------------------------------------+
+| id | 57b6f08de368b932e541591b |
+| uid | action:linux:file_touch |
+| ref | linux.file_touch |
+| pack | linux |
+| name | file_touch |
+| description | Touches a file |
+| enabled | True |
+| entry_point | |
+| runner_type | remote-shell-cmd |
+| parameters | { |
+| | "cmd": { |
+| | "default": "echo $(date +%s) > {{file}}", |
+| | "immutable": true |
+| | }, |
+| | "file": { |
+| | "required": true, |
+| | "type": "string", |
+| | "description": "Path of the file to be touched" |
+| | } |
+| | } |
+| notify | |
+| tags | |
++-------------+---------------------------------------------------------+
+```
+
+なお、実体は以下にあります。
+
+```bash
+/opt/stackstorm/packs/linux/actions/file_touch.yaml
+```
+
+実際にこのアクションを実行するときに、どんなパラメータが必要になるかは`st2 run <ACTION_NAME> -h`で確認できます。
+
+```bash
+# st2 run linux.file_touch -h
+
+Touches a file
+
+Required Parameters:
+ file
+ Path of the file to be touched
+ Type: string
+
+ hosts
+ A comma delimited string of a list of hosts where the remote command
+ will be executed.
+ Type: string
+
+Optional Parameters:
+ bastion_host
+(snip)
+```
+
+### Actionの実行
+
+実際にパラメータ込みで実行する際は、 `st2 run <ACITON_NAME> param1=value1 param2=value2 ...` で実行できます。
+
+```
+# st2 run linux.file_touch file="/tmp/test" hosts="192.168.33.11" username="vagrant" password="vagrant"
+...
+id: 57b98242e368b91cf1cf646f
+status: succeeded
+parameters:
+ file: /tmp/test
+ hosts: 192.168.33.11
+ password: '********'
+ username: vagrant
+result:
+ 192.168.33.11:
+ failed: false
+ return_code: 0
+ stderr: ''
+ stdout: ''
+ succeeded: true
+```
+
+ここでは、リモートサーバである`192.168.33.11`のサーバに`vagrant`ユーザでアクセスし、`linux.touch`アクションを実行しています。
+
+実際、リモートサーバ側で見てみると…
+
+```bash
+$ ll /tmp/test
+-rw-rw-r--. 1 vagrant vagrant 11 8月 21 06:28 /tmp/test
+$ cat /tmp/test
+1471775301
+```
+
+おお、できている。
+
+### Actionの実行履歴
+
+ACTIONの実行結果は、あとで`st2 execution list`コマンドで確認することもできます。
+
+```
+# st2 execution list
++----------------------------+----------------+--------------+-------------------------+-----------------+---------------+
+| id | action.ref | context.user | status | start_timestamp | end_timestamp |
++----------------------------+----------------+--------------+-------------------------+-----------------+---------------+
+(snip)
+| 57b98242e368b91cf1cf646f | linux.file_tou | st2admin | succeeded (4s elapsed) | Sun, 21 Aug | Sun, 21 Aug |
+| | ch | | | 2016 10:28:18 | 2016 10:28:22 |
+| | | | | UTC | UTC |
++----------------------------+----------------+--------------+-------------------------+-----------------+---------------+
+```
+
+### Actionの実行履歴の詳細
+
+`st2 execution get <ID>` コマンドで`id`を指定すれば、その時の結果を見ることもできます。
+
+```
+# st2 execution get 57b98242e368b91cf1cf646f
+id: 57b98242e368b91cf1cf646f
+status: succeeded (4s elapsed)
+parameters:
+ file: /tmp/test
+ hosts: 192.168.33.11
+ password: '********'
+ username: vagrant
+result:
+ 192.168.33.11:
+ failed: false
+ return_code: 0
+ stderr: ''
+ stdout: ''
+ succeeded: true
+```
+
+## Action Runner
+
+Action Runnerは、Actionを実行するための元となるモジュールというイメージです。
+
+* ローカルコマンド実行
+* リモートサーバでのコマンド実行
+* Windowsサーバでのコマンド実行
+* Pythonスクリプトの実行
+
+などの、実行の「手段」を定義しています。先ほどの`linux.file_touch`アクションでは、以下のように指定されていました。
+
+```
+| runner_type | remote-shell-cmd |
+```
+
+これらも、 `st2 runner list` コマンドで一覧、 `st2 runner get <RUNNER_NAME>` で詳細を見ることができますが、詳細は割愛します。割と十分な数が用意されているように見えますが、例えば`Rubyを実行`というRunnerを新たに追加することもできそうです。
+
+## Rule
+
+RuleはIFTTTみたいなもので、「何が起きたら(Trigger)」「どんな条件のときに(Criteria)」「何をする(Action)」ということを定義するものです。
+
+今回はせっかくなので、APIによってActionを発動させてみましょう。
+
+流れとしては、以下のようになります。
+
+1. trigger, criteria, action を定義するymlファイルを作る
+2. コマンドによってStackStormに反映
+
+### 定義YAMLの作成
+
+まず、定義用のymlとして`test.yml`を作成します。
+
+```yaml:test.yml
+---
+ name: "sample_rule"
+ pack: "test"
+ description: "Test Rule 1"
+ enabled: true
+
+ trigger:
+ type: "core.st2.webhook"
+ parameters:
+ url: "test1"
+
+ action:
+ ref: "core.local"
+ parameters:
+ cmd: "echo {{ trigger }}"
+```
+
+ここでは単純に、criteriaをなくして(つまりAPIにアクセスが来たら無条件に)actionを実行する形にしています。
+また、`{{ trigger }}`として、triggerから受け取る値を表示させるようにしてみます。
+
+作成したら、`st2 rule create <YAMLファイル>` コマンドで反映させます。
+
+```bash
+# st2 rule create test.yml
++-------------+----------------------------------------------------------+
+| Property | Value |
++-------------+----------------------------------------------------------+
+| id | 57b99fece368b91cf1cf6475 |
+| name | sample_rule |
+| pack | test |
+| description | Test Rule 1 |
+| action | { |
+| | "ref": "core.local", |
+| | "parameters": { |
+| | "cmd": "echo {{ trigger }}" |
+| | } |
+| | } |
+| criteria | |
+| enabled | True |
+| ref | test.sample_rule |
+| tags | |
+| trigger | { |
+| | "type": "core.st2.webhook", |
+| | "ref": "core.4e1bc9d3-7331-47ee-a894-6e68314635a3", |
+| | "parameters": { |
+| | "url": "test1" |
+| | } |
+| | } |
+| type | { |
+| | "ref": "standard", |
+| | "parameters": {} |
+| | } |
+| uid | rule:test:sample_rule |
++-------------+----------------------------------------------------------+
+```
+
+作成できました。
+なお、一度作ったRuleを更新する場合は、`st2 rule create`コマンドではなく、`st2 rule update <ID> <YAMLファイル>`コマンドで反映させます。IDは、上記の通り表示されています。(`57b99fece368b91cf1cf6475`のことです)
+
+作成したruleは、`st2 rule list`コマンドで確認できます。
+
+```bash
+# st2 rule list
++-------------------+---------+--------------------------------------+---------+
+| ref | pack | description | enabled |
++-------------------+---------+--------------------------------------+---------+
+| chatops.notify | chatops | Notification rule to send results of | True |
+| | | action executions to stream for | |
+| | | chatops | |
+| test.sample_rule | test | Test Rule 1 | True |
++-------------------+---------+--------------------------------------+---------+
+```
+
+chatops.notifyはデフォルトで入ってるものです。2つめがちゃんと入っていますね。
+
+また、webhookの設定にしているため、webhookとしての設定にも反映されています。
+webhookのリストは`st2 webhook list`コマンドで表示することができます。
+
+```bash
+# st2 webhook list
++------------------+------+-------------------------------+-------------+--------------------+
+| type | pack | name | description | parameters |
++------------------+------+-------------------------------+-------------+--------------------+
+| core.st2.webhook | core | 4e1bc9d3-7331-47ee-a894-6e683 | | {u'url': u'test1'} |
+| | | 14635a3 | | |
++------------------+------+-------------------------------+-------------+--------------------+
+```
+
+`test1`というURLエンドポイントが認識されています。
+
+### 認証トークンの作成
+
+さて、いよいよ`curl`コマンドで…と行きたいところですが、この時点で実行しても失敗します。
+
+```bash
+$ curl -k https://localhost/api/v1/webhooks/test1
+{
+ "faultstring": "Unauthorized - One of Token or API key required."
+}
+```
+
+実行には認証トークンが必要です。
+トークンの作成には、`st2 auth -t <ユーザ名>`コマンドを実行します。`-p <パスワード>`を追加すると一度に作成できます。適当に環境変数に入れておくとよいでしょう。
+
+以下の例は、`id: st2admin, password: st2admin` の例です。
+
+```bash
+$ ST2TOKEN=$(st2 auth -t -p st2admin st2admin)
+$ echo $ST2TOKEN
+4dec06213e094eb2838d0347f4cdd509
+```
+
+### APIを叩いてRuleの発動を確認
+
+さて、いよいよ確認です。先ほど取得したトークンも入れて、実行してみましょう。適当にRequest Bodyに値も入れておきます。
+
+```bash
+$ curl -XPOST -k https://localhost/api/v1/webhooks/test1 -H "X-Auth-Token: $ST2TOKEN" -H "Content-Type: application/json" -d '{"key1":"value1"}'
+{"key1": "value1"}
+```
+
+Triggerが動いたとすると、StackStormとしては`trigger-instance`という形で履歴が残ります。いわば、Triggerの実行履歴ということです。
+
+実行履歴の確認は、`st2 trigger-instance list`コマンドで分かります。
+
+```bash
+# st2 trigger-instance list
++--------------------------+--------------------------------+-------------------------------+-----------+
+| id | trigger | occurrence_time | status |
++--------------------------+--------------------------------+-------------------------------+-----------+
+(snip)
+| 57b9a7e9e368b91a5fb67434 | core.st2.generic.actiontrigger | Sun, 21 Aug 2016 13:08:57 UTC | processed |
++--------------------------+--------------------------------+-------------------------------+-----------+
+```
+
+詳細は、上記のidを利用して`st2 trigger-instance get <ID>`コマンドで分かります。
+
+```bash
+# st2 trigger-instance get 57b9a7e9e368b91a5fb67434
++-----------------+--------------------------------------------------------------+
+| Property | Value |
++-----------------+--------------------------------------------------------------+
+| id | 57b9a7e9e368b91a5fb67434 |
+| trigger | core.st2.generic.actiontrigger |
+| occurrence_time | 2016-08-21T13:08:57.589000Z |
+| payload | { |
+| | "status": "succeeded", |
+| | "start_timestamp": "2016-08-21 13:08:54.532719+00:00", |
+| | "parameters": { |
+| | "cmd": "echo {'body': {'key1': 'value1'}, 'headers': |
+| | {'X-Request-Id': 'a76e0532-362a-4983-bc16-f29fe6804d4a', 'X |
+| | -Forwarded-For': '127.0.0.1', 'Content-Length': '17', |
+| | 'Accept': '*/*', 'User-Agent': 'curl/7.29.0', 'Host': |
+| | 'localhost,localhost', 'X-Real-Ip': '127.0.0.1', 'X-Auth- |
+| | Token': '4dec06213e094eb2838d0347f4cdd509', 'Content-Type': |
+| | 'application/json'}}" |
+| | }, |
+| | "runner_ref": "local-shell-cmd", |
+| | "action_name": "core.local", |
+| | "result": { |
+| | "succeeded": true, |
+| | "failed": false, |
+| | "return_code": 0, |
+| | "stderr": "", |
+| | "stdout": "{body: {key1: value1}, headers: {X |
+| | -Request-Id: a76e0532-362a-4983-bc16-f29fe6804d4a, X |
+| | -Forwarded-For: 127.0.0.1, Content-Length: 17, Accept: */*, |
+| | User-Agent: curl/7.29.0, Host: localhost,localhost, X-Real- |
+| | Ip: 127.0.0.1, X-Auth-Token: |
+| | 4dec06213e094eb2838d0347f4cdd509, Content-Type: |
+| | application/json}}" |
+| | }, |
+| | "action_ref": "core.local", |
+| | "execution_id": "57b9a7e6e368b91a5fb67432" |
+| | } |
+| status | processed |
++-----------------+--------------------------------------------------------------+
+```
+
+どうやら動いているようです!
+
+`stdout`にも以下のように出力されている通り、`trigger`という変数にはheaderとbodyが格納されているようです。
+
+```
+{body: {key1: value1}, headers: {X-Request-Id: a76e0532-362a-4983-bc16-f29fe6804d4a, X-Forwarded-For: 127.0.0.1, Content-Length: 17, Accept: */*, User-Agent: curl/7.29.0, Host: localhost,localhost, X-Real-Ip: 127.0.0.1, X-Auth-Token: 4dec06213e094eb2838d0347f4cdd509, Content-Type: application/json}}
+```
+
+先ほどのコマンドで実行を確認しましたが、actionとしても動いた履歴が蓄積されるので、`execution_id`を元にaction側の履歴も拾うことができます。
+
+```bash
+# st2 execution get 57b9a7e6e368b91a5fb67432
+id: 57b9a7e6e368b91a5fb67432
+status: succeeded (3s elapsed)
+parameters:
+ cmd: 'echo {''body'': {''key1'': ''value1''}, ''headers'': {''X-Request-Id'': ''a76e0532-362a-4983-bc16-f29fe6804d4a'', ''X-Forwarded-For'': ''127.0.0.1'', ''Content-Length'': ''17'', ''Accept'': ''*/*'', ''User-Agent'': ''curl/7.29.0'', ''Host'': ''localhost,localhost'', ''X-Real-Ip'': ''127.0.0.1'', ''X-Auth-Token'': ''4dec06213e094eb2838d0347f4cdd509'', ''Content-Type'': ''application/json''}}'
+result:
+ failed: false
+ return_code: 0
+ stderr: ''
+ stdout: '{body: {key1: value1}, headers: {X-Request-Id: a76e0532-362a-4983-bc16-f29fe6804d4a, X-Forwarded-For: 127.0.0.1, Content-Length: 17, Accept: */*, User-Agent: curl/7.29.0, Host: localhost,localhost, X-Real-Ip: 127.0.0.1, X-Auth-Token: 4dec06213e094eb2838d0347f4cdd509, Content-Type: application/json}}'
+ succeeded: true
+```
+
+確かに動いてますね。
+
+ここでは紹介しませんが、Ruleの事前テストもできるようです。
+ルールを作ったけどうまく発動しないことを防ぐこともできますし、継続的インテグレーションもしやすそうですね。
+
+https://docs.stackstorm.com/rules.html#testing-rules
+
+
+### rule応用編
+
+もう少し応用的なものを作ってみます。
+
+```yaml:test2.yml
+---
+ name: "sample_rule2"
+ pack: "test"
+ description: "Test Rule 2"
+ enabled: true
+
+ trigger:
+ type: "core.st2.webhook"
+ parameters:
+ url: "test2"
+
+ criteria:
+ trigger.body.key1:
+ type: "regex"
+ pattern : "^value[1-3]$"
+
+ action:
+ ref: "core.remote"
+ parameters:
+ hosts: "192.168.33.11"
+ username: "vagrant"
+ password: "vagrant"
+ cmd: "echo {{ trigger.body.key1 }} >> /tmp/test.log"
+```
+
+今度は、criteriaを定義しました。また、actionにはリモートサーバでのコマンド実行と、さらにbodyの`key1`を拾うようにしています。
+
+作成したら、反映します。
+
+```bash
+# st2 rule create test2.yml
++-------------+--------------------------------------------------------------+
+| Property | Value |
++-------------+--------------------------------------------------------------+
+| id | 57b9ac3de368b91cf1cf6480 |
+| name | sample_rule2 |
+| pack | test |
+| description | Test Rule 2 |
+| action | { |
+| | "ref": "core.remote", |
+| | "parameters": { |
+| | "username": "vagrant", |
+| | "password": "vagrant", |
+| | "cmd": "echo {{ trigger.body.key1 }} >> |
+| | /tmp/test.log", |
+| | "hosts": "192.168.33.11" |
+| | } |
+| | } |
+| criteria | { |
+| | "trigger.body.key1": { |
+| | "pattern": "^value[1-3]$", |
+| | "type": "regex" |
+| | } |
+| | } |
+| enabled | True |
+| ref | test.sample_rule2 |
+| tags | |
+| trigger | { |
+| | "type": "core.st2.webhook", |
+| | "ref": "core.1d55ccf0-15ae-4194-9dc4-d39d7c35bbc2", |
+| | "parameters": { |
+| | "url": "test2" |
+| | } |
+| | } |
+| type | { |
+| | "ref": "standard", |
+| | "parameters": {} |
+| | } |
+| uid | rule:test:sample_rule2 |
++-------------+--------------------------------------------------------------+
+```
+
+さて、わざとcriteriaに引っかからない条件でPOSTしてみましょう。
+
+```bash
+$ curl -XPOST -k https://localhost/api/v1/webhooks/test2 -H "X-Auth-Token: $ST2TOKEN" -H "Content-Type: application/json" -d '{"key1":"value0"}'
+```
+
+trigger-instanceで最新のもの(`-n1`)を確認してみます。
+
+```bash
+# st2 trigger-instance list -n 1
++--------------------------+--------------------------------+-------------------------------+-----------+
+| id | trigger | occurrence_time | status |
++--------------------------+--------------------------------+-------------------------------+-----------+
+| 57b9ad04e368b91a5fb67435 | core.1d55ccf0-15ae-4194-9dc4-d | Sun, 21 Aug 2016 13:30:44 UTC | processed |
+| | 39d7c35bbc2 | | |
++--------------------------+--------------------------------+-------------------------------+-----------+
+```
+
+```bash
+# st2 trigger-instance get 57b9ad04e368b91a5fb67435
++-----------------+--------------------------------------------------------------+
+| Property | Value |
++-----------------+--------------------------------------------------------------+
+| id | 57b9ad04e368b91a5fb67435 |
+| trigger | core.1d55ccf0-15ae-4194-9dc4-d39d7c35bbc2 |
+| occurrence_time | 2016-08-21T13:30:44.078000Z |
+| payload | { |
+| | "body": { |
+| | "key1": "value0" |
+| | }, |
+| | "headers": { |
+| | "X-Request-Id": "c369cfaf-d780-4e10-997b- |
+| | a3cd48dac406", |
+| | "X-Forwarded-For": "127.0.0.1", |
+| | "Content-Length": "17", |
+| | "Accept": "*/*", |
+| | "User-Agent": "curl/7.29.0", |
+| | "Host": "localhost,localhost", |
+| | "X-Real-Ip": "127.0.0.1", |
+| | "X-Auth-Token": "4dec06213e094eb2838d0347f4cdd509", |
+| | "Content-Type": "application/json" |
+| | } |
+| | } |
+| status | processed |
++-----------------+--------------------------------------------------------------+
+```
+
+trigger-instanceとしては動いているようですが…actionが見当たりません。
+action側の最新のものを見てみます。
+
+```bash
+# st2 execution list -n1 # -> さっきと同じ結果…つまりactionが動いてない。criteriaが効いている証拠。
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+| id | action.ref | context.user | status | start_timestamp | end_timestamp |
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+| 57b9a7e6e368b91a5fb67432 | core.local | stanley | succeeded (3s elapsed) | Sun, 21 Aug 2016 | Sun, 21 Aug 2016 |
+| | | | | 13:08:54 UTC | 13:08:57 UTC |
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+```
+
+先ほどと同じものがでてきました。つまり、actionが動いていないということになります。ちゃんと、criteriaが効いているということですね。
+
+それでは、今度は「ちゃんと」動かしてみます。
+
+```
+$ curl -XPOST -k https://localhost/api/v1/webhooks/test2 -H "X-Auth-Token: $ST2TOKEN" -H "Content-Type: application/json" -d '{"key1":"value1"}'
+{"key1": "value1"}
+```
+
+以下の通り、今度はちゃんと動いています。
+
+
+```
+# st2 trigger-instance list -n 1
++--------------------------+--------------------------------+-------------------------------+-----------+
+| id | trigger | occurrence_time | status |
++--------------------------+--------------------------------+-------------------------------+-----------+
+| 57b9aef8e368b91a5fb67442 | core.st2.generic.actiontrigger | Sun, 21 Aug 2016 13:39:04 UTC | processed |
++--------------------------+--------------------------------+-------------------------------+-----------+
+```
+
+```
+# st2 trigger-instance get 57b9aef8e368b91a5fb67442
++-----------------+-------------------------------------------------------------+
+| Property | Value |
++-----------------+-------------------------------------------------------------+
+| id | 57b9aef8e368b91a5fb67442 |
+| trigger | core.st2.generic.actiontrigger |
+| occurrence_time | 2016-08-21T13:39:04.173000Z |
+| payload | { |
+| | "status": "succeeded", |
+| | "start_timestamp": "2016-08-21 13:39:01.239573+00:00", |
+| | "parameters": { |
+| | "username": "vagrant", |
+| | "password": "********", |
+| | "cmd": "echo value1 >> /tmp/test.log", |
+| | "hosts": "192.168.33.11" |
+| | }, |
+| | "runner_ref": "remote-shell-cmd", |
+| | "action_name": "core.remote", |
+| | "result": { |
+| | "192.168.33.11": { |
+| | "failed": false, |
+| | "stderr": "", |
+| | "return_code": 0, |
+| | "succeeded": true, |
+| | "stdout": "" |
+| | } |
+| | }, |
+| | "action_ref": "core.remote", |
+| | "execution_id": "57b9aef5e368b91a5fb67440" |
+| | } |
+| status | processed |
++-----------------+-------------------------------------------------------------+
+```
+
+```
+# st2 execution list -n1
++--------------------------+-------------+--------------+------------------------+----------------------+------------------+
+| id | action.ref | context.user | status | start_timestamp | end_timestamp |
++--------------------------+-------------+--------------+------------------------+----------------------+------------------+
+| 57b9aef5e368b91a5fb67440 | core.remote | stanley | succeeded (3s elapsed) | Sun, 21 Aug 2016 | Sun, 21 Aug 2016 |
+| | | | | 13:39:01 UTC | 13:39:04 UTC |
++--------------------------+-------------+--------------+------------------------+----------------------+------------------+
+```
+
+実際、リモートサーバ側の`/tmp/test.log`を見ても、ちゃんと動いていることが分かります。
+
+```bash:192.168.33.11の/tmp/test.log
+$ cat /tmp/test.log
+value1
+```
+
+ちゃんと値も取れていますね。
+
+## Workflow
+
+ここまでで既にあまりに長いので、一旦ここまでにします。RuleによってTriggerとActionは紐付けることができたのですが、実はActionは1つしか定義できません。
+ですので、Triggerを条件に複数の処理を動かす場合、必然的にWorkflowを使うことになります。
+
+### その他ハック的なもの
+
+#### 出力をtable形式から変更できないの?
+
+`-j`で`json`、`-y`で`yaml`、`-a ATTR`で出力を絞ることができます。
+
+```bash:json形式
+# st2 webhook list -j
+[
+ {
+ "name": "4e1bc9d3-7331-47ee-a894-6e68314635a3",
+ "pack": "core",
+ "parameters": {
+ "url": "test1"
+ },
+ "type": "core.st2.webhook"
+ }
+]
+```
+
+```bash:yaml形式にして更に出力を絞る
+# st2 webhook list -y -a type parameters.url
+- parameters:
+ url: test1
+ type: core.st2.webhook
+```
+
+### 出力を絞り込みたい
+
+例えば最後の一つだけ表示したいときは、`-n1`を付与すればよさそうです。
+
+```
+# st2 execution list -n 1
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+| id | action.ref | context.user | status | start_timestamp | end_timestamp |
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+| 57b9a7e6e368b91a5fb67432 | core.local | stanley | succeeded (3s elapsed) | Sun, 21 Aug 2016 | Sun, 21 Aug 2016 |
+| | | | | 13:08:54 UTC | 13:08:57 UTC |
++--------------------------+------------+--------------+------------------------+----------------------+------------------+
+```
+
+他 `-s`でソートキー、`-tg` `-tl`で時間の幅を選ぶこともできます。
+
+### 所感
+
+* `st2` コマンドはかなり直感的で使いやすかったです。 `list`を叩けば大抵の一覧、 `get <引数>`を叩けば詳細が見えます。その他、出力の整形やフィルタリングも、オプションはほぼ共通なので迷わない。これはかなり便利でした。
+* ワークフローの定義がコマンドとテキストファイルベースで管理できるので、GitHubなどの構成管理と組み合わせるとかなり強力そうです。
+* RESTful APIとしての連携も柔軟にできるので、マイクロサービスアーキテクチャとしても使いやすいのではないでしょうか。
+
+個人的には、なんで今までこれを知らなかったんだ…というくらい便利でした。
+
+次回、ワークフローについても調べて紹介します。
+
+# 参考
+
+* [イベントドリブンな StackStorm で運用自動化 | ジェダイさんのブログ](https://jedipunkz.github.io/blog/2016/07/02/stackstorm/)
+
+
+[^1]: もちろんJenkinsでもできるという反論はそうですし、そもそもJenkinsと比べること自体どうなのというツッコミもあると思いますが、自分が調査するモチベーションがそもそも「Jenkinsの代替となるか」という観点だったので、その点はご容赦ください。
+[^2]: https://docs.stackstorm.com/overview.html#about
+[^3]: https://docs.stackstorm.com/install/overview.html
+[^4]: https://docs.stackstorm.com/install/index.html#installation
+