Jenkinsを使わずにGitLabとSonarQubeを連携してみた

# 背景

ソースコードをcommit/pushした際の自動レビューと、コード品質の可視化のために[SonarQube](https://www.sonarqube.org/)を導入することになった。  
既にソースコードはGitLabで管理しており、[GitLab-CI](https://docs.gitlab.com/ce/ci/)を使ってビルドやテスト、デプロイの自動化まで実装済み。

今さら感はあるが、GitLabとSonarQubeの連携方法について調べてみた。

# Jenkinsは必要か？

調べていくと、どの事例も[Jenkins](https://jenkins.io/)を間に挟んでGitLabとSonarQubeを連携させている。
図にするとこんな感じ。

![mermaid-diagram-20200110143300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/0a256609-db56-0473-bf2d-d0098eddf273.png)

正確には、SonarQube側には`sonar-scanner`という登場人物もいて、実際の解析を行っているのは彼ですが、ココでは細かいことは割愛。

もともとJenkinsを導入済みのプロジェクトであれば、この構成でも良いと思うが、未導入の場合は無駄な構成のように感じた。
実際、私のプロジェクトもJenkinsは導入していなかった。
SonarQubeに加えてJenkinsが必要なため構築に2倍の労力がかかるし、SonarQubeのためだけにJenkinsを動かしておくのも経済的とは思えない。

一方で、Jenkinsを導入したほうが良いケースもありそう。
Jenkinsのジョブのトリガは、きめ細かに設定することができるので、「日次や週次で定期的に実行させたい」といったケースなどであれば、Jenkinsの導入が有効かもしれない。

ただ、私のプロジェクトでは、GitLabへのコードのPushやマージリクエストのタイミングで実行できれば十分だったので、やはり`私のプロジェクトではJenkinsはいらない`という結論に至った。

ということで、次の図のような、よりシンプルな構成で導入を試みることにした。

![mermaid-diagram-20200110143242.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/c771a308-3d84-ac34-07fc-6ad86f9a7d9c.png)

# 環境

* CentOS 7.5.1804
* GitLab CE 11.11.3
* gitlab-runner v12.6.0
* SonarQube Community Edition Version 7.6 (build 21501)

# SonarQubeのインストール

[公式が提供しているDockerイメージ](https://hub.docker.com/_/sonarqube)を使用する。
今回は、簡単に構築するために、[公式のdocker-composeサンプル](https://github.com/SonarSource/docker-sonarqube/blob/master/recipes/docker-compose-postgres-example.yml)を活用する。

## 公式のdocker-composeサンプルをダウンロード

```bash
mkdir SonarQube
cd ./SonarQube
curl https://raw.githubusercontent.com/SonarSource/docker-sonarqube/master/recipes/docker-compose-postgres-example.yml -o docker-compose.yml
```

## docker-compose.ymlをカスタマイズ

以下のように、`docker-compose.yml`をカスタマイズする。

```yaml:docker-compose.yml
version: "2"

services:
  sonarqube:
    # コンテナ名を明示的に指定（必須）
    container_name: sonarqube-core
    # SonarQubeのバージョンを"7.6-community"に指定（必須）
    image: sonarqube:7.6-community
    ports:
      - "9000:9000"
    networks:
      - sonarnet
    environment:
      - sonar.jdbc.url=jdbc:postgresql://db:5432/sonar
    volumes:
      - sonarqube_conf:/opt/sonarqube/conf
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
    # マシンやdockerデーモンの再起動でコンテナが立ち上がるように（お好みで）
    restart: always

  db:
    # コンテナ名を明示的に指定（推奨）
    container_name: sonarqube-postgres
    # postgresのバージョンも固定化（お好みで）
    image: postgres:12.1
    networks:
      - sonarnet
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
    volumes:
      - postgresql:/var/lib/postgresql
      # This needs explicit mapping due to https://github.com/docker-library/postgres/blob/4e48e3228a30763913ece952c611e5e9b95c8759/Dockerfile.template#L52
      - postgresql_data:/var/lib/postgresql/data
    # マシンやdockerデーモンの再起動でコンテナが立ち上がるように（お好みで）
    restart: always

networks:
  sonarnet:
    driver: bridge

volumes:
  sonarqube_conf:
  sonarqube_data:
  sonarqube_extensions:
  postgresql:
  postgresql_data:
```

## SonarQubeを起動

```bash
# 仮想メモリの上限値を変更（省略するとSonarQubeが無限に再起動を繰り返す）
sudo sysctl -w vm.max_map_count=262144

# 起動
docker-compose up
```

## SonarQubeのダッシュボードにアクセス

ブラウザで下記のURLにアクセス。
ユーザ名とパスワードは`admin`でログインできる。

```
http://{your_IP_address}:9000/
```

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

### sonar-gitlab-plugin

GitLabと連携するためのプラグイン。

ネット情報では、SonarQubeのGUI上から検索してインストールできるようだったが、私の環境では検索してもヒットせず。
このため、手作業でインストールを実施。

なお、`sonar-gitlab-plugin`がサポートするSonarQubeのバージョンは`7.6`まで。
`docker-compose.yml`でバージョンを指定したのはこのため。

```bash
# GitHubからパッケージをダウンロード
curl https://github.com/gabrie-allaigre/sonar-gitlab-plugin/releases/download/4.1.0-SNAPSHOT/sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar -o sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar

# パッケージをsonarqube-coreコンテナにコピー
# ※あらかじめdocker-compose.ymlでコンテナ名をつけているので、コンテナの指定が楽にできる
docker cp sonar-gitlab-plugin-4.1.0-SNAPSHOT.jar sonarqube-core:/opt/sonarqube/extensions/plugins
```

SonarQubeのダッシュボードにログインし、`Administration` > `System` > `Restart Server`で、SonarQubeを再起動する。

再起動後、`Administration` > `Marketplace` > `Installed`で、`GitLab`が表示されていればOK。

### その他のプラグイン

主要な言語用のプラグインなどは、プリインストールされている。
インストール済みのプラグインの確認は、`Administration` > `Marketplace` > `Installed`で確認可能。

もし不足している場合は`All`タブで検索して追加する。
SonarQube上で検索してヒットしない場合は、GoogleやGitHubなどで検索し、`sonar-gitlab-plugin`と同様の手順で手動インストールする。

私のプロジェクトの場合、JavaScriptの静的解析のプロファイルに`ESLint`を使用したかったので、`sonar-eslint-plugin`を追加でインストール。
こちらも、SonarQubeのGUI上ではインストールできなかったので、手動でインストールした。

## SonarQubeの設定

### Administration > Configuration > General Settings > GitLab

最低限、以下の項目を設定する。
その他は、必要であれば、個別の環境に合わせて設定する。

|項目|設定値|備考|
|---|---|---|
|GitLab url|{your_GitLab_URL}||
|GitLab User Token|{your_GitLab_User_Token}|[後述の手順](#botアカウントのトークンを取得)で取得したトークン|

### Quality Profiles

各言語の静的解析に使用するプロファイルを選択する。
標準では`Sonar way`という、SonarQube推奨のプロファイルが選択されている。

特にこだわりがない場合や、プロジェクトで決まっているものがなければ、デフォルトのままでも問題ない。
もし、ルールを変更する場合は、使いたいプロファイル横の歯車アイコンから`Set as Default`をクリック。

私の場合は、前述の通りJavaScriptのDefaultプロファイルをESLintに変更した。

### Projects

右上の`＋`アイコンから、新規プロジェクトを作成し、以下の情報を設定。

|項目|設定値|備考|
|---|---|---|
|Project key|{your_repository_name}||
|Display name|{your_repository_name}||

`Analyze your project`画面に遷移するので、`Generate`をクリック。
表示されるトークンをメモしておく。

さらに、作成されたプロジェクトを開き、`Administration` > `General Settings` > `GitLab` > `GitLab Project id`に、GitLab上の解析対象プロジェクトのIDを登録しておく。

# GitLabの設定

## `gitlab-runner`のインストール

`GitLab-CI`を実行するための`gitlab-runner`をインストールする。
既に`Docker executor`のgitlab-runnerが存在する場合、この手順は省略可。

```bash
# docker版の`gitlab-runner`を起動
docker run -d \
    --name gitlab-runner-docker \
    --restart always \
    -v /srv/gitlab-runner-docker/config:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock \
    gitlab/gitlab-runner:v12.6.0

# GitLabにrunnerを登録
docker exec gitlab-runner-docker gitlab-runner register \
    --non-interactive \
    --url {your_gitlab_url} \
    --registration-token {your_gitlab_runner_token} \
    --name "gitlab-runner-docker" \
    --tag-list "autotest-server,docker" \
    --executor "docker" \
    --docker-image "alpine:latest"
```

## SonarQubeが使用するBOTアカウントを設定

SonarQubeが、静的解析結果をコミットやマージリクエストに対してコメントするためのアカウントを作成する。

### BOTアカウントを作成

|項目|設定値|備考|
|---|---|---|
|Name|SonarQube|他の名前でも可。投稿に表示されるので、SonarQubeからの投稿であることが分かりやすいように命名する。|
|Username|SonarQube|他の名前でも可。|
|Email||任意のメールアドレス|
|Avatar||SonarQubeのアイコンなどを設定するとわかりやすい|

### BOTアカウントに権限を付与

作成したBOTアカウントに、対象プロジェクトへの`Developer`権限を付与する。

### BOTアカウントのトークンを取得

作成したBOTアカウントでログインし、`Settings` > `Access Tokens`で新規のアクセストークンを発行する。

|項目|設定値|備考|
|---|---|---|
|Name|SonarQube|任意の名前で可。|
|Expires at|（空欄）||
|Scopes|api||

発行したトークンは、SonarQubeの `Administration` > `Configuration` > `General Settings` > `GitLab` > `GitLab User Token`に登録する。

## SonarQubeのトークンをgitlab-runner用の環境変数に登録

解析対象のプロジェクトの、`Settings` > `CI / CD` > `Variables`にSonarQubeのトークンを追加する。

|項目|設定値|備考|
|---|---|---|
|Type|Variable||
|Key|SONARQUBE_TOKEN||
|Value|{your_sonarqube_token}|[前述の手順](#projects)で取得したトークン|

## `sonar-project.properties`の作成

解析対象のプロジェクト直下に、下記のファイルを作成する。
設定値は、各環境に合わせて変更が必要。

```.properties:sonar-project.properties
# プロジェクト情報（SonarQubeで設定した内容）
sonar.projectKey={your_repository_name}
sonar.projectName={your_repository_name}

# プロジェクトのバージョン（SonarQubeのGUI上で解析結果をバージョンごとに管理可能）
sonar.projectVersion=1.0

# ソースコードのパス
sonar.sources=src
# ソースコードから除外する条件
sonar.exclusions=**/*.spec.js

# テストコードのパス
sonar.tests=src,config
# テストコードに加える条件
sonar.test.inclusions=**/*.spec.js

# 言語を指定しない場合、自動で言語認識をしてくれる。
# 自動認識の場合は、複数言語の認識にも対応。(Javascript + CSSなど)
# sonar.language=js

# カバレッジレポートのパス
sonar.javascript.lcov.reportPaths=coverage/lcov.info

# エンコーディング
sonar.sourceEncoding=UTF-8
```

## `.gitlab-ci.yml`の作成

GitLab-CIが実行する処理を定義する。
`sonar-scanner`という、SonarQubeの解析処理を実行するプログラムが実施されるように定義していく。

なお、sonar-scannerについては[公式のDockerイメージ](https://github.com/SonarSource/sonar-scanner-cli-docker)を使用する。
インストールして使うこともできるが、不用意に環境を汚したくないし、導入が簡単なDocker版を採用。
公式のDockerイメージは、2020/01/10時点で未だにBeta版だが、今回の用途では何ら問題なく使用することができた。

```yaml:.gitlab-ci.yml
stages:
  - analysis
analysis:
  stage: analysis
  image:
    name: sonarsource/sonar-scanner-cli:4.2
    entrypoint: [""]
  allow_failure: true
  variables:
    SONAR_HOST_URL: http://{your_SonarQube_IP}:9000
    SONAR_TOKEN: ${SONARQUBE_TOKEN}
    SONAR_PROJECT_BASE_DIR: ${CI_PROJECT_DIR}
  script:
    - |-
      sonar-scanner \
        -Dsonar.gitlab.project_id=$CI_PROJECT_ID \
        -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME \
        -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA
  tags:
    - docker
```

公式のDockerイメージは、`docker run`コマンドで使用されることを前提に作成されており、渡せる環境変数やオプションが限られている。
今回は、GitLabのプロジェクトID、ブランチ名、コミットハッシュを渡したかったため、`entrypoint`を上書きしてコンテナ内で`sonar-scanner`を直接実行するようにしている。

# いよいよ実行

ここまで設定した状態で、GitLabにコードの変更がPushされると、`gitlab-runner`で`sonar-scanner`が実行され、静的解析の結果がSonarQubeに登録されるとともに、GitLabのコミットやマージリクエストに対するコメントとしても投稿される。

各登場人物の仕事を図に表すと、こんな感じ。

![mermaid-diagram-20200110143146.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/29c06cf9-5792-0299-2f8c-7b178c11d168.png)

もし、静的解析の結果をSonarQubeのダッシュボードに登録したくない場合は、`-Dsonar.analysis.mode=preview`というオプションを追加すると良い。

# その他のTips

## `SonarQube`BOTの投稿コメントに、SonarQubeダッシュボードへのリンクを追加する

SonarQube標準の設定では、GitLabに投稿されるコメントにSonarQubeダッシュボードへのリンクは含まれない。
しかし、静的解析結果を読んだら、グラフィカルなダッシュボードで詳細を見たくなるはず。

以下の設定をすることで、SonarQubeダッシュボードへのリンクを追加することができる。

`Administration` > `Configuration` > `General Settings` > `GitLab` > `Global template`

```Markdown
You can check the details on the [SonarQube dashboard](${sonarUrl}). (Login username and password is `admin`.)  
The summary of the analysis results is as follows.

---

<!-- ここに、以下のURLからコピーしたマークダウンを貼り付ける -->
<!-- https://github.com/gabrie-allaigre/sonar-gitlab-plugin/blob/master/templates/global/default.md -->

```

`Administration` > `Configuration` > `General Settings` > `GitLab` > `Inline template`

```Markdown
You can check the details on the [SonarQube dashboard](${sonarUrl}). (Login username and password is `admin`.)  
The summary of the analysis results is as follows.

---

<!-- ここに、以下のURLからコピーしたマークダウンを貼り付ける -->
<!-- https://github.com/gabrie-allaigre/sonar-gitlab-plugin/blob/master/templates/inline/default.md -->
```