【Java版】CI(継続的インテグレーション)ツール導入ガイド:第6回 Gitlab による CI の導入

JavaによるCI導入ガイドも最終回の第6回目となる。これまで Spock によるユニットテストや jacoco によるカバレッジ計測、Checkstyle によるコードスタイル検証の紹介をした。紹介した継続的インテグレーション(CI)のツールを GitLabに導入する。

目次

1. Gitlab 設置と初期設定

Gitlab の説明は既存の記事にて。 記事 を参考に Gitlab を導入する。 実行環境の保存, Runner の登録 は操作が異なるので説明する。

実行環境の保存

今回のアプリケーションのビルドと検証に必要な環境を Docker で作成する。 Gitlab のホスト上で空のディレクトリを作り、以下の Dockerfile を保存する。

Dockerfile(on Gitlab)

FROM openjdk:10.0.2-slim

ENV DEBCONF_NOWARNINGS yes

# 必要なパッケージのインストール
RUN echo "deb http://httpredir.debian.org/debian jessie-backports main contrib non-free" >> /etc/apt/sources.list && \
    apt-get update && \
    mkdir -p /usr/share/man/man1 && \
    apt-get install --yes --no-install-recommends \
      postgresql-client-10 docker.io curl python2.7 psmisc wget firefox

# Docker compose のインストール
RUN curl -L "https://github.com/docker/compose/releases/download/1.12.0/docker-compose-$(uname -s)-$(uname -m)" \
      -o /usr/bin/docker-compose && \
    chmod +x /usr/bin/docker-compose

# Chrome のインストール
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
    apt-get install --yes --no-install-recommends \
      fonts-liberation libappindicator3-1 libasound2 libxss1 lsb-release xdg-utils && \
    dpkg -i google-chrome-stable_current_amd64.deb && \
    rm -f google-chrome-stable_current_amd64.deb

# Infer のインストール
# ダウンロードが遅いので、何回も実行する場合は
# 以下のようにあらかじめダウンロードしたものを使うほうが良いかも
#
# COPY infer-linux64-v0.15.0.tar.xz /opt
# RUN cd /opt && \
#     tar xJf infer-linux64-v0.15.0.tar.xz && \
#     ...
RUN cd /opt && \
    curl -sL \
      https://github.com/facebook/infer/releases/download/v0.15.0/infer-linux64-v0.15.0.tar.xz | \
      tar xJ && \
    rm -f /infer && \
    ln -s ${PWD}/infer-linux64-v0.15.0 /infer && \
    rm -f /opt/infer-linux64-v0.15.0.tar.xz
ENV INFER_HOME /infer
ENV PATH ${INFER_HOME}/bin:${PATH}

CMD '/bin/bash'

Dockerfile を作ったディレクトリで、以下のコマンドを発行し。イメージをビルドする。

sudo docker build . -t jdk-infer-browser

jdk-infer-browser というタグでイメージが保存される。

Dockerfile のコメントにもあるが、infer のイメージの取得に時間がかかるため、上記のビルドには結構な時間がかかる。 必要なら、予め infer のビルド済みファイルを取得して Dockerfile のあるディレクトリに置いておき、 コメントの指示のようにコマンドを書き換えるのが良いだろう。

Runner の登録

上記記事と行うことは殆ど変わらないが、以下の点を読み替える。

<環境に付けるタグ名> -> jdk,infer,browser

<実行するDockerのイメージ> -> jdk-infer-browser

プロジェクトの作成

GitLab のトップページに移動し、‘Create a projec’ から新しいプロジェクトを作る。

2. Gitlab ジョブの設定

開発環境に戻り、Gitlab のジョブファイルを作成していく。

以下のファイルを作成する。Infer の実行方法のみ開発環境と異なる。 (ビルド時にビルド処理を Infer にキャプチャさせておくようにする。解析時にはキャプチャしたビルド情報をもとに解析を行わせる)

.gitlab-ci.yml

# 利用する Docker image
image: jdk-infer-browser

# Runner 上の環境変数
variables:
  # ストレージドライバの指定 
  DOCKER_DRIVER: overlay2
  # 以下データベース接続設定
  POSTGRES_HOST: messageboard-ci-postgres
  POSTGRES_DB: java_ci
  POSTGRES_USER: java_ci
  POSTGRES_PASSWORD: "password"
  POSTGRES_PORT: 5432

# タスクステージの設定
stages:
- build
- cleanup_build
- unit_test
- code_check
- e2e_test
- deploy

# 各タスクの処理前に実行するコマンド
# gradle のキャッシュを gitlab にキャッシュさせる
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle

# プロジェクト全体のキャッシュの設定
cache:
  paths:
  - build/
  - infer-out/
  - .gradle

# データベースが必要なタスクのための
# サービスの設定を YAML エイリアスにする
.services:
- &postgres-service
  name: postgres:9.6
  alias: messageboard-ci-postgres

# データベースの初期化処理を
# YAML エイリアスにする
.scripts:
- &init-postgres >
  PGHOST=$POSTGRES_HOST
  PGPORT=$POSTGRES_PORT
  PGUSER=$POSTGRES_USER
  PGPASSWORD=$POSTGRES_PASSWORD
  psql $POSTGRES_DB < ./sql/scheme.sql

# 各タスクのタグは同じにするので
# YAML エイリアスにする
.tags:
- &runner-tags
  - jdk
  - infer
  - browser


# ここからタスクの登録
# ビルドタスク
build:
  stage: build
  tags: *runner-tags
  script:
  # ビルド処理コマンド
  # この時点で infer にキャプチャをさせておく
  - infer capture -a checkers --eradicate -- ./gradlew build -x check

  # ビルド生成物の指定
  artifacts:
    when: on_success
    paths:
    - build/libs

# ビルドが失敗したときのクリーンアップタスク
cleanup_build_job:
  stage: cleanup_build
  tags: *runner-tags
  script:
  - ./gradlew clean
  - rm -fr ./infer-out
  when: on_failure

# ユニットテスト実行タスク
unit_test:
  stage: unit_test
  tags: *runner-tags
  services:
  # データベースサービスを起動する
  - *postgres-service
  before_script:
  # 初期化スクリプトを実行する
  - *init-postgres
  script:
  # データベース環境を指定してユニットテストを走らせる
  - >
    ./gradlew
    -Pdburl=jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB
    -Pdbuser=$POSTGRES_USER
    -Pdbpass=$POSTGRES_PASSWORD
    test --info 
  artifacts:
    when: always
    paths:
    - build/reports/tests/test
    - build/jacoco
  # build タスクに依存させる
  dependencies:
  - build

# スタイル解析処理タスク
checkstyle:
  stage: code_check
  tags: *runner-tags
  script:
  - ./gradlew checkstyleMain --info
  artifacts:
    when: always
    paths:
    - build/reports/checkstyle
  # build タスクに依存させる
  dependencies:
  - build

# カバレッジ解析タスク
coverage:
  stage: code_check
  tags: *runner-tags
  script:
  - ./gradlew jacocoTestReport --info 
  artifacts:
    when: always
    paths:
    - build/jacocoHtml
  dependencies:
  # unit_test タスクに依存させる
  - unit_test

# バグ解析タスク
infer:
  stage: code_check
  tags: *runner-tags
  script:
  # キャプチャの結果をもとに解析する
  - infer analyze --fail-on-issue --eradicate 
  after_script:
  # HTML レポートを出力させる
  - infer explore --html
  artifacts:
    when: always
    paths:
    - infer-out/report.html
  dependencies:
  - build

# e2e テストタスク
e2e_test:
  stage: e2e_test
  tags: *runner-tags
  # Spring のテストが起動するため
  # DB を容易しておく
  services:
  - *postgres-service
  before_script:
  # DB 初期化
  - *init-postgres
  # アプリケーションサーバをバックグラウンドで起動させる
  - >
    /usr/bin/java
    -Dspring.datasource.url=jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB
    -Dspring.datasource.username=$POSTGRES_USER
    -Dspring.datasource.password=$POSTGRES_PASSWORD
    -jar ./build/libs/message-board-0.1.0.jar &
  script:
  # e2e テストを実行させる
  - >
    ./gradlew
    -Pdburl=jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB
    -Pdbuser=$POSTGRES_USER
    -Pdbpass=$POSTGRES_PASSWORD
    -Pci=yes e2e:e2eTest --info
  after_script:
  # アプリケーションサーバを止める
  - fuser 8080/tcp -k || true
  artifacts:
    when: always
    paths:
    - e2e/build/reports/tests
  # build タスクに依存させる
  dependencies:
  - build

# 各タスクの成果物(レポートなど)をまとめて
# ブラウザからのアクセスができるようにする
pages:
  stage: deploy
  tags: *runner-tags
  script:
  - mkdir -p public
  - cp -r build/reports/checkstyle public/checkstyle
  - cp -r build/reports/tests/test public/unit_test
  - cp -r build/jacocoHtml public/coverage
  - cp -r infer-out/report.html public/infer
  artifacts:
    paths:
    - public

これで CI を回す準備ができた。 先程作ったプロジェクトに git push を行う。 自動的に CI の処理が開始される。 すると、Gitlab の当該プロジェクト内 ‘CI/CD’ 画面で、以下のようにタスクの状態を確認できるようになる。

しばらくすると、以下のように実行が完了する。


終わりに

これでCIを回すことができた。このあとは、アプリケーションを改良していきながら CI の良さを実感しよう。

改良したい点

  • リファクタリング
    • POJO クラスを Lombok を使ってスッキリさせる
    • MessageForm クラスは Immutable にできる
    • MessageBoardController にビジネスロジックが入っているので、Facade クラスを作ってそちらに追い出す
    • コマンドのパースをもう少しマシにする
  • セキュリティ対策
    • CSRF 対策
  • 機能の追加
    • 日付の表示、ハンドル名、削除機能…
  • API化
    • フロントエンドをリッチにする
  • and something you wants.

社内サーバにリモートリポジトリを作るのも一つですが、「開発にまつわる面倒事」をこの際全部、tracpath(トラックパス)に任せてみませんか?
バージョン管理サービス・プロジェクト管理サービスの「tracpath(トラックパス)」では、
ユーザー5名、リポジトリ数3つまで、無料で利用可能です。

さっそく実務でも使って見ましょう。
自らも開発を行う会社が作ったからこそ、開発チームの「作る情熱」を支える、やるべきことに集中出来るサービスになっています。
エンタープライズ利用が前提のASPサービスなので、セキュリティも強固です。