정적 분석 도구는 여러가지가 있는데 그중에서 어떤 것을 선택할 지 고민이었습니다.
가장 유명한 것은 sonarqube였는데 선택에 앞서서 명확한 근거를 가지고 채택해야 했습니다. 따라서 sonarqube와 각 정적 분석도구들의 특징들을 먼저 정리하고 비교했습니다.
이러한 분석 결과를 바탕으로 소나큐브를 사용하기로 결정했습니다.
sonarqube를 사용하기 위해서는 설치가 필요합니다.
방법은 로컬에 설치하는 것과 docker를 이용하는 법 2가지가 있습니다.
sonarqube는 기본적으로 h2 db를 사용합니다. h2는 SonarQube를 처음 설치할 때 기본으로 설정되어 있어 추가적인 설정이 필요 없고 가벼운 메모리 내장형 데이터베이스로, 간단한 테스트에 적합합니다.
다만, H2는 다수의 사용자와 대량의 데이터 처리가 필요하고 고가용성이 필요한 프로덕션 환경에는 적합하지 않습니다. 또한 내장형 데이터베이스 특성상 시스템 장애 시 데이터 손실 가능성이 높습니다.
제가 진행하는 프로젝트는 퀀트 모의투자 사이트로 지금 당장에는 고가용성이 그렇게까지 필요하지는 않았습니다. 다만, 추후에 실제 투자에도 모의투자 기능을 사용할 수 있도록 기획상으로 정해놓았습니다. 따라서, 이 경우 고가용성이 중요하다고 판단되었습니다. 더불어, 이용자가 추후에 늘어날 수도 있기도 하고 postgres를 db로 설정하는데 드는 비용도 크지 않다고 판단하였기에 postgres를 쓰기로 결정했습니다.
또한, postgres db와 sonarqube 설정을 설치없이 동시에 처리할 수 있고 다른 사람들과 같은 환경을 제공할 수 있는 docker로 구현하기로 결정하였습니다.
따라서 여러개의 컨테이너(postgres, sonarqube) 가 필요하므로 docker compose 설정을 우선 해주었습니다.
services:
sonarqube:
image: sonarqube:community
depends_on:
- db
environment:
SONAR_JDBC_URL: jdbc:postgresql://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"
ulimits:
nproc: 131072
nofile:
soft: 8192
hard: 131072
db:
image: postgres:16
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:
코드는 sonarqube 공식 사이트를 참고하였습니다.
하나씩 설명해보면
sonarqube:
image: sonarqube:community
depends_on:
- db
environment:
SONAR_JDBC_URL: jdbc:postgresql://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"
ulimits:
nproc: 131072
nofile:
soft: 8192
hard: 131072
db:
image: postgres:16
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
volumes:
- postgresql:/var/lib/postgresql
- postgresql_data:/var/lib/postgresql/data
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql:
postgresql_data:
db-1 | 2024-06-17 05:02:28.160 UTC [1] FATAL: database files are incompatible with server
db-1 | 2024-06-17 05:02:28.160 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 12.19 (Debian 12.19-1.pgdg120+1).
처음에 저처럼 다른 블로그를 참고해서 docker compose를 구성했다면 위와 같은 에러가 발생할 수 있습니다.
이는 volume의 문제이므로 이미지만 지운다고 해결되지 않습니다.
이때는 볼룸까지 같이 지워줘야 하므로 docker-compose down -v 명령어를 사용해주면 됩니다. 혹은 위의 docker compose 파일의 postgres 버전을 동일하게 변경해주면 됩니다. 위의 에러를 예로 들자면 image부분에서 postgres:12.19 -> postgres:16으로 바꿔주면 됩니다.
윈도우에서 공식문서대로 설정하고 실행할 경우 위와 같은 에러가 발생할 수 있습니다.
sonarqube가 내부적으로 elasticsearch를 사용하고있는데, elasticsearch가 요구하는 vm memory 용량을 늘려주지 않아 생긴 문제입니다.
즉, WSL2가 사용 가능한 시스템 리소스를 초과하여 발생한 문제이므로 리소스를 따로 제한해줄 필요가 있습니다.
저의 경우 docker compose 파일 구성에 리소스를 제한하는 ulimits를 설정하여 해결하였습니다.
ec2 인스턴스와 보안그룹은 설정되어있다고 가정하고 진행하겠습니다.
만약 ec2 설정이 되어있지 않는다면 아래 블로그 글을 참고하면 좋을 것 같습니다.
https://velog.io/@jonghyun3668/SpringBoot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-EC2-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0
위 블로그 글을 따라서 설정하면 되는데 보안그룹의 경우 sonarqube가 9000번을 사용하므로 인바운드에 9000번 포트 허용만 추가로 해주시면 됩니다.
EC2 인스턴스를 설정하고 접속하면 우선 Docker와 Docker Compose를 설치해야합니다.
sudo yum update -y # 패키지 업데이트
sudo yum install docker -y # docker 설치
sudo systemctl enable docker # 부팅시 docker 자동시작되도록 설정
sudo usermod -a -G docker ec2-user # 현재 사용자를 docker 그룹에 추가
sudo apt update # 패키지 업데이트
sudo apt install docker.io -y # docker 설치
sudo systemctl start docker # docker 시작
sudo systemctl enable docker # 부팅시 docker 자동시작되도록 설정
sudo usermod -aG docker $USER # 현재 사용자를 docker 그룹에 추가
sudo curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
이 명령어들을 실행하면 최신 버전의 Docker와 Docker Compose 바이너리를 다운로드하여 시스템에 설치하고, 해당 파일에 실행 권한을 부여하여 Docker 와 Docker Compose 명령을 사용할 수 있게 됩니다.
sudo nano docker-compose.yml
위에서 작성한 docker compose의 경우 nano editor를 사용해서 추가해주시면 됩니다(ctrl+s로 저장, ctrl+x로 나가기 하시면 됩니다)
sudo yum install nano -y # amazon
sudo apt install nano -y # ubuntu
만약 설치가 되어 있지 않다면 위의 명령어로 설치해주시면 됩니다.
docker-compose up -d
실행권한이 부여되었으므로 이제 docker compose 파일이 설치된 위치로 이동해서 위 명령어로 sonarqube를 실행하면 됩니다.
sonarqube의 경우 기본 4G의 메모리가 필요하므로 프리티어를 사용할 경우 메모리가 부족해서 실행되지 않을 수 있다.
이때, 메모리 스왑을 이용하면 메모리 부족 문제를 해결 할 수 있다.
스왑 파일을 생성하고 4GB로 설정합니다.
sudo fallocate -l 4G /swapfile
스왑 파일에 적절한 권한을 부여합니다.
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon --show
/etc/fstab 파일에 스왑 파일을 추가하여 시스템 재부팅 후에도 스왑이 유지되도록 설정합니다.
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
SonarQube를 실행할 때 발생하는 vm.max_map_count 오류는 시스템의 가상 메모리 매핑 수 설정이 낮기 때문에 발생합니다. SonarQube는 Elasticsearch를 사용하며, Elasticsearch는 높은 vm.max_map_count 값을 필요로 합니다. 이를 해결하려면 vm.max_map_count 값을 262144 이상으로 설정해야 합니다.
현재 vm.max_map_count 설정을 확인합니다.
sudo sysctl vm.max_map_count
설정을 일시적으로 변경하려면 다음 명령어를 실행합니다
sudo sysctl -w vm.max_map_count=262144
시스템 재부팅 후에도 설정이 유지되도록 하려면 /etc/sysctl.conf 파일에 설정을 추가합니다
sudo nano /etc/sysctl.conf
파일의 끝에 다음 줄을 추가합니다
vm.max_map_count=262144
변경 사항을 적용하려면 다음 명령어를 실행합니다
sudo sysctl -p
이렇게 설정까지 마치시고 docker를 실행하신다면 http://<EC2_PUBLIC_IP>:9000에 sonarqube가 실행 될 것입니다.
처음 아이디 비밀번호는
id : admin
password : admin
이므로 이를 입력하고 접속해주시면 아래와 같은 화면을 보실 수 있습니다.
이제 프로젝트 연결이 필요한데 저는 여기서 github를 사용할 예정이므로
github app 설정이 필요했습니다.
github 앱의 경우 setting> developer setting > new github app 으로 들어가면 github app을 만들 수 있습니다.
홈페이지와 callback url의 경우 public ip로 설정하면 됩니다. 저의 경우 ec2에 올렸으므로 http://ec2-4-30-266-34.ap-northeast-2.compute.amazonaws.com:9000 으로 설정하였습니다. webhook의 경우 당장은 사용하지 않아 일단 비활성화 시켜놓겠습니다.
권한의 경우 기본적으로
으로 설정하면 됩니다.
만약 repo가 private 일 경우 repository permissions에서
깃허브 oatuh로그인 설정을 할 경우 Account permission에서
Organization permissions에서
를 추가하면 됩니다.
깃허브 앱 설정을 하고 나면 프로젝트 setting>integration>githb app에서 방금 설정한 github app을 설치하면 됩니다.
자 이제 github app 설정이 완료되었으니 sonar qube 설정을 하면 됩니다.
위의 github setup 버튼을 누르시거나 Administration > DevOps Platform Integrations > Github > CreateConfiguration 으로 들어가면 github 프로젝트와 연동을 하실 수 있습니다.
이름은 원하시는 대로 작명하시면 되고 api url의 경우 github enterprise를 사용하는 경우 위쪽, 아닌 경우 아래쪽 url을 적어주시면 됩니다.
gihub app id, client id, client secret, private key의 경우 속해있는 조직에서 setting> developer setting> github app setting > edit 으로 들어가시면 정보를 볼 수 있습니다.
client secret, private key의 경우 발급당시에만 볼 수 있으므로 따로 저장해두시는 걸 추천합니다.
이제 원하는 project를 선택하고 세팅을 선택하면 아래와 같은 화면을 볼 수 있습니다. (저의 경우 global setting으로 진행하였습니다.)
저의 경우 githb actions를 사용할 것이므로 이를 선택하면
위와 같은 페이지가 나오는데 맞춰서 설정해주시면 됩니다.
name: Build
on:
push:
branches:
- main
jobs:
build:
name: Build and analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Cache SonarQube packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Build and analyze
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: ./gradlew build sonar --info
name: Backend CI
on:
push:
branches:
- develop
- main
- feature/ci
pull_request:
branches:
- develop
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
sonarqube:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Cache SonarQube packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Build and analyze
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }}
SONAR_PROJECT_NAME: ${{ secrets.SONAR_PROJECT_NAME }}
run: ./gradlew build sonar --info
sonar.yaml은 공식, custom.yaml은 제 코드입니다. 저의 경우 build가 성공했을 시에만 실행하고 싶어서 needs: build 라는 조건을 설정해 주었고 build.gradle에 project key와 name을 설정하는 대신 더 안전하고 관리하기 용이한 github secrets variable을 활용했습니다.
(github secrets의 경우 setting> secrets and variables> actions 에서 설정가능합니다)
우측 위의 프로필 선택> security 로 들어가면 위와 같은 화면이 나옵니다.
이름, 타입(용도에 맞게 선택, project>global>user로 갈수록 권한이 많습니다), 기한을 선택하고 생성하면 문자열 토큰 값이 생성됩니다.
이후 프로젝트 설정> secrets and variables > actions 로 들어가서 secret 변수를 생성하면 됩니다.
저의 경우 SONAR_TOKEN으로 이름을 설정하여 토큰 값을 저장하였습니다.
이렇게 설정을 마치고 나면 github action을 통해 ci가 실행될때 sonar qube가 실행되게 됩니다. sonar qube와 docker-compose 관련된 자료가 적고 오래되어서 구현하는데 꽤 많은 시간이 소요되었지만 sonar qube를 통해 코드의 품질을 측정할 수 있게 되어 유지, 향상이 수월해 질 수 있었습니다. 또한, docker-compose를 통해 환경을 일관적으로 가져갈 수 있게 되어 확장성 또한 확보할 수 있었습니다. 추후에도 이러한 코드 품질을 높일 수 있는 도구가 있다면 소개해보겠습니다. 감사합니다.
https://docs.sonarsource.com/sonarqube/latest/
https://dlwnsdud205.tistory.com/350
https://docs.sonarsource.com/sonarqube/9.9/devops-platform-integration/github-integration/