-
Notifications
You must be signed in to change notification settings - Fork 8
Sonarqube 도입기
우리 프로젝트의 정적 분석 리포트를 만든 과정을 남겨보도록 한다.
소프트웨어를 개발할 때 우리가 유념해야 할 것들이 있다.
- 컨벤션을 잘 지키고 있는가?
- 내 코드 안에 보안 취약점이 있을까?
- 코드가 반복되고 있지는 않을까?
- 의도하지 않은 버그가 발생하는가?
- 새로 작성한 코드를 위한 테스트가 잘 마련되어 있나?
그런데 여러 팀원이 다양한 기능을 구현하다보면 다양한 이유로 그 요소들을 고려하지 못하고 넘어갈 수 있다. 시간이 없어서 자세히 못 볼 수도 있고, 개발도 결국 사람이 하는 일이라 예상하지 못한 문제가 발생할 수 있기 마련이다. 프로젝트의 규모가 커질수록, 그리고 함께 작업하는 팀원의 수가 많아질수록 이런 상황이 발생할 확률이 높아진다.
문제점이 생겼을 때 확실히 발견하기 위해 개발자는 정적 분석 도구를 이용할 수 있다. '정적'이라는 표현에서 알 수 있듯 '동적 분석'도 있는데, 이 둘의 가장 큰 차이점은 문제점을 찾기 위해 소프트웨어를 실행하는지에 있다. 정적 분석은 소프트웨어를 실제로 실행하지 않고 소스코드를 이용하여 분석하는 방법이다.
정적 분석 툴의 종류로 여러가지가 있는데, 그 중 프로젝트에 사용한 것은 Sonarqube다. 위키피디아의 말을 빌리자면 Sonarqube는 20개 이상의 프로그래밍 언어에서 버그, 코드 스멜, 보안 취약점을 발견할 목적으로 정적 코드 분석을 하고 지속적으로 코드 품질을 검사한다. 검사 후 보고서를 제공하는데, 앞서 말한 보안 취약점, 중복코드, 그리고 테스트 커버리지와 같은 개발자가 유념해야할 것들에 관한 정보가 보고서에 포함된다.
docker run -d --name sonarqube -p 9000:9000 sonarqube
- 도커를 이용하여 EC2에 소나큐브를 설치했다.
- 참고로 소나큐브는 일부 기능만 무료로 제공한다. 그래서 어떤 에디션의 소나큐브를 다운받을지 선택해야 하는데, 도커에서도 에디션을 골라서 이미지를 받아올 수 있다. 위의 커맨드를 실행하면
Community Edition
,Developer Edition
, 그리고Enterprise Edition
이 모두 포함된 이미지를 pull한 뒤 컨테이너를 생성한다. - 컨테이너가 생성되면
[EC2 public ip]:9000
을 통해 소나큐브에 접속할 수 있다. 로그인을 해야하는데, 기본 계정은 다음과 같다.-
login
=admin
-
password
=admin
-
Gradle 프로젝트에서 방금 만든 소나큐브 서버에 접근할 수 있도록 토큰을 생성한다.
우측 상단의 프로필 -> My Account -> Security -> 토큰 이름 입력 -> Generate
생성한 토큰은 안전한 곳에 저장한다. 모두 완료 했으면 다시 소나큐브 메인 화면으로 돌아와 프로젝트를 생성한다.
Create Project -> manually -> 프로젝트 이름 및 Project Key 입력
여기서 Project Key는 우리 Gradle 에서 소나큐브 서버에 연결할 때 해당 소스코드가 어떤 프로젝트와 관련된 것인지 명시할 때 쓰인다.
이제 우리 프로젝트의 소스코드를 분석하고 정적 분석 리포트를 만들어야 한다. 정적 분석을 할 프로젝트의 build.gradle
파일로 간다.
plugins {
id "org.sonarqube" version "3.3"
id 'jacoco'
}
위와 같이 플러그인에 소나큐브와 jacoco(자코코)를 추가해야 한다. 그런데 자코코가 뭐지?
소나큐브가 테스트 커버리지에 대한 정적분석을 하기 위해서는 테스트 리포트를 전달받아야 한다. 자코코는 테스트를 실행할 때 이 리포트를 만들어주는 라이브러리로, 다음과 같은 기능을 제공한다.
- 바이트 단위, 라인 단위, 그리고 브랜치(조건문의 분기) 단위 테스트 커버리지 검사
- 검사 후 html, xml, 그리고 csv 형태로 리포트 생성 (선택 가능)
- 커버리지가 일정 수준을 만족하지 못하면 빌드를 금지
jacoco {
toolVersion = '0.8.5'
}
플러그인에 자코코를 추가했다면 이제 자코코 툴 버전을 명시한다. 0.8.0
버전 이상을 사용하는 것이 권장되는데, 롬복 생성 코드에 대한 커버리지를 제외하기 위해서다. 일단 build.gradle
설정을 다 끝내고 롬복 관련 설정을 마무리 짓도록 한다.
이제 jacocoTestCoverageVerification
과 jacocoTestReport
를 사용할 수 있다.
test {
jacoco {
destinationFile = file("$buildDir/jacoco/jacoco.exec")
}
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}
테스트가 테스트가 실행될 때 jacoco
설정을 명시한다. 우리 프로젝트에서는 커버리지 결과 데이터를 저장할 경로를 지정하는 설정을 명시했다. 그리고 finalizedBy 'jacocoTestReport'
를 추가하여 test
태스크 이후 jacocoTestReport
태스크가 실행되도록 한다. 명시하지 않은 설정들은 아래와 같은 디폴트 값이 들어간다.
enabled = true
destinationFile = file("$buildDir/jacoco/$.exec")
includes = []
excludes = []
excludeClassLoaders = []
includeNoLocationClasses = false
sessionId = "<auto-generated value>"
dumpOnExit = true
classDumpDir = null
output = JacocoTaskExtension.Output.FILE
address = "localhost"
port = 6300
jmx = false
jacocoTestReport {
reports {
html.enabled true
xml.enabled true
csv.enabled false
}
finalizedBy 'jacocoTestCoverageVerification'
}
해당 태스크를 이용하여 커버리지 결과를 저장할 방식을 정할 수 있다. 개발자가 보기 쉬운 형태인 html 형태와 소나큐브에 전송하기 위한 xml 형태로 저장되도록 했고, finalizedBy 'jacocoTestCoverageVerification'
을 추가해서 해당 태스크 이후 실행될 태스크를 명시했다.
해당 태스크를 이용하여 커버리지 기준을 세우고, 코드가 그 커버리지를 만족하는지 확인할 수 있다.
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
// 아래의 커버리지 기준을 클래스 단위로 검사하겠다는 설정
element = 'CLASS'
limit {
// 분기에 대한 커버리지 기준 : 최소 80%
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.80
}
limit {
// 코드 라인에 대한 커버리지 기준 : 최소 80%
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
// 커버리지 기준을 만족하는지 검사할 때 제외할 패키지
excludes = [
'botobo.core.infrastructure.**',
'botobo.core.exception.**',
'botobo.core.dto.**',
'botobo.core.DataLoader',
'botobo.core.BotoboApplication'
]
}
}
}
자코코 설정에 대한 정보는 이 곳을 참고했다.
build.gradle
파일에 소나큐브 관련 설정도 추가한다.
sonarqube {
properties {
// 소나큐브 프로젝트 생성할 때 만들었던 프로젝트 키
property "sonar.projectKey", "botobo-develop"
// 소나큐브가 실행되고 있는 인스턴스의 public ip
property "sonar.host.url", System.getenv('SONAR_URL')
// 소나큐브에서 만든 토큰
property "sonar.login", System.getenv('SONAR_LOGIN')
property "sonar.language", "java"
property "sonar.binaries", "$buildDir/classes"
property "sonar.sources", "src/main"
property "sonar.tests", "src/test/java"
property "sonar.junit.reportsPath", "$buildDir/test-reports"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.coverage.jacoco.reportPaths", "$buildDir/jacoco/jacoco.exec"
// 소나큐브에서 볼 리포트에서 제외하고 싶은 디렉토리
property "sonar.exclusions", "**/exception/**, " +
"**/dto/**, " +
"**/DataLoader.java, " +
"**/BotoboApplication.java"
}
}
ip 주소나 토큰과 같은 민감한 정보는 젠킨스 서버의 환경변수에 값을 등록한 뒤 System.getenv
를 이용하여 사용하도록 했다.
롬복으로 생성된 코드에 대한 커버리지는 제외하고 싶다면 백엔드 프로젝트의 root 디렉토리에 설정파일을 하나 추가해주면 된다.
lombok.addLombokGeneratedAnnotation = true
이제 젠킨스가 프로젝트를 테스트하고 빌드할 때 소나큐브에서 정적 분석 리포트를 만들도록 해야한다. 미리 만들어놓은 파이프라인에 아래의 스테이지를 추가하면 된다.
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv(credentialsId: 'sonar_jenkins', installationName: 'botobo-develop') {
dir('backend') {
sh "./gradlew sonarqube"
}
}
}
}
그리고 젠킨스에서 새로운 빌드를 실행하면 소나큐브 단계가 포함되는 것을 볼 수 있다. 소나큐브에서 해당 프로젝트 페이지에 들어가보면 리포트가 생성된 것을 볼 수 있다. 이 곳에서 현재 프로젝트에 어떤 취약점이 있는지, 개선할 수 있는 부분이 있는지, 그리고 중복되는 코드가 있는지와 같은 정보를 확인할 수 있다.
Code Convention
- AWS 배포 및 Jenkins CI/CD 🐳
- Nginx로 로드 밸런싱하기
- How to Git Rebase?
- 잘못된 깃 브랜치에서 탈출하기
- 서브모듈 도입기
- 소나큐브 도입기
- Flyway 도입기
- DB Replication을 위한 데이터베이스 환경 설정
- 무중단 배포 도입기
- nginx 설정파일 변경하는 방법
- 로그인, 로그아웃 흐름정리
- About Redis
- Criteria -> QueryDSL로 변경
- S3 파일 업로드 구조
2차 ~ 4차 회의 및 데일리 미팅은 디스코드에서 진행되어 이슈로 반영되었습니다.
이후 회의 및 데일리 미팅은 디스코드에서 진행되어 이슈로 반영되었습니다.