์ฐธ๊ณ
์ฐ๋ฆฌ ํ์ ์ฝ๋ ํ์ง์? ์ ์ ์ฝ๋ ๋ถ์๋๊ตฌ, ์๋ํ๋ธ ์ ์ฉ๊ธฐ
์ฝ๋ ๋ถ์ ๋๊ตฌ ์ ์ฉ๊ธฐ - 3ํธ, SonarQube ์ ์ฉํ๊ธฐ
์ ์ ์ฝ๋๋ถ์๊ธฐ๋ก ์ฝ๋๋ฅผ ๋ถ์ํ์ฌ ์ฝ๋ ํ์ง ๊ด๋ฆฌ์ ํฅ์์ ๋์์ด ๋๋ ๋๊ตฌ์ด๋ค. ์ด์ ์๋ ๋ง์ด ๋ค์์ง๋ง ๊ท์ฐฎ๋ค๋ ์ด์ ๋ก ํ๋ก์ ํธ์ ์ ์ ์ฉํ์ง ์๊ณ ์์๋๋ฐ.
์ด๋ฒ ํ์ฌ์์ ๊ตฌ๊ธ ๊ฐ์ฌ๋ถ์ ์ด๋นํ์ฌ ๊ฐ์๋ฅผ ์งํํ์์๋๋ฐ. ํด๋น ๊ฐ์ฌ๋ถ์ด ์์ ์ ์ฝ๋๊ฐ ๊ต๊ณผ์ ๊ฐ๋ค๋ ๋ง์ ๋ง์ด ๋ค์ผ์ ๋ค๊ณ ํ์
จ๋ค.
๊ทธ๋ฐ๋ฐ ์ด๋ฌํ ํน์ง์๋ ์ฝ๋ ์ ์ ๋ถ์๊ธฐ๊ฐ ๋ง์ ๋์์ ์คฌ๋ค๋ ๋ง์ ๋ฃ๊ณ ๋ฐ๋ก ์ ์ฉํด๋ณด๊ธฐ๋ก ๋ง์์ ๋จน์๋ค.
๊ทธ๋ฆฌ๊ณ ํ๋ด์์๋ ์๋ํ๋ธ๋ฅผ ์ ์ฉ์ค์ด๊ธฐ๋ ํ๋ค. ๋ด๊ฐ ๋ณผ์ ์๋๊ฑด ์์ง๋ง... ๊ทธ๋๋ ์๊ณ ์์ผ๋ฉด ๋์์ด ๋ ๊ฑฐ๋๊น!
์๋ ํ๋ธ๋ ์ ํจ์ค์ ๋น์ทํ๊ฒ ํ๋์ ์น์๋ฒ๋ฅผ ๋ง๋ค์ด์ ์ธํ ํด์ฃผ๋ ๋ฐฉ์์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํ๋์ ์๋ฒ๊ฐ ํ์ํ๋ค. ๋๋ ๊ฐ์ธ Raspberry pi4๋ฅผ ๊ฐ์ง๊ณ ์์ด์ ํด๋น ์๋ฒ์ ์ค์น๋ฅผ ์งํํ๋ค.
๊ฐ์ธ PC์ ์ค์นํด์ ์ฝ๋๋ฅผ ๋ถ์ํ ๋๋ง ์คํ์์ผ์ ์ฌ์ฉํด๋ ๊ด์ฐฎ์๊ฑฐ ๊ฐ๋ค!
version: "3"
services:
sonarqube:
image: sonarqube:community
hostname: sonarqube
container_name: sonarqube
depends_on:
- qube-db
environment:
SONAR_JDBC_URL: jdbc:postgresql://qube-db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
ports:
- "9000:9000"
qube-db:
image: postgres:13
hostname: postgresql
container_name: postgresql
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
POSTGRES_DB: sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:
๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์ docker compose ์คํ ๋ช ๋ น์ด๋ฅผ ํตํด ์คํ๋ง ํ๋ฉด ๋ฐ๋ก ์๋ฒ๊ฐ ์คํ๋๋ค.
ํ์ง๋ง ๊ฐ์ธ์๋ฒ์ ๊ฒฝ์ฐ ์ต์ ์ฌ์์ ๋ง์ถ์ง ๋ชปํด ์คํ์ด ์๋๋ ๊ฒฝ์ฐ๋ ์กด์ฌํ๋๋ฐ ๊ทธ๋ด๋ ์๋ฒ์ ์ธํ ์ ํ์ธ ํ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ต์ ์ฌ์์ ๋ง๋ ์๋ฒ๋ก ๊ต์ฒดํด์ผํ๋ค ใ ใ
์ต์ ์ฌ์
๋ฆฌ๋
์ค - vm.max_map_count 524288
๋ฆฌ๋
์ค - fs.file-max 131072
ํ์ผ ๋์คํฌ๋ฆฝํฐ ๊ฐ์ - 131072
์ค๋ ๋ ๊ฐ์ - 8192
sysctl vm.max_map_count
sysctl fs.file-max
ulimit -n
ulimit -u
ํ์ธ ํ ์ฌ์์ ๋ฏธ๋ฌํ๋ค๋ฉด ์๋ ๋ช ๋ น์ด๋ฅผ ํตํด ์ฌ์์ ์์ ํด์ฃผ์.
sysctl -w vm.max_map_count=524288
sysctl -w fs.file-max=131072
ulimit -n 131072
ulimit -u 8192
docker compose up -d --build
๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ๋ฉด์ด ์คํ๋๋ค.
plugins {
...
id 'jacoco'
id 'org.sonarqube' version '3.3'
}
dependencies {
...
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
}
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
dependsOn test
}
// jacoco
jacoco {
toolVersion = '0.8.8'
}
jacocoTestReport {
reports {
xml.enabled true // sonarqube ๋ถ์ ํ์ผ
csv.enabled false
html.enabled false
xml.destination file("${buildDir}/jacoco/index.xml") // ํ์ผ ๊ฒฝ๋ก ๋ณ๊ฒฝ
csv.destination file("${buildDir}/jacoco/index.csv")
html.destination file("${buildDir}/jacoco/index.html")
}
afterEvaluate {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(dir: it, excludes: [
'**/*Application*',
'**/*Exception*',
'**/*Advice*',
'**/dto/**',
'**/vo/**',
'**/enums/**',
'**/api/**',
'**/config/**',
// ...
])
})
)
}
finalizedBy 'jacocoTestCoverageVerification'
}
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
element = 'CLASS'
excludes = [
'*.*Application',
'*.*Exception',
'*.*Advice',
'*.dto.*',
'*.vo.*',
'*.enums.*',
'*.api.*',
'*.common.*',
'*.security.*',
'*.entity.*',
'*.entity.*.Q*',
]
limit {
counter = 'METHOD'
value = 'COVEREDRATIO'
minimum = 0.50
}
}
rule {
enabled = true
element = 'CLASS'
excludes = [
'*.*Application',
'*.*Exception',
'*.*Advice',
'*.dto.*',
'*.vo.*',
'*.enums.*',
'*.api.*',
'*.common.*',
'*.security.*',
'*.entity.*',
'*.entity.*.Q*',
]
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.50
}
}
}
}
sonarqube {
properties {
property "sonar.host.url", "{sonarqube server url}"
property "sonar.sources", "src"
property "sonar.language", "java"
property "sonar.projectKey", "appling-api"
property "sonar.projectName", "appling-api-prod"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.java.binaries", "${buildDir}/classes"
property "sonar.test.inclusions", "**/*Test.java, **/*Docs.java"
property "sonar.exclusions", "**/resources/static/**, **/Q*.class, **/test/**"
property "sonar.coverage.jacoco.xmlReportPaths", "${buildDir}/jacoco/index.xml"
}
}
์ ์ ๋ถ์์ SonarQube๋ฅผ ์ฌ์ฉํ๊ณ ๋์ ๋ถ์์ jacoco๋ฅผ ์ฌ์ฉํ์๋ค. ํด๋น ์ค์ ์ผ๋ก ์์ค์ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ ํ์ธํ ์ ์๋ค.
๋ค๋ฅธ ์ ํจ์ค์ ์๋ ์ค์ ์ด๋ ๊นํ ์ก์ ์ ํตํ ์๋์ค์ ๋ ๋ง์๋ฐ ๋๋ ๊ทธ๋ฅ ๋ช ๋ น์ด๋ก ๊ฐ๋จํ๊ฒ ์คํํ๊ณ ์ ํ๋ค. ์์ง ํ๋ก์ ํธ๊ฐ ์์ฒญ ๋ณต์กํ ๊ฒ๋ ์๋๊ธฐ ๋๋ฌธ์!
pipeline {
agent any
stages {
stage('pull') {
steps {
git branch: 'main', credentialsId: 'git', url: 'https://github.com/appling-c/appling-api.git'
}
}
stage('build') {
steps {
sh './gradlew clean build'
}
}
stage('sonar') {
steps {
sh './gradlew sonarqube -Dsonar.login={sonarqube loing user token}'
}
}
stage('clean') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'normal',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: 'sudo rm -r /home/ubuntu/appling/api/build',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: ''
)
],
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('transfer') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'normal',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '/home/ubuntu/appling/api',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: 'build/libs/appling-1.0.jar'
)
],
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('up') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'normal',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: 'cd /home/ubuntu/appling/api && sudo docker compose down ; sudo docker compose up -d --build',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: ''
)
],
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
}
}
clean ๋ถํฐ๋ ๊ฐ์ธ ์ค์ ์ด๊ธฐ ๋๋ฌธ์ ๋ณด์ง ์์๋ ๋๊ณ sonar๊น์ง ์ฐธ๊ณ ํด์ ์์ฑํ๋ฉด ์ข์๊ฑฐ ๊ฐ๋ค.
๋ฐ๋ก ํฐ ์ค์ ์์ด gradle ์ค์ ์ ํตํด์ ์๋ํ๋ธ์ ๋ฐ๋ก ์ ์ฉ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ์์ฒญ ๊ฐ๋จํ๋ค.
๊ทธ๋ฆฌ๊ณ sonarqube.loing=
๋ค์ด๊ฐ๋ ํ ํฐ๊ฐ์ ์๋ํ๋ธ์ ๋ก๊ทธ์ธ ํ user token์ ๋ฐ๊ธ ๋ฐ์์ ์ฌ์ฉํ๋ฉด ๋๋ค.
๊ทธ ํ ํ๋ก์ ํธ์ ๋ค์ด๊ฐ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ค์ ๋๋ก ๋ด ์ฝ๋๋ฅผ ๋ถ์ํด์ ์๋ ค์ค๋ค...
๋๋ฌผใ
์ด์ ์์ค ๋ถ์์ ๋ง์ถฐ์ ์์ค๋ฅผ ์์ ํด๊ฐ๋ฉฐ ์ฆ๊ฒ๊ฒ ์ฝ๋ฉ์ ํด๋ณด์!