Hướng dẫn tích hợp SonarQube với GitLab CI/CD để quét mã nguồn tự động

Hướng dẫn này trình bày các bước thiết lập pipeline trên GitLab để tự động kích hoạt phân tích chất lượng mã bằng SonarQube khi có yêu cầu hợp nhất (Merge Request - MR). Quy trình này giúp phát hiện sớm các vấn đề trong mã nguồn và hiển thị kết quả đánh giá trực tiếp trên giao diện của MR.

Sơ đồ luồng xử lý

Quy trình hoạt động sẽ diễn ra theo trình tự sau:

  1. Nhà phát triển tạo Merge Request.
  2. GitLab CI kích hoạt Pipeline.
  3. SonarScanner thực hiện phân tích mã.
  4. Kết quả được gửi tới SonarQube Server để kiểm tra Quality Gate.
  5. Server phản hồi kết quả và Pipeline tự động đăng nhận xét (comment) lên MR.
graph LR
    subgraph GitLab
    MR[Merge Request] -->|Trigger| Pipeline[CI Pipeline]
    end
    subgraph Analysis
    Pipeline --> Scanner[SonarScanner CLI]
    Scanner --> Server[SonarQube Server]
    Server --> Gate{Quality Gate Check}
    end
    subgraph Feedback
    Gate -->|Pass| Result[✅ Passed]
    Gate -->|Fail| Fail[❌ Failed]
    Result --> API[GitLab API]
    Fail --> API
    API --> Comment[Comment on MR]
    end

Các điều kiện tiên quyết

Trước khi bắt đầu, hệ thống cần đáp ứng các yêu cầu sau:

  • SonarQube Server: Đã cài đặt bản 8.9 trở lên (Community Edition hoặc cao hơn).
  • GitLab: Phiên bản 13.0 trở lên, đã cấu hình GitLab Runner.
  • GitLab Runner: Khuyến nghị sử dụng Docker executor và có thể kết nối mạng tới SonarQube Server.

Cấu hình trên SonarQube

1. Khởi tạo dự án

Đăng nhập vào SonarQube, chọn Projects > Create Project > Manually. Thiết lập Project Key (ví dụ: demo-app) và Display Name. Lưu lại Project Key để sử dụng ở các bước sau.

2. Tạo Token xác thực

Truy cập My Account > Security > Generate Tokens. Đặt tên cho token (ví dụ: ci-scanner), chọn loại Project Analysis Token và gán với dự án vừa tạo. Sao chép và lưu trữ token này cẩn thận vì nó chỉ hiển thị một lần.

3. Thiết lập Quality Gate

Tại mục Quality Gates, bạn nên thiết lập các tiêu chuẩn tối thiểu cho mã nguồn. Ví dụ:

  • Mã mới (New Code): Tỷ lệ bao phủ (Coverage) > 80%.
  • Lỗi mới (New Bugs): = 0.
  • Lỗ hổng mới (New Vulnerabilities): = 0.
  • Mùi code (Code Smells): < 10.

Gán Quality Gate này cho dự án của bạn.

Cấu hình trên GitLab

1. Thiết lập Biến môi trường (CI/CD Variables)

Trong dự án GitLab, vào Settings > CI/CD > Variables và thêm các biến sau:

Khóa (Key) Giá trị (Value) Bảo vệ (Protected) Che giấu (Masked)
SONAR_SERVER_URL https://sonarqube.company.com No No
SONAR_AUTH_TOKEN sqp_xxxxxxxxxx No Yes

Lưu ý: Biến SONAR_AUTH_TOKEN nên được đánh dấu là Masked để tránh lộ thông tin trong log.

2. Tạo file cấu hình sonar-project.properties

Đặt file này tại thư mục gốc của dự án để định nghĩa các tham số phân tích:

# Khóa định danh dự án
sonar.projectKey=demo-app
sonar.projectName=Demo Application

# Đường dẫn nguồn
sonar.sources=src/main/java
sonar.tests=src/test/java

# Bảng mã
sonar.sourceEncoding=UTF-8

# Đường dẫn file biên dịch (Java)
sonar.java.binaries=target/classes

# Loại trừ các file không cần thiết
sonar.exclusions=**/generated/**,**/node_modules/**,**/*.min.js

Cấu hình GitLab CI (file .gitlab-ci.yml)

Dưới đây là cấu hình pipeline cơ bản bao gồm bước chạy test và bước quét mã nguồn.

stages:
  - build
  - quality

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

# Job chạy unit test
build-and-test:
  stage: build
  image: maven:3.8.1-openjdk-11
  script:
    - mvn clean package
  artifacts:
    paths:
      - target/
    expire_in: 1 day
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

# Job phân tích SonarQube
code-scan:
  stage: quality
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  dependencies:
    - build-and-test
  variables:
    GIT_DEPTH: "0" 
  script:
    - >
      sonar-scanner
      -Dsonar.host.url=${SONAR_SERVER_URL}
      -Dsonar.login=${SONAR_AUTH_TOKEN}
      -Dsonar.projectKey=${CI_PROJECT_NAME}
      -Dsonar.qualitygate.wait=true
      -Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID}
      -Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
      -Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
  allow_failure: true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Giải thích các tham số quan trọng

  • GIT_DEPTH: "0": Đảm bảo clone toàn bộ lịch sử git để blame thông tin chính xác.
  • sonar.qualitygate.wait=true: Yêu cầu pipeline chờ kết quả Quality Gate từ server.
  • sonar.pullrequest.*: Cung cấp thông tin về nhánh MR để SonarQube phân tích diff.

Tính năng nâng cao: Gửi kết quả về MR (Community Edition)

Nếu sử dụng bản Community không hỗ trợ tính năng Decoration hiển thị trực tiếp, bạn có thể dùng script dưới đây để gửi kết quả dạng bảng comment vào MR.

code-scan:
  stage: quality
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  dependencies:
    - build-and-test
  variables:
    GIT_DEPTH: "0"
  script:
    - >
      sonar-scanner
      -Dsonar.host.url=${SONAR_SERVER_URL}
      -Dsonar.login=${SONAR_AUTH_TOKEN}
      -Dsonar.qualitygate.wait=true
      -Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID}
      -Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
      -Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
  after_script:
    - |
      # Lấy kết quả từ API SonarQube
      ANALYSIS_DATA=$(curl -s -u "${SONAR_AUTH_TOKEN}:" \
        "${SONAR_SERVER_URL}/api/qualitygates/project_status?projectKey=${CI_PROJECT_NAME}&pullRequest=${CI_MERGE_REQUEST_IID}")

      # Xác định trạng thái
      STATUS=$(echo "$ANALYSIS_DATA" | jq -r '.projectStatus.status')
      
      if [ "$STATUS" = "OK" ]; then
        ICON="✅"
      else
        ICON="❌"
      fi

      # Trích xuất số liệu
      BUGS=$(echo "$ANALYSIS_DATA" | jq -r '.projectStatus.conditions[] | select(.metricKey=="new_bugs") | .actualValue // "0"')
      VULNS=$(echo "$ANALYSIS_DATA" | jq -r '.projectStatus.conditions[] | select(.metricKey=="new_vulnerabilities") | .actualValue // "0"')
      COVERAGE=$(echo "$ANALYSIS_DATA" | jq -r '.projectStatus.conditions[] | select(.metricKey=="new_coverage") | .actualValue // "N/A"')

      # Tạo nội dung comment
      REVIEW_MSG=$(cat <<EOF
      ## ${ICON} Kết quả phân tích: **${STATUS}**

      | Chỉ số | Giá trị |
      |---|---|
      | 🐛 Bugs mới | ${BUGS} |
      | 🔓 Lỗ hổng mới | ${VULNS} |
      | 📊 Tỷ lệ bao phủ | ${COVERAGE}% |

      [Xem chi tiết tại SonarQube](${SONAR_SERVER_URL}/dashboard?id=${CI_PROJECT_NAME}&pullRequest=${CI_MERGE_REQUEST_IID})
      EOF
      )

      # Gửi comment qua API GitLab (cần biến GITLAB_TOKEN)
      curl -s --request POST \
        --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
        --header "Content-Type: application/json" \
        --data "{\"body\": $(echo "$REVIEW_MSG" | jq -Rs .)}" \
        "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
  allow_failure: true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'</code>

Ví dụ đầy đủ cho dự án Maven đa module

Đối với các dự án Java phức tạp, bạn có thể sử dụng Maven plugin thay vì CLI scanner để xử lý đa module dễ dàng hơn.

stages:
  - verify
  - analyze

cache:
  paths:
    - .m2/repository/
    - target/

verify:
  stage: verify
  image: maven:3.8-openjdk-11
  script:
    - mvn clean verify
  artifacts:
    paths:
      - target/*.jar
      - target/site/jacoco/jacoco.xml
    expire_in: 1 hour
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

sonar-analyze:
  stage: analyze
  image: maven:3.8-openjdk-11
  dependencies:
    - verify
  script:
    - >
      mvn sonar:sonar
      -Dsonar.host.url=${SONAR_SERVER_URL}
      -Dsonar.login=${SONAR_AUTH_TOKEN}
      -Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID}
      -Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
      -Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
  allow_failure: true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Sử dụng Template CI chung

Để quản lý tập trung, hãy đưa cấu hình SonarQube vào một file template và include vào các dự án khác.

File ci-templates/sonar-analysis.yml:

.sonar_base:
  stage: analyze
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  variables:
    GIT_DEPTH: "0"
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
  script:
    - >
      sonar-scanner
      -Dsonar.host.url=${SONAR_SERVER_URL}
      -Dsonar.login=${SONAR_AUTH_TOKEN}
      -Dsonar.qualitygate.wait=true
      -Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID}
      -Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
      -Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Sử dụng trong dự án:

include:
  - project: 'devops/ci-templates'
    ref: main
    file: '/sonar-analysis.yml'

stages:
  - build
  - analyze

sonar-check:
  extends: .sonar_base
  dependencies:
    - build-job

Xử lý sự cố thường gặp

  • Lỗi "Not authorized": Kiểm tra lại Token và đảm bảo biến CI/CD được cấu hình đúng, đặc biệt là quyền Execute Analysis.
  • SCM warning: Hãy chắc chắn rằng GIT_DEPTH được set thành "0" để lấy đầy đủ lịch sử git.
  • Quality Gate luôn NONE: Kiểm tra xem Quality Gate đã được gán vào dự án và các điều kiện có đang áp dụng cho New Code hay không.
  • Tỷ lệ bao phủ (Coverage) bằng 0%: Kiểm tra đường dẫn tới file report JaCoCo và đảm bảo job trước đó (test) đã tạo ra artifact này.

Thẻ: gitlab-ci sonarqube DevOps continuous-integration code-quality

Đăng vào ngày 14 tháng 6 lúc 21:54