AWS

AWS 배포 - AWS Code Deploy를 사용한 배포 (Github Actions)

Tommy__Kim 2023. 10. 21. 01:23

프로젝트 진행 중 Code Deploy를 적용할 일이 생겨 해당 부분 관련 하여 hands-on session 느낌으로 작성하고자 합니다. 

해당 글은 다음 환경에 대해 다룹니다. 

Framework : Spring
Language : Java
CodeDeploy : Github Actions

글에 앞서 Code Deploy의 개요도는 다음과 같습니다. 

 

 

[ 변경 감지 ]
|  Trigger가 걸린 branch에 변경 사항이 생기면 Github Actions가 작동

[ 파일 S3 전송 및 저장 ] 
|  Github Actions에서 전달하고자 하는 파일들을 S3에 전송한다. (zip 파일도 가능)

[ 배포 명령 ] 
|  Github Actions에서 Code Deploy에게 배포를 진행하라고 명령을 내린다. 

[ 빌드 결과물 전달 ] 
|  S3에 저장된 build 결과물을 전달하는 과정을 거친다. 

[ EC2 배포 및 실행 ]
|  appspec.yml, shell 에 적은 과정을 토대로 배포 및 실행을 진행한다.

Github Repository 생성 

간단하게 실습을 하기 위해 
Dependency의 경우 Spring-boot-starter-web만의존성을 추가했습니다. 

참고 사항 : build 시 plain jar 가 생성되는 것을 방지하기 위해 build.gradle 에 다음과 같은 속성을 추가했습니다. 

jar.enabled=false

실습 예제 Repository 바로가기

 

AWS 설정 

이번 실습의 경우 freetier 조건으로 실습을 진행합니다. 
혹시라도 실습에서 사용된 AWS 서버의 사양을 변경하고자 하는 경우 과금이 될 수 있습니다. 

 

IAM 생성

IAM은 Identity And Access Management의 약자로 AWS 리소스에 대한 액세스를 안전하기 위임하는 방법 중 하나입니다. 
IAM 역할은 특정 권한 정책을 가지며, 이 역할을 수행하는 AWS 서비스나 사용자는 정책에 정의된 권한을 가집니다. 

1. IAM 사용자 생성

[ IAM -> 사용자 생성 클릭 ]

[ IAM -> 사용자 생성 클릭 -> 사용자 이름 지정 ]

[ IAM -> 사용자 생성 클릭 -> 사용자 이름 지정 -> 직접 정책 연결 ]

검색 창에 다음 사항을 검색해서 추가한다. 

  • AmazonEC2FullAccess
  • AmazonS3FullAccess
  • AmazonCodeDeployFullAccess

[ IAM -> 사용자 생성 클릭 -> 사용자 이름 지정 -> 직접 정책 연결 -> 사용자 생성 ]

 

[ IAM -> 사용자 -> 액세스 키 만들기 클릭 ]

[ IAM -> 사용자 -> 액세스 키 만들기 클릭 -> Command Line Interface 클릭 ]

 

[ IAM -> 사용자 -> 액세스 키 만들기 클릭 -> Command Line Interface 클릭 ]

여기서 액세스 키와 비밀 액세스 키는 노출되지 않는 선에서 가지고 있어야 합니다. 
해당 키 값으로 Github Actions의 권한을 줄 것이기 때문에 꼭 가지고 계셔야 합니다.

2. IAM 역할 생성 (EC2)

EC2 전용 역할을 생성해 줍니다.

[ IAM -> 역할 -> 역할 만들기 클릭 ] 

 

신뢰할 수 있는 엔터티 유형 : AWS 서비스

사용 사례 : EC2 를 선택 한뒤 생성합니다. 

권한 추가의 경우 다음 두가지 권한을 추가해줍니다. 

AmazonS3FullAccess

AWSCodeDeployFullAccess

역할 이름은 아무 이름이나 생성해도 상관 없습니다. 

3. IAM 역할 생성 (CodeDeploy)

EC2 전용 역할을 생성해 줍니다.

2와 동일한 과정으로 진행하되 사용 사례에서 CodeDeploy를 선택해 줍니다. 

CodeDeploy의 경우 별다른 추가사항없이 생성해주면 됩니다. 

(이름의 경우 demo-deploy)라고 설정을 해 주었습니다.

S3 생성

S3에는 Github Actions에서 빌드한 결과물들이 올라가는 저장소 입니다. 

[ S3 -> 버킷만들기 클릭 ]

[ S3 -> 버킷만들기 클릭 -> 버킷 이름 생성]

버킷 이름의 경우 아무이름이나 설정해도 됩니다. (추후에 사용을 해야하니 이름은 메모해주세요.)

버킷 설정의 경우 추가 설정창들이 있을 텐데, 그 부분들은 default인 상태로 생성을 해 주면 됩니다.

EC2 생성

EC2는 저희가 어플리케이션을 실행할 가상의 서버입니다. 

[ EC2 -> 인스턴스 시작 클릭 ] 

 

[ EC2 -> 인스턴스 시작 -> 설정 ]

EC2 설정 정보는 다음과 같습니다. (글과 맞게 서버 구성을 해주면 됩니다.)
이름 : demo-ec2 (자유롭게 설정)
Amazon Machine Image (AMI) : Amzon Linux 2023 AMI 
인스턴스 유형 : t2.micro
네트워크 설정 : 인터넷에서 HTTP 트래픽 허용 추가 
스토리지 크기 : 30GiB

EC2에 CLI로 접근하기 위해서는 pem 키파일이 존재해야합니다. 

그렇기 때문에 하나 생성하도록 하겠습니다. (pem 키파일의 경우 추후에 필요하니 잘 가지고 계시면 됩니다.)

[ EC2 -> 인스턴스 목록 -> 작업 -> 보안 -> IAM 역할 수정 클릭 ] 

글 상단에서 IAM 생성했던 역할을 할당해 주어야 합니다. 

IAM 역할 목록에 이전에 생성한 IAM 역할이 표시될 텐데, 할당해주고 업데이트 해주면 됩니다. 

[ EC2 -> 인스턴스 목록 -> 인스턴스 상태 -> 재부팅 ]

IAM 역할을 수정했으므로 재부팅을 한번 해줍니다.

[ EC2 ->  탄력적 IP -> 탄력적 IP 주소 할당] 

EC2가 종료되고 재부팅이 되더라도 같은 IP 주소를 유지하기 위해 탄력적 IP를 할당해주도록 합니다. 

탄력적 IP의 경우 연결되지 않은 채로 있다면 과금이 되니 나중에 EC2를 지운다면 꼭 지워주세요.

IP주소를 할당 받은 후 작업 -> 탄력적 IP주소 연결을 클릭합니다. 

인스턴스 목록에 전에 생성한 EC2가 목록에 뜹니다. 

인스턴스 목록을 할당해주시면 됩니다.

확인 방법 : 인스턴스 목록 에서  EC2인스턴스를 클릭하고 요약 페이지에서 탄력적 IP주소 확인

[EC2 설정] 

EC2서버 내에서 기본적인 설치를 진행해보도록 하겠습니다. 

CLI를 사용해서 EC2에 접속하는 방법은 다음과 같습니다. 

# 실행권한 부여 
chmod +x {pem key파일 위치}

# 실행권한 수정 
chmod 600 {pem key파일 위치}

# ec2 접속
ssh -i {pem key파일 위치} ec2-user@{ec2의 public IP 주소}

처음 접근하게 되면 yes / no / ... 라고 뜨는데 yes를 입력해 주면 됩니다.

 

다음의 명령어들을 차례로 실행해 줍니다. 

# 패키지 업데이트
sudo yum update -y

# Timezone 변경
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
date # 잘 변경됐는지 확인 1

# netstat 설치
sudo yum install net-tools -y

# 프로젝트 저장 디렉토리 소유권변경
sudo chown -R ec2-user:ec2-user /srv/

# git 설치
sudo yum install git -y
git --version

# java 설치

# amazon corretto 17 RPM 다운로드 
wget https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.rpm

# RPM 패키지 설치 
sudo yum install -y ./amazon-corretto-17-x64-linux-jdk.rpm

# Java Version 확인
java -version

# Ruby 설치 
sudo yum install -y ruby

# AWS CLI 설치
sudo yum install -y aws-cli

# CodeDeploy Agent 설치 스크립트 다운로드 
cd /home/ec2-user  # Amazon Linux에서 기본 사용자는 ec2-user입니다.
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	# 정상 설치가 됐다면 PID가 나타납니다.

CodeDeploy 생성

Code Deploy는 S3에 있는 build 결과물들을 EC2로 옮겨주는 역할을 담당합니다.

[ CodeDeploy -> 애플리케이션 생성 클릭 ]

[ CodeDeploy -> 애플리케이션 생성 클릭 -> 애플리케이션 설정 ]

애플리케이션 설정은 다음과 같습니다 
애플리케이션 이름 : demo-deploy
컴퓨팅 플랫폼 : EC2/온프레미스 

[ CodeDeploy -> 애플리케이션 -> 배포 그룹 생성 클릭]

[ CodeDeploy -> 애플리케이션 -> 배포 그룹 생성 ]

배포그룹 설정은 다음과 같습니다 
배포 그룹 이름 : demo-deploy-group
서비스 역할 : demo-deploy (IAM 역할 생성시 생성 해둔 역할 할당)
배포 유형 : 현재 위치 
환경 구성  : Amazon EC2 인스턴스 
  - 키 : Name
  - 값 : demo-ec2 할당
배포 설정 : CodeDeployDefault.AllAtOnce
로드 밸런서 : 비활성화

Github Repository에 관련 IAM 설정 추가 

[ Github Repository -> Settings -> Secrets and variables -> Actions 누르기]

New repository secret에 다음 값들을 기입해 줍니다.
AWS_ACCESS_KEY_ID : IAM 액세스 키
AWS_SECRET_ACCESS_KEY : IAM 비밀 액세스 키

IAM 키는 외부에 노출되면 안됩니다. 
그렇기 때문에 Github에서 제공하는 비밀번호 저장소를 사용해 Repository에게 권한을 부여해줍니다.

Github Actions 설정 

Github Actions 설정을 시작하도록 하겠습니다. 

Github Repository 에서 Actions 탭 클릭 후 set up a workflow yourself 클릭

다음 코드들을 넣어줍니다. 

name: CI/CD Pipeline

# main branch에 push 될 때 Github Actions 동작
on:
  push:
    branches:
      - main

# 환경 변수 입력
env:
  S3_BUCKET_NAME: demo-bucket-0419

jobs:
  build:
    runs-on: ubuntu-latest

    steps: 
    - name: Checkout Code 
      uses: actions/checkout@v3
    # Java 17 설정
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'
	# Gadle Build 실행
    - name: Build With Gradle
      run: ./gradlew clean build
	# Directory 생성
    - name: Make Directory
      run: mkdir -p deploy
	# Jar file 복사
    - name: Copy Jar
      run: cp ./api/build/libs/*.jar ./deploy
	# Zip file 만들기
    - name: Make zip file
      run: zip -r ./deploy.zip ./deploy
      shell: bash
	# AWS 관련 비밀번호 할당 IAM)
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2 # 서울 region
	# S3로 업로드
    - name: Upload to S3
      run: aws s3 cp --region ap-northeast-2 ./deploy.zip s3://$S3_BUCKET_NAME/

Commit을 하고 나면 Github Actions가 자동으로 활성화되고 S3를 확인해 보면 Jar 파일이 들어간 zip 파일이 업로드 된 것을 확인할 수 있습니다.

[appspec.yml, deploy.sh 생성, main.yml 수정]

appspec.yml은 AWS환경에 애플리케이션을 배포하기 위해 Github Actions를 구현할 때 중요한 역할을 합니다. 

Proejct에서 루트 경로에 appspec.yml을 설정해줍니다. 

appspec.yml에 다음의 내용들을 기입해줍니다. 

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/app/
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 60
      runas: ec2-user

Proejct에서 루트 경로에서 scripts라는 폴더를 생성한 후  deploy.sh을 생성합니다. 

#!/usr/bin/env bash

REPOSITORY=/home/ec2-user/app

echo "> 현재 구동 중인 애플리케이션 pid 확인"

CURRENT_PID=$(sudo lsof -t -i:8080)

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

if [ -z $CURRENT_PID ]; then
  echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -9 $CURRENT_PID"
  sudo kill -9 $CURRENT_PID
  sleep 7
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 $JAR_NAME >> $REPOSITORY/nohup.out 2>&1 &

Github에서 ./github/workflows/main.yml을 수정하도록 하겠습니다. 

name: CI/CD Pipeline

on:
  push:
    branches:
      - main

env:
  S3_BUCKET_NAME: demo-bucket-0419

jobs:
  build:
    runs-on: ubuntu-latest

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

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'

    - name: Build With Gradle
      run: ./gradlew clean build

    - name: Make Directory
      run: mkdir -p deploy

    - name: Copy Jar
      run: cp ./build/libs/*.jar ./deploy
	# 추가된 부분
    - name: Copy appspec.yml
      run: cp appspec.yml ./deploy
	# 추가된 부분
    - name: Copy script
      run: cp ./scripts/deploy.sh ./deploy

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

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2 # 서울 region

    - name: Upload to S3
      run: aws s3 cp --region ap-northeast-2 ./deploy.zip s3://$S3_BUCKET_NAME/
	# 추가된 부분
    - 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 {deploy 어플리케이션 이름} --deployment-group-name {deploy group 이름} --file-exists-behavior OVERWRITE --s3-location bucket={S3 버킷 이름},bundleType=zip,key=deploy.zip --region ap-northeast-2

 

마지막으로 EC2의 보안 규칙을 풀어주도록 하겠습니다. 

[ EC2 인스턴스 -> 보안 -> 보안 그룹 클릭 ] 

[ EC2 인스턴스 -> 보안 -> 보안 그룹 클릭 -> 인바운드 규칙 편집 클릭 ] 

인바운드 규칙에서 8080 포트를 추가해줍니다. 

 

할당 받은 http://퍼블릭 IPV4주소:8080 (http://125.5.3.4:8080) 요청을 하면 다음과 같이 정상적으로 WAS가 띄워진 것을 확인할 수 있습니다. 

 

하지만 Whitelabel Error Page의 경우 보기 좋은 페이지는 아니므로 healthCheck 전용 Controller를 만들어 주겠습니다. 

 

@RestController
public class HealthCheckController {
    
    @GetMapping("/health-check")
    public String healthCheck() {
        return "WAS IS RUNNING";
    }
}

정상적으로 Deploy가 되는 것을 확인할 수 있습니다.