CI/CD 도입하기 2

변현섭·2023년 8월 17일
0

Github 사용법

목록 보기
15/17

1. IAM 권한 설정하기

① IAM > 사용자 탭에서 사용자를 선택하고, 권한으로 AmazonS3FullAccess와 AwsCodeDeployFullAccess를 추가한다.

② 이번엔 EC2 > 인스턴스에 들어가서 인스턴스를 선택한 다음 작업 > 보안 > IAM 역할 수정 버튼을 클릭한다.

③ 새 IAM 역할 생성 버튼을 클릭한다.

④ 역할 만들기 버튼을 클릭한다.

⑤ AWS 서비스, EC2를 선택하고 다음 버튼을 클릭한다.

⑥ 이번에도 AmazonS3FullAccess와 AwsCodeDeployFullAccess를 추가한다.

⑦ IAM Role의 이름과 설명을 입력하고 역할 생성 버튼을 클릭한다.

⑧ 방금 만든 IAM Role을 지정하고 IAM 역할 업데이트 버튼을 클릭한다.

2. EC2에 CodeDeploy Agent 설치하기

① 아래의 명령을 차례대로 입력한다

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ sudo wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

② ls를 입력했을 때 아래와 같이 install 파일이 나온다면 codedeploy 파일의 설치가 완료된 것이다.

③ 이번에는 아래의 명령을 차례대로 입력하여 설정 및 사용 가능한 codedeploy의 버전을 확인하자.

$ sudo chmod +x ./install
$ sudo ./install auto > /tmp/logfile
$ sudo apt-get install awscli

# 서울 리전에서 사용 가능한 codedeploy 버전 확인
$ sudo aws s3 ls s3://aws-codedeploy-ap-northeast-2/releases/ | grep '\.deb$'

④ 다양한 버전이 있는데 가장 아래에 위치한 최신 버전을 사용하기로 하겠다.

⑤ 아래의 명령을 차례대로 입력한다.

$ sudo ./install auto -v releases/codedeploy-agent_1.6.0-49_all.deb > /tmp/logfile
$ sudo service codedeploy-agent status

⑥ codedeploy-agent service가 active 상태임을 확인하면 CodeDeploy Agent 설치가 모두 완료된 것이다.

3. AWS CodeDeploy 생성하기

1) IAM Role 생성

① IAM > 역할 탭에 들어가 역할 만들기 버튼을 클릭한다.

② 사용 사례에 CodeDeploy를 선택하고 다음 버튼을 클릭한다.

③ 사용 사례에 CodeDeploy를 선택하면, 자동으로 CodeDeploy에 대한 모든 권한이 추가된다.

④ 역할 이름과 설명을 입력하고, 역할 생성 버튼을 클릭하여 IAM Role 생성을 완료한다.

2) CodeDeploy 애플리케이션 생성

① 이번엔 CodeDeploy 서비스로 들어가 애플리케이션 생성 버튼을 클릭한다.

② 애플리케이션 이름과 컴퓨팅 플랫폼을 지정하고, 애플리케이션 생성 버튼을 클릭한다.

③ 배포 그룹 생성 버튼을 클릭한다.

④ 배포 그룹 이름을 지정하고 서비스 역할로 방금 만든 IAM Role을 선택한다. 배포 유형은 현재 위치를 선택한다.

⑤ 환경 구성에서 본인의 EC2 인스턴스를 선택하면 된다.

⑥ 배포 설정은 CodeDeployDefault.AllAtOnce를 선택하고, 로드 밸런서를 사용하는 경우에는 로드 밸런싱 활성화에 체크한다.

4. cicd.yml, appspec.yml, deploy.sh 파일 작성하기

프로젝트의 root 디렉토리 하위로 appspec.yml, deploy.sh 파일을 추가한다.

AWS S3는 이미 있다고 가정하고 실습을 진행한다. AWS S3 생성 방법을 모른다면, 아래의 링크를 참조하기 바란다.
>> AWS S3 생성하기

1) cicd.yml

이전 포스팅에서 작성했던, ci.yml 파일에 우클릭 > Refactor > Rename으로 cicd.yml로 파일명을 변경한다. 이후 아래의 내용을 입력한다. 본인의 프로젝트에 알맞게 수정해주어야 할 값도 많기 때문에 주의 깊게 읽어보자.

# 워크 플로의 이름
name: CI/CD

# 워크 플로의 시작 조건: main 브랜치에 push
on:
  push:
    branches:
      - '*'

env:
  PROJECT_NAME: hello-there # 본인이 원하는 이름을 입력한다.
  BUCKET_NAME: chrome-bucket # S3 Bucket 명
  CODE_DEPLOY_APP_NAME: hello-there # CodeDeploy 애플리케이션 명
  DEPLOYMENT_GROUP_NAME: hello-there-group # CodeDeploy 배포 그룹 명

jobs:
  build:
    runs-on: ubuntu-latest # 실행 환경
    # 작업의 실행 단계를 정의
    steps:
      - uses: actions/checkout@v3
        with:
          token: ${{ secrets.GIT_TOKEN }}
          submodules: true

      - uses: actions/setup-java@v3
        with:
          distribution: 'zulu' # 자바 배포판
          java-version: '17' # 자바 버전

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash
        
      - name: Set YML
        run: |
          mkdir -p src/main/resources
          echo "${{ secrets.APPLICATION_YML }}" | base64 --decode > src/main/resources/application.yml
          # 파이어베이스 설정 파일이 있는 경우에만 입력
          echo "${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}" | base64 --decode > src/main/resources/ServiceAccountKey.json
          find src
      # Redis를 사용하는 경우에만 입력
      - name: Start Redis 
 		uses: supercharge/redis-github-action@1.1.0
  		with:
          redis-version: 6
          
	  # <-- 아래의 내용부터는 변경할만한 내용 없음 -->
      - name: Build with Gradle
        run: ./gradlew clean build
        
      # zip 파일 생성
      - name: Make zip file 
        run: zip -r ./$GITHUB_SHA.zip .
        shell: bash

      # AWS 자격 증명
      - 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 # S3 업로드
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip

      - name: Code Deploy # CodeDeploy에 배포 요청
        run: aws deploy create-deployment--application-name $CODE_DEPLOY_APP_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $DEPLOYMENT_GROUP_NAME --s3-location bucket=$BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

2) appspec.yml

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/.ssh/HelloThere
    overwrite: yes
file_exists_behavior: OVERWRITE

permissions:
  - object: /home/ubuntu/.ssh/HelloThere
    owner: ubuntu
    group: ubuntu
hooks:
  AfterInstall:
    - location: deploy.sh
      timeout: 60
      runas: ubuntu

① version

  • appspec.yml 파일의 버전을 정의한다.
  • 0.0 이외의 버전은 지원되지 않는다.

② files

  • 파일을 root 디렉토리에서 가져와 /home/ubuntu/.ssh/HelloThere 디렉토리로 붙여 넣는다.

③ permissions

  • /home/ubuntu/.ssh/HelloThere 디렉토리의 그룹과 소유자를 ubuntu로 설정한다.

④ hooks

  • 배포 단계에서 실행할 훅 스크립트를 정의한다.
  • 배포 후 실행할 스크립트로 deploy.sh 파일을 지정하고, 스크립트 실행 시간 제한을 60초로 설정한다.

⑤ AfterInstall

  • 여러 배포 단계 중 AfterInstall 단계에서 스크립트를 실행한다.

3) deploy.sh

#!/usr/bin/env bash
BUILD_JAR=$(ls /home/ubuntu/.ssh/HelloThere/build/libs/*.jar | tail -n 1) # /home/ubuntu/.ssh/HelloThere/build/libs/hello_there-0.0.1-SNAPSHOT.jar
REPOSITORY=/home/ubuntu/.ssh/HelloThere

cd $REPOSITORY

APP_NAME=hello_there-0.0.1-SNAPSHOT.jar
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep 'SNAPSHOT.jar' | tail -n 1) # hello_there-0.0.1-SNAPSHOT.jar
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME # /home/ubuntu/.ssh/HelloThere/build/libs/hello_there-0.0.1-SNAPSHOT.jar

DEPLOY_PATH=/home/ubuntu/.ssh/HelloThere/
cp $BUILD_JAR $DEPLOY_PATH # cp /home/ubuntu/.ssh/HelloThere/build/libs/hello_there-0.0.1-SNAPSHOT.jar /home/ubuntu/.ssh/HelloThere/

CURRENT_PID=$(pgrep -f $APP_NAME) # 기존 서버 프로세스의 pid

if [ -z $CURRENT_PID ] # current pid의 길이가 zero이면(= 기존 프로세스가 없으면)
then
  echo "> 종료할 애플리케이션이 없습니다."
else
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME # /home/ubuntu/.ssh/HelloThere/hello_there-0.0.1-SNAPSHOT.jar
chmod +x $DEPLOY_JAR
echo "> Deploy - $JAR_PATH "
nohup java -jar $DEPLOY_JAR > /dev/null 2> /dev/null < /dev/null &
  • APP_NAME은 EC2에서 pgrep -f의 인자로 넣었을 때 실행 중인 jar 파일의 pid가 나올 수 있도록 적절한 값을 설정해야 한다.
  • 아래와 같이 hello나 hello_there, hello_there-0.0.1-SNAPSHOT.jar 같은 것을 사용해야 한다. hello-there을 쓰면 안된다.
  • 이 pid 값을 이용해 기존에 실행 중이던 서버 프로세스를 중단하고, 서버를 재시작한다.
  • pid가 잘못되면 서버가 재시작되지 못해 변경 사항이 적용되지 않는다.

5. CI/CD 테스트

1) 깃허브 액션 & CodeDeploy 테스트

① CI/CD의 동작여부를 테스트해보기 위해 commit과 push를 진행한다.

② 만약 테스트를 사용하지 않는데, 아래와 같은 에러가 발생하면 src > test > java > com.example.hello_there > HelloThereApplicationTests 파일에서 @SpringBootTest 어노테이션을 제거해야 한다.

HelloThereApplicationTests > contextLoads() FAILED
    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:98
        Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException at ConstructorResolver.java:800
            Caused by: org.springframework.beans.factory.BeanCreationException at ConstructorResolver.java:658
                Caused by: org.springframework.beans.BeanInstantiationException at SimpleInstantiationStrategy.java:185
                    Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException at DataSourceProperties.java:186

1 test completed, 1 failed

③ 깃허브 Repository > Actions에 아래와 같이 all-checked가 나와야 한다.

④ Actions에 all-checked가 나오면 AWS CodeDeploy에서 배포 내역을 확인할 수 있다. 배포에는 10분 정도의 시간이 소요된다.

2) EC2 용량 늘리기

① 만약 EC2 인스턴스를 생성할 때 용량을 8GB로 설정했으면 용량 부족 문제로 배포가 안 될 수 있다. (처음부터 EC2 용량을 넉넉하게 잡았다면 이 과정은 건너뛰어도 된다.)

② 만약 아래와 같이 df -h를 했을 때 Use가 100%라고 나온다면 EC2의 용량을 늘려주어야 한다.

③ EC2의 인스턴스에서 본인의 인스턴스를 선택한 뒤, 스토리지 탭 > 블록 디바이스에 있는 볼륨 ID를 클릭한다.

④ 볼륨 ID에 우클릭 > 볼륨 수정 버튼을 클릭한다.

⑤ 프리티어 최대 용량인 30GB로 수정한다. 이후 인스턴스를 재부팅한다.

⑥ df -h를 다시 입력해보면 아래와 같이 EC2 용량이 늘어난 것을 확인할 수 있다.

3) 서버 배포 & API 테스트

① 깃허브의 최신 코드를 pull 받은 후 프로젝트를 빌드한다. 최신 코드를 pull 받는 방법은 프로젝트 디렉토리에서 아래와 같이 입력하면 된다.

$ git stash # 현재 Staging 영역에 있는 파일의 변경사항을 스택에 저장
$ git pull 
$ git stash pop # 변경 사항을 적용하고, 스택에서 제거 

② 업데이트한 코드를 수동으로 EC2에 무중단 배포한다.

③ 코드를 임의로 수정하고 다시 commit & push한다. 여기서는 아래의 cd-test API를 UserController에 추가하고 commit & push 하기로 한다.

// CI/CD의 정상 동작을 확인하는 API
@GetMapping("/cd-test")
public String cdcheck() {
    return "OK";
}

④ 깃허브 액션에서 결과를 확인한다.

⑤ CodeDeploy의 배포 내역에서 배포 상태가 "성공"이 될 때까지 기다린다. 10분 정도 소요된다.

  • 참고로 CodeDeploy가 "진행 중" 상태일 때는 서버가 무중단 배포 상태여도 API를 호출할 수 없다. 정상적인 상황이니 걱정하지 말자.

⑥ 만약 10분이 훨씬 지나도 배포가 진행되지 않는다면, EC2 > 로드밸런서 > 대상그룹 > 대상 탭에 들어가 본인의 인스턴스가 unhealthy 상태인지 확인해보자.

  • 인스턴스가 unhealthy 상태이면 CodeDeploy가 무한 로딩에 걸린다.

⑦ 인스턴스를 healthy 상태로 만들려면, HTTP status 200을 반환하는 임의의 api를 만들어 서버에 올려주어야 한다. 그리고 해당 API의 엔드포인트를 상태 검사의 경로로 넣어주어야 한다.

  • 이 내용에 대해서는 이전 포스팅에서 이미 다루었기 때문에 자세한 내용은 아래의 링크를 참조하기 바란다.
  • 로드밸런서 생성하기 > 대상그룹 생성하기 부분을 참고하면 된다.
    >> healthy 상태로 바꾸기

⑧ CodeDeploy의 상태가 "성공"이 되면, 다시 API를 호출할 수 있는 상태가 된다.

⑨ 새롭게 추가한 cd-test API가 잘 호출되는지 확인해보자.

  • push 이외에는 어떠한 작업도 수행하지 않았음에도 새로운 API가 잘 호출된다.

이로써 깃허브에 코드를 push하면, 서버에 자동으로 배포하는 프로세스가 완성되었다.

profile
Java Spring, Android Kotlin, Node.js 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글