まずはじめに
つい先日、はじめて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. ディレクトリ移動してコマンドを実行する方法
#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で紹介した通り、parallel
はstage
直下にのみ指定可能です。私は感覚として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点です。
-
environment
ディレクティブ内に変数を定義(top-level orstage
内) -
steps
セクション直下のscript
ディレクティブ内にgroovyで処理を記述 - 変数展開して環境変数を
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
"""
}
}
}
}