Если коротко - после этого урока у тебя в pipeline появится стейдж, который может уронить сборку не из-за упавшего теста, а из-за того, что качество кода ниже порога. И это нормально.
Зачем встраивать анализ кода в CI и что такое Quality Gate
SonarQube - это сервер статического анализа. Он берёт твой исходник, гоняет по нему сотни правил (баги, уязвимости, code smells, дубли) и считает метрики: покрытие, технический долг, цикломатическую сложность. Сам по себе он полезен, но превращается в магию только когда встроен в конвейер и умеет блокировать.
Ключевое понятие здесь - Quality Gate (ворота качества). Это набор условий, которым код обязан удовлетворять. Например: "на новом коде покрытие не ниже 80 процентов", "ноль новых уязвимостей", "дублирование меньше 3 процентов". Если код проходит - ворота зелёные, конвейер едет дальше. Не проходит - красные, и связка jenkins quality gate валит сборку.
Тонкий момент, который многие упускают: современный подход - это анализ "чистого нового кода" (Clean as You Code). Не пытайся одним прыжком вылечить весь легаси - это утопия. Gate настраивается на условие "новый код" (new code), то есть на дифф твоего PR. Старый код остаётся как есть, но всё новое, что ты добавляешь, обязано быть чистым. За год проект сам собой оздоравливается без героических рефакторингов.
По состоянию на 2026 год актуальная серверная ветка - SonarQube Server 2026 (календарная нумерация вида ГГГГ.N, LTA-релиз 2026 Release 1), плюс бесплатный SonarQube Community Build (ветка 26.x). Community-редакции хватает за глаза для одного основного языка и базовых Quality Gates. Серверу нужна Java 21 как минимум - держи это в голове, если поднимаешь его рядом с Jenkins.

Подготовка: сервер, токен и плагин SonarQube Scanner
Поднять сервер для практики проще всего в Docker:
Код: Выделить всё
docker run -d --name sonarqube \
-p 9000:9000 \
-v sonarqube_data:/opt/sonarqube/data \
-v sonarqube_extensions:/opt/sonarqube/extensions \
sonarqube:community
- Создаёшь токен: My Account -> Security -> Generate Token. Это секрет, по которому Jenkins авторизуется на сервере.
- В Jenkins ставишь плагин SonarQube Scanner for Jenkins. В Manage Jenkins -> System находишь блок "SonarQube servers", добавляешь сервер: имя (например MySonar), URL http://sonarqube:9000 и тот самый токен из Jenkins Credentials (тип Secret text).
- Настраиваешь webhook на стороне SonarQube: Administration -> Configuration -> Webhooks, URL вида http://jenkins:8080/sonarqube-webhook/. Без него waitForQualityGate будет ждать вечно - именно вебхук пинает Jenkins, что анализ готов.
Стейдж анализа в Jenkinsfile: withSonarQubeEnv и waitForQualityGate
Теперь самое интересное - declarative pipeline. Разберём связку для Java-проекта на Maven с покрытием через JaCoCo. Сначала JaCoCo считает покрытие, потом сканер отправляет данные на сервер, потом мы ждём вердикт ворот.
Код: Выделить всё
pipeline {
agent any
tools {
maven 'Maven-3.9'
jdk 'JDK-21'
}
stages {
stage('Build & Test') {
steps {
// verify прогоняет тесты и формирует jacoco.exec + xml-отчёт
sh 'mvn -B clean verify'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('MySonar') {
sh '''
mvn -B sonar:sonar \
-Dsonar.projectKey=myapp \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
'''
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
Для не-Maven проектов (JS, Python, Go) вместо mvn sonar:sonar используют отдельный CLI sonar-scanner. Тогда в Manage Jenkins -> Tools добавляешь установку "SonarQube Scanner" и зовёшь его так:
Код: Выделить всё
stage('SonarQube Analysis') {
steps {
script {
def scannerHome = tool 'SonarScanner'
withSonarQubeEnv('MySonar') {
sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=myapp"
}
}
}
}
Метрика jenkins покрытие кода - это процент строк (и веток), которые реально выполнились во время прогона тестов. Для Java стандарт - JaCoCo. Связка jenkins jacoco работает так: подключаешь плагин в pom.xml, он инструментирует байткод, после прогона тестов выдаёт jacoco.xml, а SonarQube этот xml читает и показывает покрытие в своём UI.
Минимальная настройка JaCoCo в Maven:
Код: Выделить всё
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
Код: Выделить всё
post {
always {
publishHTML(target: [
reportName: 'Coverage Report',
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
keepAll: true,
alwaysLinkToLastBuild: true,
allowMissing: false
])
}
}
Типичные грабли
- waitForQualityGate висит до таймаута. В 99 процентах случаев - не настроен или неправильно настроен webhook на SonarQube. Проверь URL и что Jenkins вообще доступен серверу по сети (особенно если оба в Docker - имена контейнеров, а не localhost).
- Покрытие всегда 0 процентов. Забыли прогнать тесты ДО анализа, либо не передали sonar.coverage.jacoco.xmlReportPaths, либо JaCoCo не успел сформировать отчёт. Порядок строгий: сначала verify, потом sonar:sonar.
- Gate зелёный, хотя код плохой. Условия настроены на "Overall Code" вместо "New Code". На большом легаси старые проблемы размазываются и порог не срабатывает. Всегда вешай условия на новый код.
- Анализ держит исполнитель. Не пихай waitForQualityGate внутрь withSonarQubeEnv или внутрь блока node без нужды - шаг ожидания не должен занимать агента. В declarative это решается отдельным stage, как в примере выше.
- Токен в Jenkinsfile открытым текстом. Никогда. Только через Credentials и withSonarQubeEnv, который сам подставит секрет.
Полезно понимать, что Jenkins тут не уникален, и идея переносима. В GitLab CI есть встроенная вкладка Code Quality и шаблон Jobs/Sonar; quality gate проверяется отдельным job, а MR блокируется через правила в настройках проекта. В GitHub Actions ставят официальный sonarqube-scan-action, а блокировку мержа делают branch protection rules с обязательной проверкой статуса. Механика везде одна: посчитать метрики -> сравнить с порогом -> не пропустить PR, если порог не взят. Jenkins даёт максимум контроля ценой того, что webhook и плагины надо настраивать руками - в облачных CI это часто из коробки.
Кстати, не строй UI вокруг Blue Ocean - в 2026 это заброшенное наследие, ему на смену давно пришёл встроенный Pipeline-граф и declarative-синтаксис.
Мини-лаба
Повтори руками, без этого тема не уляжется:
- Подними SonarQube Community в Docker и залогинься, смени пароль.
- Сгенерируй токен, добавь сервер в Jenkins (Manage Jenkins -> System) и настрой webhook на сервере.
- Возьми любой Java-проект с тестами, подключи jacoco-maven-plugin, собери Jenkinsfile с тремя стейджами: Build & Test, SonarQube Analysis, Quality Gate.
- Запусти - убедись, что покрытие появилось в SonarQube UI и отчёт виден через publishHTML.
- Специально занизь Quality Gate (например Coverage on New Code >= 90%), добавь непокрытый метод и убедись, что сборка УПАЛА на стейдже Quality Gate. Это главное упражнение урока.
- За что отвечает withSonarQubeEnv и почему нельзя просто вызвать sonar-scanner напрямую?
- Зачем нужен webhook на стороне SonarQube и что сломается без него?
- Что делает параметр abortPipeline: true в waitForQualityGate и в какой момент срабатывает?
- Почему условия Quality Gate правильнее вешать на "новый код", а не на весь проект целиком?
Мы превратили качество кода из пожелания в правило. SonarQube считает баги, уязвимости и дубли, JaCoCo даёт покрытие, а связка withSonarQubeEnv + waitForQualityGate(abortPipeline: true) делает Quality Gate настоящей блокирующей дверью: плохой код не пройдёт в main, потому что pipeline просто упадёт. Подход Clean as You Code снимает страх перед легаси - чистым обязан быть только новый код. Дальше можно подключать SonarQube к проверке PR и завязывать merge на статус сборки, чтобы дверь закрывалась автоматически.