GitLab と Jenkins を連携する (1)

# はじめに

以前は「[貧者の GitHub Enerprise](http://www.slideshare.net/takafumionaka/is-there-anynecessityofusinggithubenterprise/9)」などと言われることもあった GitLab ですが、「[GITLABはブランチ毎に書き込み権限を制限できるという点においてGitHubよりも圧倒的に優れている](https://dekokun.github.io/posts/2013-07-31.html)」という意見も見られるなど、機能面、安定面ともに着実に進歩しています。

そのインストールの難しさが話題に挙がることもありましたが、[公式インストールドキュメント](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) はとてもよく書かれていますし、[Chef の Cookbook](http://qiita.com/ogomr/items/6491a268bfc95d0a5be4) なども多数存在します。個人的に Chef より Ansible が使い易かったため、[CentOS 用の playbook](http://qiita.com/garbagetown/items/5a903e2c5035f5df0690) も書きました。

以上のような状況から、GitLab は何らかの理由で GitHub や GitHub Enterprise を利用できない環境においても、Git および Pull Request (Merge Request) による開発フローを導入する有力な選択肢として考えて良い段階に入ったと思います。

今回は、そのような GitLab の導入をさらに推し進めるため、Jenkins と連携する方法を紹介します。

ただし、最後にまとめる通り、今回の方法では Git および GitLab の長所を活かしきれません。今回は導入編として GitLab と Jenkins の連携の基礎を確認し、問題点を把握することまでを目的とします。


## 前提

以下の手順を参考に、GitLab と Jenkins を相互にアクセス可能な別々のマシンにインストールしているものとします。

- [CentOS に Ansible で GitLab をインストール - Qiita](http://qiita.com/garbagetown/items/5a903e2c5035f5df0690)
- [proxy 環境化の CentOS に Ansible で Jenkins をインストール - Qiita](http://qiita.com/garbagetown/items/cec7b1e6ed39e09b8e37)

プロジェクトが成長すると Jenkins のビルド負荷が非常に高くなり、同一マシンにインストールしていると GitLab のパフォーマンスに影響を与える場合があるため、別々のマシンにインストールすることをお勧めします。

なお、上記 playbook はひとつのマシンに GitLab と Jenkins をインストールする内容ではないので、ご注意ください。

また、上記手順では GitLab を Microsoft Azure 上に構築し、Jenkins を Vagrant 上に構築していますが、Jenkins も Azure 上などの GitLab からアクセスできる環境に構築する必要がある点にご注意ください。


## ビルド対象プロジェクト

まず、Jenkins でビルドするサンプルとして、以下のようなソースコードと（作為的な）テストコードを含む maven プロジェクトを作成しました。

```java:ソースコード
public class Main {

    public static final Double TAX_RATE = 1.05;

    public static int calc(int price) {
        return new Double(Math.floor(price * TAX_RATE)).intValue();
    }
}
```

```java:テストコード
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import org.junit.Test;

public class MainTest {

    @Test
    public void 一円に満たない消費税は切り捨てられること() {
        assertThat(Main.calc(10), is(10));
    }

    @Test
    public void 一円を超える消費税は加算されること() {
        assertThat(Main.calc(30), is(31));
    }
}
```

プロジェクト全体は [GitHub](https://github.com/garbagetown/gitlab_jenkins_sample) に公開しています。


## GitLab プロジェクトの作成

続いて、上記サンプルプロジェクトを管理する GitLab プロジェクトを作成します。

### プロジェクトの新規作成

管理者権限のあるユーザで GitLab にログインし、[Admin area] で [New Project] をクリックします。

![20140509_001.png](https://qiita-image-store.s3.amazonaws.com/0/6755/06bc1e2d-292c-c049-a1bb-c48bb084db8a.png)

[Project name] に適当なプロジェクト名を入力したら [Create Project] をクリックします。

![20140509_002.png](https://qiita-image-store.s3.amazonaws.com/0/6755/2cfb9ec8-89f0-9952-82dc-b9b1b269e171.png)

### ソースコードの追加

プロジェクトを作成したら、上記サンプルプロジェクトをリポジトリに push します。

通常は GitLab から空のリポジトリを clone してファイルを追加していきますが、今回は下記のようにして GitHub から手元の端末に clone した後、origin の URL を作成した GitLab のものに変更して push しました。`YOUR.GITLAB.URL` や `USERNAME` の部分は適宜読み替えてください。

```bash
$ git clone https://github.com/garbagetown/gitlab_jenkins_sample.git
Cloning into 'gitlab_jenkins_sample'...
(snip)

$ cd gitlab_jenkins_sample/

$ git remote set-url origin git@YOUR.GITLAB.URL:USERNAME/gitlab_jenkins_sample.git

$ git push -u origin master
(snip)
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.
```

なお、ここでは Windows 端末を使用しているので、クライアントには [Git for Windows](https://msysgit.github.io/) を使用しました。


## jenkins ユーザの設定

次に、Jenkins が GitLab からソースコードを clone できるように設定します。

上記前提条件に記載した playbook を使って Jenkins をインストールした場合、Jenkins は `jenkins` ユーザで動作しますが、このユーザにはログインシェルを設定していないので、その他の一般ユーザで Jenkins マシンにログインし、`sudo su -` して作業します。

### 鍵ペアの作成

以下のようにして鍵ペアを作成します。パスフレーズを尋ねられますが、なにも入力せずに Enter を押してください。

```bash
[root@jenkins ~]# sudo -u jenkins -H ssh-keygen -t rsa -C jenkins@YOUR.JENKINS.URL
Generating public/private rsa key pair.
Enter file in which to save the key (/var/lib/jenkins/.ssh/id_rsa):
Created directory '/var/lib/jenkins/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/lib/jenkins/.ssh/id_rsa.
Your public key has been saved in /var/lib/jenkins/.ssh/id_rsa.pub.
(snip)
[root@jenkins ~]# cd /var/lib/jenkins/.ssh/
[root@jenkins .ssh]# ll
total 8
-rw------- 1 jenkins jenkins 1675 May  9 01:08 id_rsa
-rw-r--r-- 1 jenkins jenkins  412 May  9 01:08 id_rsa.pub
```

後ほど GitLab のユーザに設定するので、公開鍵の内容を表示し、コピーしておきます（公開鍵なので具体的な内容を記載しても問題ありませんが、必要ないので以下では省略しています）。

```bash
[root@jenkins .ssh]# cat id_rsa.pub
(snip)
```

### ssh 設定

`jenkins` ユーザは GitLab から SSH でソースコードを clone するので、`YOUR.GITLAB.URL` 用の ssh config を追加します。

```bash
[root@jenkins .ssh]# vi config
[root@jenkins .ssh]# cat config
Host YOUR.GITLAB.URL
  User          git
  Hostname      YOUR.GITLAB.URL
  IdentityFile  /var/lib/jenkins/.ssh/id_rsa
```

### git 設定

最後に `jenkins` ユーザが git を使用する際のプロファイルを設定します。`YOUR.JENKINS.URL` の部分は適宜読み替えてください。

```
[root@jenkins .ssh]# sudo -u jenkins -H git config --global user.name jenkins
[root@jenkins .ssh]# sudo -u jenkins -H git config --global user.email jenkins@YOUR.JENKINS.URL
[root@jenkins .ssh]# cat /var/lib/jenkins/.gitconfig
[user]
        name = jenkins
        email = jenkins@YOUR.JENKINS.URL
```

以上で jenkins ユーザの設定は完了です。


## GitLab プロジェクトの設定

作成した jenkins ユーザが GitLab からサンプルプロジェクトを clone できるよう、GitLab 用のユーザを作成し、GitLab プロジェクトに追加します。

### ユーザの新規作成

プロジェクト作成時と同様に [Admin area] を開き、[New User] をクリックします。

![20140509_004.png](https://qiita-image-store.s3.amazonaws.com/0/6755/dc7f2447-99fc-d2f0-755a-b3e9a4f88020.png)

[Name], [Username] に `jenkins` を入力し、[Email] に実際に受信できるメールアドレスを入力したら [Create User] をクリックします。

![20140509_006.png](https://qiita-image-store.s3.amazonaws.com/0/6755/a5680c28-87c2-657e-4864-b7c2fae8578c.png)


### プロファイルの設定

しばらくすると設定したメールアドレスに `jenkins` ユーザの初期パスワードが記載されたメールが届くので、管理者権限のあるユーザをログアウトし、`jenkins` ユーザと通知された初期パスワードでログインします。

初回ログインのため、パスワードの変更を求められるので、適当なパスワードに変更します。パスワード変更後は再度ログイン画面に遷移するので、変更後パスワードでログインします。

![20140509_010.png](https://qiita-image-store.s3.amazonaws.com/0/6755/f414342d-9c31-504c-5de9-090568b382ac.png)

[Profile settings] から [SSH Keys] とリンクを辿り、[Add SSH Key] ボタンをクリックすると公開鍵登録画面が表示されるので、コピーしておいた `jenkins` ユーザの公開鍵を貼り付けて [Add key] ボタンをクリックします。

![20140509_011.png](https://qiita-image-store.s3.amazonaws.com/0/6755/6546b2e3-91fd-df16-f472-2f071b5260c1.png)

お好みでアバター画像を登録したら `jenkins` ユーザをログアウトします。

![20140509_012.png](https://qiita-image-store.s3.amazonaws.com/0/6755/9de4a19d-666b-4bc1-08a5-80a82a3223de.png)


### メンバーの追加

再度、管理者権限のあるユーザでログインしたら [Settings] リンクをクリックしてプロジェクト設定画面を開き、[Members] ボタンをクリックして、さらに [New project member] ボタンをクリックします。

![20140509_013.png](https://qiita-image-store.s3.amazonaws.com/0/6755/5b3571da-fbb8-924f-1ce3-c901c3c10b7c.png)

プロジェクトメンバ追加画面が開いたら [People] に `jenkins` を指定し、[Project Access] に `Developer` を指定します（今回の内容に限る場合は `Reporter` 権限で充分ですが、この後の記事で必要になるため、ここで `Developer` 権限を指定しました）。

![20140509_014.png](https://qiita-image-store.s3.amazonaws.com/0/6755/ee2e533c-d295-3a9e-2bed-b34bd5746bb3.png)


### Web Hook

最後に、GitLab のリポジトリにおいてイベントが発生した際に Jenkins のビルドを実行するよう、Web Hook を設定します。

[Web Hooks] ボタンをクリックして Web hooks 設定画面を開き、[URL] に `http://YOUR.JENKINS.URL/gitlab/build_now` を指定し、[Add WebHook] ボタンをクリックします。

![20140509_015.png](https://qiita-image-store.s3.amazonaws.com/0/6755/9128d3a4-c4f9-7755-f3d6-ccbd7580cd3c.png)

以上で GitLab プロジェクトの設定は完了です。


## Jenkins の設定

いよいよ Jenkins の設定です。

### プラグインのインストール

Jenkins はデフォルトでは Git を扱えないので、別途プラグインが必要です。また、Web Hook でビルドを実行するプラグインも併せてインストールする必要があります。

Jenkins にアクセスして、[Jenkins の管理] から [プラグインの管理] とリンクを辿り、プラグインマネージャー画面を開いたら、[利用可能] タブを開き、画面右上の [フィルター] に `git` と入力し、対象プラグインを絞り込みます。

![20140509_023.png](https://qiita-image-store.s3.amazonaws.com/0/6755/b8c88479-26aa-1670-151e-913de7a44314.png)

絞り込まれたプラグインから [Git Plugin] と [Gitlab Hook Plugin] にチェックを入れて [再起動せずにインストール] をクリックします。

![20140509_024.png](https://qiita-image-store.s3.amazonaws.com/0/6755/bdffde5d-bff9-ddc6-1a83-9c18cd9a56cb.png)

プラグインのインストールが正常に完了したら [ページの先頭へ戻る] リンクをクリックします。

![20140509_025.png](https://qiita-image-store.s3.amazonaws.com/0/6755/633e9e95-018c-81bd-d851-49ddf7fdab89.png)


### ジョブの作成

最後に Jenkins のジョブを作成します。

[新規ジョブ作成] リンクをクリックし、適当なジョブ名を入力したら、[Maven2/3プロジェクトのビルド] をチェックして [OK] ボタンをクリックします。

![20140509_016.png](https://qiita-image-store.s3.amazonaws.com/0/6755/26183376-9039-26e7-214b-0d828ae6e216.png)

[ソースコード管理] の [Git] にチェックを入れ、[Repository URL] に `git@YOUR.GITLAB.URL:USERNAME/gitlab_jenkins_sample.git` を入力してフォーカスアウトすると、認証エラーが発生するので、[Credentials] の [Add] ボタンをクリックして認証情報を追加します。

認証情報追加画面が開いたら、[Kind] に [SSH ユーザー名と秘密鍵] を選択して、[ユーザー名] に `jenkins` を入力します。さらに [秘密鍵] で [Jenkins マスター上のファイルから] をチェックして [ファイル] に `/var/lib/jenkins/.ssh/id_rsa` を入力したら [Add] ボタンをクリックします。

![20140509_017.png](https://qiita-image-store.s3.amazonaws.com/0/6755/ee2b9463-eb9b-946b-78b5-8c4c5a83243a.png)

ジョブ作成画面に戻るので、[Credentials] に作成した `jenkins` を選択し、認証エラーが消えることを確認してください。

その後、[Branches to build] を空にし、[ビルド・トリガ] のチェックをすべて外したら [保存] ボタンをクリックします。

![20140509_018.png](https://qiita-image-store.s3.amazonaws.com/0/6755/b0f58818-ec2d-3b10-8118-8a35840fa3a7.png)

以上で Jenkins のジョブ作成は完了です。


## ビルドの実行

それではビルドを実行してみましょう。

### Test Hook による実行

まずは GitLab の Web hooks 設定画面から [Test Hook] を実行してみます。

![20140509_019.png](https://qiita-image-store.s3.amazonaws.com/0/6755/cdcc6f62-2a4e-ad05-21fd-f11433706b2c.png)

期待通り、自動的にビルドが実行され、成功したことがわかります。

![20140509_020.png](https://qiita-image-store.s3.amazonaws.com/0/6755/df6dca3a-99d0-2159-9fc4-9428d9449d59.png)

### push による実行（テスト失敗）

続いて、ソースコードを変更して push してみましょう。以下のように消費税率を変更してみます。

```bash
$ git diff
diff --git a/src/main/java/Main.java b/src/main/java/Main.java
index 8f694a1..2990e92 100644
--- a/src/main/java/Main.java
+++ b/src/main/java/Main.java
@@ -3,7 +3,7 @@
  */
 public class Main {

-    public static final Double TAX_RATE = 1.05;
+    public static final Double TAX_RATE = 1.08;

     public static int calc(int price) {
         return new Double(Math.floor(price * TAX_RATE)).intValue();
```

コミットして push します。

```bash
$ git commit -am "消費税率を変更"
[master 32cf8a2] 消費税率を変更
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git push origin master
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 463 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
(snip)
```

期待通り自動的にビルドが実行されましたが、テストが一件失敗していることがわかります。

![20140509_021.png](https://qiita-image-store.s3.amazonaws.com/0/6755/9c2df096-6701-05c2-8d12-95aade270adf.png)


### push による実行（テスト成功）

消費税率を 5% から 8% に変更したので、30 円の商品の税込価格は 31 円ではなく 32 円でなければなりませんでした。作為的ですね。

以下のようにテストコードを修正して、コミット、push します。

```
$ git diff
diff --git a/src/test/java/MainTest.java b/src/test/java/MainTest.java
index de6e95b..c2dc042 100644
--- a/src/test/java/MainTest.java
+++ b/src/test/java/MainTest.java
@@ -12,6 +12,6 @@ public class MainTest {

     @Test
     public void 一円を超える消費税は加算されること() {
-        assertThat(Main.calc(30), is(31));
+        assertThat(Main.calc(30), is(32));
     }
 }

$ git commit -am "テストコードを修正"
[master f3de759] テストコードを修正
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git push origin master
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 486 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
(snip)
```

今度も期待通り、自動的にビルドが実行され、テストもすべて成功したことがわかります。

![20140509_022.png](https://qiita-image-store.s3.amazonaws.com/0/6755/433df814-29c8-8238-4952-6012b645c174.png)

# まとめ

以上のように、GitLab と Jenkins を連携することができました。

しかし、origin の master ブランチを svn の中央リポジトリのように使用しているため、誤ったコミットが push されてしまいました。Jenkins による自動ビルドのおかげで誤りに気付くことはできましたが、誤りが混入することを事前に防ぐことはできないのでしょうか。

Git の長所は高速なブランチ切り替えと高性能なマージであり、GitLab の長所は差分を確認しやすい Web UI とワンクリックで実行できる Merge Request です。

次回は、これらの長所を最大限に活かした Merge Request による開発手順を紹介します。


## 参考

- [Gitlab Hook Plugin - Jenkins - Jenkins Wiki](https://wiki.jenkins-ci.org/display/JENKINS/Gitlab+Hook+Plugin)
- [GitLabとJenkins連携 | テックラボ 来夢多゛](http://www.ramuda.co.jp/techlabo/archives/1170)
- [銀鷹のメモ帳](http://yutaka-j.hatenablog.com/)


## 余談

Web Hook の URL は `http://YOUR.JENKINS.URL/gitlab/build_now` のように指定しました。ビルドすべき Jenkins ジョブ名も Git ブランチ名も指定していません。

これでは、Jenkins 上に複数のジョブが存在し、また Git リポジトリ上に複数のブランチが存在していた場合、あるブランチの push をトリガーに、すべてのジョブ、すべてのブランチのビルドが実行されてしまうのではないでしょうか。

そう疑問に思って調べてみたところ、[Gitlab Hook Plugin のマニュアル](https://wiki.jenkins-ci.org/display/JENKINS/Gitlab+Hook+Plugin) に以下のように記載されていました。

> Plugin will parse the Gitlab payload and extract the branch for which the commit is being pushed and changes made.
> It will then scan all Git projects in Jenkins and start the build for those that:
> 
>・match url of the Gitlab repo
>・match committed Gitlab branch

push されたコミットの内容を解析して、Jenkins 内のすべての Git プロジェクトからビルドすべきブランチを判断する、ということのようです。よくできていますね。

また、以下のようにも記載されています。

> Notes:
> 
>(snip)
>・you don't have to setup polling for the project

リポジトリに対する push などを契機に Web Hook で Jenkins のビルドを実行するので、Jenkins 側からリポジトリをポーリングする必要はないということです。

リポジトリのポーリングは帯域などのリソースを非効率に消費しますし、リポジトリのアクセスログが大量のポーリングアクセスで埋め尽くされてしまうので、不必要に設定しないようにしましょう。