Humanity

Edit the world by your favorite way

Rundeck ジョブの実行ノード一覧を Consul から取得する

長い前置き

最近 Consul, Prometheus などを試す記事が続いてますが、 なぜかというと、私はサーバ監視などに Hinemos を使っており、いい加減別のシステムに移行したい欲が強まってきたからです。

tyru.hatenablog.com

Hinemos はオールインワンのため、ジョブ管理やら死活監視やら CPU やメモリなどの使用率をグラフで表すなどの機能が全部入ってます。 しかし Hinemos リッチクライアントと呼ばれるツールが Windows しか対応してなくて Windows からしか設定をいじったり見ることができなかったり、あと重かったり、UI がいけてなかったりでストレスが溜まっていました。

Hinemos 5 からは Hinemos Web クライアントという HTTP で見れるクライアントが提供されたらしいので試したかったのですが、 Hinemos 4 からの移行はどうやら無理のようでした(できるオプションは有償)。

そんな感じで心底嫌気がさしていたのでいい加減普通の今流行りの OSS に移行したい、ということで最近勢いで色々試しているというわけです。

最近は Consul + Prometheus(監視システム)とか、あと記事には書いてないですが Prometheus + Grafana(CPU やメモリなどの使用率をグラフ描画)*1を試してきました。 そこで次は良いジョブ管理システムがないか探したところ、前もちょこっとだけ試した Rundeck というジョブ管理システムを試すことにしました。

導入記事などはこちら。

dev.classmethod.jp

しかし実行ノードの一覧を追加するには ssh ログインしてファイルを編集しなければならないと書いてあり、 なんとかならんもんかとドキュメントを漁ってみたら結構色んなリソースからのノード一覧取得に対応しているみたいでした。

公式ドキュメントによると

  • file
  • url
    • 指定された URL に GET してノード一覧を取得
  • directory
    • 指定されたディレクトリの中のファイルの内容からノード一覧を取得
  • script

そこで今回はこの中の script を使って、Consul に join しているノードが自動的に実行対象になるようにしてみます(実行ノードはプロジェクトページで絞り込むこともできます)。

Rundeck のインストール、SSH 鍵の配置、等々

省略します。 上の導入記事などを見てください。

スクリプト配置

以下のスクリプトを実行権限付きで置いておきます(rundeck ユーザから見えるパスにしてください)。 自分は /var/rundeck/list-consul-nodes.sh に置きました。

※今後けっこう更新するかもしれないので一応この記事を書いた段階のリビジョンを伝えておくと、9d6c7d0 です。

ちなみに出力する YAML の設定ファイルの書式は以下のページを参考にしました。

RESOURCE-YAML

Rundeck でスクリプトを指定

プロジェクトの編集ページからスクリプト追加。

f:id:tyru:20170804231251p:plain

f:id:tyru:20170804231300p:plain

f:id:tyru:20170804231359p:plain

f:id:tyru:20170804231406p:plain

これで設定は完了です。

ちなみにスクリプトを指定したけど動かない(ノード一覧に何も出ない、もしくは前回のスクリプトのノード一覧出力結果から変わらない)って場合はスクリプトの終了コードが 0 以外で終了してるなどが多いです。 画面上では全くエラーが出ないのですが… /var/log/rundeck/service.log のログを見れば分かります。

こんなエラーが出ます

ERROR ExceptionCatchingResourceModelSource: [ResourceModelSource: 1.source (script), project: test2]
com.dtolabs.rundeck.core.resources.ResourceModelSourceException: failed to execute: /var/rundeck/list-consul-nodes.sh: Script execution failed with result: 1
        at com.dtolabs.rundeck.core.resources.ScriptResourceModelSource.getNodes(ScriptResourceModelSource.java:170)
        at com.dtolabs.rundeck.core.resources.ExceptionCatchingResourceModelSource.getNodes(ExceptionCatchingResourceModelSource.java:57)
        at com.dtolabs.rundeck.core.common.ProjectNodeSupport.getNodeSet(ProjectNodeSupport.java:113)
        at com.dtolabs.rundeck.core.common.ProjectNodeSupport$ProjectNodesSource.getNodes(ProjectNodeSupport.java:327)
        at com.dtolabs.rundeck.core.resources.ExceptionCatchingResourceModelSource.getNodes(ExceptionCatchingResourceModelSource.java:57)
        at com.dtolabs.rundeck.core.resources.ResourceModelSource$getNodes.call(Unknown Source)
        at rundeck.services.nodes.CachedProjectNodes.reloadNodeSet(CachedProjectNodes.groovy:42)
        at rundeck.services.nodes.CachedProjectNodes$reloadNodeSet.call(Unknown Source)
        at rundeck.services.NodeService$_loadNodes_closure3.doCall(NodeService.groovy:261)
        at rundeck.services.NodeService$_loadNodes_closure3.doCall(NodeService.groovy)
        at sun.reflect.GeneratedMethodAccessor1515.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1207)
        at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
        at groovy.lang.Closure.call(Closure.java:423)
        at groovy.lang.Closure.call(Closure.java:417)
        at com.codahale.metrics.Timer.time(Timer.java:99)
        at com.codahale.metrics.Timer$time$0.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
        at com.codahale.metrics.Timer$time$0.call(Unknown Source)
        at MetricswebGrailsPlugin$_addDynamicMetricMethods_closure26.doCall(MetricswebGrailsPlugin.groovy:190)
        at sun.reflect.GeneratedMethodAccessor338.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1207)
        at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
        at groovy.lang.Closure.call(Closure.java:423)
        at org.codehaus.groovy.runtime.metaclass.ClosureStaticMetaMethod.invoke(ClosureStaticMetaMethod.java:59)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
        at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoMetaMethodSiteNoUnwrap.invoke(PogoMetaMethodSite.java:230)
        at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:68)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:96)
        at rundeck.services.NodeService$_loadNodes_closure4.doCall(NodeService.groovy:268)
        at rundeck.services.NodeService$_loadNodes_closure4.doCall(NodeService.groovy)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1207)
        at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
        at groovy.lang.Closure.call(Closure.java:423)
        at groovy.lang.Closure.call(Closure.java:417)
        at groovy.lang.Closure.run(Closure.java:504)
        at org.springframework.core.task.SimpleAsyncTaskExecutor$ConcurrencyThrottlingRunnable.run(SimpleAsyncTaskExecutor.java:251)
        at java.lang.Thread.run(Thread.java:748)
Caused by: com.dtolabs.rundeck.core.resources.ResourceModelSourceException: Script execution failed with result: 1
        at com.dtolabs.rundeck.core.resources.ScriptResourceUtil.executeScript(ScriptResourceUtil.java:147)
        at com.dtolabs.rundeck.core.resources.ScriptResourceModelSource.getNodes(ScriptResourceModelSource.java:156)
        ... 55 more
WARN  LoggingResourceModelSourceCache: [ResourceModelSource: 1.source (script), project: test2] Returning cached model data

テスト実行

さっそくテスト実行してみましょう。 偶然チャックノリスの API を見つけたのでランダムなテキストを表示させて無駄に複数のノードで実行してる感を出してみました。

実行するコマンド

今回このために jq コマンドインストールした。 どうでもいいけど最近コマンドラインJSON 整形する必要が出てきて jq コマンド便利というのを知った。

curl -s https://api.chucknorris.io/jokes/random | jq -r '.value'

実行結果

ちゃんとランダムでチャックノリスの情報が表示されているのが分かります(ホスト名は伏せました)。

f:id:tyru:20170804233633p:plain

*1:Grafana はインストールも簡単なのでこの記事とか見ればすぐできるでしょう