깃허브 액션을 이용한 자동배포

시은·2024년 2월 18일
0
post-thumbnail

어쩌다보니 자동배포? 🤯

프로젝트를 진행하면서 개인서버에서 클라우드서버로 이전하게 된 사건이 있었습니다.
그 때 이전하면서 서버에 파일을 전송하는 프로그램 없이 그냥 깃허브에 푸시한 순간 배포가 되게 할 수는 없을까? 하는 생각이 들었고, 그렇게 자동배포의 여정이 시작 되었습니다.

CI 와 CD 란?

👩🏻‍🏫 CI와 CD는 소프트웨어 개발과 배포에서 중요한 역할을 하는 모범 사례 및 원칙들입니다.

CI (Continuous Integration, 지속적 통합)

  • CI는 개발자가 코드 변경 사항을 공유 저장소에 빈번하게 병합(통합)할 것을 권장하는 개발 방법론입니다.
  • CI 과정에는 보통 자동화된 빌드와 테스트가 포함됩니다. 이렇게 하면 코드 변경 사항이 현재의 코드베이스나 다른 코드 변경 사항과 문제 없이 잘 작동하는지 확인할 수 있습니다.
  • CI의 주요 목표는 통합 문제를 신속하게 발견하고 해결하여 소프트웨어 품질을 높이는 것입니다.

CD (Continuous Delivery & Continuous Deployment, 지속적 전달 및 지속적 배포)

  • CD는 소프트웨어를 자동화된 방식으로 빠르게, 안정적으로 고객에게 제공할 수 있도록 지원하는 원칙과 관행의 집합입니다.
  • Continuous Delivery: 소프트웨어 변경 사항이 자동화된 테스트와 빌드를 거쳐 프로덕션 환경으로 배포될 준비가 되었음을 보증합니다. 그러나 실제 배포는 수동으로 수행될 수 있습니다.
  • Continuous Deployment: Continuous Delivery의 확장판으로, 모든 변경 사항이 자동 테스트를 거친 후 자동으로 프로덕션 환경에 배포됩니다.
  • CD의 주요 목표는 빠른 피드백 루프를 제공하고, 소프트웨어의 신속한 반복을 가능하게 하며, 사용자에게 지속적으로 가치를 제공하는 것입니다.

CI/CD 파이프라인을 사용하면 팀은 코드의 변경 사항을 자주, 빠르게, 안정적으로 프로덕션 환경에 배포할 수 있게 됩니다. 이로 인해 소프트웨어 제품의 품질이 향상되며, 버그 수정이나 새로운 기능의 배포가 더욱 빠르게 이루어질 수 있습니다. CI/CD 도구로는 Jenkins, Travis CI, GitLab CI/CD, CircleCI, GitHub Actions 등이 있습니다.


깃허브 액션 vs 젠킨스

👩🏻‍🏫 가장 많이 들어보고, 그만큼 많이 쓰이는 두 개의 CI/CD 도구에 대해 알아봅시다.

Jenkins

  • 서버에 젠킨스를 설치해야 합니다.
  • 작업이 동기화 되어야 하기 때문에 배포하는 데 더 많은 시간이 소요 됩니다.
  • 계정 및 트리거를 기반으로 하며 깃허브 이벤트를 준수하지 않는 빌드를 중심으로 합니다.
  • 환경 호환성을 위해 도커 이미지에서 실행해야 합니다.
  • 캐싱 매커니즘을 지원하기 위해 플러그인을 사용할 수 있습니다.
  • 공유할 수 없습니다.
  • 문서가 다양합니다.
  • 규모가 큰 프로젝트에 적합 합니다.

GitHub Actions

  • 서버에 설치할 필요가 없습니다.
  • 비동기로 CI / CD 를 달성할 수 있습니다.
  • 모든 깃허브 이벤트에 대한 작업을 제공하고, 다양한 언어와 프레임 워크를 지원합니다.
  • 모든 환경과 호환 됩니다.
  • 캐싱이 필요한 경우 자체 캐싱 매커니즘을 작성해야 합니다.
  • 깃허브 마켓 플레이스를 통해 공유가 가능합니다.
  • 젠킨스에 비해 문서가 없습니다.
  • 프로젝트 규모가 크지 않거나 외부 클라우드 서비스를 사용하는 경우 적합 합니다.

어떤 것을 선택했나요? 🤔

저희 프로젝트의 경우 규모가 작고, 외부 클라우드(AWS) 에 서비스가 배포되기 때문에 깃허브 액션을 선택 하였습니다.


아키텍쳐로 알아보는 자동배포

👩🏻‍🏫 세세하게 들어가기 전, 아키텍쳐로 큰 그림을 이해해 봅시다.

배포과정 살펴보기

  1. 열심히 코드 작업을 한 뒤, 깃허브로 푸시를 합니다.

  2. 깃허브 액션은 깃허브에 코드가 올라오면 이를 알아채 배포과정을 밟게 됩니다.

  3. 깃허브 액션은 워크 플로우에 작성된 순서대로 작업을 수행합니다. 저의 경우,

    • jdk 설치
    • 프로젝트 빌드
    • 프로젝트 압축
    • S3 에 전달
    • 코드 디플로이가 S3 에 배포된 압축된 프로젝트를 EC2 서버에 배포

    이렇게 작성하였습니다.

  4. EC2 에 배포한 후에 미리 작성한 appspec.yml 대로 작업이 수행됩니다. 저의 경우,

    • 실행되고 있는 부트 프로젝트가 있는지 점검
    • 실행되고 있다면 종료 후, 새로 배포한 프로젝트 실행

    이렇게 작성하였습니다.


깃허브액션 워크 플로우 작성해보기

deploy.yml

  • 최상위 디렉토리에 .github/workflows ****를 생성해 주어야 합니다.
  • 서브모듈 토큰의 경우 계정의 세팅 → developer … 에서 생성 해주세요.
  • 생성한 토큰과 시크릿 키들은 배포할 코드가 들어있는 레포지토리의 설정에 등록해주세요.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle / dev

on:
  push:
    branches: [ 감지할 브랜치명을 넣어주세요. ]
  pull_request:
    branches: [ 감지할 브랜치명을 넣어주세요. ]

permissions:
  contents: read

env:
  S3_BUCKET_NAME: { s3 버킷 이름을 넣어주세요. }
  CODE_DEPLOY_APP_NAME: { 코드디플로이 어플리케이션 이름을 넣어주세요. }
  CODE_DEPLOY_GROUP_NAME: { 코드디플로이 그룹 이름을 넣어주세요.}
  
jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
      with:
       token: ${{ secrets.SUBMODULE_TOKEN }}
       submodules: true
        
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    
    - name: Build with Gradle
      uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
      with:
        arguments: build
        gradle-version: 8.1.1
    
    # 프로젝트 압축
    - name: Make zip file
      run: zip -r ./$GITHUB_SHA.zip ./
      shell: bash
    
    # AWS 권한 확인
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2
    
    - name: Upload to S3
      run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/{ 디렉토리가 존재한다면 이곳에 작성해주세요. }/$GITHUB_SHA.zip

    # Deploy
    - name: Deploy
      run: |
          aws deploy create-deployment \
          --application-name { 코드디플로이 어플리케이션 이름을 넣어주세요 } \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name { 코드디플로이 그룹 이름을 넣어주세요 } \
          --file-exists-behavior OVERWRITE \
          --s3-location bucket={ s3 버킷 이름을 넣어주세요 },bundleType=zip,key={ 디렉토리가 존재한다면 경로를 넣어주세요. }/$GITHUB_SHA.zip \
          --region ap-northeast-2 \

appspec.yml

⚠️ 이름 주의해서 작성해주세요!

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/{ 배포 경로를 입력해주세요. }
    pattern: "*.jar"
    permissions:
      - object: /
        pattern: "*.jar"
        owner: ec2-user
        group: ec2-user
        mode: '755'

hooks:
  AfterInstall: 
    - location: scripts/stop.sh
      timeout: 60
      runas: ec2-user
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ec2-user
  • AfterInstall : 디플로이가 서버에 배포를 완료하면 실행할 스크립트 입니다.
  • ApplicationStart: 애플리케이션을 실행할때 사용할 스크립트 입니다.

Script 작성하기

⚠️ 최상단 경로에 scripts 폴더를 만들고, 그 안에 스크립트 파일을 넣어 주세요.

stop.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ec2-user/{ 배포 경로 }"
JAR_FILE="$PROJECT_ROOT/build/libs/{ 빌드 파일 이름 }"

DEPLOY_LOG="$PROJECT_ROOT/deploy.log" // 원하는 경로와 이름으로 바꿔도 무방합니다.

TIME_NOW=$(date +%c)

# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)

# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
  echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
  echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
  kill -15 $CURRENT_PID
fi

start.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ec2-user/{ 배포 경로 }"
JAR_FILE="$PROJECT_ROOT/build/libs/{ 빌드 파일 이름 }"

APP_LOG="$PROJECT_ROOT/application.log" // 원하는 경로와 이름으로 바꿔도 무방합니다.
ERROR_LOG="$PROJECT_ROOT/error.log" // 원하는 경로와 이름으로 바꿔도 무방합니다.
DEPLOY_LOG="$PROJECT_ROOT/deploy.log" // 원하는 경로와 이름으로 바꿔도 무방합니다.

TIME_NOW=$(date +%c)

# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &

CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG


Code Deploy

EC2 가 두개라면 코드 디플로이는 어떻게 구분할까?

저희 프로젝트처럼 ec2 가 두개인 경우를 생각해봅시다. 코드 디플로이는 S3 에서 압축된 파일을 가져와 적절한 서버에 압축을 풀어 배포합니다. 이때 서버를 어떻게 구분하는 걸까요?

배포그룹을 사용하여 올바른 서버 찾아가기

주의해야할 점은?

이 배포그룹과 적절한 EC2 를 연결해줘야 합니다.

✅ 아래와 같이 배포그룹 생성시 EC2 에 설정한 태그를 제대로 작성해주세요.

⚠️ 주의

organization 의 접근권한 문제

  • 깃허브 액션이 해당 레포지토리에 접근할 수 없는 경우 action 이 실행되지 않습니다.
  • 아래와 같이 수정해주세요.
profile
창의력 대장이 되기 위한 여정

0개의 댓글