1. SonarQube란?
SonarQube는 정적 분석 도구 중 하나로 20가지 이상의 언어와 프레임워크를 지원한다. 코드 품질을 관리하기
사람이 코드를 작성하거나, 코드 리뷰를 할 때 놓치기 쉬운 기본적인 실수들을 하지 않게 도와준다. PullRequest와 연동하면 분석 결과를 리뷰도 달아주기까지 한다.
1-1. 어떤 것을 우리에게 제공해주는가?
- SonarQube를 활용하면 문제에 대한 분석을 자동화할 수 있다. 아래와 같은 지표를 한 눈에 파악할 수 있다.
- Code Smell: 변경 가능성, 모듈성, 이해가능성, 테스트 용의성, 재사용성 등을 분석
- 버그: 잠재적인 버그. 런타임에 예상되는 동작을 하지 않는 코드
- 취약점: 해커들에게 잠재적 약점이 될 수 있는 보안상 이슈. (ex. SQL Injection, XSS 공격)
- 코드 중복
- 복잡도: 순환 복잡도 측정, 코드 논리적 흐름 상 존재하는 인지 복잡도 측정
- 사이즈: 코드 라인 전체 라인 수 구문, 함수 클래스 파일, 디렉터리 주석 수, 코멘트 비율 등
1-2. 예시
분석 결과의 구체적인 예시는 아래와 같다.
- 문자열 하드코딩
- 불필요한 주석 (ex. 코드 자체를 주석 처리, Todo 주석)
- 테스트에서 isEqualTo(0) 사용 (대신 isZero() 사용할 수 있다.)
- 테스트에서 너무 많은 단언문을 남발
2. SonarQube 환경 구축하기
- SonarQube 환경을 구축하기 위해 Docker Compsoe를 활용했다. (Docker와 Docker Compose 설치를 해야 아래 과정대로 따라올 수 있다.)
- 소나큐브는 정적 분석 결과를 postgres DB를 사용해 저장하기 때문에 기본적으로 2개의 컨테이너(서버와 DB)가 필요하다.
2-1. Docker Compose
version: "3.9"
services:
sonarqube:
image: sonarqube:lts-community
depends_on:
- sonar_db
environment:
SONAR_JDBC_URL: jdbc:postgresql://sonar_db:5432/sonar
SONAR_JDBC_USERNAME: username
SONAR_JDBC_PASSWORD: password
ports:
- "9000:9000"
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
- sonarqube_temp:/opt/sonarqube/temp
sonar_db:
image: postgres:13
environment:
POSTGRES_USER: username
POSTGRES_PASSWORD: password
POSTGRES_DB: sonar
volumes:
- sonar_db:/var/lib/postgresql
- sonar_db_data:/var/lib/postgresql/data
volumes:
sonarqube_conf:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
sonarqube_temp:
sonar_db:
sonar_db_data:
2-2. SonarQube 실행하기
- 위의 docker compose 스크립트를 실행하고 9090 포트에 접속해보자. 처음 홈페이지에 들어가면 로그인을 해야하는데 SonarQube의 기본 아이디, 비밀번호를 사용하면 된다.
- 아이디: admin, 비밀번호: admin
- 로그인을 하면 패스워드를 새롭게 설정할 수 있다.
- 접속이 완료되면 아래와 같이 프로젝트 생성창이 뜬다.
3. SonarQube 연동하기 (Jenkins, GitLab)
- 아래 설정을 하기 전에 Jenkins에서 형상 관리 저장소(GitHub, GitLab 등) 설정이 되어 있다고 가정하고 진행한다. (만약 설정되어 있지 않다면 플러그인을 설치하고 필요한 Secret 설정을 해줘야 한다.)
3-1. SonarQube Token 발급하기
- Jenkins에서 사용할 SonarQube의 토큰을 먼저 생성해야 한다.
- 프로젝트를 생성하기 위해 Manually를 클릭해준다.
- 프로젝트를 생성해준다.
- 그렇게 새로 생성한 프로젝트에는 아래와 같이 Locally 메뉴가 있을 것이다. 클릭하자.
- 여기서 토큰 이름과 기한을 설정해 토큰을 생성하면 된다.
- Generate 버튼을 클릭하면 아래와 같이 토큰이 생성된다. 토큰을 메모장에 따로 저장해둔 다음 Contiune 버튼을 클릭하자.
- SonarQube를 실행하기 위한 정보를 보여준다.
- plugins는 Spring Project에 설정해줘야 한다.
- 사실 여기에 함정이 있다. 뒤에 5. 연동 시 주의사항에서 plugins 버전 설정을 참고 바람.
- 그리고 "and run the following command:" 에 작성된 명령어는 Jenkins 파이프라인에서 직접 실행할 명령어다.
3-2. SonarQube Jenkins Webhook 설정하기
- 최상단에 있는 메뉴인 Administration을 클릭하고 Configuration > Webhooks를 클릭하자.
- 우측 상단에 있는 Create 버튼을 클릭한다.
- URL은 Jenkins 서버의 endpoint()를 작성해주면 된다.
- endpoint 형식: http://{jenkins_url}:{port}/sonarqube-webhook/
3-3. Jenkins 설정하기 - SecretKey
- 본격적으로 SonarQube를 설정하기 전에 SecretKey를 설정해보도록 하자.
- Jenkins 관리 > Credentials를 클릭한다.
- Stores scoped to Jenkins의 System을 클릭한다
- Global credentials를 클릭한다.
- Add Credentials 버튼을 클릭한다.
- 종류를 Secret text로 설정한 뒤, SonarQube에서 발급받은 토큰을 등록해준다.
3-4. Jenkins 설정하기 - SonarQube Server
- 그렇다면 이제 젠킨스 파이프라인을 만들어보자.
- 먼저 SonarQube 플러그인이 설치되어 있어야 한다. SonarQube Scanner와 Sonar Quality Gates를 설치해주자.
- 설치가 완료되었다면 Jenkins 관리 > System의 SonarQube servers 설정을 확인하자.
- Server URL은 SonarQube의 서버 URL을 넣어주면 된다. 만약 Jenkins와 SonarQube가 같은 가상 머신 안에 있다면 Default 설정인 localhost:9000으로 두어도 상관없다.
- Server authentication token에는 위에서 입력한 credentials의 토큰을 드롭다운으로 선택할 수 있다. sonarqube token을 선택해준다.
- SonarQube Scanner Server를 따로 설치하지 않았다면 Jenkins 내에서 Install automatically를 체크하여 자동으로 설치하도록 구성해야 한다. (Jenkins 관리 > Tools > SonarQube Scanner installations)
3-5. Jenkins 파이프라인 스크립트 작성
pipeline {
agent any
stages {
stage('checkout') {
steps {
script {
SCM_VARS = git branch: '${gitlabSourceBranch}', credentialsId: '${CREDENTIAL}', url: '${GIT_URL}'
env.GIT_COMMIT = SCM_VARS.GIT_COMMIT
}
}
}
stage('Build') {
steps {
sh './gradlew clean build'
}
}
stage('SonarQube analysis') {
steps{
withSonarQubeEnv('sonarqube'){
sh """
./gradlew clean sonar \
-Dsonar.projectKey={SONAR_PROJECT} \
-Dsonar.host.url=${SONAR_URL} \
-Dsonar.login=${SONAR_TOKEN}
"""
}
}
}
stage('SonarQube Quality Gate'){
steps{
timeout(time: 1, unit: 'MINUTES') {
script{
echo "Start"
def qg = waitForQualityGate()
echo "Status: ${qg.status}"
if(qg.status != 'OK') {
echo "NOT OK Status: ${qg.status}"
updateGitlabCommitStatus(name: "SonarQube Quality Gate", state: "failed")
error "Pipeline aborted due to quality gate failure: ${qg.status}"
} else{
echo "status: ${qg.status}"
updateGitlabCommitStatus(name: "SonarQube Quality Gate", state: "success")
}
echo "End"
}
}
}
}
}
}
- "SonarQube Analysis" stage는 대상 SonarQube Server를 지정하여 분석을 진행한다.
- withSonarQubeEnv는 Jenkins 관리 > System에 등록한 SonarQube Servers의 Name과 매핑된다.
- "SonarQube Quality Gate" stage는 SonarQube Servers에서 분석 결과를 응답하기까지 대기한다.
- 대기 시간을 지정하여 무한정 대기하는 상태를 방지할 수 있다. waitForQualityGate를 사용해 Server에서 분석을 완료하고 상태를 반환할때까지 파이프라인을 중단시키는 시간을 지정한다.
4. GitLab으로 리포팅하기
- 이 기능은 Developer Edition 이상에서만 가능하다.
- 아래 블로그 링크에서 특정 플러그인을 사용해 무료로 Github 리포팅 사용하는 방법을 알아볼 수 있다.
- [Server] 소나큐브(SonarQube) 커뮤니티 무료 버전에서 PR 데코레이션(Pull Request Decoration) 설정 적용하기
4-1. GitLab OAuth2 로그인 애플리케이션 구성 및 SonarQube 등록
- 아래 문서를 참고해 OAuth2 애플리케이션을 구성해주자
- SonarQube Docs: GitLab
- GitLab에서 애플리케이션을 만들면 Application ID와 Secret을 알려준다. 그 정보를 SonarQube에 입력해줘야 한다.
- Administration > Configuration > General Setting > Authentication
- GitLab 주소 및 Application ID와 Secret을 입력해준다.
4-2. SonarQube에 GitLab 연동하기
- SonarQube Docs: Setting up the GitLab integration at the global level
- GitLab에서 Access Token을 발급받는다. scope는 api와 read_api 두 가지를 선택하도록 하자.
- Administration > Configuration > General Settings > DevOps Platform Integrations
- Create configuration 버튼을 클릭해 연동 정보를 입력해주자.
- API URL을 입력할 때 뒤에 /api/v4 라는 path를 붙여줘야 한다.
- 도메인 주소는 현재 사용하고 있는 gitlab의 주소를 넣어주면 된다.
4-3. GitLab Webhook 등록하기
- 먼저 Jenkins의 파이프라인에서 Build when a change is pushed to GitLab~~ 설정을 해준다.
- 만약 merge request에 새로운 commit이 들어갔을 때도 파이프라인이 재실행되길 원한다면 설정을 아래와 같이 수정해주자.
- 고급 설정에서 Secret token의 Generate 버튼을 클릭하여 토큰을 만들어주자.
- GitLab의 Webhook 설정에서 Jenkins의 위 설정에서 나타난 URL 정보와 Secret Token을 입력해준다.
- Trigger에서 Merge Request Event를 체크해준다.
4-4. GitLab으로부터 Merge Request 정보 수신하기
- 앞에서 설명했다시피 이 기능은 Developer Edition 이상에서만 가능하다.
- 아래의 공식문서를 참고하면 Pull Request(GitLab에서는 Merge Request)를 분석할 수 있는 기능에 대해 설명되어 있다.
- SonarQube Docs: Setting up the pull request analysis
- 그렇다면 pull request 정보를 어디로부터 받아 넘겨줘야 할까? Jenkins의 GitLab 플러그인 문서를 확인해보면 여러가지 정보를 환경변수로 넣어준다는 사실을 알 수 있다.
- Jenkins GitLab Plugin Docs
- 실제로 Jenkins에서 printenv를 찍어보면 GitLab 정보가 환경변수에 저장되어 있는 것을 확인할 수 있다. 여기서 필요한 정보는 아래와 같이 세 가지다.
gitlabMergeRequestIid=28
gitlabTargetBranch=merge
gitlabBranch=feat/sonar
- 위 내용을 pull request에 매칭해주자.
stage('SonarQube analysis') {
steps{
withSonarQubeEnv('lohasmeal-sonar'){
sh """
./gradlew sonar
... args ...
-Dsonar.pullrequest.key=${gitlabMergeRequestIid} \
-Dsonar.pullrequest.branch=${gitlabSourceBranch} \
-Dsonar.pullrequest.base=${gitlabTargetBranch}
"""
}
}
}
5. 연동 시 주의사항
5-1. SonarQube Scanner 버전 문제
- gradlew의 sonar 명령어를 사용할 때 아래와 같은 에러와 함께 명령어가 실패하는 경우가 있다.
* What went wrong:
'org.gradle.api.provider.Provider org.gradle.api.reporting.Report.getOutputLocation()'
- 아래 질문의 답변에 의하면 SonarQube 버전을 5.x.x으로 설정하면 문제를 해결할 수 있다고 한다.
- SonarQube QnA
- SonarQube Docs: SonarScanner for Gradle
- Spring Application의 SonarQube 플러그인 버전을 업그레이드했다.
plugins {
id("org.sonarqube") version("5.1.0.4882")
}
5-2. Please provide compiled classes of your project with sonar.java.binaries property
- sonar.java.binaries를 설정해주지 않으면 아래와 같은 에러가 발생할 수 있다.
* What went wrong:
Execution failed for task ':sonar'.
> Your project contains .java files, please provide compiled classes with sonar.java.binaries property, or exclude them from the analysis with sonar.exclusions property.
- gradlew sonar를 실행할 때 Spring Application에서 SonarQube의 property를 설정해줄 수 있다. sonar.java.binaries 옵션을 추가해주도록 하자.
- build.gradle.kts 코드 예시
sonarqube {
properties {
property("sonar.host.url", "http://${SONARQUBE_URL}:9000")
property("sonar.login", "{SONARQUBE_TOKEN}")
property("sonar.sources", "src")
property("sonar.language", "java")
property("sonar.sourceEncoding", "UTF-8")
property("sonar.coverage.jacoco.xmlReportPaths", "${layout.buildDirectory.get()}/reports/jacoco/test/jacocoTestReport.xml")
property("sonar.java.binaries", "${layout.buildDirectory.get()}/classes")
property("sonar.test.inclusions", "**/*Test.java")
property("sonar.exclusions", "**/test/**, **/Q*.java, **/*Doc*.java, **/resources/**")
}
}
6. SonarQube 정적 분석 결과 확인하기
- 아래와 같이 Overview에서 SonarQube의 실행결과를 확인할 수 있다.
- 이슈에서는 Code Smell과 Bug를 확인해볼 수 있다.
7. 결론
회사에서 SonarQube 서버를 구성하고 사용한 지 딱 한 달이 되었다. SonarQube를 사용하고 나서 엄청난 변화가 있진 않았지만, 그냥 지나칠 수도 있는 실수에 대해 피드백 바로 받을 수 있기 때문에 코드 품질이 나빠지는 것을 방지할 수 있었다. 코드의 상태가 조금씩 좋아지는 중이다.
SonarQube를 더 잘 활용하기 위해 아래와 같은 방식을 고려해볼 수 있다.
- SonarLint IDE 플러그인 설치하여 SonarQube와 연동하기
- SonarQube에서 Custom Ruleset 설정하고 SonarLint로 설정 가져오기
SonarQube를 사용하는 것은 인프라 리소스를 꽤나 사용해야 하기 때문에 사용하기 망설여질 수 있다. 만약 리소스가 걱정이라면 SonarLint라는 플러그인만 설치해 사용하는 것만으로도 코드의 품질을 높일 수 있다.
참고자료
- SonarQube Docs: GitLab Integration
- SonarQube with Docker compose: complete tutorial
- [Setting | DevOps] jenkins, gitlab, sonarqube 연동설정
- SonarQube 정적분석 및 Jenkins CI/CD 통합
- SonarQube + Jenkins + GitLab
- 코드분석 도구 적용기 - 3편, SonarQube 적용하기
- 소나큐브 이용 코드 정적분석 자동화
- 소나큐브 커뮤니티 버전에서 Pull Request Decoration 설정 적용하기