0
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?

More than 1 year has passed since last update.

Jenkinsで最新のビルドだけ実行したい

Last updated at Posted at 2023-04-04

TL;DR

Jenkinsで最新ビルドが欲しいのに古いジョブがキューイングされてしまっていてビルドできない!なんてことはありませんか?
実は最新ビルド以外を自動でキャンセルさせるいい方法があるんです!

それはJenkinsで最新のビルドだけ実行したい場合はdisableConcurrentBuilds(abortPrevious: true)を使うことです!
出典 : https://www.jenkins.io/doc/book/pipeline/syntax/#:~:text=options%20%7B%20checkoutToSubdirectory(%27foo%27)%20%7D-,disableConcurrentBuilds,-Disallow%20concurrent%20executions

・・・なんて思っていた時期もありました。
これには欠点があり、実行してみればわかるのですが${JOB_NAME}@Nという末尾に数字の付いたworkspaceが新規で作られてしまうのです!
つまりはジョブがほぼ同時に多数実行されてしまうと無制限(とは流石にいかないでしょうが)にディレクトリが増えていくことに・・・!
しかもそのジョブがキャンセルされなければcheckoutされてしまうディレクトリも増えるということで・・・つまり無制限にストレージ容量も食ってしまう!!!

ということで試行錯誤した結果milestonelockstagesのネストを駆使すればなんとかいけるというところまで漕ぎつけたのでまとめてみます。

未調査項目

JenkinsPipelineを使わない方法については未調査です。
また、master-slave構成ではない場合についても未調査です。
悪しからず。

要素説明

調べたり試したりした順番で記載します。

milestone

milestoneコマンドを呼び出すと、それ以前に同じ数字で呼び出した前のJobをキャンセルしてくれるらしい。
と思って試してみたものの、うまくいかず・・・。例にもれず癖のある挙動をするようです。
どうも後続のJobがより大きな数字で呼び出した=処理を追い越したと判断する模様?

おとなしくstackoverflowに載っていた↓のコードを使いましょう。

def buildNumber = env.BUILD_NUMBER as int
if (buildNumber > 1) milestone(buildNumber - 1)
milestone(buildNumber)

出典 : https://stackoverflow.com/a/55818301

agent none

milestoneでキャンセルされたよやったね!と喜んだのもつかの間、また@Nディレクトリが使用されてしまうことが発覚。
pipelineの開始時点で既にworkspaceは決定されてしまった後であるらしく、abortPreviousと同じ問題を抱えることに。

じゃあworkspaceが決定される前にmilestone処理を実行すればいいのでは?ということで、stage内でagentを指定することに。

pipeline {
  agent none

  stages {
    stage('milestone') {
      agent { label 'master' } // 管理してるだけのmasterマシン
      steps {
        script {
          def buildNumber = env.BUILD_NUMBER as int
          if (buildNumber > 1) milestone(buildNumber - 1)
          milestone(buildNumber)
        }
      }
    }
    stage('build') {
      agent { label 'slave' } // 実際にビルドが走るslaveマシン
      steps {
        ...
      }
    }
  }

agent noneについてはこちら : https://www.jenkins.io/doc/book/pipeline/syntax/#agent:~:text=example%3A%20agent%20any-,none,-When%20applied%20at

が、失敗。変わらず@Nが付いてしまう結果に…。

TIPS
ちなみにpostがある場合、以下のように書かないとエラーになります。

  post {
    success { node(label: 'slave') { ... } }
    failure { node(label: 'slave') { ... } }
  }

まとめて管理するためにenvironmentで管理すべきかもしれませんね。
以下のように書くことが可能です。

  environment {
      BUILD_MACHINE = "slave"
  }
...
    stage('build') {
      agent { label env.BUILD_MACHINE } // 実際にビルドが走るslaveマシン
      steps {
        ...
      }
    }
...
  post {
    success { node(label: env.BUILD_MACHINE) { ... } }
    failure { node(label: env.BUILD_MACHINE) { ... } }
  }

environment内でshを呼び出している場合などにおいても同じような問題があります。
こちらについてはどうしようもなさそうなのでgroovyの構文で解決できないか検討すべきでしょう。
未確認ですが、agentを指定済みのstage内にenvironmentを置くことで回避できるかもしれません。

lock

問題はmilestoneを呼び出してキャンセルしてからstage(もしくはagentかsteps)に到達するまでに前のジョブが終わっていないことだと推測
(本当にここについては私の推測にすぎません。根拠はないです。)

何かJob同士の協調ができるようなものはないかと探した結果・・・ありましたlockが!

ちなみにJenkinsのメニュー内にあるLockable Resourcesでリソースを管理するのかと思いきや、
If the resource does not exist in Global Settings it will be automatically created on build execution. Either a resource or a label need to be specified.
ということらしく、${JOB_BASE_NAME}あたりを突っ込んでおけば良さそうでした。

ただ、githubを見ても使えそうで使えないという感じなんですよね・・・。
なんせ複数のstageに跨ってlockをかけられない・・・!
色々なところに挿入してみたものの構文エラーが多発し断念・・・。

stagesのネスト

方向性は合っているはずとあきらめきれずググってみたところ↓のようなブログを発見。
https://www.prkz.de/blog/20-jenkins-using-lockable-resource-across-multiple-stages

stagesは入れ子にできるのか・・・!!!

ということで↓のような形に。

pipeline {
  agent none

  stages {
    stage('milestone') {
      agent { label 'master' }
      steps {
        script {
          def buildNumber = env.BUILD_NUMBER as int
          if (buildNumber > 1) milestone(buildNumber - 1)
          milestone(buildNumber)
        }
      }
    }
    stage ('lock') {
      options { lock("${JOB_BASE_NAME}") }
      stages {
        stage ('build') {
          agent { label 'slave' }
          steps {
            ...
          }
        }
      }
    }
  }
}

ちなみにoptionsと同じレイヤにagentを指定すると@Nが付くことがあったためstagesの中に入れています。

これで@Nが付かなくなった!workspaceは1つでよくなったぞ!目的達成!
・・・の前にもう1課題。

stagesの入れ子2

やりたいことは達成できたものの、stageすべてにagentを指定しないといけない状態でした。
と、ここで思い出します。stagesは入れ子にできた・・・ということは?

pipeline {
  agent none

  stages {
    stage('milestone') {
      agent { label 'master' }
      steps {
        script {
          def buildNumber = env.BUILD_NUMBER as int
          if (buildNumber > 1) milestone(buildNumber - 1)
          milestone(buildNumber)
        }
      }
    }
    stage ('lock') {
      options { lock("${JOB_BASE_NAME}") }
      stages {
        stage ('agent') {
          agent { label 'slave' }
          stages
  { // インデント調整用
    stage('build-step1') {
      steps {
        ...
      }
    }
    stage('build-step2') {
      steps {
        ...
      }
    }
  }}}}
  }
}

これは酷い。
酷いんだけど必要要件はこれで揃いました。
ここからもう少し楽にできないか見てみたのですがうまくいかず・・・。
私はこの辺りで妥協しました。

求)更にリファクタしたコード

完成

というわけで(妥協の結果とはいえ)古いジョブはキャンセルしつつ、最新のジョブのみを実行することができました。
が、まだ問題点も残っているようで・・・(Jenkins側の問題ではないかと疑っている)

TIPS

poststagesに対応する

つまり、以下のように書くことでlockを外す前にpost処理を行うことができます。

pipeline {
  agent none

  stages {
    stage('milestone') { ... }
    stage ('lock') {
      options { lock("${JOB_BASE_NAME}") }
      stages {
        stage ('agent') {
          agent { label 'slave' }
  stages   { // インデント調整
    ...
  }
  post
  {
    success { ... }
    failure { ... }
  }
  }}}}
  }
}

問題点

問題点1 : キャンセルされないジョブがある

タイミングによっては古いジョブがキャンセルしきれず終わらないことがありました。
その場合は単純に待つか急ぎであればコンソールログ上に表示されるClick here to forcibly terminate running stepsというリンクを踏めば良さそうです。

問題点2 : @Nが付く状態が固定化されることがある

今回の作業中試行錯誤をしたせいなのか、短い時間で連打したせいなのか@Nが固定化されてしまい、単独でトリガーしても@Nが付いたままになってしまうことがありました。
こちらについては以下のページにある通り再起動すると直ったのでJenkinsのバグだと思います。
https://stackoverflow.com/a/71828212

最後に

いかがでしたでしょうか?
最新ビルドだけを実行することができればビルドマシンを効率よく利用できますね!
かなり若干見づらいコードとなってしまったのは残念ですが、もう少し効率のいい書き方を発見できたらこの記事をアップデートしようと思います!
ではまた別の記事でお会いしましょう!

0
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
0
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?