개발

[CI/CD] (1) Github actions로 CI 구축하기

LBACK 2023. 4. 20. 19:11

목표

다음을 목표로 CI/CD 파이프라인을 구축하고 있습니다. 

 

① Master Branch로 pull request가 발생 시, 자동으로 build 및 테스트 수행

② Master Branch로 push하는 경우, 자동으로 AWS EC2에서 서버 재실행

 

이번 글에서는 Github actions를 통해 기능 ①을 구현하는 과정을 정리해보겠습니다. 

 

1. Github actions

현재 CI/CD를 구축하고자 하는 프로젝트가 큰 규모의 프로젝트가 아니기 때문에, 비교적 쉽게 배우고 적용할 수 있는 github actions를 통해 CI/CD 파이프라인을 구축하기로 했습니다. 

 

Github actions를 활용하면 특정 이벤트가 발생했을 때 수행할 작업들을 명시해주면 됩니다. 

ex) master로 PR이 발생하면, 빌드 및 테스트 수행

 

이를 yaml 형태로 정의해주면 되는데, 먼저 구체적인 개념을 살펴보겠습니다.

공식문서에 잘 설명되어 있어, 해당 내용을 참고했습니다.

(https://docs.github.com/ko/actions/learn-github-actions/understanding-github-actions )

 

Workflow

  • 이벤트가 발생하면 실행되는 단위. 순차적 또는 동시에 실행될 jobs로 구성
    이벤트 : PR, push 등등..
  • 하나의 저장소가 여러 workflow 보유 가능

- Job

  • 수행할 steps로 구성
  • 각 job 별로 고유한 VM이나 container 위에서 실행됨

- Step

  • 수행할 script 또는 action
    • actoin: github actions에서 정의된 기능

 

조금 더 디테일한 사항들이 있지만, 기본적으로는 workflow와 job 그리고 step에 대해서 이해하고 있으면 됩니다. 

 

이제 목표대로, master로 pull request가 발생하면, 자동으로 프로젝트를 빌드 및 테스트하는 workflow를 작성해보겠습니다.

 

참고로, Java/Spring Boot 프로젝트 기준입니다!

 

name: build

on:
  pull_request:
    branches: ["master"]

jobs:
  build:
    runs-on: ubuntu-20.04
    environment: CICD
    env:
      APPLICATION: ${{secrets.APPLICATION}}
      APPLICATION_TEST: ${{secrets.APPLICATION_TEST}}
    
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with: 
          java-version: '17'
          distribution: 'temurin' # jdk 제공
      
      - name: Create resources in test
        run : mkdir ./src/test/resources
        
      - name: Create application.yml(for test)
        run : touch ./src/test/resources/application.yml
        
      - name: Copy Secrets to applicatoin.yml(for test)
        run : echo "$APPLICATION_TEST" > ./src/test/resources/application.yml
        
      - name: Copy Secrets to application.yml(for build)
        run : echo "$APPLICATION" > ./src/main/resources/application.yml
      
      - name: Grant execution permission for gradlew
        run: chmod +x gradlew
        
      - name : Build
        run: ./gradlew clean build

이렇게 보니 생각보다 조금 긴데, 이해하면 간단한 작업이니 하나씩 천천히 다시 정리해보겠습니다.

 

(1) name: build

Workflow의 이름을 build로 설정합니다.

 

(2) on, pull_request, branch

어떤 이벤트가 발생했을 때 workflow가 실행될지를 명시해주는 부분입니다. 

 

'master로 pull_request가 발생했을 때' 이 workflow가 실행되도록 작성했습니다.

 

(3) jobs

Workflow 내에서 수행될 job의 목록을 적어주면 됩니다. 현재 workflow는 하나의 job으로 이뤄져있습니다.

 

(4) job - build

수행할 하나의 job 이름 또한 build입니다.

 

먼저 환경을 설정해줍니다.

  • runs-on : 앞서 job 별로 하나의 vm 또는 container 위에서 수행된다고 했는데, 그 VM의 환경을 설정해줍니다. 
    ubuntu-20.04 로 설정해 두었습니다.
  • enviroment, env: 간단히 변수라고 생각해두시고, 뒤에서 자세히 살펴보겠습니다!

다음으로 step을 표시해줍니다. 다음 순서로 이뤄져있습니다.

Step은 script나 action을 실행하는데, script는 'run : <script>', actions은 'uses: <action>'의 형태로 이뤄집니다.

 

  • uses : actions/checkout@v3
    Checkout에서 대략적으로 예상할 수 있듯이, 사용할 코드를 불러오는 것입니다. 

    개인적으로 이때 PR에 대해서는 어떤 코드를 사용하는지가 헷갈렸습니다.
    바로 정답으로 가면, merge된 코드를 이용한다고 합니다.

    그럼 여기서 또 드는 의문이, '충돌이 발생하는 PR은 어떡하지?' 였습니다. 
    PR에 대해서는 충돌이 발생하면 workflow가 동작하지 않습니다.

  • Set up JDK 17
    (여기서부터는 모든 step에 이름을 붙였기 때문에, 이름으로 구분하겠습니다)
    먼저 VM에서 java 코드를 빌드할 수 있어야 되기 때문에 JDK를 설정해줍니다.
    굳이 script를 다 작성할 필요없이, action으로 정의되어 있네요.

  • Create resources in test, Create application.yml(for test)
    테스트 환경에서는 별도의 application.yml 파일을 사용하기 위함입니다. 
    디렉토리를 생성해주고, 파일을 생성해줍니다. 

    *application.yml 직접 생성
    일반적으로 application.yml은 보안문제로 github에 업로드하면 안됩니다.
    그렇기 때문에 checkout 만으로는 프로젝트에 포함되지 않죠.
    따라서 github에서 접근할 수 있지만 보안되는 형태로, application.yml을 저장해두어야 하는데, 그럴만한 저장 공간이 바로 github secrets 입니다. ([repository] - [Settings] - [Secrets and variables])

    ① application.yml의 내용을 그대로 github secretes에 enviroment secret으로 저장
    ② 이 값에 접근 -> environment와 env 가 secrets에 접근한 것입니다!
    ③ 읽어온 값을 직접 생성한 application.yml에 출력

    Secrets를 이용하면, 위 순서로 application.yml의 내용을 활용할 수 있습니다. 

    ※ Secret으로 저장하더라도 secret에 접근 권한 등을 설정하여 보안에 유의해야 합니다!

  • Copy Secrets to application.yml(for test)
    실제 환경에서 사용될 application.yml 을 생성해줍니다.

  • Grant execution permission for gradlew
    Gradle 빌드 권한을 추가해줍니다.

  • Build
    테스트 및 빌드를 수행해줍니다.

2. 결과확인

간단한 프로젝트를 통해 workflow의 동작을 직접 확인해보겠습니다.

 

프로젝트 전체 코드는 https://github.com/LimYooyeol/cicd 에서 확인할 수 있습니다.

 

먼저 application.yml의 설정을 통해 서비스 이름을 주입받아, 인사말을 출력해주는 서비스입니다. 

다음으로 SimpleServiceTest 입니다.

 

인사말에 대문자가 포함되면 안된다고 가정하고, 테스트를 작성해두었습니다. 

 

이를 바탕으로 workflow가 어떻게 동작하나 살펴보겠습니다.

 

(1) 빌드, 테스트 실패

가장 먼저 빌드, 테스트가 실패하는 경우입니다. 

branch1 이라는 branch를 새로 만들고 작업하다, 대문자 조건을 모르고 Hi라고 수정해버렸습니다. 

이때 PR을 날리면, 

이런식으로 앞서 작성한 workflow가 동작하고, 성공/실패 여부를 확인할 수 있습니다. 

 

참고로 이때 충돌 검사 외에 check 가 동작하지 않는 다면, [repository] - [settings] - [Branches] - [protection rule] 추가하고, 아래 옵션 선택해주면 됩니다.

 

다시 workflow로 돌아가서, 실패 로그를 한번 확인해보겠습니다. 

빌드 과정에서 대문자 검사 테스트를 통과하지 못했네요.
이 브랜치는 merge하면 안되겠죠. 

 

(2) 빌드, 테스트 성공

branch1에서 Hi를 hi로 수정해줍니다.

이후 다시 PR을 날리면 아래와 같이 빌드, 테스트까지 성공하는 것을 확인할 수 있습니다.

이제 branch1의 내용은 merge 해도 문제가 없습니다.

 

(3) 충돌 발생

앞서 pull request 를 trigger로 설정하더라도, 충돌이 발생하면 workflow가 실행되지 않는다고 했는데, 한번 직접 확인해보겠습니다.

 

현재 branch1의 내용은 merge된 상태입니다.

'branch1'과 동시에 master에서 분기했던 'branch2'에서 다시 hello를 Hello로 수정하고 PR을 날려보겠습니다.

 

대문자 테스트도 문제긴 하지만, 우선 충돌이 발생할 것입니다.

 

 따라서 위와 같이 충돌 메시지만 출력되고, workflow는 실행되지 않습니다.

 

PR 트리거로 동작하는 workflow는 checkout 시 merge된 코드를 기준으로 동작하는데, 애초에 merge에서 충돌이 발생하니 실행될 수 없습니다.

 

이렇게 github actions를 통해 CI를 구축해봤고, 다음에는 CD까지 구축한 후 정리하는 글을 작성해보겠습니다.