GitLab CIでテスト・ビルド・デプロイを自動化する 

<img width="200px" src="https://qiita-image-store.s3.amazonaws.com/0/155486/4202d3bb-3786-f7e1-fb94-7125439d6cd0.png" alt="https://gitlab.com/uploads/-/system/project/avatar/250833/runner_logo.png">
<sub><a href="https://gitlab.com/gitlab-org/gitlab-ci-multi-runner">GitLab.org / gitlab-ci-multi-runner · GitLab</a></sub>

GitLab CI、Docker、Gradle、JUnit、Mochitoといった各種CI/CDツールを連携し、テストとビルドを自動化、またビルドによって出来上がったDockerイメージをDockerレジストリにデプロイするまでの一連の流れをまとめました。

<a name="動作環境"></a>
## 動作環境

本稿の動作環境は以下の通りです。

**ホストマシン**

|分類|項目|バージョン|
|---|---|---|
|Amazon EC2|AMI ID|ami-29d1e34e(CentOS Linux 7 x86_64)|
|Amazon EC2|インスタンスタイプ|t2.medium|
|Amazon EC2|OSバージョン|CentOS Linux release 7.3.1611 (Core) |
|Dockerマシン|Docker CE|17.06.0-ce|
|Dockerコンテナ|GitLab CE|9.4.5|
|Dockerコンテナ|GitLab Runner|9.4.2|

**開発者マシン**

|項目|バージョン|
|---|---|
|Gradle|3.2.1|
|Open JDK|1.8.0_141|


<a name="前提条件"></a>
## 前提条件

- Dockerホストマシン上でGitLabコンテナが起動していること
- 以下の設定（値は任意）の初期プロジェクトが準備できていること

|項目|値|
|---|---|
|グループ名|example|
|プロジェクト名|gitlab-ci-demo|

- 開発者はGitLabのプロジェクトにおいてmasterブランチにpushすることが可能な権限（Owner以上）を有していること
- 開発者はプロジェクトのリモートリポジトリとssh接続できること
- 開発者のマシン環境にGradleとOpen JDKが予めインストールされていること


<a name="「gitlab-ci」を理解する"></a>
## 「GitLab CI」を理解する
<a name="「gitlab-ci」とは"></a>
### 「GitLab CI」とは

GitLab CIはCI（Continuous Integration、継続的インテグレーション）ツールの一つです。GitLab 7.14から8.0へのメジャー・アップデートにより、GitLab CIはバージョン管理機能を有したGitLab CE/EEに統合されました。  
[From 7.14 to 8.0 | GitLab.org / GitLab Community Edition · GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.14-to-8.0.md)

<a name="「gitlab-runner」とは"></a>
### 「GitLab Runner」とは

#### 「GitLabサーバー」と「GitLab Runner」の役割

GitLabを用いて継続的インテグレーションを行うためには、「GitLabサーバー」とは別にジョブを実行するための「GitLab Runner」というサーバーが必要になります。以下にGitLabサーバーとGitLab Runnerの役割を示します。

- **GitLabサーバー（CIサーバー、Coordinator）**  
Jenkinsにおけるマスターの役割を担うサーバーです。GitLabのプロジェクトで管理しているブランチへのプッシュやマージリクエストをトリガーとして、GitLab Runnerを呼び出します。
- **GitLab Runner**  
Jenkinsにおけるスレーブの役割を担うサーバーです。サーバー内の環境でシェルを実行したり、一時的にDockerコンテナを生成してジョブを実行します。

<img width="790" alt="000001-12.png" src="https://qiita-image-store.s3.amazonaws.com/0/155486/664d820d-4295-3c10-30cd-32e4e70421a9.png">

<a href="http://www.slidedeck.io/codethebuild/slides">Coding the Next Build | SlideDeck.io</a>

<a name="「executor」とは"></a>
#### GitLab Runnerの実行方式「Executor」とは

GitLab Runnerには「Executor」と呼ばれるジョブの実行方式があります。本稿ではDockerイメージをデプロイするため、Docker Executorを使用します。

**Executorの種類**

- **Docker（GitLab推奨）**  
Runnerとは別に一時的に生成したDockerコンテナの環境内でジョブを実行します。ジョブを実行する度にコンテナを新しく生成するため、実行するジョブの冪等性が保証されます。
- **Shell（GitLab非推奨）**  
Runnerの環境内でジョブを実行します。ジョブを実行する際にRunnerの環境をクリーンアップしないため、実行するジョブの冪等性が保証されません。またShellによるRunnerの実行はGitLabサーバー上の他プロジェクトのソースコードを取得することができてしまうため、GitLabはこの実行方式を非推奨としています。  
[Security | Shell - GitLab Documentation](https://docs.gitlab.com/runner/executors/shell.html#security)  
- **その他のExecutor**  
その他にも以下のExecutorがあります。  
[Executors | GitLab.org / gitlab-ci-multi-runner · GitLab](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/executors/README.md)
    - Docker Machine and Docker Machine SSH (auto-scaling)
    - Parallels
    - VirtualBox
    - SSH
    - Kubernetes
 
 
<!--
/----------------------------------------------------------------
-->

<a name="サーバー管理者が行う作業"></a>
## サーバー管理者が行う作業

<a name="dockerレジストリを構築する"></a>
### Dockerレジストリを構築する

まずは自動デプロイするDockerコンテナのデプロイ先であるDockerレジストリを構築します。

<a name="dockerレジストリを起動する"></a>
#### Dockerレジストリを起動する

任意のパスにdocker-compose.ymlを作成します。

**ディレクトリ構成**

```
registry
└── docker-compose.yml
```

**docker-compose.yml**

```
version: '3'
services:
  registry:
    image: registry:2
    tty: true
    container_name: registry
    restart: always
    ports:
      - "5000:5000"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
```

Dockerレジストリを起動します。

```
$ docker-compose up -d
```

<a name="dockerレジストリの動作確認をする"></a>
#### Dockerレジストリの動作確認をする

Dockerレジストリが正常に動作しているかを確認するため、テスト用のイメージである「hello-world」をpullします。

```
$ docker pull hello-world
```

pullした「hello-world」イメージにタグを付与します。

```
$ docker tag hello-world localhost:5000/hello-world
```

タグが付与されたイメージをDockerレジストリにpushします。

```
docker push localhost:5000/hello-world
```

curlコマンドを入力して以下のような応答があれば成功です。

```
$ curl http://localhost:5000/v2/_catalog
{"repositories":["hello-world"]}
```

[Docker Registry | Docker Documentation](https://docs.docker.com/registry/)  
[プライベートなDockerレジストリサーバーをコンテナで立てる - Qiita](http://qiita.com/rsakao/items/617f54579278173d3c20)  
[Dockerプライベートリポジトリ(Docker Registry)構築レシピ ｜ Developers.IO](http://dev.classmethod.jp/cloud/docker-registry-recipes/)  

<!--
/----------------------------------------------------------------
-->

<a name="gitlabサーバーとgitlab-runnerを連携する"></a>
### GitLabサーバーとGitLab Runnerを連携する

続いてGitLab Runnerを構築します。

<a name="gitlab-runnerコンテナを起動する"></a>
#### GitLab Runnerコンテナを起動する

##### RunnerのRegistration tokenを確認する

Runnerを作成するにはGitLabサーバーに固有の「Registration token」が必要となります。管理者権限を有するユーザーでGitLabにログインし、以下のURLから「Registration token」を確認します。

**GitLab / Admin Area > Runners**  
http://[GitLabサーバーのURL]/admin/runners

![000001-02.png](https://qiita-image-store.s3.amazonaws.com/0/155486/a7bd42e8-a4d2-8040-f4ac-2746bf6ed313.png)

##### GitLab RunnerでDockerコンテナをビルドする方法について

GitLab RunnerでDockerコンテナをビルドする方法を以下に示します。本稿では「Docker socket binding」を利用します。  
[Using Docker Build - GitLab Documentation](https://docs.gitlab.com/ce/ci/docker/using_docker_build.html)

- **docker-in-docker executor（GitLab推奨）**  
Runnerのexecutorオプションに```docker```を指定し、「ジョブを実行するコンテナ（Executor）」として「docker-in-docker (dind) 」と呼ばれるDockerイメージを使用する方法です。Dockerホスト上にあるExecutorをDockerマシンとすることでDockerコマンドを実行します。
- **Docker socket binding**  
Runnerのexecutorオプションに```docker```を指定し、ExecutorにRunnerの```/var/run/docker.sock```（Dockerのデーモンプロセスである「dockerd」と通信するためのUNIXドメインソケット）をマウントする方法です。RunnerはDockerホストの```/var/run/docker.sock```をマウントしているため、間接的にDockerホストのDockerデーモンを使ってDockerコマンドを実行することができます。ただし、Executorに```/var/run/docker.sock```をマウントすることによって脆弱性が生じるため、使用する際は注意が必要です。  
[Dockerコンテナ内からホストマシンのルートを取る具体的な方法（あるいは/var/run/docker.sockを晒すことへの注意喚起） | 48JIGEN *Reloaded*](https://rimuru.lunanet.gr.jp/notes/post/how-to-root-from-inside-container/)  
[Dockerでホストを乗っ取られた - Qiita](http://qiita.com/titilat/items/f4f94a0cee9049125156)
- **shell executor（GitLab非推奨）**  
Runnerのexecutorオプションに```shell```を指定し、シェルスクリプトからDockerコマンドを実行する方法です。この方法を利用するには```gitlab-runner```ユーザーを```docker```グループに追加する必要があり、```gitlab-runner```ユーザーはDockerコンテナを利用して間接的にDockerホストにある全てのファイルにアクセスすることができるため、GitLabはこの方法を非推奨としています。  
[On Docker security: 'docker' group considered harmful - Andreas Jung](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful)

**本稿で「Docker socket binding」を採用する理由**  

Docker Registryとの接続は「localhost」で指定する方法とIPアドレスで指定する方法がありますが、IPアドレスを指定して接続する場合はHTTPSしか許可されておらず、また自己証明書を使用した場合は接続を拒否されてしまいます。  
本稿ではデプロイ先としてDockerホスト上のローカルレジストリ（localhost:5000）を指定しています。「docker-in-docker executor」の場合、dindイメージのExecutorからはDockerホスト上にあるローカルレジストリを```http://localhost:5000```で参照することができないため、本稿では「Docker socket binding」を採用しています。その他に「docker-in-docker executor」を使用してdindの起動コマンドに```--insecure-registry```オプションを使う方法が考えられます。

<a name="gitlab-runnerのdocker-composeymlを作成する"></a>
##### GitLab Runnerのdocker-compose.ymlを作成する

任意のパスにdocker-compose.ymlを作成します。本稿におけるディレクトリ構成を以下に示します。

**ディレクトリ構成**

```
runner/
└── docker-compose.yml
```

**runner/docker-compose.yml**

```
version: '3'
services:
  runner:
    image: gitlab/gitlab-runner:alpine-v9.4.2
    tty: true
    container_name: docker-executor
    restart: always
    environment:
      - REGISTER_NON_INTERACTIVE=true
      - CI_SERVER_URL=http://[GitLabサーバーのURL]:9090/ci
      - REGISTRATION_TOKEN=xxxxxxxxxxxxxxxxxxxx
      - RUNNER_EXECUTOR=docker
      - RUNNER_TAG_LIST=docker
      - RUNNER_NAME=Docker Executor (Docker socket binding)
      - RUNNER_LIMIT=1
      - DOCKER_IMAGE=docker:latest
      - DOCKER_VOLUMES=/var/run/docker.sock:/var/run/docker.sock
    volumes:
      - /srv/gitlab-runner/config:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
```

[Docker image installation and configuration / Run GitLab Runner in a container - GitLab Documentation](https://docs.gitlab.com/runner/install/docker.html#docker-image-installation-and-configuration)  
[Use Docker socket binding | Using Docker Build - GitLab Documentation](https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-socket-binding)

**GitLab Runnerの環境変数**  

environmentに定義している環境変数についての説明を以下に示します。

|環境変数|説明|
|---|---|
|REGISTER_NON_INTERACTIVE|ビルド時のインタラクティブな問い合わせを無効にします。|
|CI_SERVER_URL|CIサーバーのURLを指定します。|
|REGISTRATION_TOKEN|GitLabサーバーのRegistration Tokenを指定します。|
|RUNNER_EXECUTOR|Executor（shell、dockerなど）を指定します。|
|RUNNER_TAG_LIST|Runnerのタグ名を定義します。定義したタグはジョブで指定することができます。|
|RUNNER_NAME|Runnerの説明を定義します。|
|RUNNER_LIMIT|ジョブの最大同時実行数を指定します。|
|DOCKER_IMAGE|ジョブを実行するコンテナがベースとするデフォルトの<br>Dockerイメージを指定します。|
|DOCKER_VOLUMES|ジョブ用のコンテナ実行時にマウントするボリュームを<br>指定します。|

本稿では「Docker socket binding」を使用するため、GitLab Runnerの環境変数 ```DOCKER_VOLUMES```に```/var/run/docker.sock:/var/run/docker.sock```を指定してRunnerのUNIXドメインソケットをExecutorにマウントしています。

GitLab Runnerで指定できるその他の環境変数はRunnerコンテナ上で以下のコマンドを入力することで確認できます。

```
$ gitlab-runner register --help
```

##### GitLab Runnerを起動する

以下のコマンドを入力してRunnerコンテナを起動します。

```
$ docker-compose up -d
```

<a name="gitlabサーバーにgitlab-runnerを登録する"></a>
#### GitLabサーバーにGitLab Runnerを登録する

GitLab Runnerコンテナを起動しただけではGitLabサーバーに認識されません。以下のコマンドを入力してGitLabサーバーにGitLab Runnerを登録します。


```
$ docker exec -it docker-executor gitlab-runner register
docker exec -it docker-executor gitlab-runner register
Running in system-mode.                            
                                                   
Registering runner... succeeded                     runner=gFFiecFD
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 
```

以下のURLから、GitLab RunnerがGitLabサーバーに登録されていることを確認することができます。

**GitLab / Admin Area > Runners**（http://[GitLabサーバーのURL]/admin/runners）

![000001-09.png](https://qiita-image-store.s3.amazonaws.com/0/155486/6bfa1a2a-c23d-750b-d156-c60f928eebec.png)

**GitLabサーバーにおける「Runner」の分類**

GitLabサーバーはGitLab Runnerを以下の二種類のRunnerに分類して管理します。

- **Shared Runner**  
プロジェクト共通で使用できるRunnerです。ジョブのキューは「Fair usage queue」と呼ばれるアルゴリズムで実行されます。
- **Specific Runner**  
プロジェクトに固有のRunnerです。ジョブのキューはFIFO形式で実行されます。

GitLab RunnerはデフォルトではShared Runnerとして扱われます。「GitLab / Admin Area」 > 「Runners」のページから「Restrict projects for this Runner」に表示されている「group」を「Enable」にすることで「Specific Runner」に切り替えることができます。ただし、 **一度Specific Runnerにした場合、Shared Runnerに戻すことができないため注意が必要です。**  
[Shared vs specific Runners | Runners - GitLab Documentation](https://docs.gitlab.com/ee/ci/runners/README.html#shared-vs-specific-runners)  
また「Run untagged jobs」にチェックを入れることで、タグが付与されていないジョブに対してもGitLab Runnerを実行することができます。  

<!--
/----------------------------------------------------------------
-->

<a name="開発者が行う作業"></a>
## 開発者が行う作業

<a name="gradleプロジェクトを作成する"></a>
### Gradleプロジェクトを作成する

本稿はGitLab CIの動作を理解することが目的のため、git-flowによるブランチ管理は割愛してmasterブランチを直接編集します。

<a name="gitlabプロジェクトをクローンする"></a>
#### GitLabプロジェクトをクローンする

プロジェクトを配置するディレクトリは任意ですが、本稿ではホームディレクトリ直下にプロジェクトを作成します。以下のコマンドを入力してGitLabからプロジェクトをクローンします。

```
$ cd ~
```

```
$ git clone ssh://git@[GitLabサーバーのIPアドレス]:[SSHポート]/example/gitlab-ci-demo.git
```

正常にクローンできていれば「gitlab-ci-demo」ディレクトリが作成されていることを確認できます。

```
$ ls
gitlab-ci-demo
```

<a name="gitignoreを作成する"></a>
#### .gitignoreを作成する

Gitのコミット時に不要なファイルが混入することを避けるため、.gitignoreファイルを作成します。

![000001-08.png](https://qiita-image-store.s3.amazonaws.com/0/155486/3d738129-eb7d-1545-b8ce-cf417c8de30e.png)
<a href="https://www.gitignore.io/">gitignore.io - Create Useful .gitignore Files For Your Project</a>

上記のサイトで「Git」、「Java」、「Gradle」、「Eclipse」と入力し、「Create」ボタンを入力することで.gitignoreファイルを自動生成してくれます。生成されたファイルは以下のパスに配置します。

```
gitlab-ci-demo/.gitignore
```

<a name="gradleプロジェクトを作成する-1"></a>
#### Gradleプロジェクトを作成する

以下のコマンドを入力し、Gradleプロジェクトを作成します。

```
$ gradle init --type java-library
Starting a Gradle Daemon (subsequent builds will be faster)
:wrapper
:init

BUILD SUCCESSFUL

Total time: 4.775 secs
```

**gradle initコマンドで指定できるtypeについて**

- **java-library**  
Jarライブラリを作成する場合に指定するtypeです。Warライブラリを作成する場合もこのtypeを指定し、不足するディレクトリは手動で作成します。
- **java-application**  
コマンドラインによるJavaアプリケーションを作成する場合に指定するtypeです。
- **その他のtype**
    - pom
    - scala-library
    - groovy-library
    - groovy-application
    - basic

```./gradle help --task init```と入力することでデフォルトで定義されているtypeを確認することができます。  
[Build Init Plugin - Gradle User Guide Version 4.1](https://docs.gradle.org/current/userguide/build_init_plugin.html)

<a name="gradleラッパーを作成する"></a>
#### Gradleラッパーを作成する

GradleラッパーはGradleプロジェクトをビルドする時に利用するラッパースクリプトです。Gradleラッパーが提供するシェルスクリプト```./gradlew```を実行することでGradleが自動的にダウンロードされ、Gradleコマンドと同じ処理を実行できます。Gradleラッパーはプロジェクトを作成した時と同じバージョンのgradleを自動でインストールします。同じバージョンを使用することで、開発者によって異なる環境が原因でビルドを実行することができないといった状況を回避することができます。  
[Gradle初心者によるGradle事始め - Qiita](http://qiita.com/navekazu/items/af886fa200be8a683132)  
[第62章 Gradleラッパー](http://gradle.monochromeroad.com/docs/userguide/gradle_wrapper.html)



以下のコマンドを入力し、Gradleラッパーを作成します。

```
$ gradle wrapper
```

以下のコマンドを入力し、Gradleラッパーが正しくインストールされているかを確認します。

```
$ ./gradlew --version

------------------------------------------------------------
Gradle 3.2.1
------------------------------------------------------------

Build time:   2016-11-22 15:19:54 UTC
Revision:     83b485b914fd4f335ad0e66af9d14aad458d2cc5

Groovy:       2.4.7
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          1.8.0_141 (Oracle Corporation 25.141-b15)
OS:           Linux 3.10.0-514.16.1.el7.x86_64 amd64

```

ここまでの操作で「gitlab-ci-demo」プロジェクト配下は以下のような構成になっているはずです。

```
gitlab-ci-demo
|-- build.gradle
|-- gradle
|   `-- wrapper
|       |-- gradle-wrapper.jar
|       `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
|-- settings.gradle
`-- src
    |-- main
    |   `-- java
    |       `-- Library.java
    `-- test
        `-- java
            `-- LibraryTest.java
```

「Library.java」と「LibraryTest.java」は不要なサンプルファイルのため削除しておきます。

```
$ rm ~/gitlab-ci-demo/src/main/java/Library.java \
~/gitlab-ci-demo/src/test/java/LibraryTest.java 
```

<a name="buildgradleを編集する"></a>
#### 外部パッケージを定義する

デフォルトのbuild.gradleを編集し、Javaファイルをテスト・ビルドする際に必要となる外部パッケージを定義します。

**~/gitlab-ci-demo/build.gradle**

```
/*
 * This build file was auto generated by running the Gradle 'init' task
 * by 'programmer1' at '8/18/17 5:04 PM' with Gradle 3.2.1
 *
 * This generated file contains a sample Java project to get you started.
 * For more details take a look at the Java Quickstart chapter in the Gradle
 * user guide available at https://docs.gradle.org/3.2.1/userguide/tutorial_java_projects.html
 */

// Apply the java plugin to add support for Java
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'

// In this section you declare where to find the dependencies of your project
repositories {
    // Use 'jcenter' for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

// In this section you declare the dependencies for your production and test code
dependencies {
    // The production code uses the SLF4J logging API at compile time
    compile 'org.slf4j:slf4j-api:1.7.21'

    // Declare the dependency for your favourite test framework you want to use in your tests.
    // TestNG is also supported by the Gradle Test task. Just change the
    // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
    // 'test.useTestNG()' to your build script.
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.7.19'
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}
```

**build.gradle.origin（編集前）とbuild.gradle（編集後）の差分**

```
--- build.gradle.origin 2017-08-18 17:10:45.334725608 +0000
+++ build.gradle  2017-08-18 17:21:52.897550222 +0000
@@ -9,6 +9,8 @@
 
 // Apply the java plugin to add support for Java
 apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'eclipse'
 
 // In this section you declare where to find the dependencies of your project
 repositories {
@@ -27,4 +29,6 @@
     // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
     // 'test.useTestNG()' to your build script.
     testCompile 'junit:junit:4.12'
+    testCompile 'org.mockito:mockito-core:2.7.19'
+    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
 }
```

```apply plugin```として```war```と```eclipse```を追加し、また```dependencies```に```mockito-core```と```javax.servlet-api```を追加しました。

dependenciesで記述したパッケージが正しく定義されているかを確認します。以下のコマンドを入力して```FAILED```が表示されていなければ問題ありません。

```
$ gradle dependencies
```

**「apply plugin」とは**  
build.gradleに追加した「apply plugin」についての説明を以下に示します。

|プラグインID|説明|
|---|---|
|Java|Javaのコンパイル、テスト、バンドル機能をプロジェクトに追加します。|
|war|ウェブアプリケーションのWARファイルをビルドできるようにします。|
|eclipse|Eclipse IDEが使用するファイルを生成し、プロジェクトをEclipseにインポートできるようにします。|

[第22章 標準Gradleプラグイン](http://gradle.monochromeroad.com/docs/userguide/standard_plugins.html)

**「repositories」とは**  
本稿ではデフォルトと同じ「jcenter」というリポジトリを使用しています。「repositories」で指定したリポジトリからパッケージがダウンロードされます。「jcenter」以外で良く使用されるリポジトリとして「mavenCentral」があります。ただし最近は「jcenter」を指定することが推奨されているようです。  
[Android Studio – Migration from Maven Central to JCenter | Blog @Bintray](https://blog.bintray.com/2015/02/09/android-studio-migration-from-maven-central-to-jcenter/)

- **jcenter()**  
[Bintray jcenter - Maven, Gradle, Ivy, SBT, Groovy, Clojure central repository](https://bintray.com/bintray/jcenter)
- **mavenCentral()**  
[Maven Repository: Search/Browse/Explore](http://mvnrepository.com/)

**「dependencies」とは**

「dependencies」には必要な外部パッケージと併せて以下の依存関係を指定します。

|名前|意味|
|---|---|
|compile|プロジェクトのプロダクトコードをコンパイルするのに必要な依存関係|
|runtime|プロダクトのクラスを実行するときに必要になる依存関係。デフォルトで、コンパイル時の依存関係もここに含まれる。|
|testCompile|プロジェクトのテストコードをコンパイルするのに必要な依存関係。デフォルトで、コンパイルされたプロダクトクラスと、コンパイル時の依存関係も含まれる。|
|testRuntime|テストを実行するのに必要な依存関係。デフォルトで、compile、runtime、testCompileの各依存関係もここに含まれる。|
|providedCompile|warプラグインによって追加されるコンフィグレーション。compileと同じスコープだが、**WARアーカイブに含まれない。**|

「junit」と「mockito-core」はテストにおけるコンパイル時のみ必要なパッケージであり、ビルド時には不要なパッケージであるため、```testCompile```を指定しています。  
また本稿ではビルドによって出来上がったwarファイルをTomcatサーバーに配備します。「javax.servlet-api」はTomcatに含まれており、warファイルに含める必要がないため```providedCompile```を指定しています。  
[第8章 依存関係管理の基本](http://gradle.monochromeroad.com/docs/userguide/artifact_dependencies_tutorial.html)  
[Dependency management](https://docs.gradle.org/current/userguide/war_plugin.html#sec:war_dependency_management)

<a name="eclipseプロジェクトを作成する"></a>
#### Eclipseプロジェクトを作成する

本稿ではviでファイルを作成するため必須の作業ではありませんが、開発者がEclipseを使ってファイルを編集する場合はコンソールから以下のgradleコマンドを実行します。

```
$ ./gradlew eclipse
```

コマンドを入力することでEclipseプロジェクトに必要な以下のファイルが生成されます。

- .classpath
- .project
- .settings

[第38章 Eclipse プラグイン](http://gradle.monochromeroad.com/docs/userguide/eclipse_plugin.html)  
[EclipseのGradleプロジェクト作成方法比較 - Qiita](http://qiita.com/grachro/items/d1ebad3857a794895426)  
[備忘録!GradleでWARを作る - Qiita](http://qiita.com/_daisuke0802/items/f314709b3baa0e0f65b5)

<!--
/----------------------------------------------------------------------------------
-->

<a name="webアプリケーションを作成する"></a>
### Webアプリケーションを作成する

Gradleが公開している「[Building Java Web Applications | Gradle Guides](https://guides.gradle.org/building-java-web-applications/)」
の例に従ってソースコードを配備します。  
基本的にGradleが公開しているソースコードをそのまま利用していますが、一部誤りがあるため注意してください。以下に修正箇所を示します。

|修正箇所|行番号|修正前|修正後|
|---|:---:|---|---|
|HelloServlet.javaのパス|-|src/main/java/**com**/gradle/demo/|src/main/java/**org**/gradle/demo/|
|HelloServlet.java|10行目|urlPatterns = \{"**hello**"}|urlPatterns = \{"**/hello**"}|


**プロジェクトのレイアウト**

以下はWebアプリケーションに必要なソースコード作成後のディレクトリ構成です。

```
.
├── Dockerfile
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── gradle
    │   │           └── demo
    │   │               └── HelloServlet.java
    │   └── webapp
    │       ├── index.html
    │       └── response.jsp
    └── test
        └── java
            └── org
                └── gradle
                    └── demo
                        └── HelloServletTest.java
```



<a name="サーブレットを配備する"></a>
#### サーブレットを配備する

まずはサーブレットを```src/main/java```配下に作成します。

```
$ mkdir -p ~/gitlab-ci-demo/src/main/java/org/gradle/demo
$ vi ~/gitlab-ci-demo/src/main/java/org/gradle/demo/HelloServlet.java
```

**~/gitlab-ci-demo/src/main/java/org/gradle/demo/HelloServlet.java**

```
package org.gradle.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"}, loadOnStartup = 1) 
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        response.getWriter().print("Hello, World!");  
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        String name = request.getParameter("name");
        if (name == null) name = "World";
        request.setAttribute("user", name);
        request.getRequestDispatcher("response.jsp").forward(request, response); 
    }
}
```

**「@WebServlet」とは**

Servlet3.0から導入されたServlet用のアノテーションより、これまでweb.xmlに記述していた設定値をアノテーションで記述できるようになりました。  
[Javaの道：Servlet（１４．アノテーション）](http://www.javaroad.jp/servletjsp/sj_servlet14.htm)

**urlPatternsの書式**

WebServletアノテーションで指定するurlPatternsの書式を以下に示します。

|書式|説明|
|---|---|
|/??? |正確にURLが合致した場合にサーブレットが起動します。|
|/???/\* |/???/で始まる全てのURLでアクセスがあった場合に、サーブレットが起動します。<br/>例：「/resource/\*」と指定した場合は、「resource」で始まる全てのURLにマッチします。|
|\*.???|???を拡張子にもつ全てのURLでアクセスがあった場合に、サーブレットが起動します。<br/>例：「\*.cgi」と指定した場合は、「cgi」を拡張子に持つURL全てにマッチします。|
|/|適応するものが無かった場合に実行される、デフォルトサーブレットを示します。|

[1. サーブレットの設定と実行 (3) | TECHSCORE(テックスコア)](http://www.techscore.com/tech/Java/JavaEE/Servlet/1-3/)

**「load-on-startup」とは**

サーブレットは初めてリクエストされた時にインスタンス化されるため、最初のリクエストがあった時に初期化パラメータを読み込むことができます。load-on-startup要素を指定すると、アプリケーションがロードされた時に、指定したサーブレットがインスタンス化され、サーブレット内のinit()メソッドが実行されます。load-on-startupタグに定義している数字はロード順です。アプリケーションロード時に複数のサーブレットをロードしておく必要がある場合は、1、2、3という風にロード順番を設定します。  
[load-on-startupの利用-サーブレット（servlet）入門：IT TRICK](http://it-trick-java.appspot.com/servlet/c48068/page50012.html)

<a name="htmlとjspを配備する"></a>
#### htmlとjspを配備する

Gradleのwarプラグインはデフォルトで```src/main/webapp```配下のファイルをjarに含める設定になっています。初期のディレクトリ構成では「webapp」ディレクトリが存在しないため新規作成します。  
[第10章 Webアプリケーションクイックスタート](http://gradle.monochromeroad.com/docs/userguide/web_project_tutorial.html)

```
$ mkdir ~/gitlab-ci-demo/src/main/webapp
$ vi ~/gitlab-ci-demo/src/main/webapp/index.html
```

**~/gitlab-ci-demo/src/main/webapp/index.html**

```
<html>
<head>
  <title>Web Demo</title>
</head>
<body>
<p>Say <a href="hello">Hello</a></p> 

<form method="post" action="hello">  
  <h2>Name:</h2>
  <input type="text" id="say-hello-text-input" name="name" />
  <input type="submit" id="say-hello-button" value="Say Hello" />
</form>
</body>
</html>
```

```
$ vi ~/gitlab-ci-demo/src/main/webapp/response.jsp
```

**~/gitlab-ci-demo/src/main/webapp/response.jsp**

```
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Hello Page</title>
    </head>
    <body>
        <h2>Hello, ${user}!</h2>
    </body>
</html>
```

<a name="テストクラスを配備する"></a>
#### テストクラスを配備する

```
$ mkdir -p ~/gitlab-ci-demo/src/test/java/org/gradle/demo
$ vi ~/gitlab-ci-demo/src/test/java/org/gradle/demo/HelloServletTest.java
```

**~/gitlab-ci-demo/src/test/java/org/gradle/demo/HelloServletTest.java**

```
package org.gradle.demo;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class HelloServletTest {
    @Mock private HttpServletRequest request;
    @Mock private HttpServletResponse response;
    @Mock private RequestDispatcher requestDispatcher;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void doGet() throws Exception {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);

        when(response.getWriter()).thenReturn(printWriter);

        new HelloServlet().doGet(request, response);

        assertEquals("Hello, World!", stringWriter.toString());
    }

    @Test
    public void doPostWithoutName() throws Exception {
        when(request.getRequestDispatcher("response.jsp"))
            .thenReturn(requestDispatcher);

        new HelloServlet().doPost(request, response);

        verify(request).setAttribute("user", "World");
        verify(requestDispatcher).forward(request,response);
    }

    @Test
    public void doPostWithName() throws Exception {
        when(request.getParameter("name")).thenReturn("Dolly");
        when(request.getRequestDispatcher("response.jsp"))
            .thenReturn(requestDispatcher);

        new HelloServlet().doPost(request, response);

        verify(request).setAttribute("user", "Dolly");
        verify(requestDispatcher).forward(request,response);
    }
}

```

<a name="ジョブ設定ファイルを作成する"></a>
### ジョブ設定ファイルを作成する

**「.gitlab-ci.yml」とは**  
「.gitlab-ci.yml」はジョブのテスト手順やビルド手順を記述したGitLab CIのジョブ設定ファイルです。GitLabで管理しているプロジェクト（Gitリポジトリ）のルートディレクトリに配備します。


**.gitlab-ci.yml**

GitLabプロジェクトのルートディレクトリに「.gitlab-ci.yml」を作成します。

```
 vi ~/gitlab-ci-demo/.gitlab-ci.yml
```

**~/gitlab-ci-demo/.gitlab-ci.yml**

```
stages:
  - build
  - package

gradle-build:
  image: java:8-jdk
  stage: build
  script:
    - ./gradlew build
  artifacts:
    paths:
      - build/libs/*.war
    expire_in: 1 week
  tags:
    - docker

docker-build:
  image: docker:latest
  stage: package
  script:
    - docker build -t localhost:5000/runnner-artifact .
    - docker push localhost:5000/runnner-artifact
  tags:
    - docker
```

[Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes | GitLab](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)


|項目|説明|
|---|---|
|stages|stageの実行順序を指定します。|
|gradle-build, docker-build|任意のジョブ名を指定します。|
|image|Docker Executorを使ってジョブを実行する際のDockerイメージを指定します。|
|stage|任意のstageを指定します。|
|script|ジョブの実行コマンドを記述します。|
|artifacts|CI においてビルドが正常に完了した後に入手できる成果物をアーティファクトと呼びます。ジョブの実行結果を他のジョブで使う際に記述します。またここで指定した成果物はzipファイルとして GitLab上からダウンロードすることができます。|
|tags|使用するGitLab Runnerをタグで指定します。|

[はじめてのGitLab-CI - Qiita](http://qiita.com/yurano/items/a7804d987ccff37b1a9d)  
[artifacts / Configuration of your jobs with .gitlab-ci.yml - GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/#artifacts)  
[GitLab CI/CDでiOSアプリをビルドし、ipaを配布する - Qiita](http://qiita.com/enomotok_/items/966144d6bc40ec093ac3#artifacts)


<a name="dockerfileを作成する"></a>
#### Dockerfileを作成する

「.gitlab-ci.yml」で記述した```docker build```を実行する際に使用するDockerfileを作成します。

```
 vi ~/gitlab-ci-demo/Dockerfile
```

```
FROM tomcat:9.0
COPY build/libs/*.war /usr/local/tomcat/webapps
CMD ["catalina.sh", "run"]
```

artifactsで指定した```build/libs/*.war```をtomcatのwebapps配下に展開しています。

<a name="自動テスト・ビルド・デプロイを実行する"></a>
### 自動テスト・ビルド・デプロイを実行する

#### リモートリポジトリへpushする

作成したソースコードをGitLabプロジェクトのリモートリポジトリへpushします。

```
$ git add .
$ git commit -m "test gitlab-ci"
$ git push
```

<a name="ジョブを確認する"></a>
#### ジョブを確認する

リモートリポジトリへpushすると自動でジョブが実行されます。ジョブのステータスは以下の画面から確認することができます。

**Pipelines > Jobs**

![000001-10.png](https://qiita-image-store.s3.amazonaws.com/0/155486/cc77348f-9ec2-dd86-cb8b-2c376bc5bd5c.png)

<a name="dockerレジストリにデプロイされたコンテナを起動する"></a>
## Dockerレジストリにデプロイされたコンテナを起動する

以下のコマンドを入力してDockerレジストリにイメージが追加されているかを確認します。```"runnner-artifact"```が追加されていればジョブが正しく実行されています。

```
$ curl http://localhost:5000/v2/_catalog
{"repositories":["hello-world","runnner-artifact"]}
```
最後に追加されたDockerイメージをコンテナとして起動します。本稿ではtomcatの公開ポートとして8888を指定しています。

```
docker run -it --rm --name artifact -p 8888:8080 localhost:5000/runnner-artifact
```

以下のURLを入力して、「Web Demo」画面が表示されれば成功です。

```
http://[DockerホストのURL]:8888/gitlab-ci-demo/
```

![000001-11.png](https://qiita-image-store.s3.amazonaws.com/0/155486/3dddbd2c-7067-e4d4-c788-6938de933d8c.png)