์ด์ ๋ ธ์ ๋ธ๋ก๊ทธ์ ์๋ํ๋ธ์ Jacoco๋ก ์ฝ๋๋ฅผ ๊นจ๋ํ๊ฒ ์ ์งํ๊ธฐ (2023.09.07)๋ก๋ถํฐ ๋ง์ด๊ทธ๋ ์ด์ ๋ ๊ธ์ ๋๋ค.
โ๏ธ Setup
EC2 ์๋ฒ์ SonarQube ์ค์น ๋ฐ ์ค์
1. ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ๋ฅผ ์ํ Swap ์ค์
- ํ์ผ ์์ฑ ๋ฐ ์ค์น
touch ~/swapfile
sudo fallocate -l 2G ~/swapfile
sudo chmod 600 ~/swapfile
sudo mkswap ~/swapfile
sudo swapon ~/swapfile
sudo yum install java-17-amazon-corretto-headless
- ์๋ ํ์ผ๋ค์ ์ด์ด์ ๊ฐ์ฅ ์๋์ ์ถ๊ฐ
/etc/fstab
~/swapfile swap swap defaults 0 0
/etc/sysctl.conf
vm.max_map_count=524288
fs.file-max=131072
ulimit -n 131072
ulimit -u 8192
/etc/security/limits.conf
* - nofile 131072
* - nproc 8192
2. ์๋ ์ค์น ๋ฐ ์ค์
๋นํจ์จ์ ์์ ๋๋ผ๊ณ , ๋์ปค ๊ธฐ๋ฐ ์๋ํ ์ค์ ์์
2. Docker ๊ธฐ๋ฐ ์๋ ์ค์น ๋ฐ ์ค์
- Docker์ Docker-compose ์ค์น
sudo yum install docker
sudo service docker start
sudo usermod -a -G docker ec2-user
// auto-start์ docker ๋ฑ๋ก
sudo chkconfig docker on
sudo yum install docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- docker-compose.yml ์์ฑ
- sckwon770/sonarqube:9.9.1-community : SonarQube + PR decoration ํ๋ฌ๊ทธ์ธ์ด ํฉ์ณ์ง ์ปค์คํ ๋์ปค ์ด๋ฏธ์ง
version: "3"
services:
sonarqube:
image: sckwon770/sonarqube:9.9.1-community
container_name: sonar
ports:
- "8080:9000"
ulimits:
nofile:
soft: "262144"
hard: "262144"
networks:
- sonarnet
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
db:
image: postgres:13
container_name: postgres
ports:
- "5432:5432"
networks:
- sonarnet
environment:
- POSTGRES_USER=sonar
- POSTGRES_PASSWORD=sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data
networks:
sonarnet:
driver: bridge
volumes:
sonarqube_conf:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:
- SonarQube์ PostgreSQL ์์
docker-compose up -d
docker logs sonarqube -f
Github App ๋ฑ๋ก
1. Github App ์์ฑ
- https://github.com/settings/apps/new์์ Github App ์์ฑ
- Github App์ SonarQube์ ์ฐ๋ํ์ฌ PR์ ์ฝ๋ฉํธ๋ฅผ ์์ฑํ ์ ์๋ ๊ถํ์ ๋ถ์ฌํ๋ ๋ฐ ์ฌ์ฉ๋จ
- callback URL๊ณผ webhook URL์ SonarQube ์๋ฒ ์ฃผ์๋ฅผ ์ ๋ ฅ (ex: http://10.0.0.1:8080)
- Repository permissions ์ค์
- Checks์ Read and write
- Commit statuses ์ Read-only
- Metadata์ Read-only
- Pull requests ์ Read and write
- Account๋ Any account ์ฒดํฌ
2. SonarQube์ Githhub App ๋ฑ๋ก
- SonarQube ํ์ด์ง > Create project > Github > Github Action
- ์ง์์ฌํญ์ ๋ง๊ฒ build.gradle์ ์ถ๊ฐ, Github Action์ flow.yaml์ ์ถ๊ฐ
- build.gradle์์ plugins ๋ถ๋ถ์ ์ง์์ฌํญ๊ณผ ๋ค๋ฅด๊ฒ ์๋์ ๋ฒ์ ์ผ๋ก ์ถ๊ฐํด์ผ ํจ. SonarQube 3๋ฒ์ ์ด ์ต๊ทผ ๋ฒ์ ์ธ Gradle 8์์ deprecated๋ ๊ทธ๋ฃจ๋น ๋ฌธ๋ฒ์ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์, Github Actions์์ ๋น๋ ์ค๋ฅ ๋ฐ์ํจ
- SONAR_TOKEN๊ณผ SONAR_HOST_URL์ Github action secret์ ์ ์ฅ
- flow.yaml ์ ์๋ secrets.GITHUB_TOKEN ์ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋จ
- SonarQube ํ์ด์ง > Administration > Configuration > General > Server base URL์ ์๋ํ๋ธ URL ์ค์ (ex: http://10.0.0.1:8080)
- PR Decoration์ ๋งํฌ ์ฃผ์๋ก ์ฌ์ฉ
3. ๋ถ์ ๋ฐ PR ์ฝ๋ฉํธ ๋ฑ๋ก์ด ์๋ฃ๋ ๋, ์ฌ๋ ์๋ฆผ ๋ฐ๊ธฐ
- ์ฌ๋์์ ์๋ฆผ์ ๋ฐ์ ์ฑ๋ ์์ฑ
- https://api.slack.com/apps ์์ Create New App > App Home > Edit name
- ์ด ์ค์ ์ ํ์ง ์์ ๊ฒฝ์ฐ, Webhook URL์ ์์ฑํ๋ ๊ณผ์ ์์ ์ฑ์ ์ค์นํ ๋ด ์ฌ์ฉ์๊ฐ ์์ต๋๋ค. ์ด ๋ฐ์ํจ. ์ฃผ์
- Incoming Webhooks > Add New Webhook to Workspace์์ ๋ฐํํ URL์ Github action secret ์ ์ ์ฅ (ex. SLACK_BE_PR_ANALYSIS_ALARM_WEBHOOK )
action-slack:
if: ${{ always() }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Slack Alarm
uses: 8398a7/action-slack@v2
with:
status: ${{ job.status }}
author_name: GitHub-Actions development automation
fields: repo,message,commit,author,ref,job,took
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_BE_PR_ANALYSIS_ALARM_WEBHOOK }}
if: always() # Pick up events even if the job fails or is canceled.
Jacoco ๋ฑ๋ก
Jacoco๊ฐ ์์ผ๋ฉด SonarQube PR Comment์์ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ์ฒจ๋ถ๋์ง ์๊ธฐ ๋๋ฌธ์, Jacoco๋ก ๋ํ๋์์ ์ถ๊ฐํด์ผ ํจ.
Jacoco๋ฅผ ์ถ๊ฐํ๋ฉด jacocoTestReport ๊ณผ jacocoTestCoverageVerification ๊ฐ ์ถ๊ฐ๋๋๋ฐ, ํ ์คํธ ์คํ๊ณผ ํจ๊ป ์งํ๋์ผ ํ๋ฏ๋ก ์คํํ ํ ์คํฌ๊ฐ ๋๋ฌด ๋ง๋ค. ์ปค์คํ ๊ทธ๋ ์ด๋ค ํ ์คํฌ testCoverage๋ก ํ ๋ฒ์ ์คํํ ์ ์๋ค. Jacoco ๋น๋ ๊ฒฐ๊ณผ HTML์ build/jacocoHtml/index.html ์ ์๋ค.
plugins {
id "jacoco"
}
jacoco {
toolVersion = '0.8.7'
}
jacocoTestReport {
dependsOn test
mustRunAfter test
reports {
xml.required = false
csv.required = false
html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
}
afterEvaluate {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(dir: it, excludes: [
'**/ReviewMateApplication*',
'**/*Formatter*',
'**/*Interceptor*',
'**/GlobalControllerAdvice*',
'**/SwaggerConfig*',
'**/MultipartJackson2HttpMessageConverter*',
'**/StaticRoutingConfiguration*',
'**/*BaseEntity*'
])
})
)
}
}
jacocoTestCoverageVerification {
mustRunAfter jacocoTestReport
violationRules {
rule {
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.0
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.00
}
limit {
counter = 'METHOD'
value = 'COVEREDRATIO'
minimum = 0.0
}
}
rule {
element = 'METHOD'
excludes = [
"*"
]
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 200
}
}
}
}
task testCoverage(type: Test) {
group 'verification'
description 'Runs the unit tests with coverage'
dependsOn('test',
'jacocoTestReport',
'jacocoTestCoverageVerification')
}
1. ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง์์ ์ ์ธํ ํด๋์ค ์ค์
โ ๏ธ ์ฃผ์ํ ์ : ๊ฐ๋ฐ ๋ธ๋ก๊ทธ์ ์ฌ๋ผ์ ์๋ ๋ง์ ์์ Jacoco ํฌ์คํ ์ด Jacoco ํน์ ๊ทธ๋ ์ด๋ค ์ต๊ทผ ๋ฒ์ ๋ค๊ณผ ํธํ๋์ง ์์. ํนํ, ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง์์ ์ ์ธ ํ ํด๋์ค ์ค์ ํ๋ ๊ฒ์ ์๋๋ฅผ ๋ฐ๋ผ์ผ ํจ.
jacocoTestReport {
afterEvaluate {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(dir: it, excludes: [
'**/ReviewMateApplication*',
'**/*Formatter*',
'**/*Interceptor*',
'**/GlobalControllerAdvice*',
'**/SwaggerConfig*',
'**/MultipartJackson2HttpMessageConverter*',
'**/StaticRoutingConfiguration*',
'**/*BaseEntity*'
])
})
)
}
}
2. ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง์์ ์ ์ธํ ํจ์ ์ค์
https://sungsan.oopy.io/1d6e3f0c-7a3e-48f2-bb62-ad0e37e3c888
3. Lombok์ด ์์ฑํ ํจ์๋ค ์ ์ธ
ํ๋ก์ ํธ์ ๋ฃจํธ ๊ฒฝ๋ก์ lombok.config ํ์ผ์ ์์ฑํ๊ณ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํ๋ฉด ๋๋ค.
lombok.addLombokGeneratedAnnotation = true
์ ์ฉ ๊ฒฐ๊ณผ
์ฌํ ๊ฐ๊ตฌ๋ฆฌ๊ฐ ๋ถ์์ด ์๋ฃ๋๋ฉด, ํด๋น PR์ ๋ณ๊ฒฝ์ ์ ํ์ ์ผ๋ก ํต๊ณผ ์ฌ๋ถ์ ์ฝ๋ ํ๋ฆฌํฐ, ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์์ฑํด์ค๋ค. ์ฌ์ง์์ Failed๋ ์ด์ ๋ ์ฌ์ฉํ์ง ์๋ import๋ฌธ ํ๋๊ฐ ์์ด์ ๋ฐ์ํ๋ค. ์ด ์ ๋๋ก ๋นก๋นกํ๋ฏ๋ก, ์ ์ด์ IDE์์ SonarLint๋ฅผ ์ค์นํด์ Pushํ๊ธฐ๋ ์ ์ ๋ฏธ๋ฆฌ ์ฒดํฌํด๋๋ ๊ฒ์ด ์ข์๋ฏ ์ถ๋ค.
๋ถ์ํ๊ณ PR ์ฝ๋ฉํธ๊ฐ ์์ฑ๋๋๋ฐ ์๊ฐ์ด ๊ฝค ๊ฑธ๋ฆฌ๋ฏ๋ก, ์๋์ฒ๋ผ ์ฌ๋ ์๋ฆผ์ ์ค์ ํ๋ ๊ฒ๋ ์ข๋ค.
๐ ํธ๋ฌ๋ธ์ํ
1. Docker ๊ด๋ จ ๋ช ๋ น์ด ์คํ ์ค, /var/run/docker.sock์ permission denied ๋ฐ์
$ docker ps -a
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/json?all=1: dial unix /var/run/docker.sock: connect: permission denied
์์ธ
Docker ๋ช ๋ น์ด ์ฌ์ฉ์ ํ์ํ docker.sock์ ์คํํ ๊ถํ์ด ์๋ ์ํ
ํด๊ฒฐ ๋ฐฉ๋ฒ
- /var/run/docker.sock ํ์ผ์ ๊ถํ์ 666์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๊ทธ๋ฃน ๋ด ๋ค๋ฅธ ์ฌ์ฉ์๋ ์ ๊ทผ ๊ฐ๋ฅํ๊ฒ ๋ณ๊ฒฝ
sudo chmod 666 /var/run/docker.sock
- ๋๋ chown ์ผ๋ก group ownership ๋ณ๊ฒฝ
sudo chown root:docker /var/run/docker.sock
2. SonarQube๊ฐ ์ ์์ ์ผ๋ก ์คํ๋์๊ณ ๋คํธ์ํฌ์์ ํฌํธ ์ด์์ง๋ง, ์ฐ๊ฒฐ์ด ๊ฑฐ๋ถ๋จ
์์ธ
docker-compose.yml ์์ ํฌํธ ์ค์ ์ด ์๋ชป์ธ์์
(ex. ํฌํธํฌ์๋ฉ์ด ์๋ชป๋ ๊ฒฝ์ฐ)
services:
sonarqube:
ports:
- "8080:8080"
ํด๊ฒฐ๋ฐฉ๋ฒ
posts ์ค์ ์ ๋ค์๊ณผ ๊ฐ์ด ์ด๋ฃจ์ด์ ธ ์์
services:
sonarqube:
ports:
- "์ปจํ
์ด๋๊ฐ_์์ฒญ๋ฐ๋_ํฌํธ:์์ฒญ์_ํฌํธํฌ์๋ฉ_ํ _ํฌํธ"
๊ทธ๋ฆฌ๊ณ ์๋ํ๋ธ์ ๊ธฐ๋ณธ ์ ์ ํฌํธ๋ 9000 ์ ๋๋ค. ๋ฐ๋ผ์ ์ธ๋ถ์์ 8080ํฌํธ๋ก ๋ค์ด์ค๊ธฐ๋ก ์ค์ ํ๋ค๋ฉด, ๊ทธ ํฌํธ๋ฅผ 9000 ํฌํธ๋ก ํฌ์๋ฉํด์ค์ผ ๋จ.
(ex. ์ ์ ์ค์ )
services:
sonarqube:
ports:
- "8080:9000"
3. max virtual memory areas vm.max_map_count is too low. ~
์์ธ
docker-compose.yml ์์ vm.max_map_count ๋๋ฆฌ๋ ๋ถ๋ถ์ด ๋น ์ก๊ฑฐ๋, ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ค์ ์ด ์๋ชป๋ ๊ฒฝ์ฐ ๋ฐ์
ํด๊ฒฐ๋ฐฉ๋ฒ
docker-compose.yml ์ ํด๋น ๋ถ๋ถ์ด ๋น ์ก๋ค๋ฉด, ์ถ๊ฐ
ulimits:
nofile:
soft: "262144"
hard: "262144"
๊ทธ๋๋ ๋์ง ์๋ ๊ฒฝ์ฐ, EC2 ์ปค๋์ ๋ฉ๋ชจ๋ฆฌ ์ค์ ์ฌ์๋
์ฐธ๊ณ ์๋ฃ
https://creampuffy.tistory.com/196
https://gblee1987.tistory.com/105
'๐ค Backend > SpringBoot' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
java.sql.SQLException: Incorrect string value (0) | 2024.02.25 |
---|---|
์คํ๋ง jpa application.properties ์ค์ (0) | 2024.02.25 |
ํ๋ก ํธ๊ฐ ํผ๋์ ๋น ์ง์ง ์๋ ์๋ฒ์ API ๋ฌธ์ ๋ง๋ค๊ธฐ (1) | 2024.02.25 |
Swagger์์ MultipartFile๊ณผ DTO ํ ๋ฒ์ ๋ฐ๋ @RequestPart ์์ฒญ์ ์คํํ ์ ์๋๋ก ๋ง๋ค๊ธฐ (0) | 2024.02.25 |
enum ํ๋๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌํ๊ธฐ (0) | 2024.02.25 |