SonarQubeで始める静的コード解析

アプリボットの技術基盤グループであるA.R.T.(Applibot Root Technologies)が導入を進めている、SonarQube というプログラムコードの解析と自動レビューをする仕組みについて紹介を行います。
ちなみに、SonarQubeにはクラウド版のSonarCloudもあり、オープンソースプロジェクトだけでも2017年9月時点で5000以上のプロジェクトから使用されています。

1. SonarQubeをなぜ導入しようと考えたか

担当していたJava言語プロジェクトでプログラムコード解析のために、FindBugsやCheckStyle等、複数のツールを使用してJenkinsサーバーで解析をしていました。
しかし、設定が煩雑なうえJenkins上での解析結果も見づらいため、改善方法を検討していました。

SonarQubeの事を知り試してみたところ、設定がしやすく解析結果もわかりやすいため、従来の解析方法より良いと感じました。
また、GitLabやJenkinsと連携することで、GitLabでのマージリクエストを自動レビューしコメントをすることや、プログラム実装中にIDE上でリアルタイム解析(SonarLint)が実現可能なことも導入しようと考えたポイントです。

SonarQubeを導入して実現した仕組みの全体像は以下をご覧ください。

2. SonarQube Serverの構築

SonarQube Serverの構築には、ローカル環境でも手軽に試せるdocker-composeを使用しました。
なお、docker-composeはVersion2フォーマットの定義ファイルを使うため以下のソフトウェアが必要です。

必須ソフトウェア

  • docker (1.10.0+)
  • docker-compose (1.10.0+)

2–1. docker-compose 定義ファイルの作成

公式docker-compose 定義ファイルを参考にしましたが、データベースはMySQLとしました。

docker-compose.yml

version: "2"
services:
  sonarqube:
    image: sonarqube:6.4
    ports:
      - '9000:9000'
    networks:
      - sonarnet
    environment:
      - SONARQUBE_JDBC_USERNAME=sonar
      - SONARQUBE_JDBC_PASSWORD=sonar
      - SONARQUBE_JDBC_URL=jdbc:mysql://db:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
    volumes:
      - sonarqube_conf:/opt/sonarqube/conf
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_bundled-plugins:/opt/sonarqube/lib/bundled-plugins
  db:
    image: mysql:5.7
    command:
      mysqld
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --max_allowed_packet=64MB
    networks:
      - sonarnet
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_USER=sonar
      - MYSQL_PASSWORD=sonar
      - MYSQL_DATABASE=sonar
    volumes:
      - mysql:/var/lib/mysql
networks:
  sonarnet:
    driver: bridge
volumes:
  sonarqube_conf:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_bundled-plugins:
  mysql:

コマンドラインからdocker-compose up -dを実行するとSonarQube Serverが起動します。その後docker-compose logsを実行すると起動ログの確認ができます。

初回起動時は、MySQLコンテナの初回準備に時間がかかりSonarQubeコンテナからMySQLに接続できない場合があります。
その場合はSonarQubeコンテナを再起動してみます。SonarQubeコンテナの再起動は、コマンドラインからdocker-compose restart sonarqube を実行です。

起動したらブラウザで http://localhost:9000 を開きSonarQube Serverにアクセスしてみます。

以上でローカル環境での構築は完了です。実サーバーでも同様な手順で構築ができます。

2–2. SonarQube Serverの設定

まず、GitLabにbotアカウントを作成しプライベートトークンを発行しておきます。 botアカウントは自動レビューをした結果をコメントする時に使用します。

2–2–1. SonarQube プラグイン設定

SonarQube Serverはプラグインをインストールすることで以下の様なことができます。

  • 外部アナライザの追加 ( Findbugs / Checkstyle / PMD 等 )
  • 解析対象プログラム言語の追加( Java / CSS / JS / Python / C# / PHP 等 )
  • ユーザー認証( Google / GitHub / GitLab / LDAP 等 )
  • 日本語化( 残念ながら一部です )

今回はGitLabと連携するために以下のプラグインをインストールします。
GitLab

プラグインのインストール機能は System administrators 権限のユーザーでログイン後、[Administration] -> [System] -> [Update Center] -> [Available] とメニューを辿ります。
そこで GitLab プラグインをインストールし、SonarQube Serverを再起動します。

次に、[Administration] -> [Configuration] -> [General Settings] -> [GitLab] とメニューを辿り、以下の項目を設定します。

  • GitLab User Token
    • 事前に準備しておいたbotアカウントのプライベートトークンを設定します。
  • GitLab url
    • GitLabサーバーのURLを設定します。

これでSonarQube Serverの構築及び設定は完了です。

次は、Java言語のMavenプロジェクトを対象に設定を進めます。

3. SonarQube によるプログラムコード解析の設定

Java言語のMavenプロジェクトで、プログラムコード解析を実行するための設定を紹介します。
なお、Java言語以外でもSonarQube Scannerを使うことでコマンドラインからプログラムコード解析が可能です。

Mavenのpom.xmlファイルに以下の設定を追加します。 propertiesセクションは以下になります。

<properties>
  <!-- maven-surefire-plugin設定(plugin設定でargLineを既に設定している場合は、既存設定の先頭に`@{jacocoArgs}`を追記) -->
  <argLine>@{jacocoArgs}</argLine>
  <!-- sonar-qube設定 -->
  <!-- 【例】プログラムコード解析から除外したいファイルを指定する -->
  <sonar.exclusions>
    **/sample/*,
    **/*Test.*,
    **/*.js,
  </sonar.exclusions>
  <!-- 【例】 プロジェクト名設定`{分類名}/${project.artifactId}`等 -->
  <sonar.projectName>sample-project/${project.artifactId}</sonar.projectName>
  <!-- 【例】 SonarQube Server のURL設定 -->
  <sonar.host.url>https://sonarqube.sample.com</sonar.host.url>
  <!-- SonarQube Server側の設定で`Force user authentication`をtrueにした場合は、
       sonar.login に何れかのユーザートークンを設定する必要があります。 -->
</properties>

pluginsセクションは以下になります。

<plugins>
  <!-- テストコードカバレッジ計測用プラグイン設定 -->
  <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.9</version>
    <configuration>
      <propertyName>jacocoArgs</propertyName>
    </configuration>
    <executions>
      <execution>
        <id>prepare-agent</id>
        <phase>test-compile</phase>
        <goals>
          <goal>prepare-agent</goal>
        </goals>
      </execution>
      <execution>
        <id>report</id>
        <phase>prepare-package</phase>
        <goals>
          <goal>report</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
  <!-- SonarQube Scanner実行用プラグイン設定 -->
  <plugin>
    <groupId>org.sonarsource.scanner.maven</groupId>
    <artifactId>sonar-maven-plugin</artifactId>
    <version>3.3.0.603</version>
  </plugin>
</plugins>

4.Jenkinsジョブの作成

プログラムコード解析ではテストカバレッジの計測も行うため、テストコードを実行します。
テストコードで接続しているRDBやNoSQLを用意するため、docker-compose で環境を準備しました。

なお、docker-composeはVersion2フォーマットの定義ファイルを使うため以下のソフトウェアが必要です。

必須ソフトウェア

  • docker (1.10.0+)
  • docker-compose (1.10.0+)

4–1. docker-compose 定義ファイルの作成

以下の構成でテストコード実行環境を準備しました。
Jenkins上で実行することを前提としているため、Jenkinsジョブ上で得られる環境変数を使用しています。

準備したdocker-compose.ymlは対象Gitリポジトリにコミットしておきます。

# Jenkinsジョブ上で得られる以下の環境変数を使用
# - $UID: Jenkinsジョブ実行ユーザーID
# - $JENKINS_HOME: Jenkinsジョブ実行ユーザーのホームパス
# - $WORKSPACE: Jenkinsジョブ実行ワークスペースパス
version: "2"
services:
  # mavenコンテナ
  maven:
    image: maven:3.5.0-jdk-8
    command: /bin/bash
    tty: true
    environment:
      - MAVEN_OPTS=-Xmx1536m
      - MAVEN_CONFIG=$JENKINS_HOME/.m2
    # mavenのローカルリポジトリ(.m2)を共有するためにJenkins Homeと対応付け
    volumes:
      - $JENKINS_HOME:$JENKINS_HOME
    # maven実行時結果をJenkinsジョブのWorkspaceに出力するように設定
    working_dir: $WORKSPACE
  # mysqlコンテナ(mavenコンテナから、127.0.0.1:3306 で接続)
  mysql:
    image: mysql:5.6.35
    network_mode: service:maven
    # 文字コード設定やMySQLを厳格モードにするため等の設定
    command:
      mysqld
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --loose_performance_schema=OFF
      --loose_explicit_defaults_for_timestamp
      --loose_table_open_cache_instances=8
      --sql_mode=TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY
      --max_allowed_packet=16MB
    environment:
      # rootパスワード
      - MYSQL_ROOT_PASSWORD=******
      # 最初に準備しておくdatabase(無くても良い)
      - MYSQL_DATABASE=hoge
    # データベースマイグレーション実行用にJenkinsジョブと対応付け
    volumes:
      - $WORKSPACE:$WORKSPACE
    working_dir: $WORKSPACE
  # redisコンテナ(mavenコンテナから、127.0.0.1:6379 で接続)
  redis:
    image: redis:3.2.4
    network_mode: service:maven
  # memcachedコンテナ(mavenコンテナから、127.0.0.1:11211 で接続)
  memcached:
    image: memcached:1.4.34
    network_mode: service:maven

4–2. 日次プログラムコード解析ジョブ

プログラムコード解析を行い、解析結果をSonarQube Serverに登録する日次ジョブを作成します。
Jenkinsジョブを作成し、日次実行するように設定します。

Gitlabから対象Gitリポジトリの取得を定義します。
${host}等のプレースホルダは対象Gitリポジトリを示す値に変更してください。

次にシェルスクリプトを定義します。

# docker-compose.ymlの配置場所設定(例)
export COMPOSE_FILE=${WORKSPACE}/docker-compose.yml
# ジョブ毎にdockerコンテナを分ける設定(ジョブの並列実行も可能となる)
export COMPOSE_PROJECT_NAME=${BUILD_TAG}
# docker環境立上げと停止トラップ設定
docker-compose up -d
trap "docker-compose down" EXIT
# mysql server起動待ち
sleep 10
docker-compose logs
# ここでデータベースマイグレーションの実行(例)
docker-compose exec -u $UID -T mysql \
  bash -c "hoge"
# mavenコマンド実行
docker-compose exec -u $UID -T maven \
  mvn clean verify sonar:sonar -B -Dmaven.test.failure.ignore=true -DskipTests=false -DfailIfNoTests=false -Duser.home=${JENKINS_HOME}
このジョブの作成は完了です。
最後にジョブを実行し、プログラムコード解析結果がSonarQube Serverに登録されることを確認します。

4–2–1. 日次プログラムコード解析結果イメージ

SonarQube Serverに登録された解析結果はこのような形で確認できます。
※画像はSonarQube Server Version:6.6 のため画面が若干違います。

画面引用: https://next.sonarqube.com/sonarqube/projects

プロジェクト一覧画面

複数のプロジェクトを一覧で確認できます。

ダッシュボード画面

コード解析結果が集約されて、ひと目でわかります。

測定値画面

コード解析結果の種類毎にどのファイルが問題を多く持っているか等、わかりやすく可視化されます。

問題画面

問題のある箇所が、コード上でわかりやすく表示されます。

4–3. GitLabへの自動レビューコメントジョブ

GitLabでのマージリクエストや、特定のブランチへプッシュした際に、4–2.日次プログラムコード解析ジョブ でSonarQube Serverに登録されているプログラムコードからの変更分を自動レビューしコメントをするジョブを作成します。
GitLabのWebhookを使用するために、Jenkinsに以下のプラグインが入っていることを前提とします。

必須Jenkinsプラグイン
GitLab Plugin

Jenkinsジョブを作成し、Gitlabから対象Gitリポジトリの取得を定義します。
${gitlabSourceRepoURL}等のプレースホルダは、GitLabのWebhook受信時に自動設定されるためこのまま使用できます。
対象Gitリポジトリが複数あってもJenkinsジョブが1つで対応できるように、Check out to a sub-directory でGitリポジトリ毎にディレクトリを分けました。

次に、GitLabのWebhookの受信設定をします。 赤い箇所に表示されるURLはGitLab側を設定する時に使用するのでメモしておきます。

この設定で以下のWebhookを受けることが可能になります。

  • マージリクエストの作成( 作業中(WIP)マージリクエストも含む )
  • マージリクエストの更新
  • develop ブランチへのプッシュ

次にシェルスクリプトを定義します

シェルスクリプト

# docker-compose.ymlの配置場所設定(例)
export COMPOSE_FILE=${WORKSPACE}/docker-compose.yml
# ジョブ毎にdockerコンテナを分ける設定(ジョブの並列実行も可能となる)
export COMPOSE_PROJECT_NAME=${BUILD_TAG}
# レビュー対象コミットSHA値の抽出
if [ "${gitlabTargetBranch}" = "${gitlabSourceBranch}" ]; then
  # PUSH
  FROM_COMMIT=${gitlabBefore}
  TO_COMMIT=${gitlabAfter}
else
  # MERGE
  FROM_COMMIT=origin/${gitlabTargetBranch}
  TO_COMMIT=origin/${gitlabSourceBranch}
fi
cd ${gitlabSourceRepoName}
COMMIT_SHAS=$(git log --pretty=format:%H ${FROM_COMMIT}..${TO_COMMIT} | tr '\n' ',')
# mavenコマンド実行(sonar.analysis.mode=preview とすることで、解析結果はSonarQube Serverに登録しない)
docker-compose run -u $UID -T maven \
  mvn clean verify sonar:sonar -B -f${gitlabSourceRepoName} -Dsonar.analysis.mode=preview -Dsonar.gitlab.commit_sha=${COMMIT_SHAS} -Dsonar.gitlab.ref_name=${gitlabSourceBranch} -Dsonar.gitlab.project_id=${gitlabSourceNamespace}/${gitlabSourceRepoName} -Dunique_issue_per_inline=true -Duser.home=${JENKINS_HOME}

このジョブの作成は完了です。

5. GitLabのWebhook設定

GitLabでのマージリクエストやプッシュをJenkinsに連携するWebhookの設定をします。
対象GitリポジトリのWebhooks設定を開き、赤い箇所に 4–3.GitLabへの自動レビューコメントジョブ 作成時にメモしておいたURLを入力してWebhookを作成します。

なお、Webhook設定するためには対象GitリポジトリのMaster権限以上が必要です。

Webhookを作成したら、 Test を押してJenkinsに接続できるか確認します。

5–1. GitLabのへの自動コメント用botアカウントを追加

対象Gitリポジトリのメンバーに 2–2. SonarQube Serverの設定 で準備したbotアカウントをDeveloper権限で追加します。

以上でSonarQube、Jenkkns、GitLabで連携し、プログラムコードの自動レビューができるようになりました。

5–1–1. GitLabのへの自動コメントイメージ

GitLab上でMerge Requestを出すと自動レビューされ、このような形でコメントされます。
画面引用: Sonar Gitlab Plugin

  • Comment commits
    • コミットに対しての集約コメントです。コード行に対してのコメント概要が集約されています。
  • Comment line
    • コード行に対してコメントされます。
  • Add build line
    • コード解析した結果、致命的な問題があると failed になります。

6. IDEへのSonarLint設定

SonarLint はIDE上でプログラムコードのリアルタイム解析を行うIDE拡張ツールです。

EclipseVisual Studio Code等の、各種IDEに対応しています。
ここではEclipseでの使用方法を紹介します。

まずEclipse Marketplaceで SonarLint を検索してインストールし、再起動します。
再起動後 SonarQube Servers ビューを開き以下の通り設定します。

  1. Connect to a SonarQube server… をクリックします。
  2. Choose connection type
    • SonarQube を選択します。
  3. SonarQube Server URL
      1. SonarQube Serverの構築 で構築したSonarQube ServerのURLを入力します。
  4. Choose authentication method
    • Token を選択します。
  5. SonarQube Server Authentication Token
    • Generate token を押し、SonarQube ServerのTokensページでIDE用のTokenを発行してコピーします。
    • Eclipse SonarQube Servers ビューにToken を記入します。
  6. Connection name
    • デフォルト(SonarQube Server のホスト名)のままとします。
  7. 対象プロジェクトを右クリックし、[SonarLint] -> [Bind to a SonarQube project…] -> [Auto bind selected projects] を選択しSonarQube Serverに関連付けをします。Auto bind がうまくいかない場合は、SonarQube Projectを手動入力して関連付けをします。

この設定を行うと、4–2.日次プログラムコード解析ジョブ でSonarQube Serverに登録されているプログラムコードからの変更分のみ解析することが出来るようになります。

これでプログラムコード開発中、リアルタイムにコード解析結果を確認できるようになりました。

6–1. リアルタイム解析イメージ

Eclipse上ではこのような形で解析結果が表示されます。

画面引用: http://www.sonarlint.org/eclipse/

7. まとめ

アプリボットでは全PJで導入を進めました。導入される前と比べるとプログラムコードの品質が格段に上がりました。
また、SonarQubeで自動レビューされることにより簡単な指摘事項はエンジニア自身で気付くことが可能となりました。そのためレビューをする方も、より業務寄りのレビューに注力できるようになりました。

今後は解析ルールの調整を進めて、より有意義な自動レビューができるようにしていきたいと思います。