はじめに

こんにちは株式会社TIELECのタカシユウトです。 この記事ではJenkins Pipelineジョブの使い方応用編として、パラレル処理のやり方や、対話形式での入力などのやり方を紹介していきます。 少し高度なパイプラインを書こうかなと考えている人の参考になれば幸いです。

【Jenkins】Pipelineジョブを使ってみよう ~ 応用編 ~

シナリオ

上記で作成した猫の画像をダウンロードするジョブ(フリースタイルジョブとパイプラインジョブ)をパラレルで実行します。このときに対話式で実行するかの確認を求めるようにする。

Pipelineジョブを作成する

前回と同様ジョブを作成します。 ジョブの名前は parallel_download_kitten_image_pipeline とします。

設定の入力

設定は省略します。 必要に応じて 古いビルドの破棄 など設定しましょう。

スクリプト作成

型を作る

pipelineの型を埋めていきます。

pipeline{
    agent{
        label "slave1"
    }
    stages{
        stage("Confirm download kitten image"){
            steps{
                echo "========executing A========"
            }
        }

        stage("Parallel download kitten image"){
            steps{
                echo "========executing A========"
            }
        }
    }
    post{
        always{
            cleanWs()
        }
    }
}
Confirm download kitten image

stage Confirm download kitten image の中身を実装していきます。

やりたいことの確認

  1. 次のステージに進む前に対話式で実行の確認をできるようにしたい。
  2. 承認ステージをスキップしたい場合に対応できるようにしたい。

この二つを実現していきます。

1. 次のステージに進む前に対話式で実行の確認をできるようにしたい。

まず一つ目の対話式での確認ですが、inputというステップを使います。 また、input を使うときは入力がされないままジョブが滞留するということを防ぐためにtimeouteというステップと併用します。

pipeline20

timeout(time: 1, unit: 'HOURS') {
    // some block
}

pipeline21

input 'download kitten image?'
timeout(time: 1, unit: 'HOURS') {
    input 'download kitten image?'
}
pipeline{
    agent{
        label "slave1"
    }
    stages{
        stage("Confirm download kitten image"){
            steps{
                timeout(time: 1, unit: 'HOURS') {
                    input 'download kitten image?'
                }
            }
        }

        stage("Parallel download kitten image"){
            steps{
                echo "========executing A========"
            }
        }
    }
    post{
        always{
            cleanWs()
        }
    }
}

2. 承認ステージをスキップしたい場合に対応できるようにしたい。

続いて二つ目の承認ステージをスキップしたい場合に対応できるようにしていきます。

パラメータにスキップするオプションを定義し、条件に当てはまるときはスキップするという方針で実装します。

使うのは parameterswhen を使います。

前回まではジョブの設定でparameterを組みましたが、実はpipelineの中で定義することも可能です。

今回は真偽値のパラメータをSKIP_CONFIRM_DOWNLOADという名前で定義してそれをフラグとして利用することにします。

pipeline22

parameters {
  booleanParam defaultValue: false, description: 'チェックを入れた場合ダウンロードの確認をスキップします。', name: 'SKIP_CONFIRM_DOWNLOAD'
}
pipeline{
    agent{
        label "slave1"
    }

    parameters {
        booleanParam defaultValue: false, description: 'チェックを入れた場合ダウンロードの確認をスキップします。', name: 'SKIP_CONFIRM_DOWNLOAD'
    }

    stages{
        stage("Confirm download kitten image"){
            steps{
                timeout(time: 1, unit: 'HOURS') {
                    input 'download kitten image?'
                }
            }
        }

        stage("Parallel download kitten image"){
            steps{
                echo "========executing A========"
            }
        }
    }
    post{
        always{
            cleanWs()
        }
    }
}

SKIP_CONFIRM_DOWNLOADがtrueではないときに実行するという条件で設定します。

pipeline23

when {
  not {
    environment name: 'SKIP_CONFIRM_DOWNLOAD', value: 'true'
  }
}
pipeline{
    agent{
        label "slave1"
    }

    parameters {
        booleanParam defaultValue: false, description: 'チェックを入れた場合ダウンロードの確認をスキップします。', name: 'SKIP_CONFIRM_DOWNLOAD'
    }

    stages{
        stage("Confirm download kitten image"){
            when {
                not {
                    environment name: 'SKIP_CONFIRM_DOWNLOAD', value: 'true'
                }
            }

            steps{
                timeout(time: 1, unit: 'HOURS') {
                    input 'download kitten image?'
                }
            }
        }

        stage("Parallel download kitten image"){
            steps{
                echo "========executing A========"
            }
        }
    }
    post{
        always{
            cleanWs()
        }
    }
}

このあたりでVSCodeの Pipeline Linter でシンタックスエラーがないか確認してみます。

※ Pipeline Linter について わからない方は前回の記事を御覧ください↓

/ja/tech/ci-cd/jenkins-pipeline-basic

linterの使い方

Ctrl + Shift + P > Validate Jenkinsfile を実行

pipeline24

Jenkinsfile successfully validated. が表示されればOKです。

pipeline25

これでConfirm download kitten imageのステージの実装が完了しました。


Parallel download kitten image

続いてParallel download kitten imageのステージを実装します。 ただ、ジョブを二つパラレルで実行しても面白くないのでScripted Pipelineの書き方を使ってみます。

やりたいこと

  1. 日付をjson形式で取得できるAPIを利用し、今日の日付を返す関数を作る。
  2. 取得した日付を用いてダウンロードする画像のファイル名を準備する。
  3. 日付が入ったファイル名をジョブのパラメータとし kitten_image_generatorkitten_image_generator_pipelineの二つを並列実行する。

1. 日付をjson形式で取得できるAPIを利用し、今日の日付を返す関数を作る。

関数の定義はpipelineブロックの外側で定義することができます。

関数はこのように定義することができます。 やっていること

変数の中身は以下のようなデータが格納されています(datetime フィールドから日付部分を抽出します)。

{
    "datetime": "2024-01-15T12:34:56.789+09:00",
    "timezone": "Asia/Tokyo",
    "day_of_week": 1,
    "day_of_year": 15
}
pipeline{
    agent{
        label "slave1"
    }

    parameters {
        booleanParam defaultValue: false, description: 'チェックを入れた場合ダウンロードの確認をスキップします。', name: 'SKIP_CONFIRM_DOWNLOAD'
    }

    stages{
        stage("Confirm download kitten image"){
            when {
                not {
                    environment name: 'SKIP_CONFIRM_DOWNLOAD', value: 'true'
                }
            }

            steps{
                timeout(time: 1, unit: 'HOURS') {
                    input 'download kitten image?'
                }
            }
        }

        stage("Parallel download kitten image"){
            steps{
                echo "========executing A========"
            }
        }
    }
    post{
        always{
            cleanWs()
        }
    }
}

def getTodayFromJsonApi(){
    sh 'curl -f -o date.json "https://worldtimeapi.org/api/timezone/Asia/Tokyo"'
    json = readJSON file: 'date.json'
    def dateTime = json.datetime.split('T')[0]
    echo "TODAY is ${dateTime}"
    return dateTime
}

2. 取得した日付を用いてダウンロードする画像のファイル名を準備する。

Decralative pipelineの中でScripted Pipelineのような書き方をする場合はscriptブロックを使います。scriptブロックの中では関数の呼び出しや、変数の変更など柔軟な書き方ができます。Groovyの知識が必要となるのは主に、このscript ブロックを使いたいときになります。

script{
    def today = getTodayFromJsonApi()
    def image = "kitten-image-${today}.jpg"
}

このようにscriptブロックの中で関数の呼び出しや変数の定義を行うことができます。

3. 日付が入ったファイル名をジョブのパラメータとし "kitten_image_generator" と "kitten_image_generator_pipeline"の二つを並列実行する。

まずはパラレル実行はparallelステップを使います。 書き方がちょっと特殊なのでいかにテンプレートを用意します。

parallel(
    "A":{
        echo '========executing A========'
    },
    "B":{
        echo '========executing B========'
    }
)

A、Bにはそれぞれわかりやすい名前を入れてください。

次に kitten_image_generator を呼び出す処理を書きます。

ジョブの呼び出しはPipelineSyntaxから作るとパラメータなどのサジェストがされるので便利です。

pipeline26

build job: 'kitten_image_generator', parameters: [string(name: 'KITTEN_IMAGE_FILE_NAME', value: image)]
script{
    def today = getTodayFromJsonApi()
    def image = "kitten-image-${today}.jpg"
    parallel(
        "kick freestyle job": {
            build job: 'kitten_image_generator', parameters: [string(name: 'KITTEN_IMAGE_FILE_NAME', value: image)]
        },
        "kick pipeline job": {
            build job: 'kitten_image_generator_pipeline', parameters: [string(name: 'KITTEN_IMAGE_FILE_NAME', value: image)]
        }
    )
}

これを Parallel download kitten image の中に記載したら完成です。

Linterでシンタックスエラーを確認するのもお忘れなく。

完成!! こちらはすべて書き終わったあとのJenkinsFile です。

pipeline {
    agent {
        label "slave1"
    }
    parameters {
    booleanParam defaultValue: false, description: 'チェックを入れた場合ダウンロードの確認をスキップします。', name: 'SKIP_CONFIRM_DOWNLOAD'
    }
    stages {
        stage("Confirm download kitten image") {
            when {
                not {
                    environment name: 'SKIP_CONFIRM_DOWNLOAD', value: 'true'
                }
            }

            steps {
                timeout(time: 1, unit: 'HOURS') {
                    input 'Download kitten image?'
                }
            }
        }
        stage("Parallel download kitten image") {
            steps {
                script{
                    def today = getTodayFromJsonApi()
                    def image = "kitten-image-${today}.jpg"
                    parallel(
                        "kick freestyle job": {
                            build job: 'kitten_image_generator', parameters: [string(name: 'KITTEN_IMAGE_FILE_NAME', value: image)]
                        },
                        "kick pipeline job": {
                            build job: 'kitten_image_generator_pipeline', parameters: [string(name: 'KITTEN_IMAGE_FILE_NAME', value: image)]
                        }
                    )
                }
            }
        }
    }
    post {
        always {
            cleanWs()
        }
    }
}

def getTodayFromJsonApi(){
    sh 'curl -f -o date.json "https://worldtimeapi.org/api/timezone/Asia/Tokyo"'
    json = readJSON file: 'date.json'
    def dateTime = json.datetime.split('T')[0]
    echo "TODAY is ${dateTime}"
    return dateTime
}

Pipelineジョブを実行する

このようなパイプラインになりました。


Jenkins Pipelineジョブを使ってみよう ~ 応用編 ~の内容は以上です。お疲れ様でした。

まとめ

いかがでしたでしょうか。今回の記事ではJenkinsPipeline の応用編としてパラレル処理と対話式で入力処理のやり方について記載しました。質問やコメントとうございましたらぜひフィードバックお願いします。

関連ページ

おすすめ書籍

[📦 商品リンク: moshimo-book-jenkins-jissen]

[📦 商品リンク: moshimo-book-jenkins-blue]

[📦 商品リンク: moshimo-book-server-infra]

もっと深く学びたい方へ

この記事で、Pipelineの応用技術(パラレル処理、対話式入力)の実装方法は理解できたと思います。

でも、「どのタイミングでパラレル化すべきか」「手動承認をどこに入れるか」という設計判断は、プロジェクトの性質によって変わります。

より深い理解のために、こちらの記事もおすすめです:

/ja/tech/ai-development/optimal-verification-cycle-ai-driven-development/

/ja/tech/sre/jenkins-efs-final-report/