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:
- Nhà phát triển tạo Merge Request.
- GitLab CI kích hoạt Pipeline.
- SonarScanner thực hiện phân tích mã.
- Kết quả được gửi tới SonarQube Server để kiểm tra Quality Gate.
- 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.