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