깃허브 액션을 사용한 CI/CD

깃허브 액션

이전 포스팅에서 작성했던 대로 깃허브 액션을 사용하여 CI/CD 파이프라인 구축에 성공했다. 물론 정말 많은 실패가 있어서 하루를 다 날려먹었다.

실패의 기록

이번 포스팅에서는 어떤 실수가 있었는지, 그리고 깃허브 액션의 기본적인 사용 방법을 작성해보겠다.

실패의 기록

빌드

깃허브 액션에서 공식으로 지원하는 템플릿을 변형하여 사용하기로 했다. 홈페이지에서 build with gradle을 선택하여 yml 파일을 다운받았다.
가장 먼저 빌드 과정에서 gradle wrapper를 사용하는 과정에서 에러가 났다. gradlew를 찾지 못했다는게 이유인데 프로젝트 내에 gradlew는 멀쩡하게 있었다.

에러1

혹시라도 gradle wrapper라는게 안깔려있는 것인가 했지만 내가 지금까지 사용하던 gradlew가 gradle wrapper의 약자였다. gradle wrapper는 사용자가 맞는 버전의 gradle을 먼저 설치해둘 필요 없이, gradle을 프로젝트에 포함하여 배포함으로써 환경에 구애받지 않도록 해주는 스크립트이다.

문제는 폴더 내부 다른 디렉토리에 프로젝트가 저장되어 있는 것이었는데, 깃허브 액션의 yml 파일 내에서

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    defaults :
      run:
       working-directory: ./toy

위와 같이 defaults에 CI/CD가 작동할 디렉토리를 넣어줌으로써 해결했다.

그 다음 발생한 에러는 실행권한이 없어 gradlew를 사용한 빌드가 거부당한 것이다.

에러2

이건 chmod 명령어로 권한을 부여하는 코드를 추가해서 해결했다.

이걸로 build 과정의 에러는 모두 해결하여 정상적으로 build가 되었고, dependency-submission 과정에서 에러가 났는데 이 부분은 외부 모듈을 사용하는 거라 내가 손대기도 힘들고 굳이 필요없는 과정이라 제거했다.

빌드가 끝났으니 이제 EC2에 배포를 자동화할 차례다. EC2 내부에서 빌드하는 것보다는 깃허브에서 빌드 후 EC2에서 배포만 하는 것이 서버에 부담(부담갈 사이즈의 프로젝트도 아니지만)이 덜할 것 같아 이와 같이 구성했다.

배포 자동화

먼저 깃허브 마켓플레이스에서 SSH 연결을 위한 오픈소스를 인기순으로 찾아 사용하였는데, 연결하는 단에서 에러가 나버렸다.
python을 사용한 paramiko라는 연결을 위한 라이브러리를 사용하는데, 이 부분에서 에러가 나니 해결방법을 찾기도 힘들고 뭔지도 모르겠어서 다른 사람들이 많이 사용하는 appleboy 모듈을 사용하기로 했다.

ssh 연결에 대한 사전 지식이 없이 무작정 따라하다 보니 여기서 에러도 많았고 많은 시간이 걸렸다. 가장 처음 생긴 문제는 연결이 거부당한 것이다.

에러3

왜 이런 에러가 나는지 찾을 수가 없어서 이런저런 시도를 계속 하느라 시간을 좀 잡아먹었다. 결국 SSH의 기본 포트는 22번이고, 나도 EC2 인스턴스의 인바운드 규칙에 그렇게 정했었는데 평소 쓰던 포트번호인 8080번으로 입력했던 것이 문제였다. 포트 번호를 제대로 입력하니 해결되었다.

그 다음 문제가 시간을 가장 많이 잡아먹었는데, 바로 이 에러이다.

에러4

에러 문구를 읽어보면 KEY가 유효하지 않아 인증을 실패하여 연결이 되지 않는다는 것 같다. RSA key가 잘못되었나 싶어 첫줄과 끝줄을 제외한 키값만을 넣어보기도 하고, 구글링하다 찾은대로 ssh 설정에서 RSA 키를 받도록 설정도 변경해보았지만 전혀 효과가 없었다.
SSH 서버 재시작도 해보고 별 방법을 다 해보았지만 결국에 문제는 내가 USERNAME을 잘못 입력한 것이었다….
나는 EC2 인스턴스를 만들때 AMI를 ubuntu로 했고, 따라서 인스턴스의 ssh username은 기본값인 ubuntu이다. 하지만 이러한 지식 없이 무작정 블로그를 따라하다 보니 Amazon Linux의 기본값인 ec2-user를 입력해두었고 그래서 이러한 에러가 발생했던 것이다. 해당 부분을 제대로 입력해주니 제대로 작동하였다.

성공!

이제 깃허브 액션이 내가 브랜치를 push하는 것 만으로도 자동으로 내부의 runner애서 gradle로 빌드하고, EC2 인스턴스에 SSH로 접속하여 git pull로 빌드한 파일을 받아…와야..하는데…
이 포스팅을 작성하며 갑자기 생각난 것인데 가상 runner에서 빌드한 파일은 저장되지 않는다!! 그저 빌드가 잘 되는지, 테스트코드가 잘 돌아가는지 시험해봤을 뿐이다. 이렇게 되면 EC2에서 pull을 해도 빌드된 파일을 받아올 수 없다. 이 부분은 포스팅 작성 후에 추가해야겠다.

깃허브 액션

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    defaults :
      run:
       working-directory: ./toy

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    - name : chmod
      run : chmod +x ./gradlew

    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

    - name: Build with Gradle Wrapper
      run: ./gradlew build -x test

  deploy:
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: ssh pipelines
        uses: appleboy/ssh-action@master
        with:
          host: $
          username: $
          key: $
          port: $
          script: |
            cd /home/ubuntu/clone/toy-web/toy
            git pull
            cd /home/ubuntu/clone/toy-web/toy/build/libs
            nohup java -jar demo-0.0.1-SNAPSHOT.jar &

위 코드는 내가 수정한 깃허브 액션 yml이다. 깃허브 액션에서는 이 yml 파일을 읽고 행동들을 수행한다. 각 구성 요소들을 하나씩 살펴보자.

  • Workflow : 한 개 이상의 job을 실행할 수 있는 자동화된 작업이다. 위와 같이 yml 파일로 저장되며 event를 조건으로 실행된다. 위 Workflow의 이름은 Java CI with Gradle이다.
  • Event : Workflow를 발동시키는 특정한 조건이다. 깃허브의 push, pull request 등의 대부분의 작업들을 Event로 지정할 수 있다. on: 으로 지정되며 위 코드의 Event는 ‘main’ 브랜치에 대한 push 및 pull request이다.
  • Job : 1개의 runner에서 실행되는 여러 개의 step 들의 모음. step들은 순서대로 실행되며, job은 병렬로 실행된다. 위 코드에서 job은 build, deploy 2가지로 build와 deploy가 병렬로 실행되는 것을 방지하기 위해 needs: build로 의존관계를 정립해놓았다.
  • Actions : 복잡하고 자주 사용되는 코드의 경우 외부에서 미리 정의해 코드를 간결하게 할 수 있다. 이렇게 정의해둔 코드를 action이라 하고, 깃허브 마켓플레이스에 올라온 action들도 가져와 사용할 수 있다. 나는 appleboy/ssh-action@master라는 action을 가져와 사용했다.

위 구성요소를 적절히 사용하면 깃허브가 제공하는 가상의 환경인 runner에서 코드에 원하는 작업을 수행하는 workflow를 작성할 수 있을 것이다.

정리

CI/CD 파이프라인 구축과정과 깃허브 액션의 구성요소들을 살펴보았다. 사실 CI/CD는 여러 명의 개발자가 하나의 프로젝트를 작업할 때 편의성을 증대시키고 코드의 신뢰성을 높이는 작업인 만큼 내 토이 프로젝트에서는 규모에서도 그렇고 여러모로 꼭 필요하진 않다.
하지만 처음으로 CI/CD 파이프라인을 구축해보는 것은 분명 꽤 재미있고 의미있는 경험이었다. 위에 작성한 대로 yml에 추가 로직을 작성하러 가야겠다.