宣言型 vs スクリプト型: Jenkins パイプライン構文の選択

宣言型とスクリプト型の Jenkins パイプライン構文を比較し、それぞれの使用例と実践的なガイダンスを提供します。

宣言型 vs スクリプト型: Jenkins パイプライン構文の選択

Jenkins パイプラインでは、Jenkinsfile を記述する方法として宣言型とスクリプト型の2つがあります。どちらもアプリケーションのビルド、テスト、デプロイが可能ですが、可読性、検証、Groovy の柔軟性において異なるトレードオフがあります。

チームが新しいパイプラインの構文を選択する場合、6か月後に維持する必要があるワークフローから始めてください。宣言型は通常、読みやすくレビューしやすいです。スクリプト型は、パイプラインが本当に動的な動作を必要とする場合に、より直接的な Groovy 制御を提供します。

Jenkins パイプラインの理解

構文の詳細に入る前に、Jenkins パイプラインとは何かを簡単に再確認しましょう。パイプラインは、Jenkins への継続的デリバリーパイプラインの実装と統合をサポートするプラグインのスイートです。これは基本的に、コードコミットからデプロイまでのソフトウェアデリバリープロセス全体を定義する自動化されたステップのシーケンスです。これらのステップは Jenkinsfile で定義され、通常は Groovy で記述され、複雑なビルド、テスト、デプロイメントシナリオを管理する強力な方法を提供します。

Jenkins Pipeline as Code は、いくつかの重要な利点を提供します:

  • バージョン管理: Jenkinsfile はアプリケーションコードと同様にソース管理に保存され、バージョン管理、監査、コラボレーションが可能になります。
  • 再現性: 異なる環境や実行全体でデリバリープロセスの一貫した実行を保証します。
  • 可視性: デリバリープロセス全体の明確で理解しやすいビューを提供します。
  • 耐久性: パイプラインは Jenkins マスターの再起動後も存続できます。
  • 拡張性: 共有ライブラリを通じて、複雑なロジックを抽象化して再利用できます。

宣言型パイプライン

宣言型パイプラインは、パイプラインの作成と理解を容易にするために設計された、より新しい意見のある構文です。事前定義されたブロックを使用した構造化されたアプローチを提供し、チームが Jenkinsfile の一貫性を保つのに役立ちます。

特性と構文

宣言型パイプラインは、pipelineagentstagesstepspostenvironmentparametersoptionstriggerstoolsinputwhen などのトップレベルブロックによって定義された特定の構造を強制します。この構造は、ワークフローのさまざまな部分に明確な境界を提供することで、パイプライン定義を簡素化します。

以下は宣言型パイプラインの基本的な構造です:

pipeline {
    agent any // または 'label'、'docker' など

    stages {
        stage('Build') {
            steps {
                echo 'アプリケーションをビルドしています...'
                sh 'mvn clean install'
            }
        }
        stage('Test') {
            steps {
                echo 'テストを実行しています...'
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                echo '本番環境にデプロイしています...'
                script {
                    // どうしても必要な場合、スクリプト型のようなロジックをここに配置できます
                    // 例: 共有ライブラリ関数の呼び出し
                    // mySharedLibrary.deployApplication()
                }
            }
        }
    }

    post {
        always {
            echo 'パイプラインが終了しました。'
        }
        success {
            echo 'パイプラインが成功しました!'
        }
        failure {
            echo 'パイプラインが失敗しました。'
        }
    }
}

宣言型パイプラインの利点

  • シンプルさと可読性: 事前定義された構造により、専門家でなくてもパイプラインを簡単に読み、理解できます。設定ファイルのように感じられます。
  • 構造化されたアプローチ: パイプライン全体でベストプラクティスと一貫性を強制し、学習曲線とエラーの可能性を減らします。
  • 組み込み機能: 条件付き実行 (when)、ビルド後アクション (post)、並列ステージ実行、パイプラインフロー管理のためのさまざまなオプションなど、一般的な CI/CD パターン向けの豊富な組み込み機能を提供します。
  • 学習が容易: 広範な Groovy 知識がなくても、開発者はその意見のある構文のおかげですぐに使い始めることができます。
  • 検証: 宣言型はルールが厳格であるため、Jenkins は実行前により多くの構造を検証できます。

宣言型パイプラインの制限

  • 柔軟性が低い: 事前定義されたブロックの外でカスタム Groovy ロジックを必要とする高度に複雑または動的なワークフローには制限があります。
  • 直接的な Groovy アクセスが限定的: script ブロックを使用してスクリプト型パイプライン構文を注入できますが、過度に使用すると宣言型構文の利点が損なわれ、パイプラインが読みにくくなる可能性があります。

宣言型パイプラインを使用する場合

宣言型パイプラインは、ほとんどの一般的な CI/CD シナリオ で推奨される選択肢です。以下の場合に最適です:

  • Jenkins または Pipeline as Code に不慣れなチーム。
  • 単純または中程度の複雑さのビルド、テスト、デプロイプロセスを持つプロジェクト。
  • 多くのパイプライン全体で一貫性と保守性を確保する場合。
  • 並列実行、条件付きステージ、通知などの一般的なパターンに Jenkins の組み込み機能を活用する場合。

スクリプト型パイプライン

スクリプト型パイプラインは、Groovy プログラミング言語の上に直接構築されており、Jenkins Pipeline as Code の元々の構文でした。最大の柔軟性とパワーを提供し、開発者が高度にカスタマイズされた動的な自動化フローを実装できるようにします。

特性と構文

スクリプト型パイプラインは、従来の Groovy スクリプトと同様に、上から下へ順次実行されます。Groovy の完全な構文を使用し、nodestagecheckoutshgit などのメソッドを通じて Jenkins パイプライン DSL(ドメイン固有言語)を活用します。これにより、Jenkins API と Groovy 言語の完全なパワーに直接アクセスできます。

以下はスクリプト型パイプラインの基本的な構造です:

node('my-agent-label') {
    stage('Prepare') {
        echo 'ワークスペースを準備しています...'
        checkout scm
    }

    stage('Build') {
        echo 'アプリケーションをビルドしています...'
        try {
            sh 'mvn clean install'
        } catch (err) {
            echo "ビルドに失敗しました: ${err}"
            // カスタムエラーハンドリング
            currentBuild.result = 'FAILURE'
            throw err
        }
    }

    stage('Test') {
        echo 'テストを実行しています...'
        // テストスイートを動的に決定
        def testSuites = sh(script: 'find tests -name "*.test"', returnStdout: true).trim().split('\n')
        if (testSuites.isEmpty()) {
            echo 'テストが見つかりませんでした。'
        } else {
            for (suite in testSuites) {
                echo "テストスイートを実行中: ${suite}"
                sh "./run-test.sh ${suite}"
            }
        }
    }

    stage('Deploy') {
        // 複雑な条件付きロジック
        if (env.BRANCH_NAME == 'main' && currentBuild.currentResult == 'SUCCESS') {
            echo '本番環境にデプロイしています...'
            sh './deploy-prod.sh'
        } else if (env.BRANCH_NAME == 'develop') {
            echo 'ステージング環境にデプロイしています...'
            sh './deploy-staging.sh'
        } else {
            echo 'このブランチのデプロイはありません。'
        }
    }

    // ビルド後アクションは try-finally ブロックまたはカスタムロジックで実装可能
    // 例: 通知の送信
    if (currentBuild.result == 'SUCCESS') {
        echo 'パイプラインが正常に完了しました!'
        // notifySuccess()
    } else {
        echo 'パイプラインが失敗しました。'
        // notifyFailure()
    }
}

スクリプト型パイプラインの利点

  • 最大の柔軟性: Groovy の完全なパワーを提供し、高度に複雑で動的なロジック、カスタムループ、エラーハンドリング、データ操作が可能です。
  • 直接的な Jenkins API アクセス: Jenkins および Groovy API の使用の余地が広がりますが、一部の操作はプラグイン、権限、スクリプトセキュリティサンドボックスに依存します。
  • 動的な動作: 動的なエージェント割り当て、実行時条件に基づく並列実行、または高度なリソース管理を必要とするワークフローに最適です。
  • 拡張性: 宣言型パイプライン向けに再利用可能な複雑なロジックをカプセル化する洗練された共有ライブラリを作成するのに優れています。

スクリプト型パイプラインの制限

  • 学習曲線が急: Groovy のしっかりとした理解が必要であり、言語に不慣れなチームにとっては障壁となります。
  • 意見が少ない: 厳格な構造がないため、パイプラインが一貫性を欠き、プロジェクトや開発者間で読みにくく、保守が難しくなる可能性があります。
  • エラーが発生しやすい: Groovy の柔軟性はコーディングエラーの機会を増やし、宣言型と比較して組み込みの検証が少なくなります。
  • 可読性の課題: 複雑なスクリプト型パイプラインはすぐに解析や理解が難しくなり、コラボレーションやトラブルシューティングを妨げます。
  • パイプライン固有の構文が少ない: 多くの一般的な CI/CD パターン(post アクションや when 条件など)は、Groovy の構造(例: try-catch-finallyif 文)を使用して手動で実装する必要があります。

宣言型 vs スクリプト型: 比較表

違いをまとめるために、比較表を示します:

機能 宣言型パイプライン スクリプト型パイプライン
構文構造 意見のある、事前定義されたトップレベルブロック。 柔軟な、Groovy ベースの順次実行。
学習曲線 初心者にとって簡単、Groovy 知識はあまり必要ない。 急、Groovy の専門知識が必要。
可読性 構造化されたブロックと明確な構文により高い。 複雑なスクリプトでは低くなる可能性あり、開発者のスタイルに依存。
柔軟性 事前定義された構造に制限; Groovy 用の script ブロック。 無制限、Groovy の完全なパワー。
組み込み機能 一般的な CI/CD パターン向けの豊富なセット(postwhenparallel)。 Groovy 構造を使用した手動実装が必要。
エラーハンドリング グローバルまたはステージ固有のアクション用の post ブロック。 手動の try-catch-finally ブロック。
拡張性 複雑な Groovy ロジックに共有ライブラリを活用。 複雑な Groovy ロジックを直接記述。多くの場合、共有ライブラリを作成
エージェント制御 グローバル agent またはステージレベルの agent node ブロック、任意の場所でエージェントを定義可能。
使用例 標準的な CI/CD ワークフロー、単純から中程度の複雑さ。 高度に動的で複雑なカスタムワークフロー; 共有ライブラリ開発。
JSON/YAML 感覚 設定言語に近い。 純粋なプログラミング言語。

適切な構文の選択

宣言型パイプラインとスクリプト型パイプラインのどちらを選択するか決める際は、以下の要素を考慮してください:

  1. チームの Groovy 専門知識: チームに強力な Groovy スキルがない場合、宣言型は学習曲線がはるかに浅く、迅速な採用を促進します。
  2. ワークフローの複雑さ: ほとんどの標準的な CI/CD ワークフロー(ビルド、テスト、デプロイ)では、宣言型は十分に適切であり、可読性と組み込み機能により優れていることがよくあります。高度に動的、条件付き、またはカスタムリソース集約型のタスクには、スクリプト型が必要になる場合があります。
  3. 保守性と可読性: 宣言型パイプラインは一般的に読みやすく保守しやすく、特に多くのパイプラインと開発者がいる大規模組織ではその傾向が強いです。この一貫性により認知負荷が軽減されます。
  4. 既存のパイプラインエコシステム: 既存のスクリプト型パイプラインやスクリプト型構文で構築された堅牢な共有ライブラリセットがある場合、一貫性のためにそれを使い続けるか、適切な場合に段階的に宣言型に移行することができます。
  5. 将来の成長: 宣言型パイプラインは通常十分であり、共有ライブラリを通じてカスタムロジックで拡張できます。共有ライブラリ自体は通常スクリプト型 Groovy で記述されます。これは多くの場合、最良のハイブリッドアプローチです。

意思決定のベストプラクティス

  • 宣言型から始める: 新しいパイプラインでは、デフォルトで宣言型を使用します。これは CI/CD ユースケースの大部分をカバーし、一貫性と可読性を促進します。
  • 共有ライブラリを活用する: 宣言型パイプラインで繰り返しまたは複雑なロジックに遭遇した場合、そのロジックを共有ライブラリに抽象化します。共有ライブラリは主にスクリプト型 Groovy で記述されるため、宣言型の構造とスクリプト型の柔軟性の両方を組み合わせることができます。
  • 宣言型の過剰なスクリプト化を避ける: 宣言型では script ブロックが許可されていますが、これらを最小限に抑えるようにしてください。script ブロックが大きくなりすぎたり複雑になったりした場合、そのロジックを共有ライブラリ関数に移動する必要がある強い兆候です。
  • 移行を検討する: レガシーのスクリプト型パイプラインが保守困難になっている場合、複雑な部分を共有ライブラリに移行しながら、宣言型構文にリファクタリングすることを検討してください。

まとめ

新しい Jenkinsfile では、具体的な理由がない限り宣言型を選択してください。繰り返しまたは複雑な Groovy は共有ライブラリに移動し、完全なスクリプト型パイプラインは宣言型のステージ、条件、ステップにきれいに収まらないワークフローにのみ予約してください。