一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

Rundeck in practice [運用編]

この記事は一休.comアドベントカレンダー2018の9日目です。

qiita.com


導入編に続き、運用編です。
ここ2年間 Rundeckを運用してきて発生したトラブルとその対処について書きます。
※この記事で言及するRundeckはバージョン2.6.9です。
トラブルはふたつありました。

  • データベースが高負荷になり動作が不安定になった
  • なぜかジョブが起動しない

データベースが高負荷になり動作が不安定になった

原因は複数ありました。

データベース(AWS RDS)のインスタンスタイプが小さすぎた

完全にサイジングのミスでした。動作確認で複数のジョブを大量に動かしたときでも、t2.smallのインスタンスで十分に動作したので、t2.smallで大丈夫だろうと、そのまま本番導入したのですが、運用開始して2ヶ月くらいで、高負荷になりました。速やかにt2.mediumにスペックアップしました。

コネクションプールの設定が漏れていた。

Rundeckは、rundeck-config.propertiesというファイルにデータベースの接続情報を記述します。
デフォルトでは、H2 Databaseを使用する前提の接続情報になっています。
これをRDS(MySQL)を使うように修正したのですが、その際、コネクションプールの設定が漏れていました。そのため、接続が一切プールされない、という状態になっていました。
以下の記述をrundeck-config.propertiesに追加することで適切にプールをするように修正しました。

dataSource.pooled=true
dataSource.properties.removeAbandoned=true
dataSource.properties.removeAbandonedTimeout=60

データベースのインデックス不足

このissueで議論されていますが、RundeckのデータベースにはGUIの性能を大きく改善できるインデックスがいくつかあります。これらのインデックスはデフォルトでは付与されていないようです。運用開始後、GitHubやGoogle Groupに投稿されている情報を調査し、このissueで紹介されているインデックスや#1547で紹介されているインデックスを付与することで性能を改善できました。

実行ログがたまり続ける

これは運用を開始する前からわかっていた課題だったので、データベースの高負荷の原因にはなりませんでしたが、対処が必要な課題ではありました。
Rundeckはデータベース上の実行ログを削除しません。長期間運用して実行履歴が大量に溜まった段階で実行履歴の検索を行なった場合、データベースの負荷が高まる可能性があるので、定期的に削除する仕組みが必要だと判断しました。
削除する実行ログの条件は以下の通りです。

  • ジョブは最新の1万件の実行ログを保持する。1万件を超えたら古い順に削除される。

月次1回だけ実行されるジョブもあれば、1時間以内に複数回実行されるジョブもあります。また、調査のために古い履歴を調べることがあるかもしれません。このような前提を考慮して、上記のようなルールにしました。

そして、Rundeckのデータベースの構造を理解してどのテーブルのデータを削除すればよいのかを見つけ、定期的に削除するプログラムを作成しました。

調査したところ以下のdelete文の実行すれば良さそうです。

delete from log_file_storage_request where execution_id in ( @jobhistoryids )
delete from execution where id in ( @jobhistoryids )
delete from base_report where jc_exec_id in (@jobhistoryids )

@jobhistoryidsは、base_reportテーブルのjc_exec_id列の値です。
あとは、上記の条件に合致するジョブのIdと削除件数を特定する必要があります。
それは次のSQLで取得できました。

select
    inntable.jc_job_id as JobId,
    inntable.counts - 10000 as DeleteCount
from
    (
        SELECT
            jc_job_id,
            count(jc_exec_id) counts
        FROM
            base_report
        group by
            jc_job_id
    ) inntable
    inner join
        scheduled_execution se
    on  se.id = inntable.jc_job_id
where
    se.execution_enabled = 1
and inntable.counts > 10000
order by
    inntable.counts desc

このselect文で、実行回数が1万回を超えているジョブのIdと超過回数がわかります。
※例えば10300回実行されたいたら300回が超過回数になります。

そして、次のSQLで削除対象のjc_exec_idを特定します。

SELECT
    jc_exec_id
FROM
    base_report
where
    jc_job_id = @jobId -- 上のselect文で見つかった JobId
order by
    date_completed asc -- 完了日時で昇順でソートすることで1万件を超過した実行ログのIdを特定できる
limit @deleteCount -- 上のselect文で計算した削除対象件数 DeleteCount

あとは、このselect文で取得できたjc_exec_idをパラメータにして上述した3つのdelete文を実行すれば、削除完了です。

なぜかジョブが起動しない

データベースのトラブルが治った後はしばらく順調に動作していました。しかし、指定した時間なのにバッチに起動しない、という現象が発生するようになりました。
詳しく状況を見てみると、バッチ実行が遅延しているようでした。
いろいろと調べてみると、Rundeckが内部で使っているジョブスケジューラライブラリのQuartzのパラメータが原因でした。
Rundeckの公式ドキュメントによれば、The maximum number of threads used by Rundeck for concurrent jobs by default is set to 10と書いてある通り、デフォルトでは最大で10本のジョブの同時実行が可能です。
一方、一休では利用が促進された結果、タイミングによっては10以上のジョブが同時に実行されるような状況になっており、その結果、実行が遅延するようになっていました。この設定を変えるには、以下の記述をrundeck-config.propertiesに追加します。

quartz.props.threadPool.threadCount=30

この記述によって最大30まで同時実行できるようになり、問題が起きなくなりました。

終わりに

今回は実際に運用してきて発生したトラブルとその対処について紹介しました。参考になれば幸いです。 一休ではWindowsのタスクスケジューラからRundeckへ移行しました。Rundeckは未知のツールだったので苦労する点もありましたが、起こった問題は調査すれば解決策が見つかるものばかりだったので、移行は十分成功したと感じています。

おまけ

GUIの日本語化

管理画面が英語だとわかりにくいので、一休ではガイドラインにしたがって、主要な部分だけですが日本語にしています。 部分的なローカライゼーションではありますが、ないよりマシ、なレベルではあるので、本家の方にも導入できるようにPRを送っています。次のバージョンで取り込まれるかもしれません^ - ^

この記事の筆者について

  • システム本部CTO室所属の 徳武 です。
  • サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。