[Vue + Spring] Github Actions를 이용한 CI/CD 구축하기 (+ Jacoco PR Comment 기능)

Denia·2024년 2월 8일
0

TroubleShooting

목록 보기
18/25

Github Actions 및 Docker를 사용한 이유

많은 회사에서 사용중이고 관련 레퍼런스가 많은 JenkinsGithub Actions 중에 고민했으나 Github Actions를 사용하기로 결정

1. Github Actions 사용한 이유

  • 소규모 개인 프로젝트 → 간편하게 설정이 가능한 기술 스택을 선호
  • 배포는 AWS EC2t2.micro 인스턴스를 활용 → GitHub Actions는 추가적인 인프라 설정 없이 GitHub의 클라우드 인프라에서 실행이 가능 (리소스가 제한적인 환경에 보다 적합)

2. Docker 사용한 이유

  • 학습 목적

    1. 컨테이너 기술에 대한 실질적인 경험 쌓기

    2. Docker를 통한 애플리케이션의 배포 및 관리 방법에 익숙해지기

  • Docker의 이식성

    1. 애플리케이션과 그 종속성을 컨테이너 내에 패키징함으로써, 어떤 환경에서든 일관된 실행 환경을 보장

CI 설정

  • vue 코드가 수정될 경우

    1. npm run build를 실행하고, 정상적으로 build가 되는지 검사
  • spring 코드가 수정될 경우

    1. gradlew test를 실행하고, 정상적으로 test가 통과하는지 검사

    2. gradlew build를 실행하고, 정상적으로 build가 되는지 검사

    3. jacoco를 이용하여, 수정한 파일의 테스트 커버리지를 측정 후 커버지리를 PR Comment에 첨부

      ※ 테스트 커버리지를 PR Comment에 첨부하는 기능은 다른 프로젝트를 둘러보다가 발견한 기능인데 너무 좋아보여서, 저도 바로 도입을 해봤습니다.

repository는 vue 폴더와 spring 폴더가 같이 들어있는 형태라서, 저랑 폴더 구조가 다르신 분들은 본인의 repo에 맞게 수정해서 사용하시면 될 것 같습니다.

※ 사용한 actions들은 모두 최신 버전을 사용했습니다.

continuous-integration-front

name: Vue build test

# master 브랜치로의 PR이 있으면 workflow를 실행함 
# paths 설정이 있으므로 -> my-garden-fe의 하위 파일들이 변경된 경우에만 실행됨
on:
  pull_request:
    branches:
      - master
    paths:
      - my-garden-fe/**

# 환경 변수 정의
env:
  NODE_VERSION: 20 # 본인이 사용하는 node 버전에 맞춰서 정의해주세요!

jobs:
  front-build:
    runs-on: ubuntu-latest

    # steps에 대한 기본 작업 디렉토리 설정
    defaults:
      run:
        working-directory: ./my-garden-fe

    steps:
      # 저장소 코드를 체크아웃합니다. (PR 올린 코드를 가져오는 행위)
      - uses: actions/checkout@v4

      # Node.js 환경 설정
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }} # 지정된 Node.js 버전 사용
          cache: npm # setup-node 의 캐시 기능을 사용함 (저는 npm을 사용하므로 npm을 썼습니다. -> 본인의 세팅에 맞춰서 변경해야 합니다.)
          cache-dependency-path: my-garden-fe/package-lock.json # 캐시 기능을 사용할 때 캐시의 기준이 될 파일을 지정

      - name: Install Dependencies
        run: npm install

      - name: Build with npm
        run: npm run build

vue 쪽 코드에 대해서 PR이 올라오면 실행되는 workflow 입니다.

크게 어려운 내용이 없으므로 주석으로 설명을 대체하고 넘어가겠습니다.

setup-nodecacheGithub에 들어가 보시면 더 많은 내용이 있습니다.

continuous-integration-back

name: Java CI with Gradle & Add Jacoco Report

# master 브랜치로의 PR이 있으면 workflow를 실행함 
# paths 설정이 있으므로 -> my-garden-be의 하위 파일들이 변경된 경우에만 실행됨
on:
  pull_request:
    branches:
      - master
    paths:
      - my-garden-be/**

jobs:
  back-test-and-build:
    runs-on: ubuntu-latest
	# job을 처리할 때 필요한 권한들을 여기서 설정
    permissions:
      contents: read
      pull-requests: write # PR Comment를 달기 위해서 필요함

	# steps에 대한 기본 작업 디렉토리 설정
    defaults:
      run:
        working-directory: ./my-garden-be

    steps:
      # 저장소 코드를 체크아웃합니다. (PR 올린 코드를 가져오는 행위)
      - uses: actions/checkout@v4

      # JDK 17 설정
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      # gradlew에 실행 권한 부여
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      # Gradle 설정
      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0

      # Gradle을 사용하여 테스트 실행
      - name: Test with Gradle
        run: ./gradlew test

      # PR에 커버리지 추가
      - name: Add coverage to PR
        id: jacoco
        uses: madrapps/jacoco-report@v1.6.1
        with:
		  # paths에 들어가는 경로는 본인의 jacoco report가 나오는 경로를 확인하셔야 합니다.
          paths: |
            ${{ github.workspace }}/**/build/reports/jacoco/**/jacocoTestReport.xml
          token: ${{ secrets.GITHUB_TOKEN }}
		  # 성공과 실패 기준에 대해서 설정
          min-coverage-overall: 50
          min-coverage-changed-files: 65

      # Gradle Wrapper로 빌드
      - name: Build with Gradle Wrapper
        run: ./gradlew build -x test # 위에서 테스트를 실행하므로, 테스트는 제외

spring 쪽 코드에 대해서 PR이 올라오면 실행되는 workflow 입니다.

크게 어려운 내용이 없으므로 주석으로 설명을 대체하고 넘어가겠습니다.

jacoco-report에 관한 더 자세한 내용은 해당 블로그를 참고하시면 큰 도움이 됩니다.

[TestCode] jacoco를 사용하여 test coverage report 적용하기

CD 설정

master 브랜치에 PR이 머지가 되면, 머지된 코드를 바탕으로 Docker Image를 만들고 AWS EC2에서 Docker Image를 Pull 받아 머지된 코드가 배포 되도록 해보겠습니다.

참고한 블로그 (두 글 모두 설명이 자세해서 큰 도움이 되었습니다.)

CD 진행 구성

  • 이미지의 왼쪽 상단 (Developer)부터 AWS EC2 까지 진행되는 구성입니다.

CI/CD 과정

  1. 로컬 PC에서 개발 후 Github에 Push

  2. Github Actions workflow에 지정한 특정 branch에 push가 되면 Github Action이 동작

  3. Github Actions가 신규 소스코드를 기반으로 Dockerize를 진행하고 Github Container RegistryDocker 이미지를 Push

  4. AWS EC2에서 빌드된 이미지를 Pull 받아온 후 기존 이미지를 삭제하고 새로운 이미지로 실행

설치 순서

  1. Docker 파일 생성하기

    1. Repo의 Root에 Docker 파일 생성하기.
    # 용량을 줄이기 위해 jdk 17 alpine 버전을 사용함
    FROM openjdk:17-jdk-alpine3.14
    # Actions에서 Build job을 진행하므로 Build 된 jar 파일을 복사한다.
    ARG JAR_FILE=my-garden-be/build/libs/*.jar
    COPY ${JAR_FILE} app.jar
    # 해당 이미지가 Container가 되면 실행할 명령어
    # prod 프로필로 실행한다.
    ENTRYPOINT ["java","-jar", "-Dspring.profiles.active=prod", "/app.jar"]
  2. Github Access Token 발급 받기

    1. 본인 계정을 클릭하여 Setting에 들어간다.

    2. 왼쪽 제일 하단에 있는 Developer Settings 을 클릭

    3. Personal access tokens 클릭 후 Tokens (classic) 에서 Generate new token 으로 classic 으로 Token을 새로 생성한다.

    4. 유효기간은 적당히 아무렇게나 설정하셔도 되고, 권한은 workflow, write:packages, delete:packages를 선택 후 Token을 생성합니다.

    5. Token 값을 복사합니다.

  3. Actions Secrets 설정하기

    1. CD를 진행할 Repo의 Setting에 들어갑니다.

    2. 왼쪽 중간쯤에 있는 Security - Secrets and variables - Actions를 클릭

    3. new repository secret를 클릭

    4. Name은 GHCR_TOKEN , Value는 아까 복사한 Token 값을 입력 후 저장합니다.

  4. Actions 설정하기

    1. Repo의 Actions를 클릭 후 Action을 새로 생성합니다.

    2. 경로는 다음과 같습니다. .github/workflows/continuous-deployment.yml

    3. 주의할 사항은 Docker Image를 만들 때 대문자가 포함되어서는 안됩니다.

    name: Continuous Deployment With Docker
    
    # master 브랜치로 PUSH 되면 workflow를 실행함 
    # paths 설정이 있으므로 -> my-garden-be 혹은 my-garden-fe 의 하위 파일들이 변경된 경우에만 실행됨
    on:
      push:
        branches:
          - master
        paths:
          - my-garden-be/**
          - my-garden-fe/**
    
    env:
      # GitHub Container Registry에 푸시할 이미지 정보 (ACTOR: GitHub 사용자명 / DOCKER_IMAGE: 이미지명 / VERSION: GitHub SHA / NAME: 컨테이너명)
      # DOCKER_IMAGE를 만들때는 대문자가 포함되어서는 안됨, ACTOR에는 대문자가 포함되어도 상관없지만, 통일성을 위해 소문자로 작성 => 본인 계정에 맞게 변경해서 사용할 것
      ACTOR: denia-park
      DOCKER_IMAGE: ghcr.io/denia-park/my-garden
      VERSION: ${{ github.sha }}
      NAME: my-garden
    
    jobs:
      # spring을 빌드
      build:
        name: Build
        runs-on: ubuntu-latest
    
    		# 해당 action에 대해서 권한 설정
    		permissions:
    		      contents: read
    		      packages: write
    
        defaults:
          run:
            working-directory: ./my-garden-be
    
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up JDK 17
            uses: actions/setup-java@v4
            with:
              java-version: '17'
              distribution: 'temurin'
    
          - name: Grant execute permission for gradlew
            run: chmod +x gradlew
    
          - name: Setup Gradle
            uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
    
          - name: Build with Gradle Wrapper
            run: ./gradlew build
    
          # docker build 수행시에 필요한 builder
          - name: Set up docker buildx
            id: buildx
            uses: docker/setup-buildx-action@v3
    			
    	  # docker layer 캐시 (같은 소스 코드에 대해서 캐시 적용)
          - name: Cache docker layers
            uses: actions/cache@v4
            with:
              path: /tmp/.buildx-cache
              key: ${{ runner.os }}-buildx-${{ env.VERSION }}
              restore-keys: |
                ${{ runner.os }}-buildx-
    
          # GitHub Container Registry에 로그인
          - name: Login to GitHub Container Registry
            uses: docker/login-action@v3
            with:
              registry: ghcr.io
              username: ${{ env.ACTOR }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          # Docker 빌드 & GitHub Container Registry에 푸시
    	  # context를 설정하지 않으면 임시 폴더를 만들고 해당 폴더에서 build가 진행되므로, . 으로 root를 지정해줬습니다.
    	  # cache-from, cache-to 는 Docker Layer에서 중복되는 Layer를 캐싱합니다.
          - name: Build and push
            id: docker_build
            uses: docker/build-push-action@v5
            with:
              context: .
              builder: ${{ steps.buildx.outputs.name }}
              push: true
              tags: ${{ env.DOCKER_IMAGE }}:latest
              cache-from: type=gha # 여기서 gha 는 Guthub Actions 용 캐시를 의미합니다.
              cache-to: type=gha,mode=max
    
      # Dokcer 이미지 배포
      deploy:
        name: Deploy
        needs: build  # build 후에 실행되도록 정의
        runs-on: [ self-hosted, gardener ] # AWS ./configure에서 사용할 label명
        steps:
          - name: Login to ghcr
            uses: docker/login-action@v3
            with:
              registry: ghcr.io
              username: ${{ env.ACTOR }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          # 조건부 실행을 위해 컨테이너가 존재하는지 확인 -> 중지, 삭제 (이미지 포함) -> 새로운 이미지로 실행
    	  # 저는 https를 사용하고 있고, http로 접속시 https로 리다이렉트 하도록 설정이 되어있습니다.
    	  # -e 를 통해 TimeZone을 서울로 설정, --restart always 를 통해 재부팅되도 해당 이미지를 실행합니다.
          - name: Docker run
            run: |
              if [ $(docker ps -aq -f name=${{ env.NAME }}) ]; then
              docker stop ${{ env.NAME }}
              docker rm ${{ env.NAME }}
              docker rmi ${{ env.DOCKER_IMAGE }}:latest
              fi
              docker run -d -p 443:443 -p 80:80 -e TZ=Asia/Seoul --name ${{ env.NAME }} --restart always ${{ env.DOCKER_IMAGE }}:latest
  5. AWS EC2Docker 설치

    1. 저는 AWS EC2 OSAmazon Linux 2023 이라서 해당 링크에 따라서 Docker를 설치했습니다. 다른 버전을 사용하시면 그에 맞춰서 진행하시면 될 것 같습니다.

    2. 보안 그룹의 인바운드 설정이 제대로 되어있는지 확인해주세요.

      • 본인에게 필요한 포트가 열려있는지 확인이 필요합니다. (저 같은 경우 443, 80 포트)
  6. AWS EC2Github Actions Runner 설치

    1. CD를 진행할 Repo의 Settings를 들어간다

    2. Code and automationActionsRunners 클릭

    3. New self-hosted runner 클릭

    4. Runner image는 Linux 클릭 → Architecture는 본인의 AWS EC2에 맞게 설정

    5. Download 및 Configure를 순서대로 차근 차근 수행합니다.

    6. ./config.sh 실행하면 몇가지 입력하라고 하는데, Enter any additional label 말고는 그냥 다 default로 했습니다.

      • label은 Runner를 알아보기 위해 필요한 설정인데, 저는 yaml에 설정되어 있는 대로 gardener 로 했습니다.
    7. 마지막 ./run.sh 는 백그라운드로 돌 수 있도록 nohup ./run.sh & 로 합니다.

트러블 슈팅

  1. ./config.sh 수행시에 에러가 발생할 수 있습니다. 저 같은 경우는 닷넷코어가 설치되어 있지 않아서 에러가 발생했습니다.

    다음 링크를 참고하여 문제를 해결했습니다.

    sudo dnf install dotnet-sdk-6.0
  2. 본인 Repo의 파일 경로를 제대로 파악하고 지정해주셔야 합니다.

    저도 진행하면서 경로를 잘못지정해서 시간을 엄청 썼습니다.
    다른 분들은 꼭 경로를 제대로 확인하셔서 시간을 아끼셨으면 좋겠습니다.

  3. yaml에 보면 해당 job에 대해서 권한을 주고 있는데, 이래도 ghcr.io에 push시에 403 에러가 발생한다면 (failed to solve: failed to push ghcr.io/)

    다음과 같이 설정해주세요.

    Repo의 Settings 클릭 → Code and automationActions 클릭 → General 클릭 → 맨 아래에 있는 Workflow permissions 에서 권한을 Read and write permissions 로 설정한다.

참고 링크

GitHub Actions에서 도커 캐시를 적용해 이미지 빌드하기

profile
HW -> FW -> Web

0개의 댓글