작업 환경

aws ec2 ubuntu 20.04

 

공식 문서

https://docs.docker.com/config/containers/logging/awslogs/

 

Amazon CloudWatch Logs logging driver

 

docs.docker.com

 

1. ec2 IAM에 CloudWatch 권한 역할 부여

ec2->인스턴스->작업->보안->IAM 역할 수정

기존에 IAM을 생성해두지 않은 상태로 새 IAM 역할 생성 -> 역할 만들기 -> AWS 서비스-> EC2

CloudWatchLogsFullAccess 권한 부여 -> 역할 생성

참조) docker 공식 문서에서는 아래와 같은 권한 2개 부여하라는데 권한 검색에 검색하면 안뜬다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

구글링 통해 fullaccess 정책 권한 부여했다. 공식 문서 업데이트가 필요해보인다.

이후 ec2 -> 인스턴스 -> 관련 인스턴스 선택 -> 보안 -> IAM 역할 수정에서 방금 만든 역할을 선택해 연결한다.

 

2. CloudWatch 로그 그룹/로그 스트림 생성

로그를 보내는 역할 설정을 완료했으므로 이제 로그를 받을 수 있는 공간 설정을 해야한다.

AWS CloudWatch -> 로그 -> 로그 그룹 -> 로그 그룹 생성 

생성한 로그 그룹 클릭 -> 로그 스트림 -> 로그 스트림 생성

3. docker-compose에 로깅 관련 정보 추가

services:
  backend:
    image: 가나다
    container_name: 라마바사
    
    ##아래 부분 생성한 정보대로 내용 추가하시면 됩니다.
    logging:
      driver: awslogs
      options:
        awslogs-group: higoods-docker-log
        awslogs-region: ap-northeast-2
        awslogs-stream: backend

 

정상 작동 확인

참고사이트

https://docs.docker.com/config/containers/logging/awslogs/

https://kitty-geno.tistory.com/67

https://ch-visu4l.tistory.com/14

https://devnm.tistory.com/8

 

최근에 진행 중인 2개의 프로젝트에 jacoco와 sonar cloud를 적용했다.

sonar cloud만 적용할 경우 아래와 같이 테스트 커버리지 값은 노출되지 않는다.

AS IS

jacoco를 활용해 테스트 커버리지를 측정하고 해당 측정 값을 소나클라우드로 전송해 소나클라우드 상에서도 테스트 커버리지를 함께 다룰 수 있는 방법을 소개하려고 한다.

TO BE

1. 전체 흐름

먼저 작업을 들어가기 전에 전체 작업 맥락은 다음과 같다.

jacoco를 통해 테스트 커버리지를 측정하고 측정 값을 xml 파일로 저장한다.

프로젝트 test 실행시 jacoco 테스트 커버리지가 측정 되며 지정된 경로에 xml 파일로 결과가 저장된다.

저장한 xml 파일을 소나클라우드로 전송한다.

소나클라우드에서는 해당 값을 활용해 테스트 커버리지 값을 매핑하고 다른 정적 검사 지표들과 함께 노출시킨다.

필자는 ci 스크립트를 활용해 main, develop 브랜치로 풀리퀘를 보낼 때 소나클라우드 검사 결과가 comment와 같이 뜨도록 설정하고자한다.

또한, 예시 코드는 코틀린으로 사용 중인 언어에 맞게 아래 코드들을 수정해 사용하면 된다.

2. jacoco 연동

jacoco란?

자바 코드 커버리지 측정 오픈 소스 라이브러리

 

build.gradle에 관련 연동 정보 추가

plugins {
    id("jacoco")
}

멀티 모듈 사용 중으로 subprojects 블록에 아래 jacoco 관련 설정 내용을 추가해준다.

subprojects {
    apply(plugin = "jacoco")
    tasks.test { //test 실행 시 jacocoTestReport 실행
        finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
    }
    tasks.jacocoTestReport {
        dependsOn(tasks.test) // tests are required to run before generating the report
        reports {
            xml.required.set(true) // xml 설정
            xml.outputLocation.set(File("$buildDir/reports/jacoco.xml")) //xml로 output
        }
        classDirectories.setFrom(
            files(
                classDirectories.files.map {
                    fileTree(it) { // 테스트 커버리지 측정 제외 목록
                        exclude(
                            "**/*Application*",
                            "**/*Config*",
                            "**/*Dto*",
                            "**/*Request*",
                            "**/*Response*",
                            "**/*Interceptor*",
                            "**/*Exception*"
                            //필요한 제외 목록 더 추가하시면 됩니다.
                        )
                    }
                }
            )
        )
    }
    }

test 실행 시 jacocoTestReport가 실행된다.

해당 결과 레포트를 xml로 받으며 파일의 저장 위치를 지정해줬다.

이때, 테스트 커버리지 측정에서 제외할 목록들 또한 설정해줬다.

 

테스트 실행시 지정된 경로에 측정 값 잘 저장되는 거 확인 가능

3. 소나클라우드 가입 및 설정

소나클라우드에 가입하여 자신이 작업 중인 조직과 레포지토리를 생성한다.

 

quality gate 생성을 통해 팀원들과 test coverage 통과 비율과 같은 수치들을 논의해 생성 후 프로젝트와 매핑하면 된다.

생성안할시 sonar way가 기본 디폴트 quality gate이며 해당 값들은 아래와 같다.

소나클라우드는 기본적으로 자동 검사를 지원한다. 필자는 ci 스크립트를 통해 분석할 것이기 때문에 자동 검사 옵션을 꺼줘야한다.

소나클라우드 레포지토리->administration->analysis method에서 비활성화한다.

 

이때, 레포지토리에서 administration이 안뜬다면 조직에 대한 권한이 없는거다.

조직 권한자에 부탁해 권한을 받은 후 추가적으로 조직->administration->groups->owners에서 자신의 소나클라우드 계정을 추가해야지만 administration 설정창이 뜬다.

analysis method를 비활성화하지 않고 스크립트에서도 실행할 경우 충돌이 일어나며 깃헙 액션즈 fail이 발생하므로 옵션을 꼭!! 꺼줘야한다.

소나클라우드 security 페이지에서 토큰을 생성한다.

해당 토큰은 ci 스크립트에 들어가는 토큰으로 깃헙 액션즈에 넣어야하므로 잘 기억해두자.

4. sonar cloud 연동

build.gradle에 관련 연동 정보 추가

plugins {
    id("org.sonarqube") version "4.2.1.3168"
}

sonarqube {
    properties {
        property("sonar.projectKey", "프로젝트명")
        property("sonar.organization", "조직명")
        property("sonar.host.url", "https://sonarcloud.io")
        // sonar additional settings
        property("sonar.sources", "src")
        property("sonar.language", "Kotlin")
        property("sonar.sourceEncoding", "UTF-8")
        property("sonar.test.inclusions", "**/*Test.java")
        property("sonar.exclusions", "**/test/**, **/Q*.kt, **/*Doc*.kt, **/resources/** ,**/*Application*.kt , **/*Config*.kt, **/*Dto*.kt, **/*Request*.kt, **/*Response*.kt ,**/*Exception*.kt ,**/*ErrorCode*.kt")
        property("sonar.java.coveragePlugin", "jacoco")
    }
}

subprojects {
    sonarqube {
        properties {
            property("sonar.java.binaries", "$buildDir/classes")
            //아래에 jacoco 연동시 지정해줬던 경로를 넣어줘야한다.
            property("sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco.xml")
        }
    }
}

5. ci 스크립트 작성

on:
  pull_request:
    branches: [ "main", "develop" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        
      - name: Cache SonarCloud packages
        uses: actions/cache@v3
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar

      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle

    - name: test and analyze
      run: ./gradlew test sonar --info --stacktrace --no-daemon
      env:
        GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

깃헙 토큰 발급 방법

프로필 사진 -> settings -> developer settings -> personal access tokens

레포지토리 settings에서 소나토큰과 깃헙 토큰을 넣어준다.

 

+ caused by: java.lang.outofmemoryerror: metaspace 에러 발생시

한 프로젝트에서는 ci 스크립트에 sonar 실행 시 oom 오류가 발생했고 한 프로젝트는 정상 작동했다.

프로젝트에 따라 heap 영역 메모리 사용량이 달라 발생했던 문제로

문제가 발생했던 프로젝트의 sonar 실행 명령어에 -Dorg.gradle.jvmargs="-Xmx2g" 옵션을 추가해 메모리를 늘려 해결했다.

ex. ./gradlew sonar -Dorg.gradle.jvmargs="-Xmx2g" --stacktrace --no-daemon

프로젝트 gradlew 설정에서 프로젝트의 -xmx -xms 값을 늘려 해결하는 방법도 있었는데 sonar task만을 위해 확장하는게 효율적이지 않다 생각하여 스크립트 상에서 soanr 실행시에만 메모리를 늘려 해결하는 방식을 택했다.

 

 

참고 사이트

https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/github-actions-for-sonarcloud/

https://techblog.woowahan.com/2661/

https://hsik0225.github.io/sonarqube/sonarcloud/tool/2021/11/23/SonarCloud-SonarCloud-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0/

https://devnm.tistory.com/36

https://hoohaha.tistory.com/37

https://velog.io/@haerong22/Spring-Cloud-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-MSA-6.-%EC%BD%94%EB%93%9C-%ED%92%88%EC%A7%88-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0jacoco-sonarqube

+ Recent posts