2. CI/CD 구축하기 - SonarQube 및 Jenkins연동하기

2023. 4. 7. 04:50초기 과업/BackEnd

작성자알 수 없는 사용자

728x90
반응형

 

안녕하세요. 기깔나는 사람들에서 백앤드를 맡고있는 Hardy입니다.

 

이번에는 1편에 이어서 SonarQube 도 연결해 보도록 하겠습니다.

 

SonarQube도 도커로 설치할 예정입니다. 이것도 AWS ec2에서 운영하려고 했으나, 프리티어 인경우 SonarQube가

 

돌지 않는 현상이 발생합니다. 아마도 메모리가 작아서 그런거지 않을까 생각이 됩니다.

 

이제 시작 하겠습니다.

 

 


SonarQube

 

소나 큐브도 AWS가 아닌 로컬에서 docker-compose로 설치를 진행하겠습니다

 

version: "3.9"

services:
  sonarqube:
    image: sonarqube:latest
    container_name: sonarqube
    ports:
      - "9000:9000"
    environment:
      - SONARQUBE_JDBC_USERNAME=sonar
      - SONARQUBE_JDBC_PASSWORD=sonar
      - SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar
    networks:
      - sonarnet
    volumes:
      - /Users/iseung-gi/docker/sonar/conf:/opt/sonarqube/conf
      - /Users/iseung-gi/docker/sonar/data:/opt/sonarqube/data
      - /Users/iseung-gi/docker/sonar/logs:/opt/sonarqube/logs
      - /Users/iseung-gi/docker/sonar/extensions:/opt/sonarqube/extensions

  db:
    image: postgres:12
    container_name: sonarqube_db
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
      - POSTGRES_DB=sonar
    networks:
      - sonarnet
    volumes:
      - /Users/iseung-gi/docker/post/data:/var/lib/postgresql/data

networks:
  sonarnet:

 

environment는 소나 큐브 환경변수 , 소나큐브가 사용하는 db 정보를 설정해줬어요! sonar큐브가 내부적으로 디비를 사용하기 때문에 디비 설정 정보를 넣어 줬습니다! 

 

volumes는 소나큐브,db에서 활용가능한 디렉토리 들은 로컬에 있는 디렉토리랑 연결 시켜줬습니다. 

 

해당 컴포즈 파일은 두개의 서비스가 구동이 되는데요!

하나는 소나큐브 , 하나는 소나큐브가 사용하는 디비도 설정을 해주었습니다.

 

docker-compose up -d

 

을 실행하게 되면  sonarnet이라는 네트워크가 생기고 해당 네트워크 안에서 db, soanrqube가 구동되기 시작합니다.

 

이렇게 뜬걸 확인 하실수 있습니다.

 

 

 

 

 

이제 브라우저에서 localhost:9000을 입력해서 소나큐브 사이트에 접속을 해봅시다.

포트가 9000인 이유는 docker-compose에 9000:9000이라고 포워딩을 해놨기 때문입니다. 포트는 언제든지 바꿀 수 있어요!

 

첫 화면 입니다!

초기 계정 정보는 admin/admin으로 접속을 하시면 됩니다.

 

여기까지 간단한 소나큐브 구동 방법을 알아보았습니다.

 

 

 


SonarQube 와 Jenkins연동

 

소나큐브도 gitea와 마찬가지로 Token을 만들어서 등록을 해줘야 해요!

 

token생성하러가기

 

위에 보시면 A를 선택하시고 MyAccount에 들어갑니다.

 

 

token generate

 

 

Security탭에서 토큰을 생성할 수 있어요

이름을 입력 하시고 token type은 User Token으로 해주세요! 그리고 Generate를 누르면

 

토큰 발급

이렇게 토큰이 만들어집니다. 해당 토큰을 jenkins에 설정하러 가보겠습니다.

 

 

 

 

 

젠킨스로 돌아와서

 

credential 등록

 

Gitea와 마찬가지로 젠킨스에서 소나큐브와 통신을 하기 위해 credential를 등록 해줍니다. 위 화면에서 Add Credentials를 선택합니다.

 

 

 

 

credential등록

여기서 반드시 KIND를 Secret Text로 선택을 해주세요

 

SCOPE는  GLOBAL로 세팅해주세요 GLOBAL은 어디서든지 사용하겠다고 생각하시면 됩니다.

 

SECRET은 아까 소나큐브에서 발급받은 키값을 저장합니다.

 

ID는 Jenkinsfile안에서 사용할 것이기 때문에 적당한걸로 생성해주세요 

 

 

 

 

 

 

sonar plugin

 

GITEA와 마찬가지로 sonar로 검색한 후 SonarQube Scanner for Jenkins를 설치해줍니다.

파이프라인에서 SonarQube를 사용하기 위해서 반드시 설치가 필요로 합니다.

 

 

 

 

이제 다시 소나큐브로 돌아와서 프로잭트를 생성해보겠습니다.

소나큐브 메인 페이지로 가게 되면

소나큐브 프로젝트

이렇게 프로젝트를 생성할 수 있어요! 여기서 Manually를 선택합시다

 

 

 

 

소나큐브 프로젝트 생성

프로젝트 이름과 , 프로젝트키를 입력해주세요 해당 값들은 젠킨스에서 활용되니 잘 기억해주세요

여기까지 소나큐브에 설정은 끝이 났습니다.

이제 젠킨스로 가서 마지막 설정을 하도록 하겠습니다.

 

 

 

 

젠킨스에서 소나큐브 설정

대시보드 > 젠킨스 관리 > 시스템에 가면 SonarQube Server가 있을꺼에요

 

Name은 적당한걸 적어주세요!!

ServerUrl은 로컬서버주소(소나큐브 설치된 서버 주소)를 적어주시면 됩니다. 하지만 그냥 로컬서버 주소를 입력하게 되면 접속을 하지 못하기 때문에 외부에서도 내부 네트워크로 들어 올 수 있도록 설정하는법을 아래에서 설명하겠습니다.

Server authentication token은 아까 credential에 등록한 키값을 사용하면 됩니다.

 

고급을 누르시면 Additional analysis properties 입력 칸이 있습니다.

해당 입력칸에는 소나큐브에서 생성된 어떤 프로젝트를 사용할 것인지를 명시하는 칸입니다.

아까 소나큐브에서 생성한 프로젝트에 projectKey값 , name을 그대로 기입해 주시면 됩니다.

 

여기 까지 왔다면 젠킨스에서의 소나큐브 설정도 끝이 났습니다.

 

TIP!!!!

와이파이 연결한 노트북 포워딩 하는 방법

iptime

저 같은 경우 iptime을 사용하기 때문에 url창에 192.168.0.1을 입력합니다. 그 이후 접속 하고 들어갑니다.

 

 

 

 

외부주소

 

접속하고 들어가면 외부주소가 보이는데 이것을 잘기억해줍니다!!!

 

port 지정

 

위의 사진처럼 소나큐브 포트번호를 포워딩 해줍니다. 이렇게 되면 젠킨스 서버에서 외부주소IP:9000을 입력하게 되면 제 노트북에 있는 소나큐브 컨테이너에 요청을 보내게 됩니다.

 

 

 

 


JACOCO

 

소나큐브를 사용하게 되면 자코코가 필요로 하게 됩니다. 자코코는 자바 코드 커버리지를 확인할 수 있는 라이브러리 입니다.

소나큐브에 해당 jacoco 결과 파일도 전송이 됩니다.

 

프로젝트에서 jacoco설정하는 방법을 기재하겠습니다. 해당 설정은 build.gradle에 들어 있으면 됩니다.!!!

jacocoTestReport {
    dependsOn test
    reports {
        xml.required = true
        csv.required = false
        html.enabled false
        xml.destination file("${buildDir}/jacoco/jacoco.xml")
    }

    def Qclass =[]
    for (qPatter in '**/QA'..'**/QZ'){
        Qclass.add(qPatter+'*')
    }
    afterEvaluate {
        classDirectories.setFrom(
                files(classDirectories.files.collect {
                    fileTree(dir: it, excludes: [
                            "**/*Application*",
                            "com/junyharang/springinitproject/exception/**",
                            "com/junyharang/springinitproject/config/**",
                            "com/junyharang/springinitproject/model/dto/**",
                            "com/junyharang/springinitproject/common/log/**",
                            "com/junyharang/springinitproject/common/http/**",
                            "com/junyharang/springinitproject/common/log/&&"
                    ] + Qclass)
                })
        )
    }
}

소나큐브는  jacoco report를 보낼때는 반드시 xml형식으로 보내야 합니다.

아래 코드는 Qclass와 코드커버리지에서 제외할 클래스들을 정의한 부분입니다.

제외를 하게 되면 report에서도 제외가 됩니다.

 

 

 

 

jacocoTestCoverageVerification{
    def Qdomains = []
    for (qPattern in '*.QA'..'*.QZ') {
        Qdomains.add(qPattern + '*')
    }
    violationRules {
        rule {
            element='CLASS'
            excludes = [
                    "**.*Application*",
                    "**.*Config*",
                    "**.*Dto*",
                    "**.*Request*",
                    "**.*Response*",
                    "**.*Interceptor*",
                    "**.*Exception*"
            ] + Qdomains
        }
    }
}
test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
}

해당 부분은 어떤룰로 커버리지를 체크할껀지 정의한 부분입니다. 저는 기본적인 class단위로 진행을 했습니다.

 

여기까지 완료가 되었다면 jacoco설정도 끝이 났습니다

 

 

 


Jenkinsfile작성

 

pipeline {
    agent any
    stages {
        stage('checkout') {
            steps {
                checkout scm
            }
        }

        stage('build') {
            steps {
                sh './gradlew clean build -Pprofile=local'
            }
        }

checkout은 checkout scm 명령을 통해 GITEA에서 소스코드를 가져오는 과정입니다.

 

Build 는 checkout scm으로 부터 가져온 소스코드를 gradlw을 통해 빌드하는 과정입니다.

 

 

 

stage('sonarqube analysis') {
    when {
        branch "main"
    }
    steps {
        script {
            def scannerHome = tool 'scanner';
            withSonarQubeEnv('sonar') {
                sh "${scannerHome}/bin/sonar-scanner \
                                        -Dsonar.language=java \
                                        -Dsonar.java.source=17 \
                                        -Dsonar.sources=src/main/java \
                                        -Dsonar.test=src/test/java \
                                        -Dsonar.test.inclusion=**/*Test.java \
                                        -Dsonar.issuesReport.console.enable=true \
                                        -Dsonar.junit.reportPaths=build/test-results/test \
                                        -Dsonar.java.binaries=build/classes \
                                        -Dsonar.java.coveragePlugin=jacoco \
                                        -Dsonar.sourceEncoding=UTF-8 \
                                        -Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco/jacoco.xml \
                                        -Dsonar.exclusions=**/config/**/*,**/model/dto/**/*,**/log/**/*,**/http/**/*,**/controller/TestController.java \
                                        "
            }
        }
    }

 

when구문은 브랜치가 main일때 실행됨을 의미합니다.

 

sh명령어를 통해 jenkins에서 쉘 스크립트를 실행하고 , 소나큐브 서버로 결과를 전송합니다.

 

sh스크립트 실행시 옵션에 대한 정보는 아래에 적어 두었습니다.

 

-Dsonar.language=java // 분석 대상의 언어 설정
-Dsonar.java.source=17 \ // 분석 대상 java의 언어 버전
-Dsonar.sources=src/main/java \ // 소스 코드가 위치한 디렉토리를 지정
-Dsonar.test=src/test/java \ // 테스트 코드가 위치한 디렉토리를 지정
-Dsonar.test.inclusion=**/*Test.java \ 테스트 코드 파일명이 Test로 끝나는 파일만 테스트 대상으로 지정
-Dsonar.issuesReport.console.enable=true \ 분석 결과를 콘솔에 출력
-Dsonar.junit.reportPaths=build/test-results/test \ // JUnit 테스트 결과가 위치한 디렉토리를 지정
-Dsonar.java.binaries=build/classes \ // 컴파일된 자바 클래스 파일이 위치한 디렉토리를 지정
-Dsonar.java.coveragePlugin=jacoco \ // 코드 커버리지 분석 플러그인으로 JaCoCo를 사용
-Dsonar.sourceEncoding=UTF-8 \ // 소스 코드 인코딩을 UTF-8로 설정
-Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco/jacoco.xml \ //  JaCoCo 코드 커버리지 분석 결과가 위치한 XML 파일 경로
-Dsonar.exclusions=**/config/**/*,**/model/dto/**/*,**/log/**/*,**/http/**/*,**/controller/TestController.java \ // 분석 대상에서 제외할 파일 경로를 지정

 

 

stage('SonarQube Quality Gate') {
    when {
        branch "main"
    }
    steps {
        timeout(time: 1, unit: 'HOURS') {
            script {
                def result = waitForQualityGate()

                if (result.status != 'OK') {
                    echo "${result.status}"
                    error("Pipeline failed because static code analysis failed.")
                } else {
                    echo "OK Status: ${result.status}"

                }

            }
        }
    }

}

 

해당 스탭은 소나큐브 게이트 웨이로 부터 분석 결과를 기다리는 구문 입니다. 위에서 실행된 결과를 소나큐브에 전송을 하면

소나큐브 게이트웨이는 해당 분석 결과를 jenkins로 다시 전송을 합니다. 

waitforQualityGate()메소드를 통해 소나큐브 분석 결과값을 받을 수 있습니다.

 

결과 값이 OK이면 성공 , OK가 아닌경우에는 ERROR을 보낼꺼에요!!

 

그럼 어떤 기준으로 성공 실패를 나눌끼? 이부분은 소나큐브 Quality Gates에서 확인 할 수 있습니다.

디폴트는 최초에 만들어져 있는 sonar way의 기준으로 실행이 됩니다.

아래 이미지와 같이 설정된것들이 모두 통과해야 소나큐브 게이트 웨이로 부터 성공 메시지를 받을 수 있습니다.

 

디폴트를 사용하지 않고 자신만의 커스텀한 Quality Gates를 생성 하고 프로젝트에 적용할 수 있습니다.

 

sonar qube gates

 

 

위와 같이 jenkinsfile을 구성했다면 gitea에 jenkinsfile을 푸쉬 해보도록 하겠습니다.

 

 

gitea에 push가 되었다면 자동으로 젠킨스 파이프 라인이 돌고 있을꺼에요!!

 

결과화면

 

위 이미지를 보시면  jenkinsfile에 stage들이 정상적으로 실행된것을 알 수 있습니다. 

 

그리고 소나큐브에 들어가 보시면 아래와 같이 결과 화면을 보실 수 있고 프로젝트를 클릭하시면 더 자세하게 확인해 보실 수 있어요

 

소나큐브 결과

 

여기까지 젠킨스 소나큐브 연동이였습니다. 다음 챕터에서는 nginx설정 및 블루/그린 배포 방식에 대해 작성하겠습니다.

 

 


 

 

 

 

 

728x90
반응형