교육/devops

[SONARQUBE&JENKINS] 소나 큐브, 젠킨스 파이프라인 적용

가이버2 2022. 11. 26. 18:49

목표 : 미리 세팅된 소나 큐브를 젠킨슨과 연동하자.

추가 목표: 파이프 라인에 소나큐브를 등록하여 빌드/배포시 코드 품질을 자동으로 검사하게 하자.
참고 : 상당히 복잡할수 있다. 잘 따라하면 되고 막히는 부분은 검색으로 해결하자.

  1. 도커 설치. 
    1. 리눅스 설치 링크 : https://docs.docker.com/desktop/install/linux-install/
    2. 윈도우 설치 링크 : https://docs.docker.com/desktop/install/windows-install/
    3. 실리콘 맥에서 설치 권장하지 않음.
  2. Docker compose 사용하여 구축
    1. 이미 구축했던 젠킨스 & 소나큐브의 종합판. docker-compose.yml을 첨부 한다.(글 하단 추가)
    2. 파일과 같은 폴더에서 docker compose up을 이용하여 컨테이너를 띄운다.
    3. 앞의 글들을 참고하여 젠킨슨과 소나큐브를 세팅한다.
    4. 트러블 슈팅 또한 앞의 글들을 보며 해결한다.
    5. Maven, Gradle, .Net은 프로젝트 설정 파일에 플러그인 버전 표기등 필요한 수정을 해준다.
    6. TypeScript와 같이 Sonnar Scanner를 사용할 경우 테스트 분석시에는 필요없고 이후 상세 설정시 수정이 필요하다.
  3. 소나큐브에서의 작업
    1. Jenkins에서 사용될 토큰을 생성해야 한다.
    2. 소나 큐브 메인 창에서 우측 상단 A를 클릭하여 My Account에 들어간다.
    3. 우측 Security 탭을 누른다.
    4. Generate Tokens 에 값들을 입력한다.
      1. Name에 Jenkins
      2. Type에 UserToken
      3. Expires in에 기간을 설정
      4. Generate
    5. 이후 token 값이 생성된다.
  4. 소나큐브에서 젠킨슨 Webhook 적용
    1. 상단 메뉴중 Administration 클릭
    2. 좌측 상단 메뉴중 Configuration > Webhooks 클릭
    3. 우측 Create 클릭
    4. Create Webhook 창에 값을 입력한다.
      1. Name에 식별 가능한 이름 입력 (예. Jenkins)
      2. URL에 http://localhost:8080/sonarqube-webhook/
      3. secret는 공란
    5. 입력후 Create를 클릭한다.
  5. 젠킨슨에서의 작업
    1. Jenkins 관리에 들어가서 플러그인관리를 들어간다.
    2. 설치가능 탭에서 sonar를 검색한다.
    3. SonarQube Scanner, Sonar Quality Gates Plugin을 설치한다.
    4. 다시 Jenkins관리에 들어가서 Manage Credentials에 들어간다.
    5. System 옆 (global)을 클릭하고 우측 + Add Credentials를 클릭한다.
      1. Kind 는 Secreet Text
      2. Secret에 소나큐브에서 생성한 User token을 입력한다.
      3. ID에 식별 가능한 이름을 입력한다. (예. sonarqube)
    6. 다시 Jenkins관리에 들어가서 시스템 설정에 들어간다.
    7. SonarQube servers탭에 값들을 입력한다.
      1. Name 입력(예. sonar)
      2. 서버는 빈칸
      3. token은 드랍되는 상자를 클릭하여 5.3에서 미리 등록해둔 sonarqube를 선택한다.
    8. 다시 Jenkins관리에 들어가서 Global Tool Configuration에 들어간다.
    9. SonarQube Scanner의 Add SonarQube Scanner 클릭하고 값들을 입력한다.
      1. Name 식별 가능한 이름으로 입력한다. (ex.native)
      2. 체크된 Install automatically를 확인후 버전을 확인한다.
      3. 트러블 슈팅이 나면 그때 다시 수정하기로 하고 하단 Save를 누른다. 
  6. 깃 리포지토리의 Jenkinsfile에서의 작업 
    1. Jenkinsfile을 확인한다.(글 하단 추가) 
    2. 기존의 Jenkinsfile에 SonarQube stage를 추가합니다.
      1. //소나 스캐너 분석지점 이라는 주석을 찾아서 자신의 Jenkisfile에 알맞게 작성합니다. 
        1. ${FRONT_SONAR_PROJECTKEY}, ${BACK_SONAR_PROJECTKEY}는 앞에 글 소나큐브 프로젝트 생성 당시 입력했던 프로젝트명입니다.
        2. ${SONAR_URL}은 앞에글 http://localhost:9000 입니다.
        3. ${WEB_SONAR_TOKEN}, ${BACK_SONAR_TOKEN}는 프로젝트 생성 당시 프로젝트에 적용하라고 했던 스크립트에서 token을 확인할수 있습니다.
        4. 토큰을 까먹었다면 3.3-4 에서 type값을 Project Analysis Token으로 바꾸고 우측에 새로생긴 Project 상자에서 자신의 프로젝트는 선택하고 Generate 하면 새로 생성됩니다.
        5. dir('./backend'), dir('./web') 은 프로젝트안의 소스폴더를 의미하며 파일들을 인식합니다.
        6. Java, TypeScript 등을 인식후 분석을 진행합니다.
        7. 두  부분이 있는 이유는 다른 언어로 소나큐브 프로젝트를 2개 생성했기 때문입니다.
      2. //분석결과 대기 & 코드 품질 게이트 종료
        1. 소나 스캐너의 분석이 끝나는 것을 기다리는 구간
          1. 시간이 초과되거나 (unit : 1, time: "hour")
          2. 미리 정해둔 코드품질 조건을 미치지 못할경우(coverage: 80%)
          3. 빌드를 중지한다. (abortpipeline : true) 초기 도입, 개인프로젝트시 false를 추천
        2. 제한시간내에 분석이 끝나고 코드 품질이 조건을 충족시키면 계속 빌드가 진행된다.
      3. TypeScript와 같은 Sonar Scanner를 사용하는 구간에서 분석실패
        1. 원인 Jenkins Plugin -> SonarScanner가 제대로 설치되지 않는 문제가 있다.
        2. 해결 방법 직접 설치를 해주면 된다.
        3. docker ps 로 젠킨스도커 CONTAINER ID를 확인한다.
        4. root로 docker에 접속한다.
        5. docker exec -it -u 0 젠킨스도커CONTAINER /bin/bash
        6. 도커 컨테이너에 접속한 뒤 하단 명령어를 한줄 씩 입력한다.
        7. 스크립트 확인(하단 첨부)
        8. apt-get update
        9. apt-get install wget
        10. wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747-linux.zip
        11. unzip sonar-scanner-cli-4.7.0.2747-linux.zip
        12. mv sonar-scanner-4.7.0.2747-linux /var/opt/sonar-scanner
        13. rm sonar-scanner-cli-4.7.0.2747-linux.zip
        14. 끝나고 Jenkinsfile의 dir(.web) //소나 스캐너 분석지점에서 하단과 같이 수정한다.
        15. sonar-scanner \를 /var/opt/sonar-scanner/bin/sonar-scanner \로 수정한다.
        16. 수정후 다시 빌드를 시도해 보고 빌드 시 권한 문제가 발생한다면
        17. /var/opt/sonar-scanner/bin 폴더에서 chmod +x sonar-scanner 등의 명령어등을 사용한다.
        18. 권한은 Jenkins 유저로 적용해도 좋고 모두 접근가능하게 줘도 좋다.
  7. 소나 큐브 페이지(http://localhost:9000)에서 해당 소스파일의 분석결과를 확인할수 있다.

 

 

docker-compose.yml

version: "3"

services:

  jenkins:
    image: jenkins/jenkins:lts-jdk11
    volumes:
       - jenkins_data:/var/jenkins_home
       - jenkins_home:/home
       - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - '8080:8080'
      - '50000:50000'
    expose:
      - '8080'
    environment:
      TZ: Asia/Seoul

  sonarqube:
    image: sonarqube:community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
      TZ: Asia/Seoul
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
    expose:
      - "9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
      TZ: Asia/Seoul
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:
  jenkins_data:
  jenkins_home:
pipeline {  
    agent any
    tools{ 
        nodejs "nodejs" 
        gradle "gradle"
    }

    stages {
        stage('Prepare') {
            steps {
                echo  '준비 단계'
                git branch: 'main', 
                credentialsId: "${SSH_CREDENTIAL_ID}", 
                url: "${GIT_URL}"
                echo '01. OAuth_Client&Url'

                sh' sed -i "s/{KAKAO_CLIENT_ID}/${KAKAO_CLIENT_ID}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'
                sh' sed -i "s/{KAKAO_CLIENT_SECRET}/${KAKAO_CLIENT_SECRET}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'
                sh' sed -i "s/{FRONT_URL}/${FRONT_URL}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'
                sh' sed -i "s/{FRONT_URL_REDIRECT}/${FRONT_URL_REDIRECT}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'

                echo '02. JWT_TOKEN'
                sh 'sed -i "s/{JWT_SECRET}/${JWT_SECRET}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'
                sh 'sed -i "s/{TOKEN_SECRET}/${TOKEN_SECRET}/" "${WORKSPACE}/backend/src/main/resources/application.yml"'

                echo '03. SERVERR_URL'
                sh 'sed -i "s/{SERVER_URL}/${SERVER_URL}/" "${WORKSPACE}/nginx/nginx.conf"'
            }
            post {
                success { 
                    sh 'echo "Successfully Cloned Repository"'
                }
                failure {
                    sh 'echo "Fail Cloned Repository"'
                }
            }
        }
        stage('Build'){
            parallel{
                stage('Front Build') { 
                    steps {
                        script{
                            echo  '프론트 빌드 단계'
                            dir('./web'){
                                sh 'pwd'
                                sh 'npm install'
                                sh 'CI=false npm run build'
                            }

                            dir('.'){
                                sh 'cp -r web/dist nginx/build'
                            }
                        }
                    }
                    post {
                        success {
                            echo 'Front Build build success'
                        }
                        failure {
                            echo 'Front Build build failed'
                        }
                    }
                }
                stage('Backend Build') { 
                    steps {
                        echo  '백엔드 빌드 단계'
                        dir('./backend'){
                            sh './gradlew clean'
                            sh './gradlew build'
                        }
                    }
                    post {
                        success {
                            echo 'Backend Build success'
                        }
                        failure {
                            echo 'Backend Build failed'
                        }
                    }
                }
            }
        }
        stage('Analysis & Build File Transfer'){
            parallel{
                stage('Front SonarQube analysis'){
                    steps{
                        dir('./web'){
                        	// 소나스캐너 분석지점 시작
                            withSonarQubeEnv('sonar') {
                                sh """
                                    sonar-scanner \
                                        -Dsonar.projectKey=${FRONT_SONAR_PROJECTKEY} \
                                        -Dsonar.sources=. \
                                        -Dsonar.host.url=${SONAR_URL} \
                                        -Dsonar.login=${WEB_SONAR_TOKEN}
                                    """
                            }
                            // 소나스캐너 분석지점 끝
                        }
                    }
                    post{
                        success{
                            echo 'SonarQube analysis Gate success'
                        }
                        failure{
                            echo 'SonarQube analysis Gate failed'
                        }
                    }
                }
                stage('Backend SonarQube analysis'){
                    steps{
                            dir('./backend'){
                            	// 소나 스캐너 분석지점
                                withSonarQubeEnv('sonar') { // Will pick the global server connection you have configured
                                    sh """
                                            ./gradlew sonarqube \
                                              -Dsonar.projectKey=${BACK_SONAR_PROJECTKEY} \
                                              -Dsonar.host.url=${SONAR_URL} \
                                              -Dsonar.login=${BACKEND_SONAR_TOKEN}
                                        """
                                }
                                // 소나스캐너 분석지점 끝
                            }
                    }
                    post{
                        success{
                            echo 'SonarQube analysis Gate success'
                        }
                        failure{
                            echo 'SonarQube analysis Gate failed'
                        }
                    }
                }
                stage('Front build File Transfer') {
                    steps{
                        echo '빌드 파일 전송 단계'
                        script{
                            sshagent (credentials: ["${SSH_CREDENTIAL_ID}"]){
                                dir('./web'){
                                    sh "rm -rf build"
                                    sh "mv dist build"
                                    sh """
                                        ssh ${REMOTE_USER}@${REMOTE_HOST} "rm -rf ${PROJECT_FOLDER}/nginx/build"
                                        scp -r build ${REMOTE_USER}@${REMOTE_HOST}:${PROJECT_FOLDER}/nginx
                                        """
                                }
                            }
                        }
                    }
                    post{
                        success{
                            echo 'Front build File Transfer success'
                        }
                        failure{
                            echo 'Front build File Transfer failed'
                        }
                    }
                }
                stage('Backend build File Transfer') {
                    steps{
                        echo '빌드 파일 전송 단계'
                        script{
                            sshagent (credentials: ["${SSH_CREDENTIAL_ID}"]){
                                dir('./backend'){
                                    sh """
                                        scp Dockerfile ${REMOTE_USER}@${REMOTE_HOST}:${PROJECT_FOLDER}/backend/
                                        scp build/libs/newfamily*.jar ${REMOTE_USER}@${REMOTE_HOST}:${PROJECT_FOLDER}/backend/
                                        """
                                }
                            }
                        }
                    }
                    post{
                        success{
                            echo 'Backend build File Transfer success'
                        }
                        failure{
                            echo 'Backend build File Transfer failed'
                        }
                    }
                }
                stage('GateWay File Trasfer'){
                    steps{
                        echo '게이트웨이 파일 전송'
                        script{
                            sshagent (credentials: ["${SSH_CREDENTIAL_ID}"]){
                                dir('./nginx'){
                                    sh """
                                        scp nginx.conf ${REMOTE_USER}@${REMOTE_HOST}:${PROJECT_FOLDER}/nginx/
                                        scp Dockerfile ${REMOTE_USER}@${REMOTE_HOST}:${PROJECT_FOLDER}/nginx/
                                        """
                                }
                            }
                        }
                    }
                    post{
                        success{
                            echo 'GateWay File Transfer success'
                        }
                        failure{
                            echo 'GateWay File Transfer failed'
                        }
                    }
                }
            }
        }
        stage("Quality Gate & Docker Build Dploy"){
            parallel{
                stage('Front SonarQube Quality Gate'){
                	//분석결과 대기 & 코드 품질 게이트 시작
                    steps{
                        timeout(time: 1, unit: 'HOURS') {
                            waitForQualityGate abortPipeline: true
                        }
                    }
                    //분석결과 대기 & 코드 품질 게이트 종료
                    post{
                        success{
                            echo 'SonarQube Quality Gate success'
                        }
                        failure{
                            echo 'SonarQube Quality Gate failed'
                        }
                    }
                }
                stage('Backend SonarQube Quality Gate'){
                	//분석결과 대기 & 코드 품질 게이트 시작
                    steps{
                        timeout(time: 1, unit: 'HOURS') {
                            waitForQualityGate abortPipeline: true
                        }
                    }
                    //분석결과 대기 & 코드 품질 게이트 종료
                    post{
                        success{
                            echo 'SonarQube Quality Gate success'
                        }
                        failure{
                            echo 'SonarQube Quality Gate failed'
                        }
                    }
                }
                stage('Remote Docker build & Deploy'){
                    steps{
                        echo '원격 도커 빌드 단계'
                        sshagent (credentials: ["${SSH_CREDENTIAL_ID}"]){
                            sh """
                                ssh ${REMOTE_USER}@${REMOTE_HOST} "cd /home/ubuntu/${PROJECT_FOLDER} && docker compose down && docker compose build --no-cache"
                                ssh ${REMOTE_USER}@${REMOTE_HOST} "cd /home/ubuntu/${PROJECT_FOLDER} && docker compose up -d"

                            """
                        }
                    }
                    post{
                        success{
                            echo 'Docker build & Deploy success'
                        }
                        failure{
                            echo 'Docker build & Deploy failed'
                        }
                    }
                }
            }
        }

    }
}

 

init_sonar.sh

docker exec -it -u 0 젠킨스컨테이너ID /bin/bash

apt-get update
apt-get install wget
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747-linux.zip
unzip sonar-scanner-cli-4.7.0.2747-linux.zip
mv sonar-scanner-4.7.0.2747-linux /var/opt/sonar-scanner
rm sonar-scanner-cli-4.7.0.2747-linux.zip

출처 :