定期実行といえばcronの阿部です。
定期実行するときにみなさまはどのようなソフトウェアをご利用でしょうか? この記事はFLOSS1サポート業務でジョブ管理ツールのRundeckについて調査したのでその事例紹介です。
(本題と全く関係ないのですが、最近、systemdのtimerという機能でも定期実行できることを知りました。)
はじめに
思ったより書くことが多くなりそうだったので、だいぶ簡潔に書きました。 いろいろ省略したことで説明が正確ではない部分があるかもしれませんがご容赦ください。
- Rundeckの説明
- この事例に関連するところだけを簡単に説明
- どのような問題があったのか説明
- 問題を調査した結果
の流れで説明していきます。
Rundeckとは
Rundeck: https://www.rundeck.com/
Rundeckはジョブ管理ツールです。 cronに馴染みのある方であれば、cron + crontab + いい感じのGUI という理解で60%くらい合っていると思います。
以降で、サポート事例を紹介するにあたりRundeckについて知っていたほうが良い部分だけ簡単に説明します。
Rundeckの概要
- Rundeckは常駐プログラムです
- systemdでstartしたりstopしたりします
- Rundeckのジョブとはcrontabでいうところの
5 0 * * * COMMAND
の1行- という理解で30%くらい合ってます(もう少し複雑な設定ができます)
- 実行したいコマンドを登録して、スケジュールの設定とかができます
- 参考: https://docs.rundeck.com/docs/manual/jobs/creating-jobs.html#scheduled-jobs
- Rundeckがジョブを実行するときはスレッドで実行します
- Rundeckはジョブを実行しているスレッドが、ジョブのステータス(正常終了したとかエラーだったとか)をDB(H2とかPostgreSQLとか)に保存します
- クラスターモードで冗長構成が実現できます
補足: クラスターモード
記事の内容に関係があるのでクラスターモードについて少し補足します。
クラスターモードにすると、Rundeckサーバを複数台 + DBサーバは共通の1台、のような構成ができます。
複数台のサーバでジョブを実行することになるので、ジョブを実行したサーバがわかるように、クラスターモードの場合はサーバIDも一緒にDBに保存します。
クラスターモードでなければジョブにサーバIDを登録しておく必要がないのでnull
が登録されます。
サーバIDはサーバごとに一意に設定します。(Rundeckのインストール時に自動生成され、自動で設定ファイルに書き込まれます。)
問題
Rundeckを再起動すると、終了しないジョブが現れました。
ジョブに登録してあるコマンドは完了しているのですが、RundeckのUIで確認できるジョブのステータスが「実行中」のまま、という状況です。
どうしてこのような状態になるのかを確認して報告するのが今回のサポート業務です。
Rundeck 2.xからRundeck 4.xにバージョンアップしたところ発生したとのことで、2.xのときはこのような状態は発生していませんでした。
調査結果
調査の過程を順を追って紹介しようと思い、この記事を書き始めたのですが、順序立てて説明するのが難しかったので、結論だけ書くことにします。
Rundeckを再起動すると、終了しないジョブが現れる事象ですが、Rundeckのバグでした。
どのようなバグか簡単に説明します。
バグを理解するための前提知識1: 終了しないジョブが発生する仕組み
Rundeckはスレッドでジョブを実行して、ジョブが終了したらDBのステータスを終了に更新します。 しかしジョブの実行中にRundeckを停止や再起動してしまうと、DBのステータスを終了に変更できず実行中のままになります。 再起動後にステータスが実行中のジョブは、そのジョブを更新するスレッドも残っていないので、永遠に実行中のまま残り続けます。
バグを理解するための前提知識2: 起動時のお掃除処理
終了しないジョブが発生する仕組みとしては上述の通りなのですが、発生しないように対策がされています。 具体的には「起動時に実行中になっているジョブをすべて終了状態にする」という対策がRundeckには入っています。 (ので、これが正常に動作していれば終了しないジョブは発生しなかったのですが…。)
この記事を書いている時点の最新のコードを引用しますが2.xや4.xにも同じ処理が入っています。
if ('sync' == cleanupMode) {
timer("executionService.cleanupRunningJobs") {
executionService.cleanupRunningJobs(clusterMode ? serverNodeUUID : null, cleanupStatus, new Date())
}
} else {
log.debug("executionService.cleanupRunningJobs: starting asynchronously")
executionService.cleanupRunningJobsAsync(
clusterMode ? serverNodeUUID : null,
cleanupStatus,
new Date()
)
}
起動時に実行される関数内でcleanupRunningJobs()
(cleanupRunningJobsAsync()
は同期実行か非同期実行かの違いだけで中身は同じです) が実行されています。
これにより起動時点で実行中になっているジョブをすべてcleanup(= 終了状態)にするわけです。
「クラスターモードの概要」でクラスターモードにするとジョブにサーバIDを保存して、そうでなければnull
が保存されていると説明しました。
cleanupRunningJobs()
の引数にあるclusterMode ? serverNodeUUID : null
がそれに対応しています。
クラスターモードでなければジョブにnull
が設定されているものを対象にcleanupし、クラスターモードであれば引数で指定したサーバID(serverNodeUUID
)が設定されているジョブを対象にcleanupをします。
クラスターモードだと複数台のRundeckサーバで運用しているので、Rundeckを再起動したサーバとは違うサーバで起動しているジョブを終了しないようにするため、このようになっています。
バグの説明と終了しないジョブが発生する原因
バグは「クラスターモードがONでもOFFでもジョブにサーバIDが設定されている」というものです。
これによりクラスターモードがOFFの環境で、サーバを再起動するとcleanupRunningJobs()
が期待通りにcleanupしてくれなくなります。
クラスターモードがOFFなので、cleanupRunningJobs()
の条件でサーバIDはnull
となりますが、上述のバグの影響でジョブにはサーバIDが設定されているので、cleanup対象のジョブが見つからなくなるためです。
クラスターモードがONでもOFFでもジョブにサーバIDが設定されるようになってしまったコミット:
https://github.com/rundeck/rundeck/commit/79482490a3f35ad915736882d0d6d305ddf421c3#diff-9c8f843935d358de32ba0a6ea3ae1a370e48556a5b1912ba978472d9c11ca9feL109-L126
getServerUUID()
のサーバIDの取得方法が変わっています。
変更前はSERVER_UUID
から取得しています。これは起動時にクラスターモードがONの場合のみ設定されるもので、クラスターモードがOFFであればnull
になります。
変更後はSystem.getProperty(SYS_PROP_SERVER_ID)
で取得しています。
RundeckをインストールするとクラスターモードのON/OFFに関係なくサーバIDは自動生成されて設定ファイルに書かれるので、System.getProperty(SYS_PROP_SERVER_ID)
で必ずサーバIDが取得できるようになりました。これの影響でジョブの実行時にサーバIDが必ず設定されるようになりました。
v3.3.4でこの変更が入ったので、2.xでは発生せず、4.xで発生したという状況にも合っています。
参考: 該当Issue
この問題はRundeckのIssueでも報告されています。
https://github.com/rundeck/rundeck/issues/7139
サポートの過程でわかったことをそのIssueに報告しています。
まとめ
Rundeckにてサービスを再起動すると終了しないジョブが現れる原因を調査したサポート事例の紹介でした。
このサポート事例では「サービスを再起動すると終了しないジョブが現れる理由の調査」までのご依頼でしたので、原因の調査までに留めましたが、不具合が改善するように修正パッチを送るなど、upstreamに働きかけるサポートも可能です。
Rundeckの事例でしたが他のFLOSSでもサポート可能ですので、ご利用中のFLOSSで何かお困りの際はお問い合わせよりご連絡ください。
-
"Free/Libre and Open Source Software"の頭文字から。自由ソフトウェアとオープンソースソフトウェアを包括的に表すときに使われる。 用語集 > FLOSS ↩