스프링

Github Action과 AWS CodeDeploy를 사용한 CI/CD 구축 방법

walbe0528 2023. 1. 2. 13:43
728x90
반응형
전체 아키텍쳐 흐름도

배포 방법으로 여러가지가 있지만,

AWS의 흐름은 “소스코드를 압축해서 AWS 스토리지에 저장 후 서버에 전달해서 실행”이다.

크게 두가지 방법이 있다.

  1. AWS S3 빌드파일 압축해서 업로드 → AWS EC2에 배포(CodeDeploy 활용)
  2. AWS ECR에 도커 이미지 업로드 → AWS ECS에 배포(Task Definition 활용)

나는 EC2에 배포할 것 이므로, 1의 방법을 사용!

 

<순서>

 

1. 개발자가 코드를 변경하고 Github에 푸시하면, Github Actions에서 프로젝트 빌드 후(CI) jar파일을 압축해서(.zip) S3에 업로드한다.

2. CodeDeploy에게 S3에 있는 jar파일로 배포를 진행해달라고 전달한다.

3. CodeDeploy는 배포할 EC2 인스턴스 내부에 있는 CodeDeploy Agent에게 배포 명령을 내리고, Agent는 jar파일을 S3에서 받아서 주어진 스크립트에 따라 배포를 진행한다. (CD)

4. Spring Boot WAS를 띄우고, Nginx 스위칭을 통해 무중단 배포를 할 수 있도록 Agent에게 배포 스크립트 제공

 

 

개발자가 코드 수정하고 깃헙에 push → github action이 돌아가며 빌드, 테스트(CI)를 하고 압축파일 생성(.zip) → 압축파일을 AWS S3에 업로드 → 업로드한 압축파일을 넘겨주며 CodeDeploy를 이용해 배포(CD)

 

⇒ 즉, 서버를 띄울 EC2, 배포할 결과물을 저장할 S3, 배포를 도와줄 CodeDeploy

여기서, Github Actions에서 CodeDeploy, S3에 접근하기 위한 권한이 필요하고 EC2에서 S3에 접근하기 위한 권한도 필요!

 

 

먼저, github에서 Actions 탭에서 Workflow를 추가해준다.

 

 

Java with Gradle 선택.

Configure 버튼을 누르면, 기본적으로 gradle.yml파일이 제공된다. (직접 작성해도 되고, 템플릿 활용해도 된다)

 

 

Github Action의 개념

Github Actions는 빌드, 테스트 및 파이프라인을 자동화 할 수 있는 CI/CD 플랫폼이다. repository에서 이벤트가 발생할 때 workflow를 실행한다.

  • Workflow
    • 자동화된 전체 프로세스
    • workflow파일은 YAML파일로 작성되고, 레포지토리의 .github/workflows 폴더에 위치
    • 레포지토리 안에 여러 workflow 가질 수 있음
    • github에게 YAML 파일로 정의한 자동화 동작을 전달하면, 해당 파일을 기반으로 그대로 실행시킨다.
  • Event
    • workflow 실행을 트리거하는 특정 활동이나 규칙
  • Job
    • job는 여러 step으로 구성되고, 가상 환경의 인스턴스에서 실행된다.
    • job들 간의 의존관계 설정도 가능하다. (순서)
  • Step
    • job안에서 순차적으로 실행되는 프로세스 단위
    • step에서 명령을 내리거나, action을 실행할 수 있다.
  • Action
    • job을 구성하기 위한 step들의 조합으로 구성된 독립적인 명령, workflow의 가장 작은 빌드 단위이다.
    • 재사용이 가능한 컴포넌트로 반복적인 코드의 양을 줄일 수 있다.

 

1️⃣ Github Action CI

  • Github Action은 event, job, step을 정의하기 위해 YAML파일을 사용
  • 경로 : .github > workflows > gradle.yml
name: Deploy to Amazon EC2

on:
  push:
    branches:
      - master

#본인이 설정한 값을 여기서 채워넣기
#리전,버킷 이름, CodeDeploy앱 이름, CodeDeploy배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: greme-bucket
  CODE_DEPLOY_APPLICATION_NAME: greme-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: greme-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'

# not executable 에러로 권한 부여 해줌
- name: Run chomod to make gradlew executable
  run: chmod +x ./gradlew

# (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 }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_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

 

 

gradle.yml 코드 설명

 

 

 

 

name: "Deploy to Amazon EC2" : github repository의 액션 탭에 노출되는

workflow의 이름

 

 

 

 

 

 

 

on:
  push:
    branches:
      - master
      
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production
    
    steps:
    		.
    		.
  • workflow 파일을 자동으로 트리거하는 이벤트를 명시한다.
  • push 이벤트를 명시해주면, 레포지토리에 push하는 시점마다 job이 실행된다. (특정 브랜치, tag, path에서만 실행되도록 옵션 설정 가능)
  • runs-on : 어떤 OS에서 실행할 것인지를 명시
  • steps : job이 가질 수 있는 동작의 나열. 각각의 step은 독립적인 프로세스를 가진다.
    • uses : 해당 step에서 사용할 액션. github 마켓플레이스에 올라온 action사용 가능. {owner}/{repo}@{ref|version}의 형태이다.
    • name : step의 이름
    • run : job에 할당된 컴퓨팅 자원의 shell을 이용하여 커맨드 라인을 실행
  • aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} , aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} : 민감한 정보들은 따로 환경변수로 빼서 사용한다. secrets 변수를 통해 저장한 키 값들을 가져와 사용 가능하다. 해당 Github 레포 > Settings > Secrets > Actions 에서 저장해준다.

github에서 별도로 저장한 키값들

 

 

2️⃣ S3에 빌드한 jar업로드하기

CI/CD 배포 플로우에 S3를 사용하면, build한 jar 파일을 업로드하고, 배포하는 쪽에서 다운로드하여 배포하는 방식을 많이 사용한다.

  1. S3 버킷을 생성해준다. (버킷 = S3의 저장 공간 단위)
    • 설정 권한 부분에서는, 모든 퍼블릭 엑세스 차단을 체크해준다. 외부에서 접근하려면 퍼블릭 권한을 줘야 하는 것이 맞지만, IAM 사용자를 만들어 또 다른 AWS의 권한 서비스를 이용하여 접근하도록 구축할 예정이기에, 해당 설정을 차단해준다.
    • 객체 소유권 : ACL 비활성화
  2. IAM 권한 사용자 생성하기
    • IAM은 AWS 서비스에 대한 엑세스 권한을 최소한으로 부여하고 관리할 수 있게 해주는 서비스(외부에서 AWS의 서비스에 접근할 때, AWS 서비스 간에 접근하고자 할 때 권한 부여가능)
    • Github Actions가 해당 S3에 접근할 수 있도록 권한을 생성해준다.
    • 엑세스 관리 > 사용자 > 사용자 추가로 사용자를 생성해준다.
    • AWS 엑세스 유형은 프로그래밍 방식 엑세스 선택 → Github Actions에서 AWS CLI 명령을 통해 엑세스할 예정
    • 기존 정책 직접 연결 > 부여할 권한 정책 : AmazonS3FullAccess, AWSCodeDeployFullAccess 선택
    • 태그는 다른 사용자 역할과 구분하기 위함이니, 안해도 상관없다.
    • 사용자를 생성하면 마지막 페이지에서 엑세스 키 ID와 비밀 엑세스 키가 나오는데, 따로 저장해두거나 CSV 파일 다운받아두기 ⇒ 이 정보들을 Github Secrets에서 환경변수로 빼서 따로 저장한다!

 

 

S3 버킷에 가서 확인해보면 압축파일이 올라와있다!

 

 

3️⃣ AWS EC2 생성 (+태그)

  1. AWS EC2를 만들어준다.
    • CodeDeploy를 생성할 때, 어떤 인스턴스에서 수행하는지 구분하는 값으로 태그를 사용하기 때문에 태그가 필요하다.
    • 이미 만들어둔 인스턴스에 대해 추가 가능 (인스턴스 선택 > 작업 > 인스턴스 설정 > 태그관리 > 추가)
  2. IAM 역할 추가
    • EC2 인스턴스에서 S3에 올라와 있는 파일에 접근할 수 있도록 권한을 준다.
    • 사용 사례 : EC2
    • 접근 권한 : AmazonS3FullAccess
  3. 인스턴스에서 IAM 연결
    • 해당 인스턴스 선택 > 인스턴스 상태 > 보안 > IAM 역할 수정 > 방금 만든 IAM 역할 선택

 

4️⃣ Ubuntu 환경에서 CodeDeploy Agent 설치

 

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

 

5️⃣ AWS CodeDeploy로 인스턴스에 자동 배포 설정

Amazon EC2, Aws Lambda 및 온프레미스 서보와 같은 다양한 컴퓨팅 서비스에 대해 소프트웨어 배포를 자동화하는 완전 관리형 배포 서비스이다.

→ 이 서비스를 사용하면 인스턴스에 배포를 자동으로 해준다.

(CodeDeploy에게 S3에 있는 jar파일 가져가서 해당 배포 그룹의 EC2에 배포해줘! 명령내리기)

  • CodeDeploy의 배포 과정
    • 애플리케이션의 루트 경로에 appspec.yml 파일을 작성해준다(배포에 필요한 정보, 절차를 적어둔 파일)
    • CodeDeploy에게 프로젝트의 특정 버전을 배포해달라고 요청한다.
    • CodeDeploy는 배포를 진행할 EC2 인스턴스에 설치된 CodeDeploy Agent와 통신하며 Agent에게 요청받은 버전을 배포하라고 요청
      • CodeDeploy Agent는 EC2 인스턴스에 설치되어 CodeDeploy의 명령을 기다리는 프로그램이다. → 실제 배포는 Agent가 해주기에, 반드시 EC2에 설치해줘야 한다.
    • Agent는 프로젝트 전체를 서버에 내려받고, appspec.yml파일을 읽어 해당 파일에 적힌 절차대로 배포를 진행한다.
    • Agent가 배포를 진행하고 배포의 결과를 CodeDeploy에게 전달한다.

 

  1. IAM 역할 생성

 

CodeDeploy에게 역할을 부여해줘야 하기에, IAM > 역할 > 역할 만들기 에서 새로 만든다.

권한 정책 : AWSCodeDeployRole

사용사례 : CodeDeploy

 

 

2. 애플리케이션 생성하기

 

CodeDeploy > 배포 > 애플리케이션 > 애플리케이션 생성으로 만들어준다.

컴퓨팅 플랫폼 : EC2/온프레미스

 

 

3. 배포 그룹 만들기

  • CodeDeploy 애플리케이션에서 사용하는 배포그룹을 생성해준다.
  • 생성한 애플리케이션 선택 > 배포 그룹 생성
    • 배포 유형 : 현재 위치
    • 환경 구성 : Amazon EC2 인스턴스
    • 해당 EC2에 맞는 태그 선택(어떤 인스턴스에서 동작할지를 선택)
    • 로드 밸런싱은 사용하지 않는다면 체크

 

4. Github Actions에서 사용할 사용자 추가

 

Github Actions 워크플로우에서 AWS에 접근하려면 권한이 따로 필요하다.

지금까지 IAM 역할만 추가해서 EC2, CodeDeploy 등의 서비스에 부여했지만, 이번엔 IAM 사용자를 추가해본다. → 근데 이거 아까 (2)에서 생성했음

 

 

6️⃣ appsepc.yml 파일

CodeDeploy에서 배포를 위해 참조할 appsepc.yml 파일을 작성해준다.

해당 파일을 통해서 프로젝트의 어떤 파일들을 EC2의 어떤 경로에 복사할지 설정 가능하고, 배포 프로세스 이후에 수행할 스크립트를 지정하여 자동으로 서비스를 띄울 수 있다.

⇒ 루트 디렉토리에 위치해야 한다.

 

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

 

1. file 섹션 

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes
  • source : 인스턴스에 복사할 디렉토리 경로
  • destination : 인스턴스에서 파일이 복사되는 위치
  • overwrite : 복사할 위치에 파일이 있는 경우 대체

 

2. permissions 섹션

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu
  • object : 권한이 지정되는 파일 또는 디렉토리

 

3. hooks 섹션

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu
  • 파일 설치 뒤, AfterInstall에서 기존에 실행되던 애플리케이션을 종료시키고, ApplicationStart에서 새로운 애플리케이션을 실행시킨다.

 

7️⃣ 배포 스크립트 작성

  1. start.sh
#!/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

2. 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

 

 

전체 디렉토리 구조

728x90