2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

再起動にも負けないJenkins pipelineで長時間のジョブも自動化してしまおう

2
Posted at

はじめに

前回の記事では、SonarQube環境をDockerで安定稼働させるまでを解説しました。
今回は、その環境を使って400以上のJavaプロジェクトを解析するというミッションに挑みます。全プロジェクトの解析には数日を要するため、途中でサーバーが再起動したり、ジョブが止まったりしても、「最初からやり直さずに続きから再開する」仕組みが必須でした。

Jenkins Pipelineが持つ再起動にも負けない仕組みと、チェックリスト方式によるレジューム術を紹介します。


1. シェルスクリプトによるループの限界

当初は単純なシェルスクリプトでループを回していましたが、以下の課題に直面しました。

  • 進捗が見えない: 今何件目なのか、あと何時間かかるのかが不透明。
  • 中断に弱い: プロセスが死ぬと、どこまで進捗したかを調べる手間が発生。
  • エラー管理: 1件失敗した時に、ループ全体を止めるのか飛ばすのかの制御が複雑。
  • リストの保守: プロジェクトが増減するたびに、対象リスト(テキストファイル)を手動で更新するのが苦行。

これらを解決するために、Jenkins Pipeline (Groovy) への移行を決断しました。


2. 環境のシャットダウンにも耐える Jenkins

Jenkinsでジョブを回している最中、サーバーの再起動が発生しました。平日のみ11:00-20:00で環境を稼働させていたため、週末を跨いで月曜にJenkinsの画面を開くとこんな表示がありました。

Build Duration: 2 days 20 hr

なんと、ジョブは死ぬことなく、サーバー復活と共に中断したその行から自動で再開していたのです。

なぜ再開できたのか?

Jenkins Pipeline(特に Jenkinsfile 形式)には、Durable Pipeline(耐久性パイプライン) という強力な仕組みが備わっているからです。

  • CPS (Continuation Passing Style) 変換: Jenkinsはスクリプトを1行実行するたびに、「今どの変数がどんな値で、どこまで進んだか」の状態をディスクにシリアル化して保存しています。
  • 冬眠と目覚め: サーバーが落ちるとジョブは「一時停止」状態になり、再起動時にディスクから状態を読み戻して、中断したステップから動き出します。

詳細はjenkinsのページに記載がありました。和訳が以下です。

コマンドラインから実行される通常の Groovy プログラムとは異なり、パイプラインビルドのプログラムの完全な状態は、非同期操作が実行されるたびにディスクに保存されます。これには、ほとんどのパイプラインステップが含まれます。ビルドの実行中に Jenkins を再起動しても、中断した箇所からプログラムの実行が再開されます。これは効率性を意図したものではないため、Jenkins の機能に直接関連する高レベルの「グルー」コードに限定すべきです。プロジェクト独自のビルドロジックは、ビルドノード上の外部プログラムから、sh または bat ステップで実行する必要があります。


3. 「動的リスト取得」と「二重チェックリスト」によるレジューム術

プラットフォーム側の耐久性に加え、アプリケーション層でも強力なレジューム機能を実装しました。

実装のポイント

  1. 動的なプロジェクトスキャン: findコマンドでディレクトリを直接スキャンし、target@tmpを除外した「最新のリスト」を都度生成。
  2. 完全一致での判定: String.contains() ではなく、リスト化した上での contains() を使い、部分一致による誤スキップを防止。
  3. 失敗も「処理済み」として扱う: 成功リスト(status)と失敗リスト(failed)を分け、どちらかに記載があればスキップすることで、壊れたプロジェクトに捕まってループが止まるのを防ぎます。

実装コード(抜粋)

stage('Loop Analysis') {
    steps {
        script {
            def allProjectsFile = "resources/all_projectsList.txt"
            def statusFile = "resources/status_${env.TARGET_TAG}.txt"
            def failedFile = "resources/failed_${env.TARGET_TAG}.txt"
            sh "touch ${statusFile} ${failedFile}"

            // 1. ディレクトリから動的にプロジェクト一覧を取得
            def findCmd = """
                find ${env.PRJ_ROOT}/app -maxdepth 1 -mindepth 1 -type d -printf '%f\n' | \
                grep -vE '^(EXCLUDED1|EXCLUDED2)\$|@tmp\$'
            """.stripIndent()
            
            def allProjects = sh(script: findCmd, returnStdout: true).trim().split('\n').collect { it.trim() }
            writeFile file: allProjectsFile, text: allProjects.join('\n') // 最新リストを保存

            for (project in allProjects) {
                // 2. 成功・失敗リストを読み込み、完全一致でチェック
                def finishedList = readFile(statusFile).split('\n').collect { it.trim() }
                def failedList = readFile(failedFile).split('\n').collect { it.trim() }

                if (finishedList.contains(project) || failedList.contains(project)) {
                    echo "SKIP: ${project} (処理済み)"
                    continue
                }

                // 3. 解析実行
                try {
                    dir("app/${project}") {
                        sh "mvn sonar:sonar ..."
                    }
                    sh "echo '${project}' >> ${statusFile}" // 成功記録
                } catch (Exception e) {
                    sh "echo '${project}' >> ${failedFile}" // 失敗記録
                    echo "ERROR: ${project} を失敗リストに記録しました。"
                }
            }

            // 4. 全件(成功数 + 失敗数)が完了したら後続ジョブへ
            def processedCount = readFile(statusFile).split('\n').size() + readFile(failedFile).split('\n').size()
            if (processedCount >= allProjects.size()) {
                build job: 'Report_Generator', ...
            }
        }
    }
}


4. この構成にして良かったこと

  • 「放置できる」という信頼感: 400件中1件がMavenエラーになっても、それを failed.txt に放り込んで次の解析に進んでくれるため、朝起きたらジョブが止まっているという絶望がなくなりました。
  • メンテナンスフリー: プロジェクト用ディレクトリを増減させるだけで、リスト更新から解析、集計までが自動で追従します。
  • 正確なスキップ判定: リスト形式での判定に切り替えたことで、名称が似たプロジェクトの未解析問題を完全に解消しました。

まとめ

「解析に数日かかる」という物理的な制約を、Jenkinsの耐久性チェックリストロジックの合わせ技で克服しました。
これまでSSH接続をして手動でシェルを蹴っていた手間が完全に無くなり、CI/CDツールとしてのJenkinsの底力を改めて実感しています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?