SonarQubeをCI/CDパイプラインに統合する:静的コード解析と自動Quality Gateの実践

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

背景と目的:本番環境へのリリース前に欠陥を検知する

多くのエンジニアリングチームが同じような経験をしています。Pull Requestのレビューでは完璧に見えたコードが、2日後にステージング環境でメモリリークやデータベースのコネクションプール枯渇を引き起こして破綻する、といった事態です。スプリントの納期に追われる中、エンジニアはビジネス要件の精査に集中せざるを得ません。その結果、アーキテクチャの不整合やコピペによるボイラープレート、潜在的なセキュリティホットスポットが見落とされてしまいます。

締め切りが迫る状況下で、手動レビューだけに頼るのはスケールしません。チームによってレビューの厳密さにバラつきが生じ、600行を超える差分を読んでいるうちに疲労で集中力も途切れてしまいます。ビルドチェーンに組み込まれた自動的な品質チェックがなければ、静的解析は四半期に一度のローカルでのアドホックな実行に甘んじるか、監査直前のパニック時に慌てて対応する羽目になります。

SonarQubeをCIパイプラインに直接組み込むことで、コード検証を本来あるべき「シフトレフト」へと移行できます。コミットやPull Requestごとに、バグ、セキュリティホットスポット、構文の不備、テスト不足の即時スキャンがトリガーされます。

新しく追加されたコードが品質基準を満たさない場合、パイプラインは即座に停止します。決済ゲートウェイサービスにこれを導入した際、リリース後のバグレポートは2四半期で約35%減少しました。作業のコンテキストがまだ頭に残っているうちに問題を修正できれば、後からのコンテキストスイッチにかかる時間を大幅に節約できます。

インストール:Docker ComposeによるSonarQubeの構築

CI Runnerを接続する前に、安定したSonarQubeインスタンスが必要です。独自にツールをホストするチームにとって、専用のPostgreSQLバックエンドと組み合わせたDocker Compose環境は、メンテナンスをシンプルかつ再現性の高いものにしてくれます。

1. ホストOSのカーネルパラメータの準備

SonarQubeには、プロジェクトの検索インデックス作成用にElasticsearchエンジンが組み込まれています。ElasticsearchはホストOS上で仮想メモリとファイルディスクリプタの上限を引き上げる必要があります。これが設定されていないと、コンテナ起動時にメモリ不足エラー(OOM)でクラッシュします。Linuxホスト上で直接、以下の設定を適用してください。

sudo sysctl -w vm.max_map_count=524288
sudo sysctl -w fs.file-max=131072
echo 'vm.max_map_count=524288' | sudo tee -a /etc/sysctl.conf
echo 'fs.file-max=131072' | sudo tee -a /etc/sysctl.conf

2. Docker Composeの設定

専用のディレクトリを作成し、以下の docker-compose.yml を配置して、PostgreSQLとアプリケーションコンテナを連携させます。

version: "3.8"

services:
  sonarqube-db:
    image: postgres:15-alpine
    container_name: sonarqube_postgres
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar_secret_password
      POSTGRES_DB: sonarqube
    volumes:
      - postgresql_data:/var/lib/postgresql/data
    networks:
      - sonarnet
    restart: unless-stopped

  sonarqube:
    image: sonarqube:10.4-community
    container_name: sonarqube_server
    depends_on:
      - sonarqube-db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://sonarqube-db:5432/sonarqube
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar_secret_password
    ports:
      - "9000:9000"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    networks:
      - sonarnet
    restart: unless-stopped

networks:
  sonarnet:
    driver: bridge

volumes:
  postgresql_data:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:

バックグラウンドでサービスを起動し、起動ログを確認します。

docker compose up -d
docker compose logs -f sonarqube

ログでSonarQubeの起動が確認できたら、http://<your-server-ip>:9000 を開きます。デフォルトの認証情報(admin / admin)でサインインし、すぐにパスワードを変更してください。

設定:パイプライン統合とQuality Gate

サーバーが正常に稼働したら、CIシステムと連携し、明確なQuality Gateを設定します。

1. 認証トークンとプロジェクトキーの発行

  1. My Account > Security に移動し、解析権限を持つUser Token(例:ci-token-backend)を作成します。
  2. Projects > Create Project > Manually に進みます。判別しやすい表示名とプロジェクトキー(例:backend-api-service)を入力します。

2. プロジェクトへのスキャナー設定ファイルの追加

リポジトリのルートディレクトリに sonar-project.properties ファイルを配置し、スキャナーに対象ディレクトリを指示します。

sonar.projectKey=backend-api-service
sonar.projectName=Backend API Service
sonar.projectVersion=1.0.0

# ソースディレクトリ
sonar.sources=src
sonar.tests=tests
sonar.sourceEncoding=UTF-8

# ビルド生成物と依存関係の除外
sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**

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

3. CI/CDパイプラインの設定(GitHub Actionsの例)

GitHubリポジトリの設定(Settings)で、2つの暗号化シークレット(SONAR_HOST_URL と SONAR_TOKEN)を登録します。次に、以下のワークフローファイルを .github/workflows/ci.yml に配置します。

name: ビルド & コード品質チェック

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  analysis:
    runs-on: ubuntu-latest
    steps:
      - name: コードのチェックアウト
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 正確なgit-blameアノテーションのために全履歴の取得が必要

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: 依存関係のインストールとテストの実行
        run: |
          npm ci
          npm test -- --coverage

      - name: SonarQubeスキャン
        uses: sonarsource/sonarqube-scan-action@v2
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: SonarQube Quality Gateチェック
        uses: sonarsource/sonarqube-quality-gate-action@v1
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

検証とモニタリング:Quality Gateの適用

問題のあるコミットをブロックせずにコードをスキャンするだけでは、静的解析が単なるバックグラウンドノイズになってしまいます。SonarQubeはQuality Gateを適用し、欠陥のある変更がメインブランチにマージされる前に確実に阻止します。

1. Quality Gateルールの調整

SonarQubeの Quality Gates を開きます。デフォルトの Sonar way プロファイルは、「Clean as You Code」アプローチを重視しており、優れた出発点となります。Pull Requestに対しては、以下のベースライン閾値を設定することをおすすめします。

  • 0 New Vulnerabilities(新規脆弱性 0件):SQLインジェクション、エスケープ漏れ、ハードコードされたJWTシークレットなどを検知します。
  • 0 New Bugs(新規バグ 0件):未処理のPromise rejection、オフバイワンエラー(境界値のループ処理ミス)、null参照の罠を検出します。
  • Coverage >= 80%(カバレッジ 80%以上):新しく記述されたドメインロジックに自動テストスイートが確実に含まれるようにします。
  • Duplicated Lines < 3%(重複行 3%未満):安易なコピペによる修正を防ぎ、共通ユーティリティの再利用性を維持します。

2. パイプライン失敗時の対応

sonarqube-quality-gate-action ステップは、SonarQubeのステータスAPIをポーリングします。パラメータ化されていないデータベースクエリがプッシュされたり、新規コントローラーのテストが省略された場合、ジョブは即座に異常終了します。

QUALITY GATE ステータス: 失敗
- 新規コードの脆弱性: 1 (期待値: 0)
- 新規コードのカバレッジ: 42.5% (期待値: >= 80.0%)
エラー: プロセスは終了コード 1 で完了しました。

GitHub上ではマージボタンが無効化されます。作成者はActionのログに記載されているダッシュボードのURLを直接開き、指摘された該当行を確認した上で、修正をコミットしないとPull Requestを進めることができません。

3. 継続的なトレンドモニタリング

スプリントを通じてサービスの健全性を追跡するために、SonarQubeの Measures タブを定期的に確認してください。循環的複雑度(Cyclomatic Complexity)のスコアや長期的な技術的負債の比率には特に注意を払いましょう。負債が増加傾向にある場合は、スプリントプランニングで半日のリファクタリングセッションを確保し、常態化する前に再発している警告を解消することをおすすめします。

Share: