Jenkinsを使った自動テスト環境を作る(前編)

継続的インテグレーション(CI)ツールとして有名なJenkinsは、ソフトウェア開発におけるテストやビルドと言った作業を自動化するツールだ。本記事ではJenkinsの最新版となるバージョン2系で正式に導入された、パイプライン機能を使ったビルド/テスト環境の構築を紹介する。

CIツールと「Jenkins」

ソフトウェア開発の現場において、そのテストはソフトウェアの設計やコーディングと同じくらい重要な過程である。近年のWebアプリケーションやスマートデバイス向けアプリケーション開発ではアプリケーションのリリース間隔が短くなっている傾向があり、そのためテストもより迅速かつ頻繁に行わなければならくなっている。そういった環境で有用なのが、継続的インテグレーション(CI)ツールだ。

CIは、元々は「ソフトウェアの開発コストを下げるためには開発の初期から頻繁にテストを行ってフィードバックを行うべき」という考え方から生まれた言葉であり、継続的にテストやビルドなどの作業を繰り返すことなどを指している。これを支援するためのツールがCIツールだ。

CIツールでは「特定のイベントをトリガーにしてあらかじめ指定しておいた処理を実行する」という機能が提供される。トリガーとして利用できるイベントはツールによって異なるが、たとえば毎日特定の時間にビルドを実行したり、バージョン管理ツールへのコミットをトリガーにしてビルドを実行する、といったことが可能だ。もちろん、手動でビルドを実行させることもできる。

CIツールはサーバーにインストールして利用するものが主流だったが、近年ではGitなどのソースコード管理システムやGitHubなどのサービスと連携して利用するクラウド型CIツールも登場している。また、XcodeやVisual Studioなどの開発環境と連携して利用できるツールもある。今回紹介する「Jenkins」はサーバーにインストールして利用するタイプのCIツールであり、オープンソース(MITライセンス)で提供されているため誰もが無償で利用できる。

Jenkinsの特徴

Jenkinsは当初は「Hudson」という名称でリリースされていたCIツールで、2011年に発生したOracleとの商標問題を受けて現在の「Jenkins」という名称に変更された。記事執筆時点の最新版は、2016年4月26日にリリースされたバージョン2.0系だ。

Jenkinsはサーバーにインストールして利用するタイプのCIツールだが、Webブラウザ経由で設定や管理を行えるGUIが提供されており、ほぼすべての操作をGUIで行えるのが特徴だ(図1)。

図1 JenkinsのGUI
図1 JenkinsのGUI

 また、Jenkinsでは「ジョブ」という単位で実行する処理を管理する。複数のジョブを1つのJenkinsで管理でき、複数のプロジェクトで1つのJenkinsを共用することも可能だ。

Jenkinsの大きな特徴として、プラグインで機能を拡張できるという点がある。また、プラグインを検索・インストールする機能もJenkins本体に用意されており、容易にプラグインを導入できる(図2)。

図2 Jenkinsのプラグインブラウザ
図2 Jenkinsのプラグインブラウザ

 JenkinsはJavaで実装されており、Java実行環境が用意されているさまざまなプラットフォームで動作する。Linux上で使われているケースが多いが、WindowsやMac OS Xでの利用も可能だ。

なお、Jenkinsではさまざまなタイプのジョブを定義できるが、本記事ではJenkinsバージョン2系で正式導入された、「パイプライン」を利用した各種処理の自動化について解説する。

Jenkinsのインストールと初期設定

JenkinsはJavaのServlet形式であるWAR形式で配布されている。記事執筆時点でのLTS(Long-Term Support、長期サポート)版は6月8日にリリースされたバージョン1.651.2、最新版(Weekly Release)は6月5日にリリースされたバージョン2.8だ。それぞれJenkins公式Webサイトの「Donwloads」リンクからダウンロードできる。

また、Red Hat系ディストリビューション向けのRPMパッケージ(最新版安定版)やopenSUSE向けパッケージ(最新版安定版)、Debian向けdebパッケージ(最新版安定版)なども公式に提供されている。

各環境でのインストール方法は上記のリンク先で説明されているが、たとえばCent OS 7でJenkinsを利用する場合、以下のように設定ファイルをダウンロードし、yumコマンドでインストールを行うだけでJenkinsが利用可能になる。

↓Jenkinsのパッケージリポジトリを追加する
# wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
↓Jenkinsが提供するパッケージの検証を行うための鍵を追加する
# rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
↓Jenkinsをインストールする
# yum install jenkins
↓Jenkinsを起動する
# systemctl start jenkins

Jenkinsはデフォルトでは8080番ポートでの待ち受けを行うので、このポートにアクセスできるようファイアウォールの設定等も行っておこう。

なお、JenkinsではDockerHub経由でDocker向けイメージも提供されている。こちらでは安定版(jenkins)および最新版(jenkinsci/jenkins)が入手可能だ。こちらについては次回記事で解説する予定だ。

Jenkinsの初期設定

インストールが完了したら、Webブラウザで「http://<Jenkinsを稼働させているホスト>:8080/」にアクセスすると、初期設定画面が表示される。ここでJenkinsへの初回アクセス時には意図しないユーザーによる設定を防ぐための「アンロック」作業が必要となる(図3)。

図3 Jenkinsへの初回アクセス時には「アンロック」作業が必要となる
図3 Jenkinsへの初回アクセス時には「アンロック」作業が必要となる

 ここで画面に表示されたファイル(ここでは「/var/lib/jenkins/secrets/initialAdminPassword」)に記載されているパスワードを入力して「Continue」をクリックするとアンロックが完了し、Jenkinsの初期設定作業を開始できる。まず行うのはインストールするプラグインの設定だ(図4)。

図4 プラグインの設定を行う「Customize Jenkins」画面
図4 プラグインの設定を行う「Customize Jenkins」画面

 ここで「Install suggested plugins」を選択すると、よく使われているプラグインが自動的にインストールされる。また、「Select plugins to install」を選択すればインストールするプラグインを選択できる(図5)。

図5 プラグインの選択画面
図5 プラグインの選択画面

 プラグインのインストールや削除は後からでも行えるので、よく分からなければ「Install suggested plugins」を選択するのが良いだろう。こちらを選択するか、「Select plugins to install」でインストールするプラグインを選択するとインストール作業が行われる(図6)。

図6 「Install suggested plugins」を選択すると、GitプラグインやPipelineプラグインといった基本的なプラグインがインストールされる
図6 「Install suggested plugins」を選択すると、GitプラグインやPipelineプラグインといった基本的なプラグインがインストールされる

 続いて、管理用のユーザーを作成する画面が表示される(図7)。ここでユーザー名やパスワード等を入力し、「Save and Finish」をクリックする。

図7 管理用ユーザーを作成するための「Create First Admin User」画面
図7 管理用ユーザーを作成するための「Create First Admin User」画面

 以上でJenkinsの初期設定は完了だ。「Jenkins in ready!」画面が表示されたら、「Start using Jenkins」をクリックするとJenkinsのトップ画面が表示される(図8、9)。

図8 設定が完了したら「Jenkis is ready!」画面が表示される
図8 設定が完了したら「Jenkis is ready!」画面が表示される
図9 Jenkinsのトップ画面
図9 Jenkinsのトップ画面

Jenkinsのパイプライン機能を使ったビルドと成果物の配布

続いては、Jenkinsを使ってソフトウェアのビルドを実行し、その成果物を配布する、という作業を実行する例を紹介しよう。

今回Jenkinsで実行する作業は、以下のようなものになる。

  • GitHub上のリポジトリからソースコードをチェックアウトする
  • makeコマンドを実行してソフトウェアをビルドし、RPMパッケージを作成する
  • 作成したRPMパッケージを指定したサーバーにSCPでコピーする

Jenkinsでは、前述のとおり「ジョブ」という単位で実行する処理を定義する。新規にジョブを作成するには、Jenkinsのトップ画面左メニューの「新規ジョブ作成」リンクをクリックする。

Jenkinsではいくつかの種類のジョブが用意されているが、今回は「Pipeline」を選択する(図10)。PipelineはJenkins 2.0以降で標準機能としてサポートされるようになった機能で、ビルドやテストなど実行する作業を「ステージ」として定義することで、容易にそれらの管理を行えるようにするものだ。また、「Enter an item name」欄には作成するジョブの名称を入力する。

図10 Jenkinsのジョブ新規作成画面
図10 Jenkinsのジョブ新規作成画面

 ジョブを新規に作成したら、続いてジョブの詳細を定義していく(図11)。

図11 ジョブの詳細設定画面
図11 ジョブの詳細設定画面

 ここではいくつかの設定項目が用意されているが、まずはデフォルトの設定でテストし、その後使い方に応じて設定を行うと良いだろう。また、そのジョブでどのような作業を実行するかは画面下の「Pipeline」エリアで指定する(図12)。

図12 「Pipeline」エリアで実行する処理を定義する
図12 「Pipeline」エリアで実行する処理を定義する

スクリプトで実行する処理を記述する

Pipelineでは、専用のスクリプトを記述することで実行する処理を定義する。このスクリプトはGroovyというプログラミング言語をベースとした独自言語(DSL)で記述する。ここで記述する内容は処理毎に異なるが、おおむね下記のようなものになる。

node {
  stage '<ステージ名>'
    <そのステージで実行する処理>
    
  stage '<ステージ名>'
    <そのステージで実行する処理>
    
}

Jenkinsのpipelineでは、「node」(ノード)という単位で処理を実行する。1つのノードには「workspace」と呼ばれる一つのディレクトリが割り当てられ、この中で各種処理が実行される。最初の「node {」という行は、このノードを作成することを宣言するものだ。

また、1つのスクリプト内では複数の「stage」(ステージ)が定義できる。ステージは実行する処理をその内容ごとに分割して記述するためのもので、これによって記述や実行時の結果をを分かりやすくすることができる。今回は先に述べた「チェックアウト」「ビルド」「scpでアップロード」という3つの作業を行うので、最低でもこの3つのステージを作成することとなる。

それでは、まずチェックアウトを行うステージを記述してみよう。Jenkinsではよく使われる処理のコードを自動生成する機能が用意されており、これを利用することで簡単にスクリプトを作成することができる。コードの自動生成機能を利用するには、画面下の「Pipeline Syntax」をクリックする。すると新しいタブで「Snippet Generator」画面が表示される(図13)。

図13 「Snippet Generator」画面
図13 「Snippet Generator」画面

 この画面では「Sample Step」で実行したい処理を選択すると、その処理で設定可能なパラメータが表示される。それらをGUIで設定して「Generate Groovy」ボタンをクリックすると、コードが画面下に表示される仕組みだ。たとえばソースコードのチェックアウトを行うコードが必要な場合、「checkout: General SCM」を選択する。この場合、設定できるパラメータとしては使用するSCMやリポジトリのURL、チェックアウトするブランチなどが用意されている。今回はGitHubの公開リポジトリを利用しているためURLの入力だけで良いが、アクセスに認証が必要なリポジトリの場合は認証情報を適切に入力しておく必要がある。

これらを設定して「Generate Groovy」ボタンをクリックすると、その下のテキストボックスにコードが表示される(図14)。

図14 「Generate Groovy」ボタンをクリックするとコードが生成される
図14 「Generate Groovy」ボタンをクリックするとコードが生成される

 今回生成されたコードは以下のようになっている。

checkout([$class: 'GitSCM', branches: [[name: '*/release']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/hylom/grrreader']]])

これをコピーし、ジョブの設定画面に戻ってPipelineエリアの「Script」部分にペーストする(図15)。また、このコードにはノードやステージの定義は含まれていないので、適宜追加しておく。また、見やすいように適当にインデントを入れておくと良いだろう。現時点でこれらの作業を行ったコードは以下のようになった。

node {
    stage 'Checkout'
    checkout([$class: 'GitSCM',
        branches: [[name: '*/release']],
        doGenerateSubmoduleConfigurations: false,
        extensions: [],
        submoduleCfg: [],
        userRemoteConfigs: [[url: 'https://github.com/hylom/grrreader']]
    ])
}
図15 チェックアウトを実行するスクリプト
図15 チェックアウトを実行するスクリプト

ジョブの実行

続いて、このスクリプトが正常に動作するかどうかを実際に動かしてテストを行ってみよう。まず「保存」をクリックして設定画面を閉じ、ジョブのトップ画面に戻る(図16)。

図16 ジョブのトップ画面
図16 ジョブのトップ画面

 ここで画面左メニュー内にある「ビルド実行」をクリックすると、スクリプトで定義した処理が実行される。ビルドの途中経過や実行結果は「Stage View」エリア内に表示される(図17)。

図17 ビルドが実行され、進捗や結果が「Stage View」エリア内に表示される
図17 ビルドが実行され、進捗や結果が「Stage View」エリア内に表示される

 Stage Viewではステージごとに実行にかかった時間や結果を確認できるようになっており、マウスポインタを確認したいステージ(ここでは「Checkout」)に乗せるとその結果が表示される(図18)。ここでは「Success」と表示されており、処理が正常に実行されたことを確認できる。

図18 stageにマウスポインタを乗せるとその結果が表示される
図18 stageにマウスポインタを乗せるとその結果が表示される

 また、ここで「Logs」をクリックすると実行ログが表示される(図19)。

図19 実行された処理の内容をログとして確認できる
図19 実行された処理の内容をログとして確認できる

シェル経由でコマンドを実行する

正しく実行できることが確認できたら、続けて残りの「ビルド」および「アップロード」を行うstageについても同様に記述していく。

今回サンプルに使用したソフトウェアでは、「dist」というディレクトリ内にMakefileが用意されており、そのディレクトリ内でmakeコマンドを実行することでパッケージを生成できるようになっている。スクリプト内で任意のコマンドを実行するには「sh」というコマンドを使用する。このコマンドは引数として与えた文字列をシェル経由で実行するというものだ。今回はこれを利用してmakeコマンドを実行する。また、makeコマンドを実行する前に、不要なファイルをクリーンする「Build-prep」ステージも用意した。これらを追加したスクリプトは下記のようになる。

node {
    stage 'Checkout'
    checkout([$class: 'GitSCM',
        branches: [[name: '*/release']],
        doGenerateSubmoduleConfigurations: false,
        extensions: [],
        submoduleCfg: [],
        userRemoteConfigs: [[url: 'https://github.com/hylom/grrreader']]
    ])
    
    stage 'Build-prep'
    sh "cd dist && make clean"
    
    stage 'Build'
    sh "cd dist && make"
}

プラグインを導入してSCPでファイルをアップロードする

最後の「SCPで成果物をアップロード」するというステージはやや面倒だ。scpコマンドでアップロードを行う際にパスワードやパスフレーズの入力が必要となるためだ。これらをスクリプト内に直で書き込んでおくことも可能ではあるが、「SSH Agent Plugin」というプラグインを利用することで、SSHで使用するアカウントの管理をJenkinsに任せることができるようになる。今回はこれを利用してみよう。

SSH Agent Pluginは標準ではインストールされないので、まずはこのプラグインをインストールする必要がある。プラグインのインストールは、Jenkinsトップページから「Jenkinsの管理」→「プラグインの管理」を順にクリックして表示される「プラグインマネージャー」で行える(図20

図20 「Jenkinsの管理」をクリックし、続いて「プラグインの管理」をクリックする
図20 「Jenkinsの管理」をクリックし、続いて「プラグインの管理」をクリックする

 プラグインマネージャで「利用可能」タブをクリックして選択し、続いて「フィルター」欄に「ssh」と入力すると、SSHに関連するプラグインが表示される。ここで「SSH Agent Plugin」にチェックを入れ、「ダウンロードして再起動後にインストール」をクリックするとインストールが行われる(図21)。

図21 「利用可能」タブでプラグインを選択してインストールできる
図21 「利用可能」タブでプラグインを選択してインストールできる

SSH向けの認証情報を追加する

Jenkinsには各種認証に使用する情報を一元管理する機構があり、今回はこれを利用してSSH Agent Pluginに認証情報を渡すことにする。まずJenkinsのインストール時に自動的に作成される「jenkins」ユーザーのホームディレクトリ(通常は/var/lib/jenkins)内の.sshディレクトリ(~/.ssh)内にSSH公開鍵および秘密鍵を作成しておく。

# sudo -u jenkins ssh-keygen
  鍵の作成に必要な情報を入力する
  
  

Jenkinsトップページ右メニュー内の「認証情報」→「System」→「グローバルドメイン」を順にクリックして「グローバルドメイン」画面を開き、ここで「認証情報の追加」をクリックすることで認証情報を追加できる(図22)。

図22 「グローバルドメイン」画面で認証情報を追加する
図22 「グローバルドメイン」画面で認証情報を追加する

 認証情報の追加画面では、「種類」として「SSHユーザー名と秘密鍵」を選択する。ここでは秘密鍵として先ほど作成したものを利用するので、「jenkinsマスター上の~/.sshから」を選択する。続いてパスフレーズや適切なID、説明などを入力し、最後に「保存」をクリックする(図23)。

図23 種類として「SSHユーザー名と秘密鍵」を選択して認証情報を追加する
図23 種類として「SSHユーザー名と秘密鍵」を選択して認証情報を追加する

SSH Agent Pluginを利用する

SSH Agent Pluginをインストールすると、Snippet Generatorで新たに「sshagent: SSH Agent」が利用できるようになる(図24)。ここではパラメータとして使用する認証情報を選択できるので、先ほど作成した認証情報を選択して「Generate Groovy」をクリックし、コードを生成しよう。

図24 Snippet Generatorに「sshagent: SSH Agent」が追加される
図24 Snippet Generatorに「sshagent: SSH Agent」が追加される

 ここで生成される「sshagent([‘jenkins’]) {}」というコードでは、中括弧で囲まれた部分でSSH Agentが有効になり、SSHやSCPを利用した場合に自動的に指定した認証情報が使われるようになる。これを利用して以下のように記述することで、指定した認証情報を使ってSCPでファイルをコピーできる。

sshagent(['jenkins']) {
  sh 'scp <コピー元ファイル> <ユーザー>@<コピー先ホスト>:<コピー先>'
}

これを追加したスクリプトは下記のようになる。

node {
    stage 'Checkout'
    checkout([$class: 'GitSCM',
        branches: [[name: '*/release']],
        doGenerateSubmoduleConfigurations: false,
        extensions: [],
        submoduleCfg: [],
        userRemoteConfigs: [[url: 'https://github.com/hylom/grrreader']]
    ])
    
    stage 'Build-prep'
    sh "cd dist && make clean"
    
    stage 'Build'
    sh "cd dist && make"
    
    stage 'upload'
    sshagent(['jenkins']) {
        sh 'scp dist/*.rpm foo@exmaple.net:~/rpms/'
    }
}

最後にSCPでファイルをアップロードできるよう、jenkinsユーザーの公開鍵(~/.ssh/id_rsa.pub)をアップロード先マシンの対応するユーザーの「~/.ssh/authorized_keys」ファイルに追加しておこう。

この状態でビルドを実行すると、次のように4つのステージが順に実行されるようになる(図25)。

図25 「Checkout」、「build-prep」、「Build」、「upload」の4つのステップが実行される
図25 「Checkout」、「build-prep」、「Build」、「upload」の4つのステップが実行される

トリガ設定を利用する

さて、以上でJenkinsを使ってソフトウェアのビルド作業を自動化できたわけだが、ここまでの設定ではビルドを実行する際に手動で「ビルド実行」をクリックしなければならなかった。Jenkinsでは自動的にビルドを実行させるための設定が用意されており、これらを利用することで設定したタイミングで自動的にビルドを実行させることができる。

ビルドを自動実行させるには、ジョブの設定内にある「ビルド・トリガ」で設定を行えば良い(図26)。

図26 ジョブの設定内「ビルド・トリガ」で自動的にビルドを実行させる設定を行える
図26 ジョブの設定内「ビルド・トリガ」で自動的にビルドを実行させる設定を行える

 Jenkinsの初期設定で「Install suggested plugins」を選択した場合、ここには表1の5つが表示されるはずだ。

表1 「Install suggested plugins」を選択した場合に利用できるビルド・トリガの設定
設定名 説明
リモートからビルド (例: スクリプトから) 「<JenkinsをインストールしたサーバーのURL>/job/<ジョブ名>/build?token=<設定したトークン>」などのURLにアクセスが発生するとビルドを開始する
Build when a change is pushed to GitHub GitHubが提供するフック機能を利用してビルドを開始させる
SCMをポーリング 定期的にリポジトリにアクセスし、変更があったらビルドを開始する
他プロジェクトの後にビルド 指定したほかのジョブが完了したらビルドを開始する
定期的に実行 指定した曜日や時刻など、あらかじめ指定した感覚でビルドを自動実行する

「リモートからビルド」はやや分かりにくいかもしれないが、ビルドを実行したいタイミングでcurlやwgetのようなコマンドラインクライアントから指定したURLにアクセスさせることでビルドを開始させる、といった使い方ができる。

今回はリポジトリとしてGitHubを使っているので、「Build when a change is pushed to GitHub」を利用し、GitHub上のリポジトリに変更が加えられたらビルドを実行するように設定を行う方法を紹介する。この場合、インターネットからJenkinsがインストールされたサーバーにアクセスできるようになっている必要がある点には注意したい。

Jenkins側で必要な設定は、上記の「ビルド・トリガ」内で「Build when a change is pushed to GitHub」にチェックを入れ、「保存」をクリックして設定を保存しておくだけだ(図27)。

図27 「ビルド・トリガ」設定で「Build when a change is pushed to GitHub」にチェックを入れる
図27 「ビルド・トリガ」設定で「Build when a change is pushed to GitHub」にチェックを入れる

 また、GitHub側での設定も必要だ。まず、対象とするリポジトリの「Settings」画面を開き、「Webhooks & services」をクリックする。続いて表示される画面内にある「Services」横の「Add service」をクリックし、「Jenkins (GitHub plugin)」を選択する(図28)。

図28 GitHubの「Settings」画面内の「Webhooks & services」で設定を行う
図28 GitHubの「Settings」画面内の「Webhooks & services」で設定を行う

 するとURL等の設定を行う画面が表示されるので、「Jenkins hook url」内に「http://<Jenkinsが稼動しているURL>/github-webhook/」と入力する(図29)。最後に「Add service」をクリックして設定を保存すれば、設定完了だ。

図29 「Jenkins hook url」でJenkinsが稼動しているサーバーのURLを指定する
図29 「Jenkins hook url」でJenkinsが稼動しているサーバーのURLを指定する

 これでGitHubの対象リポジトリにプッシュやコミットが行われると、そのタイミングでジョブが実行されるようになる。

Dockerと組み合わせて利用することでより柔軟な設定が可能に

ここまでは、Jenkinsがインストールされたサーバー内でビルドなどの各種ジョブを実行することを前提に説明を行っていた。これは分かりやすいいっぽう、Jenkinsを動かすサーバー上にソフトウェアをビルド・テストする環境を構築しておかなければならないという問題がある。そのため、異なるプラットフォーム向けのソフトウェアを同一のサーバー上でJenkinsを使ってビルド/テストしたい、といったケースにはこれでは対応できない。この場合、JenkinsとDockerを連携させて利用することで、より容易に異なる環境向けのビルドやテストが実行できるようになる。

後編では、こういったJenkinsとDockerの連携について紹介する。