본문 바로가기

DEVELOPER/Programming

[CI/CD] Github Action, AWS로 Java 자동 빌드/배포하기

오늘은 Java로 개발한 프로젝트를 Github Action과 AWS의 서비스를 활용하여 빌드/배포 자동화를 해보도록 하겠습니다.

CI/CD를 구축하는 방법은 다양하게 있습니다. 저는 그중 Github Action과 AWS의 CodeDeploy를 많이 활용하는데요. 구축 방법도 쉬울뿐더러, 한번 구축해 두면 프로젝트를 깃헙에 Push(merge)만 하여도 테스트와 배포를 자동으로 진행하는 덕분에 필수로 애정하며 도입하는 기술입니다.

현재 프로젝트를 함께 진행중인 세바개 팀에서도 Github Action과 AWS를 활용한 CI/CD를 도입하였습니다. 그래서 이번 게시글에서 위 기술을 활용한 CI/CD 구축 방법을 정리해보려고 합니다.

사용 기술

  • Java 17, Spring boot, Gradle
  • Git, Github
  • Github Action, AWS S3, AWS EC2, AWS CodeDeploy

CI/CD Architecture

CI/CD를 구축하기 전에, Java, Spring boot 등 위에 명시한 기술을 기반으로 한 프로젝트가 있다고 가정하여 설명하겠습니다.

 

GITHUB ACTION


Github Action build script 작성

프로젝트가 업로드된 Github Repository에서 Action 페이지로 들어옵니다. 해당 페이지에서 set up workflow yourself를 클릭하여 main.yml을 생성합니다.

 

main.yml에 위 명령어를 작성하고 커밋합니다. 중간에 java-version이 있는데요. 프로젝트에서 사용한 자바 버전에 맞게 입력하면 됩니다. 저는 temurin의 17 버전을 사용했기 때문에 위와 같이 작성했습니다.

gradle이 build할 때마다 프로젝트 내에 작성된 테스트 로직을 수행합니다. 이로 인해서 빌드 속도가 많이 느려집니다. 또는 테스트로 인해서 build가 실패할 수도 있습니다. 초기 Github action 구축 단계에서는 테스트 로직을 제외하여도 무방하기 때문에 아래 명령어로 과정을 스킵할 수 있습니다.

run: ./gradlew clean build -x test

build 성공 화면

위 이미지처럼 초록색의 체크 표시를 확인하면 성공적으로 build 된 것입니다.

- main.yml

더보기
name: Sebadog-book server CI/CD

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

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    
    - name: Build with Gradle
      # run: ./gradlew clean build
      run: ./gradlew clean build -x test # 테스트 Skip

 

AWS 구축


1. IAM 사용자 생성

AWS의 IAM(Identity and Access Management) 서비스로 접속합니다. 그리고 액세스 관리-사용자 탭에서 사용자 생성 버튼을 클릭합니다.

1단계[사용자 세부 정보 지정]에서 사용자 이름 입력 후 다음으로 넘어갑니다.

2단계[권한 설정]에서 직접 정책 연결을 체크하고, 권한 정책에서 아래 세 가지를 검새하여 추가합니다.

  • AmazonEC2FullAccess
  • AmazonS3FullAccess
  • AWSCodeDeployFullAccess

3단계[컴토 및 생성]에서 확인 후 사용자 생성을 클릭하고, 생성이 완료될 때까지 기다립니다.

생성한 사용자 정보로 들어오고, 위 이미지와 같이 액세스 키 만들기를 클릭합니다.

기타를 체크하고, 다음 단계로 넘어갑니다. 2단계[권한 설정]는 선택 사항입니다. 지금은 넘어가도록 하겠습니다.

마지막 단계에서 위와 같이 액세스 키 ID비밀 액세스 키가 발급됩니다. Github Action에서 사용할 정보이기 때문에 저장하거나 복사합니다.

 

2. Github Action에 액세스 키 저장

Github Repository - Settings - Secrets and variables - Actions으로 들어갑니다.

해당 페이지의 Repositorty secrets에서 New repository secret 버튼을 클릭합니다.

AWS_ACCESS_KEY_ID : IAM에서 얻은 액세스 키 ID

AWS_SECRET_ACCESS_KEY : 비밀 액세스 키

각각의 정보를 위와 같이 저장합니다.

 

3. S3 생성

AWS에서 S3 접속 후 버킷을 생성합니다.

AWS 리전을 서울로 선택하고, 버킷 이름을 작성합니다. 보안을 위해 모든 퍼블릭 액세스 차단을 체크하여 버킷 만들기 버튼을 클릭합니다.

IAM에서 만든 사용자로 S3의 FullAccess 권한을 얻기 때문에 버킷 생성에서는 모든 퍼블릭 액세스를 차단한 것입니다.

 

4. Githhub Action main.yml에 S3 정보 입력

main.yml

더보기
name: Sebadog-book server CI/CD

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

env:
  S3_BUCKET_NAME: 버킷이름

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    
    - name: Build with Gradle
      # run: ./gradlew clean build
      run: ./gradlew clean build -x test # 테스트 Skip

    # 디렉토리 생성
    - name: Make Directory
      run: mkdir -p deploy
        
    # Jar 파일 복사
    - name: Copy Jar
      run: cp ./build/libs/*.jar ./deploy

    - name: Make zip file
      run: zip -r ./sebadog.zip ./deploy
      shell: bash

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        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 ./sebadog.zip s3://$S3_BUCKET_NAME/

main.yml에 위 코드를 작성하고 커밋합니다.

build 한 java 프로젝트를 실행할 jar파일을 압축하고, 해당 폴더를 S3로 업로드하는 방식입니다.

1~3분 후 S3 버킷에 zip파일이 잘 올라간 것을 확인할 수 있습니다.

 

5. IAM 역할 생성

2개의 역할을 생성할 것입니다.

EC2용 역할

IAM-액세스 관리-역할에 들어가서 역할 생성 버튼을 클릭합니다.

AWS 서비스 체크 후 사용 사례에서 EC2를 선택하고 2단계[권한 추가]로 넘어갑니다. 그리고 아래 2가지를 추가합니다.

  • AmazonS3FullAccess
  • AWSCodeDeployFullAccess

역할 이름을 작성하고, 위에서 언급한 권한이 추가된 것까지 확인 후 역할을 생성합니다.

CodeDeploy용 역할

또 한 번 IAM 역할에서 역할 만들기를 합니다. 이번엔 사용 사례에 CodeDeploy를 선택합니다.

다음 2단계[권한 설정]까지 건드리지 않고 넘어갑니다. 마지막 역할의 이름을 작성하고 생성합니다.

 

6. 클라우드 가상 서버 EC2 생성

EC2 접속 후 가장 먼저 우측 상단 드롭박스에서 region을 서울로 변경합니다. (중요)

그리고 인스턴스 시작을 클릭합니다.

인스턴스의 이름을 작성합니다. 운영체제는 Ubuntu를 선택합니다. 인스턴스 유형은 프리티어인 t2.micro를 선택하였습니다. 이어서 새 키 페어를 생성합니다. 키 페어 생성 시 다운로드된 pem 키는 잘 보관합니다.

모든 설정이 완료되었으면 인스턴스를 시작합니다.

생성한 인스턴스를 체크하고, 드롭박스 [작업] - 보안 - IAM 역할 수정을 클릭합니다.

아까 만든 EC2용 IAM 역할을 선택하고 업데이트 버튼을 클릭합니다.

 

7. EC2 연결

생성한 EC2에 연결합니다. 아래 명령어로 설치를 진행합니다.

sudo apt update
sudo apt install ruby-full
sudo apt install wget

cd /home/ubuntu

sudo apt install awscli
aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2

chmod +x ./install
sudo ./install auto

# 아래 코드로 설치 확인
sudo service codedeploy-agent status

정상 설치 결과

# jdk 17버전 설치
sudo apt-get install openjdk-17-jdk

# java 설치 확인
java -version

프로젝트 개발환경과 일치하는 버전의 jdk를 설치합니다.

 

8. CodeDeploy 생성

CodeDeploy에서 애플리케이션 생성을 진행합니다. 이름을 작성하고, 플랫폼을 EC2/온프레미스로 선택하고 생성합니다.

배포 그룹 생성을 클릭합니다.

배포 그룹 이름을 작성하고 서비스 역할에 위에서 생성했던 애플리케이션 그룹용 역할(CodeDeploy용 IAM 역할)을 클릭합니다.

아래 환경구성에서 Amazon EC2 인스턴스를 체크합니다. 키에는 Name, 값에는 연결항 EC2 인스턴스를 연결합니다.

이제 배포 그룹을 생성합니다.

 

9. main.yml에 CodeDeploy 연결 정보 작성

1) appspec.yml 설정

프로젝트 최상단 경로에 appspec.yml 파일을 생성하고, 아래 코드를 입력합니다.

더보기
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ubuntu/app/
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 60
      runas: ubuntu

2) deploy.sh 생성

해당 스크립트를 통해서 프로젝트를 배포 후 자동으로 실행하게 되어 무중단 배포를 유지할 수 있습니다.

최상단 경로에 scripts 폴더 > deploy.sh 파일을 생성하고, 아래 코드를 입력합니다.

더보기
#!/usr/bin/env bash

REPOSITORY=/home/ubuntu/app

echo "> 현재 구동 중인 애플리케이션 pid 확인"
PORT = $(8080)
CURRENT_PID=$(sudo lsof -t -i:$PORT)

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

if [ -z "$CURRENT_PID" ]; then
  echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -9 $CURRENT_PID"
  kill -9 $CURRENT_PID
  sleep 5
fi

echo "> 새 애플리케이션 배포"

JAR_NAME=$(ls -tr $REPOSITORY/*SNAPSHOT.jar | tail -n 1)

echo "> JAR NAME: $JAR_NAME"

echo "> $JAR_NAME 에 실행권한 추가"

chmod +x $JAR_NAME

echo "> $JAR_NAME 실행"

nohup java -jar -Duser.timezone=Asia/Seoul $JAR_NAME >> $REPOSITORY/nohup.out 2>&1 &

3) main.yml 수정

아래 코드로 수정합니다.

더보기
name: Sebadog-book server CI/CD

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

env:
  S3_BUCKET_NAME: sebadogapp-deploy

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    
    - name: Build with Gradle
      # run: ./gradlew clean build
      run: ./gradlew clean build -x test # 테스트 Skip

    # 디렉토리 생성
    - name: Make Directory
      run: mkdir -p deploy
        
    # Jar 파일 복사
    - name: Copy Jar
      run: cp ./build/libs/*.jar ./deploy

    # appspec.yml 파일 복사
    - name: Copy appspec.yml
      run: cp appspec.yml ./deploy

    # script files 복사
    - name: Copy script
      run: cp ./scripts/*.sh ./deploy

    - name: Make zip file
      run: zip -r ./[압축파일 이름].zip ./deploy
      shell: bash

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        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 ./sebadog.zip s3://$S3_BUCKET_NAME/

    # Deploy
    - name: Deploy
      env:
      	AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      	AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      run: |
        aws deploy create-deployment \
        --application-name coinTalk[만든 애플리케이션 이름] \
        --deployment-group-name coinTalk-group[배포 그룹 이름] \
        --file-exists-behavior OVERWRITE \
        --s3-location bucket=cointalk-deploy[S3버킷이름],bundleType=zip,key=[압축파일 이름].zip[S3버킷 속 압축파일] \ 
        --region ap-northeast-2

배포 그룹과 애플리케이션의 이름은 CodeDeploy에서 만든 그룹 또는 애플리케이션에서 확인할 수 있습니다.

 

10. 포트 열기 (EC2 인바운드 규칙 편집)

EC2 - 네트워크 및 보안 - 보안 그룹 - 인스턴스에 적용된 보안 그룹 체크 후 인바운드 규칙 편집 클릭

인스턴스를 클릭하면 적용된 보안 그룹을 바로 확인하거나 편집할 수 있습니다.

위와 같이 허용하고자 하는 포트를 열어줍니다.

 

CI/CD 테스트


1. Github Action, 코드 변경 감지

코드를 수정하거나 변경 내용을 커밋합니다.

위와 같이 빌드, S3, CodeDeploy까지 통합하는 과정이 성공적으로 진행되었는지 확인합니다. 녹생 체크 표시가 뜨는 것을 확인했다면 통합 및 테스트 과정은 성공한 것입니다.

2. CodeDeploy 배포

AWS CodeDeploy에서 배포 상태를 확인합니다. 성공이라 적혔다면, 배포도 성공하였습니다.

이제 EC2-인스턴스의 퍼블릭 IP:port 혹은 퍼블릭 주소:port 사용하여 확인합니다.

추가로 코드를 변경하였을 때 웹이 정상적으로 변경되는지 확인하면 되겠습니다.

Github Action 혹은 CodeDeploy 과정에서 다소 시간이 걸릴 수 있습니다. 혹여나 과정이 완료되지 않았는데, push나 commit 등 재시도를 한다면 배포 충돌로 인하여 오류가 발생할 수 있습니다.

 

반응형