LoginSignup
106
105

More than 3 years have passed since last update.

Jenkinsfileの書き方 (Jenkins Pipeline)

Last updated at Posted at 2018-11-10

まずはじめに

つい先日、はじめてjenkins pipelineのためのJenkinsfileを作成し、PHPアプリケーションのワンクリックデプロイを実現しました。今回は振り返りの意味も込めて、その際に事前に知っておくと良かった点をまとめていきます。これから、Pipelineを構築したい方は参考にしてみてください。

環境は以下の通りです。
CentOS: 7.3.1611
Jenkins Version: 2.73.2

私の場合は、Pipelineを作成する前にRubyで書かれたCapistranoというデプロイツールでリリース用のスクリプトをすでに構築・運用済みでした。慣れてしまえば十分な環境でしたが、毎回コマンドを叩く手間と増加するチームメンバーへの共有コストを考えるとよりシンプルな手順が必要だと感じてきたため、Jenkinsへの移行を決断しました。

移行プロセスとして、ゼロからCapistranoのタスクを全てPipeline上の各ステージで書き直すことも考えましたが、Capistrano側の修正でそのまま再利用可能であると判断したためPipeline上でCapistranoの各タスクを実行する方針で進めました。

私のケースと同様にすでに別の方法である程度リリースを自動化されているのであれば、Pipelineの構築はそれほど難しい作業ではないので、ぜひ試してみることをオススメします。

ポイント

それでは早速ポイントに入っていきましょう。

1. Declarative vs Scripted 2種類のSyntaxの違い

(公式)Declarative versus Scripted Pipeline syntax

こちらのドキュメントが参考になります。
要約すると、Jenkinsfileの記法はDeclarative記法とScripted記法の2種類あり、Declarative記法が後発です。Declarative記法の方がより柔軟でかつ読み書きしやすい記法です。
Jenkins Pipelineの投稿記事をググると記事によって記法が様々ですので、まずはじめにこの点を押さえて記法の違いを認識しておくことが大事です。

簡単な見分け方は開始タグを確認することです。

開始タグ 記法
pipeline Declarative
node Scripted

ちなみに私はDeclarative記法で記述したので、次のポイントからはその点にご注意ください。

2. 環境変数の設定方法

(公式)Environment

こちらの公式ドキュメントに記載の通り、環境変数はpipeline直下またはstageディレクティブ内でのみ定義が可能です。#8. Script実行結果をコマンドに渡す方法 で紹介しますが、実行コマンドの結果を保存する際にも活用可能です。

pipeline {
    agent any
    environment {
        work_dir='/home/jenkins/work'
        bundle='/home/jenkins/bin/bundle'
        deploy_dir='/home/jenkins/deploy'
    }
    stages { // 私の場合、特にステージごとの環境変数は必要なかったです。
        stage('Check Environment') {
            environment { 
                LOCAL_VAR='/home/jenkins/target_dir' 
            }
            steps {
                sh 'printenv'
            }
        }
    }
}

3. ビルド時のユーザー入力

(公式)parameters

こちらのparametersを設定すると、Jenkinsの「Build Now」が「Build with Parameters」へと変化し、ビルド開始時にユーザー入力を受け付けるようになります。私の場合、リリース用のgit情報とデプロイ対象サーバーを選択できるようにしました。

pipeline {
    agent any
    environment {
        work_dir='/home/jenkins/work'
        bundle='/home/jenkins/bin/bundle'
        deploy_dir='/home/jenkins/deploy'
    }
    parameters {
        // 公式ドキュメントではchoiceの場合、choices: ['one', 'two', 'three']
        // のようにかけるそうですが、なぜか私の環境ではsyntax errorがでてしまったため、
        // 以下のように\n改行コードを入れることでセレクトボックス入力が可能になりました。
        choice(name: 'BRANCH_OR_TAG', choices: 'Branch\nTag\n', description: 'Select Checkout Type')
        string(name: 'CHECKOUT_POINT', defaultValue: 'develop / v1.0.0', description: 'Input Branch / Tag Name')
        choice(name: 'SERVERS', choices: 'hogehoge.com\nfugafuga.com\n', description: 'Select Deploying Servers')
    }
    stages {
        stage('Check Environment') {
            environment { 
                LOCAL_VAR='/home/jenkins/target_dir' 
            }
            steps {
                // 公式ドキュメントより
                // このように記述することで各ステージの途中で入力を受け付けるようになる
                input {
                    message "Should we continue?"
                    ok "Yes, we should."
                    submitter "alice,bob"
                    parameters {
                        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
                    }
                }
                // 以下のようにsteps内からparametersの値へアクセス可能
                // 変数展開のためにダブルクオート
                sh """
                    printenv
                    echo ${params.BRANCH_OR_TAG}
                    echo ${params.CHECKOUT_POINT}
                    echo ${params.SERVERS}
                """
            }
        }
    }
}

4. 各Section/Directiveの階層関係

(公式)Pipeline Syntax

書き進めていく際にSection/Directiveの階層構造を認識していくことが大事です。Declarative記法の場合、割と厳密な階層構造が指定されているので雰囲気で記述していくと頻繁にsyntax errorが発生します。

Section定義位置・子要素早見表

Section top-level stage直下 指定可能な
子section
指定可能な
子directive
agent
stages stage
post
steps

Directive定義位置・子要素早見表

Directive top-level stage
直下
stages
直下
steps
直下
指定可能な
子section
指定可能な
子directive
environment
options
指定可能なオプションは限定
parameters
triggers
stage steps
stages
parallel
tools
input
when
parallel stage
script stage

5. ディレクトリ移動してコマンドを実行する方法

(公式)Pipeline: Basic Steps

#4で紹介した基本的なDeclarative記法のDirectiveの機能に加え、pluginという形で様々機能が提供されています。その中の一つdir()を使うことで、ディレクトリを移動してからコマンドを叩くことが可能になります。dir()などのプラグインはstepsセクション内でのみ使用可能です。

pipeline {
    agent any
    environment {
        work_dir='/home/jenkins/work'
        bundle='/home/jenkins/bin/bundle'
        deploy_dir='/home/jenkins/deploy'
    }
    stage('Change Directory And Echo') {
        steps {
            dir(work_dir) {
                 sh "echo ${deploy_dir}"
            }
        }
    }
}

6. 並列処理の方法

(公式) Parallel

pipeline {
    agent any
    stages {
        stage('Parallel Stage') {
            parallel {
                stage('Branch A') {
                    agent {
                        label "for-branch-a"
                    }
                    steps {
                        echo "On Branch A"
                    }
                }
                stage('Branch B') {
                    agent {
                        label "for-branch-b"
                    }
                    steps {
                        echo "On Branch B"
                    }
                }
                stage('Branch C') {
                    agent {
                        label "for-branch-c"
                    }
                    stages {
                        stage('Nested 1') {
                            steps {
                                echo "In stage Nested 1 within Branch C"
                            }
                        }
                        stage('Nested 2') {
                            steps {
                                echo "In stage Nested 2 within Branch C"
                            }
                        }
                    }
                }
            }
        }
    }
}

上の例は公式ドキュメントからの引用です。#4で紹介した通り、parallelstage直下にのみ指定可能です。私は感覚としてstages直下にそのままparallelを指定し複数のstageを実行しようと試みたのですが、うまくいきませんでした。

平行処理をする場合は、一度平行処理全体のstageを作成し、その配下にparallel+複数のstageを指定しなければいけません。

7. Bashでコマンド実行する方法

こちらのポイントに関しては、私自身もベストプラクティスを模索中です。
私の場合、すでにCapistranoでデプロイしていたため、サーバ上の.bashrcに環境変数がある程度指定してあり、かつCapistranoタスクがbashに依存していました。

理想的にはJenkinsfile一つで必要な環境変数の準備が完結することが望ましいですが、今回はbashの環境変数を使用するために以下のようにしました。

pipeline {
    agent any
    environment {
        work_dir='/home/jenkins/work'
        git_dir='/home/jenkins/repository'
        cap_dir='/home/jenkins/capistrano'
        bundle='/home/jenkins/bin/bundle'
        deploy_dir='/home/jenkins/deploy'
    }
    stage('Change Directory And Echo') {
        steps {
            dir(work_dir) {
                 sh """
                     echo 'source ~/.bashrc' > composer.sh
                     echo 'cd ${cap_dir}' >> composer.sh
                     echo 'GIT_DIR=${git_dir} ${bundle} exec cap production composer:install' >> composer.sh
                     bash ./composer.sh
                     rm ./composer.sh
                 """
            }
        }
    }
}

8. Script実行結果をコマンドに渡す方法

最後に簡単なロジックを挟み、その結果をコマンド実行時に利用する方法です。
ポイントは以下の3点です。

  1. environmentディレクティブ内に変数を定義(top-level or stage内)
  2. stepsセクション直下のscriptディレクティブ内にgroovyで処理を記述
  3. 変数展開して環境変数をsh内で展開して使用

以下の例は、ビルド開始時にユーザー入力からCapistranoへ渡すrolesの指定を行う場面です。

pipeline {
    agent any
    environment {
        work_dir='/home/jenkins/work'
        git_dir='/home/jenkins/repository'
        cap_dir='/home/jenkins/capistrano'
        bundle='/home/jenkins/bin/bundle'
        deploy_dir='/home/jenkins/deploy'
        // ポイント1
        role=''
    }
    parameters {
        choice(name: 'ROLE', choices: 'first\nsecondthird\n', description: 'Server Cluster')
    }
    stage('Change Directory And Echo') {
        steps {
            // ポイント2
            script {
                if (params.ROLE =~ /first/) {
                    role = "first"
                } else if(params.ROLE =~ /second/) {
                    role = "second"
                } else if(params.ROLE =~ /third/) {
                    role = "third"
                }
            }
            dir(work_dir) {
                 // ポイント3
                 sh """
                     echo 'source ~/.bashrc' > composer.sh
                     echo 'cd ${cap_dir}' >> composer.sh
                     echo 'GIT_DIR=${git_dir} ${bundle} exec cap production --roles=${role} composer:install' >> composer.sh
                     bash ./composer.sh
                     rm ./composer.sh
                 """
            }
        }
    }
}
106
105
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
106
105