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

https://gitlab.com/uploads/-/system/project/avatar/250833/runner_logo.png
GitLab.org / gitlab-ci-multi-runner · GitLab

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

動作環境

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

ホストマシン

分類 項目 バージョン
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

前提条件

  • Dockerホストマシン上でGitLabコンテナが起動していること
  • 以下の設定(値は任意)の初期プロジェクトが準備できていること
項目
グループ名 example
プロジェクト名 gitlab-ci-demo
  • 開発者はGitLabのプロジェクトにおいてmasterブランチにpushすることが可能な権限(Owner以上)を有していること
  • 開発者はプロジェクトのリモートリポジトリとssh接続できること
  • 開発者のマシン環境にGradleとOpen JDKが予めインストールされていること

「GitLab CI」を理解する

「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

「GitLab Runner」とは

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

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

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

000001-12.png

Coding the Next Build | SlideDeck.io

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
  • その他のExecutor
    その他にも以下のExecutorがあります。
    Executors | GitLab.org / gitlab-ci-multi-runner · GitLab
    • Docker Machine and Docker Machine SSH (auto-scaling)
    • Parallels
    • VirtualBox
    • SSH
    • Kubernetes

サーバー管理者が行う作業

Dockerレジストリを構築する

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

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

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

プライベートなDockerレジストリサーバーをコンテナで立てる - Qiita

Dockerプライベートリポジトリ(Docker Registry)構築レシピ | Developers.IO

GitLabサーバーとGitLab Runnerを連携する

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

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

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

GitLab RunnerでDockerコンテナをビルドする方法を以下に示します。本稿では「Docker socket binding」を利用します。

Using Docker Build - GitLab Documentation

  • 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
    Dockerでホストを乗っ取られた - Qiita
  • shell executor(GitLab非推奨)
    Runnerのexecutorオプションにshellを指定し、シェルスクリプトからDockerコマンドを実行する方法です。この方法を利用するにはgitlab-runnerユーザーをdockerグループに追加する必要があり、gitlab-runnerユーザーはDockerコンテナを利用して間接的にDockerホストにある全てのファイルにアクセスすることができるため、GitLabはこの方法を非推奨としています。
    On Docker security: 'docker' group considered harmful - Andreas Jung

本稿で「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オプションを使う方法が考えられます。

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

Use Docker socket binding | Using Docker Build - GitLab Documentation

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 ジョブを実行するコンテナがベースとするデフォルトの
Dockerイメージを指定します。
DOCKER_VOLUMES ジョブ用のコンテナ実行時にマウントするボリュームを
指定します。

本稿では「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

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

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

また「Run untagged jobs」にチェックを入れることで、タグが付与されていないジョブに対してもGitLab Runnerを実行することができます。

開発者が行う作業

Gradleプロジェクトを作成する

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

GitLabプロジェクトをクローンする

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

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

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

$ ls
gitlab-ci-demo

.gitignoreを作成する

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

000001-08.png
gitignore.io - Create Useful .gitignore Files For Your Project

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

gitlab-ci-demo/.gitignore

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

Gradleラッパーを作成する

GradleラッパーはGradleプロジェクトをビルドする時に利用するラッパースクリプトです。Gradleラッパーが提供するシェルスクリプト./gradlewを実行することでGradleが自動的にダウンロードされ、Gradleコマンドと同じ処理を実行できます。Gradleラッパーはプロジェクトを作成した時と同じバージョンのgradleを自動でインストールします。同じバージョンを使用することで、開発者によって異なる環境が原因でビルドを実行することができないといった状況を回避することができます。

Gradle初心者によるGradle事始め - Qiita

第62章 Gradleラッパー

以下のコマンドを入力し、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 

外部パッケージを定義する

デフォルトの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としてwareclipseを追加し、またdependenciesmockito-corejavax.servlet-apiを追加しました。

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

$ gradle dependencies

「apply plugin」とは

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

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

第22章 標準Gradleプラグイン

「repositories」とは

本稿ではデフォルトと同じ「jcenter」というリポジトリを使用しています。「repositories」で指定したリポジトリからパッケージがダウンロードされます。「jcenter」以外で良く使用されるリポジトリとして「mavenCentral」があります。ただし最近は「jcenter」を指定することが推奨されているようです。

Android Studio – Migration from Maven Central to JCenter | Blog @Bintray

「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章 依存関係管理の基本

Dependency management

Eclipseプロジェクトを作成する

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

$ ./gradlew eclipse

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

  • .classpath
  • .project
  • .settings

第38章 Eclipse プラグイン

EclipseのGradleプロジェクト作成方法比較 - Qiita

備忘録!GradleでWARを作る - Qiita

Webアプリケーションを作成する

Gradleが公開している「Building Java Web Applications | Gradle Guides
の例に従ってソースコードを配備します。

基本的に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

サーブレットを配備する

まずはサーブレットを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(14.アノテーション)

urlPatternsの書式

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

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

1. サーブレットの設定と実行 (3) | TECHSCORE(テックスコア)

「load-on-startup」とは

サーブレットは初めてリクエストされた時にインスタンス化されるため、最初のリクエストがあった時に初期化パラメータを読み込むことができます。load-on-startup要素を指定すると、アプリケーションがロードされた時に、指定したサーブレットがインスタンス化され、サーブレット内のinit()メソッドが実行されます。load-on-startupタグに定義している数字はロード順です。アプリケーションロード時に複数のサーブレットをロードしておく必要がある場合は、1、2、3という風にロード順番を設定します。

load-on-startupの利用-サーブレット(servlet)入門:IT TRICK

htmlとjspを配備する

Gradleのwarプラグインはデフォルトでsrc/main/webapp配下のファイルをjarに含める設定になっています。初期のディレクトリ構成では「webapp」ディレクトリが存在しないため新規作成します。

第10章 Webアプリケーションクイックスタート

$ 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>

テストクラスを配備する

$ 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);
    }
}

ジョブ設定ファイルを作成する

「.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

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

はじめてのGitLab-CI - Qiita

artifacts / Configuration of your jobs with .gitlab-ci.yml - GitLab Documentation

GitLab CI/CDでiOSアプリをビルドし、ipaを配布する - Qiita

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配下に展開しています。

自動テスト・ビルド・デプロイを実行する

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

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

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

ジョブを確認する

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

Pipelines > Jobs

000001-10.png

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