클라우드 공부/AWS

Git Action과 AWS CodeDeploy로 SpringBoot 배포

su-mmer 2023. 12. 27. 13:24
728x90

목적

Git Action과 AWS CodeDeploy 스택이 필요해서 직접 사용해보려고 합니다.
Git Action으로도 CI/CD를 모두 진행할 수 있는데 왜 그렇게 하지 않는지도 생각해보았습니다.
 
제가 찾은 Git Action 배포 방식은 워크플로우에서 서버에 접속하여 수동으로 배포하는 방법 뿐 입니다.
그런데 워크플로우에서 실행시킨 프로세스는 워크플로우 종료와 함께 모두 종료시키는 것이 워크플로우의 원칙입니다.
실제로 Git Action에서 nohup으로 프로세스를 실행시키는 명령을 주더라도 워크플로우가 종료될 때 프로세스도 함께 종료됩니다.
임의로 뭔가를 건드려서 종료시키지 않을 수도 있지만 그러라고 만든 게.. 아니니까 다른 방법을 쓰는 것이 맞다고 생각합니다.
 
제가 Git Action을 사용하는 환경을 구성한다면 Git Action으로 테스트 환경과 빌드까지만 하고 배포는 AWS CodeDeploy를 사용할 것 같습니다.
다만 서버에 파일을 전달하는 것까지가 목표라면 거기까진 Git Action으로 구성할 것 같습니다.
 

EC2에 직접 빌드하기

Git Action을 사용하기 전 EC2 위에서 깃 레포를 클론하여 직접 빌드하고 실행해보았습니다.
직접 실행해보고 자동화 코드를 짜는 것이 틀릴 확률이 적어져 이 방법을 선호하는 편입니다.

EC2를 생성했습니다.

  • Ubuntu
  • EIP 할당
  • t3.micro

 
인바운드에 http, https, ssh(내 IP에서만 접근 가능), 8080포트를 열어주었습니다.

아웃바운드는 전체 대역으로 두었습니다.
 

sudo apt-get update
sudo apt-get install -y openjdk-11-jdk
# 설치 확인
java -version

git clone https://github.com/Jeromy0515/cloud-skills-sample-spring-boot-app.git
gradlew clean build
# cloud-skills-sample-spring-boot-app내에서 빌드하면 build/libs/내부에 jar파일 생성
nohup java -jar build/libs/cloud_skills_spring_boot_exam-0.0.1-SNAPSHOT.jar &

사용할 레포지토리에 Java 11 버전으로 명시되어 있습니다.
필요한 툴들을 설치하고 빌드, 실행합니다.
 
nohup 명령은 서버와 SSH 연결이 끊기더라도 프로그램이 계속 실행됩니다.
& 명령은 프로그램이 백엔드로 실행됩니다.
 

ubuntu@ip-000-00-0-000:~/cloud-skills-sample-spring-boot-app$ cat nohup.out

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.3)

2023-11-24 05:34:28.702  INFO 1620 --- [           main] s.c.CloudSkillsSpringBootExamApplication : Starting CloudSkillsSpringBootExamApplication using Java 11.0.20.1 on ip-000-00-0-000 with PID 1620 (/home/ubuntu/cloud-skills-sample-spring-boot-app/build/libs/cloud_skills_spring_boot_exam-0.0.1-SNAPSHOT.jar started by ubuntu in /home/ubuntu/cloud-skills-sample-spring-boot-app)

nohup 명령 실행 시 nohup.out 파일에 로그가 기록됩니다.

정상 작동하는 것을 확인했습니다.
 

Git Action 빌드 + AWS CodeDeploy 배포

샘플로 사용할 레포지토리를 클론해 사용했습니다.
https://github.com/su-mmer/cloud-skills-sample-spring-boot-app

GitHub - su-mmer/cloud-skills-sample-spring-boot-app: cloud-skills-spring-boot-example with Java/Spring Boot (Gradle)

cloud-skills-spring-boot-example with Java/Spring Boot (Gradle) - GitHub - su-mmer/cloud-skills-sample-spring-boot-app: cloud-skills-spring-boot-example with Java/Spring Boot (Gradle)

github.com

 

레포지토리에서 Action Workflow를 생성합니다.

EC2

AWS IAM instance profile 생성

EC2에서 S3에 접근해 빌드 파일을 가져와야 하므로 S3FullAccess policy를 가진 IAM 역할을 생성하고 EC2에 연결합니다.
 
CodeDeploy에서 태그로 배포할 EC2를 선택하기 때문에 EC2에 태그를 추가합니다.
 

AWS CodeDeploy Agent 설치

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html

Ubuntu Server용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

출력을 임시 로그 파일에 쓰는 것은 Ubuntu Server 20.04에서 install 스크립트를 사용하여 알려진 버그를 해결하는 동안 사용해야 하는 해결 방법입니다.

docs.aws.amazon.com

sudo apt update
sudo apt install ruby-full -y
sudo apt install wget
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
sudo chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status

EC2에 접속하여 CodeDeploy 에이전트를 설치합니다.

agent의 동작을 확인합니다.
 

S3

빌드한 파일을 저장할 버킷을 생성합니다.
 

AWS CodeDeploy

IAM Role 생성

CodeDeploy에 연결할 역할을 생성합니다.

Tag를 이용해 배포할 EC2를 정하므로 Tagging을 읽는 권한이 포함됩니다.
 

AWS CodeDeploy app 생성

앱을 만듭니다.
 

그룹도 만들어줍니다.
배포할 EC2 인스턴스 세트를 그룹이라고 합니다.
 

위에서 생성한 CodeDeployRole policy를 가진 IAM Role을 연결해줍니다.
 

배포할 인스턴스에 추가한 태그로 설정해줍니다.

AllAtOnce를 선택하면 배포 구성의 성공 조건은 최소 0개의 호스트가 동작중일 때 입니다.

 

GitHub에서 사용할 IAM 사용자 생성

사용자를 생성합니다.

외부에 유출되면 안 되므로 레포지토리에 Secret을 등록합니다.
 

파일 구성

appspec.yaml

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

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

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu

루트 디렉터리 위치에 appspec.yaml을 작성합니다. 파일 이름을 반드시 똑같이 지켜줘야 인식됩니다.

더보기
  • files
    • source: 인스턴스에 복사할 파일 또는 디렉터리 경로
      • 디렉터리의 경우 내부 파일만 복사됨
      • ‘/’의 경우 파일과 디렉터리 모두 복사됨
      • ‘/’의 경우 appspec.yml 포함하여 복사됨
    • destination: 인스턴스에서 파일이 복사 되는 위치
    • overwrite: 복사할 위치에 파일이 있는 경우 대체 여부
  • permissions
    • object: 권한을 지정할 파일 또는 디렉터리
    • pattern(optional): 매칭되는 패턴에만 권한 부여
      • 지정하지 않거나 “**”을 사용하면 권한이 type에 따라 일치하는 모든 파일 또는 디렉터리에 적용됨
    • owner(optional): object의 소유자 이름
    • group(optional): object의 그룹 이름
  • hooks: 파일 설치 후 기존 애플리케이션 종료(AfterInstall), 새 애플리케이션 실행(ApplicationStart)
    • location: hooks에서 실행할 스크립트의 위치
    • timeout(optional): 스크립트 실행 최대 허용 시간, 넘으면 배포 실패 처리
    • runas(optional): 스크립트를 실행하는 사용자

 

스크립트 파일

scripts 폴더를 만들고 하위에 start.sh와 stop.sh 스크립트 파일을 두었습니다.

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

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

stop.sh 

더보기
  • 실행 중인 애플리케이션이 있을 경우 종료
  • date +%c: 날짜와 시간 출력
  • -z $CURRENT_PID: 문자열 길이가 0이면 참
  • kill -15: 프로세스 정상 종료
#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

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

start.sh
새 애플리케이션 시작

빌드를 위한 GitActions workflow/build.yaml 파일

name: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-cicd-test-bucket-hh
  CODE_DEPLOY_APPLICATION_NAME: my-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: my-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production  # 작업이 참조하는 환경 정의, 메인페이지에 표시됨

    steps:
    # (1) 기본 체크아웃
    - name: Checkout
      uses: actions/checkout@v3

    # (2) JDK 11 세팅
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        distribution: 'temurin'
        java-version: '11'

    # (3) Gradle build (Test 제외)
    - name: Build with Gradle
      uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
      with:
        arguments: clean build -x test

    # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
    - 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: ${{ env.AWS_REGION }}

    # (5) 빌드 결과물을 S3 버킷에 업로드
    - name: Upload to AWS S3
      run: |
        aws deploy push \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --ignore-hidden-files \
          --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
          --source .

    # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
    - name: Deploy to AWS EC2 from S3
      run: |
        aws deploy create-deployment \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

workflows에 파일을 저장하면 자동으로 액션이 실행되며 빌드가 되는 것을 확인할 수 있습니다.

더보기
  • push
    • application-name: CodeDeploy 애플리케이션 이름
    • ignore-hidden-files: 숨김 파일은 빼고 업로드
    • s3-location: 업로드할 S3 경로, S3 버킷 이름과 개체 키를 모두 지정해주어야 함
    • source: S3에 업로드할 파일들과 AppSpec파일의 위치
  • create-deployment
    • application-name: CodeDeploy 애플리케이션 이름
    • deployment-config-name: 배포 구성, 기본값으로 CodeDeployDefault.AllAtOnce, 최소 정상 호스트값 0
    • deployment-group-name: CodeDeploy 배포 그룹 이름
    • s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
      • bucket: S3 bucket 이름
      • key: S3 객체 키 이름
      • bundleType: S3에 저장할 때 포맷

 

빌드에 성공했습니다.

인스턴스에 접속해서 확인해보면 spring-webapp.jar 파일이 제대로 생성되었습니다.

실행 중인 프로세스 목록에 spring-webapp.jar 파일이 있습니다.

배포된 앱에 접속해보면 제대로 작동합니다.

CodeDeploy 배포 로그 입니다.

CodeDeploy Hooks 로그입니다.
 
다음엔 테라폼으로 만들어보겠습니다.
 

참고

블로그
https://bcp0109.tistory.com/356

AWS 1편: EC2 생성 후 Spring Boot 띄우기

Overview AWS EC2 인스턴스를 생성하고 Spring Boot 서버를 띄워보는 것까지 진행합니다. 주 목표는 서버를 외부에 제공하는 거라서 따로 배포 시스템을 구축하지 않고 단순히 빌드 파일을 복사해서 수

bcp0109.tistory.com

https://bcp0109.tistory.com/363

Github Actions CD: AWS EC2 에 Spring Boot 배포하기

Overview 애플리케이션을 개발하면 외부에서도 접근 가능하도록 클라우드 환경에 배포합니다. 이전에 포스팅 했던 AWS 1편에서는 마지막에 scp 명령어로 로컬에 존재하는 빌드 파일을 EC2 인스턴스

bcp0109.tistory.com

샘플 프로젝트
https://github.com/Jeromy0515/cloud-skills-sample-spring-boot-app

GitHub - Jeromy0515/cloud-skills-sample-spring-boot-app: cloud-skills-spring-boot-example with Java/Spring Boot (Gradle)

cloud-skills-spring-boot-example with Java/Spring Boot (Gradle) - GitHub - Jeromy0515/cloud-skills-sample-spring-boot-app: cloud-skills-spring-boot-example with Java/Spring Boot (Gradle)

github.com

 

728x90