본문 바로가기
Cloud/Docker | K8s + CI, CD

Github Actions를 이용한 CI 빌드, 테스트 자동화

by HelloJudy 2023. 5. 6.

 
Github Actions는 Github에서 제공하는 CI/CD 툴이다. 예전에 스터디에서 CircleCi, ArgoCD를 공부한 적이 있다.
(Travis Ci도 Github 연동이 편해서 많이 쓰더라)
 
이때 개인 프로젝트에서 위와 같은 CI/CD 툴은 공부하기 어려웠고, CircleCi는 ㅎ 실습하다가 망해서 github repo에 private으로 영원히 묻어뒀다. 언젠간 열어줄게.
 
댕청해 보여서 마음에 드는 argo와 circleci에 고통받는 나의 모습을 본 스터디원..
 
👤 : 정윤님!! Github Actions가 세상 편한데! 이걸로 하면 금방 하실 수 있어요!!
🐰 : CI/CD 늪에서 꺼내줘!!!
 

댕청해 보이는 로고

 
그렇게 작년부터는 계속 Github Actions를 쓰고 있다!
 
오늘은 Github Actions를 이용해서 CI 빌드, 테스트 자동화를 해보겠다.
 


 

0. 요구사항

 

⚙️ 프로젝트 환경

  • java : 11
  • Spring Boot : 2.7.11
  • 멀티 모듈 프로젝트
  • 서브 모듈 사용 (yml파일 관리)

 

✅ 요구사항

  • main branch로 pull request가 발생했을 때 ci workflow를 실행한다. (main branch는 push 금지)
  • dev branch로 push, pull request가 발생했을 때 ci workflow를 실행한다.
  • Gradle을 이용해 빌드하고 ci 속도를 높히기 위하여 의존하는 라이브러리들을 캐싱한다.
  • ci가 성공했을 때만 merge가 가능하도록 한다.

 

1. Actions secrets 등록

우리 서비스는 환경설정 파일을 서브모듈로 private repo에서 가져와서 쓰고 있다.
그래서 private repo에 대한 접근 권한을 가진 토큰을 Secrets를 등록해 주자!
 
레포의 Settings > Security > Secrets and variables에 들어가면 Actions가 있다.
여기서 New repository secret을 등록하자.
 

 
 

2. Workflow 정의

그래서 결론이 뭔데!!!!라고 외칠 독자(?)를 위해서 전체 코드부터 올리고 시작한다!
(스포주의 붙여야 하나,,?)
 

name: Java CI with Gradle
on:
  push:
    branches:
      - dev
  pull_request:
    branches:
      - main
      - dev

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.SUBMODULE_TOKEN }}
          submodules: recursive

      - name: Git Submodule Update
        run: git submodule update --remote --recursive

      - name: Set Up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2
        with:
          arguments: build
          cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}

      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: '**/build/test-results/test/TEST-*.xml'

      - name: If the test fails, register a check comment in the failed code line
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          token: ${{ github.token }}

 
 

0) .github/workflows 폴더 생성

 
workflow는 하나 이상의 작업을 실행시키는 자동화된 프로세스다.
.github/workflows 폴더 아래 yml 파일로 작성할 수 있다.

 

1) workflow 이름과 event 정의

 
workflow 이름과 event 정의한다.
event는 workflow를 트리거하는 특정한 활동을 말한다.
나는 push 되는 이벤트와 pull_repuest 이벤트에 대한 브랜치를 설정해 줬다.

name: Java CI with Gradle
on:
  push:
    branches:
      - dev
  pull_request:
    branches:
      - main
      - dev

 

2) job 수행하는 OS와 JDK 설정

 
여기서부터 job에 대해 정의한다.
나는 ubuntu를 사용할 것이고, 그 이외에 다른 라벨은 여기서 확인할 수 있다.

jobs:
  build-and-test:
    runs-on: ubuntu-latest

 
✔️ actions/checkout@v3
 
Repository로부터 ci을 수행할 서버로부터 코드를 내려받는 action이다.
 
with에 token은 라이브러리에 대한 추가적인 설정을 하는 것이다.
우리 서비스는 환경설정 파일을 서브모듈로 private repo에서 가져와서 쓰고 있다.
그래서 private repo에 대한 접근 권한을 가진 토큰을 설정해 둔다.
 

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.SUBMODULE_TOKEN }}
          submodules: recursive

      - name: Git Submodule Update
        run: git submodule update --remote --recursive

 
✔️ actions/setup-java@v3
 
jdk를 다운로드하고 캐싱해 주는 action이다.
이때 'temurin'는 jdk를 제공하는 vender사 이름이다. (ex. zulu, adopt, microsoft)

      - name: Set Up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

 

3) 권한 추가

 
gradlew를 실행할 수 있는 권한을 부여해야 한다.
아래 리눅스 명령어를 추가했다.

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

 
 

4) 의존하는 라이브러리들을 캐싱

 
job은 매 번 새로운 ci 서버에서 실행된다. 매번 프로젝트에 의존하는 라이브러리를 받는 것은 비효율적이다.
그래서 바뀌지 않는 라이브러리를 캐싱한다.
 
✔️ actions/cache@v3 : 기존에 많이 보던 방식

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

 
✔️ gradle/gradle-build-action@v2 : 내가 사용한 방식
 
다음 action은 Gradle로 build 하는 것을 도와주는 action이다.
build하는 작업 이외에도 더 효율적인 캐싱을 제공한다고 한다.

- name: Setup Gradle
  uses: gradle/gradle-build-action@v2
  with:
    arguments: build
    cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }}

 

5) Test 결과를 PR comment에 추가

 
build 후에는 test-results 경로에 test 결과를 담은 파일이 있다.
이 파일을 확인하기 편하도록 PR comment로 추가하는 action을 추가했다.

- name: Publish Unit Test Results
  uses: EnricoMi/publish-unit-test-result-action@v2
  if: always()
  with:
    files: '**/build/test-results/test/TEST-*.xml'

 
이런 식으로 보기 좋게 포매팅되어 나온다.

 
Jacoco Report를 많이 사용하던데 아직까지 필요성을 크게 못 느껴서 추가하지 않았다.
테스트 코드가 쌓이고 최소 커버리지를 충족하는지 체크하는 것이 필요해진다면 추가할 예정이다!

 

+ 코드 안정성과 코드 퀄리티를 위해 Jacoco를 추가했다!

 

[Gradle] Jacoco 적용하고, Codecov로 테스트 커버리지 관리하기


 

6) 오류가 발생한 코드에 코멘트 자동 생성

 
테스트가 실패한 경우에 어떤 코드에서 실패했는지 편하게 확인할 수 있다.

      - name: If the test fails, register a check comment in the failed code line
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          token: ${{ github.token }}

Files changed에 들어가면 코멘트 확인이 되고, 해당 부분이 수정되면 코멘트는 자동으로 삭제된다.
 

 
 

3. 테스트가 통과해야 merge할 수 있도록 설정

ci 세팅이 끝나고 일부러 테스트가 실패하도록 작성해서 PR을 보냈는데 ci 실패했을 때도 merge가 가능하더라.
흐음.. 이건 내가 원하던 게 아닌데 🧐

룰을 추가해 보자!
Settings > Branches > Branch protection rules에서 Add rule을 할 수 있다.
일단 main에 merge시 반드시 build-and-job이라는 내가 정의한 job이 통과해야 활성화되도록 했다.
 
(추가로 main branch는 PR로만 머지 가능하도록 보호하는 룰도 추가하는 게 좋다.)
 

 

 


+ 그리고 따로 Lint 검사하는 step을 추가하려다가 멈췄다.
SonarLint 쓰던데 ci에서 작업이 많아지면 시간이 너무 오래걸릴거라고 판단했고, ci시 말고 커밋할 때 Lint 성공해야 하도록 설정하려고 한다. node에서 쓰는 허스키 같은 git hook 찾아봐야겠다. 자바 버전으로 만드신 분이 계시긴하던데
 


번외 : 🐛 Error 해결

 

1) github/workflows/ci.yml push 안됨.

 
어우.. 신나게 ci 코드 작성 완성하고 push 하는데 거절당했다. 🤨

refusing to allow a Personal Access Token to create or update workflow

 
지금 발급받은 Access token으로는 workflow에 접근하지 못하는 오류였다.
 
👉 Token 스코프 변경하러 가기.
 
위 링크로 가서 사용 중인 토큰을 선택하고 아래 workflow를 체크해 준다.
 

 

2) 403 Error

 
끝났나?! 했더니 
403 에러가 떴다. Forbidden이라면서... 
우쒸 친구야.. 제발 좀 넘어가자. ci test 실패했더니 메일 한 바가지 왔다.
이런 깃허브의 관심.. 원하지 않았어 나는.

나한테 왜이래!?!!

 
 
repo의 settings > Code and automation > Actions > General에 들어가면 Workflow permissions를 설정할 수 있다.
아이참 이 친구 예민하네^^...
 
Read and write permissions으로 변경해 주면 정상적으로 통과한다.


 

예상치 못한 에러가 생겨서
생각보다 세팅하는데 오래 걸렸다.

 



간단한 세팅도 오래 걸리다니.. 킹지만 포기하지 않는 자가 프로다 ^____^


📌 Reference

반응형

댓글