[AWS] Github Actions로 CI/CD 파이프라인 구성해보기

이진서·2023년 12월 6일

AWS

목록 보기
3/3

1. CI/CD

  • 애플리케이션 개발 단계를 지동화하여 애플리케이션을 더욱 짧은 주기로 제공하는 방법.
  • 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포
  • 통합 및 테스트부터 제공 및 배포에 이르는 애플리케이션의 라이프사이클 전체에 걸쳐 지속적인 자동호와 모니터링을 제공

1-1. CI (Continuous Integration,지속적 통합)

  • 현대적인 개발에서는 여러 개발자들이 애플리케이션의 각기 다른 기능을 동시에 개발한다. 이렇게 개발된 코드들을 충돌을 피해 병합하기 위해서 반복적인 수작업에 많은 시간을 소모하게 된다.
  • CI를 통해 개발자들은 이러한 병합 작업을 수월하게 수행할 수 있다. 개발자가 적용한 변경 사항이 병합되면 이러한 변경 사항이 애플리케이션을 손상시키지 않도록 자동으로 애플리케이션을 구축하고 각기 다른 레벨의 자동화된 테스트 실행을 통해 변경 사항이 제대로 적용되었는지를 확인한다.

1-2. CD (Continuous Delivery, 지속적 제공)

  • CI의 빌드 자동화, 유닛 및 통합 테스트 수행 후, 지속적 제공 프로세스를 통해 유효한 코드를 자동으로 리포지토리에 릴리스한다.

1-3. CD (Continuous Deployment, 지속적 배포)

  • 애플리케이션을 프로덕션으로 릴리스하는 작업을 자동화한다.
  • 지속적 배포가 제대로 이루어지려면 테스트 자동화가 제대로 설계되어 있어야 한다.

1-4. 자주 사용되는 CI/CD 툴

  • Jenkins
  • Github Actions

2. Github Actions: CI 환경 구축

2-1. Workflow 선택

  • Git Repository의 Actions 탭에서 새로운 Workflow를 추가할 수 있다.
  • 자주 사용하는 언어나 프레임워크는 이미 기본적인 템플릿이 제공된다. (Java with Gradle 선택)
  • Configure 버튼을 누르면 gradle.yml 파일이 제공된다.

2-2. gradle.yml 파일 수정

# 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

on:
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:

    # 1. 워크플로우 실행 전 체크아웃 필요
    - name: Checkout
      uses: actions/checkout@v3

    # 2. JDK 17버전 설치
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    # 3-1. Gradle에 excute 권한 부여
    - name: Grant excute permission for gradlew
      run: chmod +x gradlew

    # 3-2. Gradle 사용하여 빌드
    - name: Build with Gradle
      run: ./gradlew clean build
  • on : 워크플로우를 수행할 이벤트를 결정
    • 위 코드에서는 main 브랜치에 Pull Request가 들어오면 수행한다고 선언했다.
  • jobs : 수행할 워크플로우를 작성
  • 위 코드는 main 브랜치에 Pull Request가 들어오면 자동으로 빌드하여 jar파일로 만들어주는 워크플로우이다.
  • CI/CD를 해보기 위해 만든 프로젝트라 테스트파일이 존재하지 않아 테스트 과정이 없지만, 원래는 빌드 실행시 테스트코드를 모두 통과하여야만 빌드가 성공한다.

3. Github Actions: CD 환경 구축

3-1. 배포 과정의 도식화

  1. CI 과정중에서 테스트코드를 통과하였으므로 테스트는 생략
  2. Github Actions에서 코드를 빌드한다.
  3. AWS 인증을 받는다.
  4. 빌드된 파일을 압축하여 AWS S3에 업로드한다.
  5. AWS CodeDeploy를 실행하여 S3의 파일을 EC2에 배포한다.

3-2. EC2 설정 추가

3-2-1. 배포할 인스턴스에서 인스턴스 설정 -> 태그 관리에 들어가 태그를 추가한다.

  • my-ec2-tag-key를 추가했다.

3-2-2. IAM 역할 추가

  • AmazonS3FullAccess 권한을 가진 my-ec2-iam-role 역할 추가

3-2-3. EC2 인스턴스에 IAM 연결

3-3. CodeDeploy Agent 설치

  • EC2 인스턴스에서 다음 파일들을 설치해준다.
sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
# https://{bucket-name}.s3.{리전식별자}.amazonaws.com/lastestinstall
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

3-4. AWS S3 생성

  • AWS 콘솔에서 S3에 접근한 후 버킷 생성

3-5. CodeDeploy 생성

3-5-1. CodeDeploy 전용 IAM 만들기

  • AWSCodeDeployRole 권한으로 생성한다.

3-5-2. CodeDeploy 애플리케이션 생성

  • CodeDeploy 애플리케이션을 생성한다.

3-5-3. EC2 인스턴스 선택

  • 아까 생성해주었던 my-ec2-tag-key 태그 추가

3-5-4. 기타 설정

  • AWS CodeDeploy 에이전트 설치
    • 한 번만
  • 배포 설정
    • CodeDeployDefault.AllAtOnce
  • 로드 밸런싱 비활성화

3-6. Github Actions에서 사용할 IAM 사용자 추가

3-6-1. IAM 사용자 이름 및 액세스 유형 설정

  • AWSCodeDeployFull, AWSCodeDeployFullAccess 권한을 추가한다.
  • 생성 후 액세스 키와 시크릿 키를 받아 저장해둔다.

3-6-2. Github Repository의 Secrets 추가

  • 생성된 두 키를 Github Repository의 Setting - Secrets - Actions에서 등록

3-7. AppSpec 파일 작성

  • 프로젝트의 어떤 파일들을 EC2의 어떤 경로에 복사할 지 설정 가능
  • 배포 프로세스 이후에 수행할 쉘 스크립트파일을 작성해서 자동 구현 가능
  • AppSpec파일은 스프링 프로젝트의 root 디렉토리에 있어야 한다.
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
  • files : 배포 파일에 대한 설정

    • source : 인스턴스에 복사할 디렉토리 경로
    • destination : 인스턴스에서 파일이 복사되는 위치
    • overwrite : 복사할 위치에 파일이 있는 경우 대체
  • permissions : files 섹션에서 복사한 파일에 대한 권한 설정

    • object: 권한이 지정되는 파일 또는 디렉토리
    • pattern: (optional) : 매칭되는 패턴에만 권한 부여
    • owner: (optional) : obectt의 소유자
    • group (optional) : object의 그룹 이름
  • hooks : 배포 이후에 수행할 스크립트를 지정할 수 있다.

    	- location : hooks 에서 실행할 스크립트 위치
    • timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
    • runas (optional): 스크립트를 실행하는 사용자
  • script/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
  • script/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
  • build.gradle 추가 : 아래 코드를 넣어줘야 빌드시 jar파일이 하나만 생성됨
jar {
	enabled = false
}

3-8. Github Actions Workflow 작성

# 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: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

# 본인이 설정한 값을 여기서 채워넣습니다.
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-github-actions-s3-bucket-gary
  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 17
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        distribution: 'temurin'
        java-version: '17'

    # 3-1. Gradle 권한부여
    - name: Grant for Gradle
      run: chmod +x gradlew
      
    # 3-2. Gradle build
    - name: Build with Gradle
      run: ./gradlew clean build

    # 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_SECRETS_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
    1. AWS 인증
    • AWS에 접근하기 위해 인증하는 부분이다.
    • 위에서 Secret에 저장해둔 IAM 사용자의 Access Key, Secret Key와 리전을 받아와 AWS 인증을 처리한다.
    • 실제 코드를 노출시키지 않고 사용할 수 있다.
    1. AWS S3 버킷에 업로드
    • 아까 CI과정에서 만들어진 JAR파일을 압축하여 4번에서 인증한 IAM 사용자를 통해 S3 버킷에 올리는 과정이다.
    • --applicaion-name : CodeDeploy 애플리케이션 이름
    • --s3-location : 압축 파일을 업로드 할 S3 버킷
    • --ignore-hidden-files : 숨김파일도 업로드 할지 여부
    • $GITHUB_SHA : 깃허브에서 매번 커밋할 때 마다 생성하는 랜덤 변수값으로, 파일 업로드시 이름이 중복되어 충돌이 나는 것을 방지한다.
    1. CodeDeploy 실행하여 EC2에 배포
    • S3 버킷에 업로드한 ZIP파일을 EC2로 가져와 압축을 풀고 appspec.yml 파일에서 선언했던 쉘스크립트를 실행한다.
    • --application-name : CodeDeploy 애플리케이션ㅇ ㅣ름
    • --deployment-config-name : 배포 방식 (default = CodeDeployDefault.AllAtOnce)
    • --deployment-group-name : CodeDeploy 배포 그룹
    • --s3-location : 버킷 이름, 키 값, 번들 타입으로 가져올 파일을 찾는다. 위에서 랜덤 변수를 파일명으로 사용했으므로 충돌이 나지 않는다.

4. Github Actions 사용해서 배포

  • main에 push한 후 Github repo에서 워크플로우의 진행 상황을 확인할 수 있다.
  • CodeDeploy 콘솔로 가면 배포 내역을 확인할 수 있다.
  • EC2 서버에 접속하여 백그라운드에 배포한 파일이 실행되고 있는지 확인하면 Github Actions를 이용한 CI/CD 파이프라인을 구축하여 배포까지 완료된다.

5. 참고

0개의 댓글