[AWS] CodeDeploy를 이용한 CD 구현

박상혁·2023년 4월 1일
0

Cloud Service

목록 보기
2/4

개요

무중단 배포 기술을 경험하기 위해 CodeDeploy를 사용하여 배포를 진행해보려 합니다.

서론

CodeDeploy를 사용하여 어떻게 배포해야 하는지, 배포하는 과정에서 어떤 오류를 만났고 어떻게 해결해야 하는지를 소개합니다..

목차

사전 지식
구현 과정
로그
오류

본문

📁 사전 지식

⚡ Code Deploy
EC2, On-premise, Lambda, ECS로 Application 배포를 자동화하는 배포 서비스

해당 포스팅은 무중단 배포는 구현하지 않고, CD만을 다룹니다.

⚡ workflow

⚡ lifecycle

⚡ Appspec.yml
OS, Files, permission, hooks를 포함하고 있는 yaml파일입니다.
codedeploy-agent에게 배포, 실행파일들의 순서, 결로를 알려주는 역할을 담당합니다.

⚡ codedeploy-agent
EC2에 설치하는 프로그램입니다.
계정, 배포기록, 배포스크립트 등을 EC2 루트 디렉토리에 저장합니다.

/apt/codedeploy-agent/deployment-root

📁 구현 과정

👉🏼 1. IAM role 생성
Code Deploy를 이용하기 위해서 IAM이 2가지가 필요합니다.
각 역할을 만들어줍니다.

  • EC2에서 Code Deploy Agent가 빌드된 아티팩트들을 S3로부터 가져오는 작업을 위한 권한
    AmazonS3FullAccess

S3 List를 읽고 가져오는 권한만 있으면 됩니다!

  • Code Deploy에서 EC2 인스턴스 식별 및 CloudWatch, S3 통합권한
    AWSCodeDeployRole

👉🏼 2. EC2 IAM 수정
기존에 만들어놓은 EC2 인스턴스의 IAM을 위에서 만들어놓은 EC2 Role로 교체합니다.
인스턴스 작업 -> 보안 -> IAM 역할 수정

👉🏼 3. code deploy agent 설치
이제, EC2 SSH에 접속하여 code deploy agent를 설치합니다.
해당 프로그램은 CodeDeploy로부터 명령을 받아 직접 빌드 파일 다운로드 및 배포 스크립트 실행 과정을 수행합니다.

우선 SpringBoot로 API 테스트를 위해
테스트 환경 세팅을 진행합니다.

# JDK 설치
yum list | grep java-11
sudo yum install -y java-11-amazon-corretto.x86_64

이후 yum을 이용하여 codedeploy-agent를 설치합니다.
codedeploy-agentruby로 동작하기 때문에 ruby의 설치가 선행되어야 합니다.

  • yum? Yellow dog Updater, Modified의 약자로 RPM 기반의 시스템을 위한 자동 업데이터 겸 패키지 설치/제거 도구
  • wget? 웹 서버로부터 콘텐츠를 가져오는 컴퓨터 프로그램, HTTP, HTTPS, FTP 프로토콜을 통해 내려받기를 지원

앞으로 build 디렉토리 안에 빌드 파일을 다운로드 할 예정이니 build 디렉토리도 만들어줍니다.

sudo yum -y update
sudo yum -y install ruby
sudo yum -y install wget

mkdir build

cd /home/ec2-user
# wget을 통해 codedeploy-agent 파일 내려받기
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
sudo service codedeploy-agent start

👉🏼 4. AWS CodeDeploy Application 생성
애플리케이션 생성 및 컴퓨팅 플랫폼 설정. EC2로 설정하면 됩니다.

👉🏼 5. 배포 그룹 생성
배포 그룹은 환경에 따라 나눌 수 있습니다. 보통 live, qa, dev.. 이렇게 나눕니다.
IAM은 저희가 만들어놓은 code-deploy용 IAM을 선택합니다.

배포 유형은 현재 위치로 하며
EC2 인스턴스를 선택하여 키, 값을 지정합니다.

👉🏼 6. appspec.yml 파일 생성
이후, codeBuild처럼
codedeploy에게 어떤 일을 수행하라는 명령들이 담긴 파일을 생성해야 합니다.

보통 lifecycle의 hook마다 쉘 스크립트를 따로 배치하여 수행합니다.
이를 위해 저희는 일단

  • 빌드 파일을 어디에 저장할것인지
  • 각 hook마다 어떤 쉘 스크립트를 실행할 것인지

를 설정하는 yaml파일을 생성합니다.

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ec2-user/build # 인스턴스에서 파일이 저장될 위치
hooks:
  AfterInstall:
    - location: deploy.sh
      timeout: 60
      runas: root

👉🏼 7. 배포 스크립트 생성
S3로부터 빌드 파일을 다운로드 받은 이후
배포 스크립트가 없다면 아무일도 일어나지 않습니다.

저희는 shell에게 배포하라고 명령해야 합니다.

java -jar app.jar

이러한 명령들을 수행할 수 있는 쉘 스크립트를 제작합니다.

# deploy.sh

#!/bin/bash
BUILD_PATH=$(ls /home/ec2-user/build/*.jar)
JAR_NAME=$(basename $BUILD_PATH)
echo "> build 파일명: $JAR_NAME"

echo "> build 파일 복사"
DEPLOY_PATH=/home/ec2-user/
cp $BUILD_PATH $DEPLOY_PATH

echo "> springboot-deploy.jar 교체"
CP_JAR_PATH=$DEPLOY_PATH$JAR_NAME
APPLICATION_JAR_NAME=springboot-deploy.jar
APPLICATION_JAR=$DEPLOY_PATH$APPLICATION_JAR_NAME

ln -Tfs $CP_JAR_PATH $APPLICATION_JAR

echo "> 현재 실행중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f $APPLICATION_JAR_NAME)

if [ -z $CURRENT_PID ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

echo "> $APPLICATION_JAR 배포"
nohup java -jar $APPLICATION_JAR > /dev/null 2> /dev/null < /dev/null &
# healthCheck.sh

#!/bin/bash
echo "> Health check 시작"
echo "> curl -s http://localhost:8080/health "

for RETRY_COUNT in {1..15}
do
  # RESPONSE body 데이터에 'UP'이라는 글자가 있다면 성공, 없다면 다시 체크
  RESPONSE=$(curl -s http://localhost:8080/actuator/health)
  UP_COUNT=$(echo $RESPONSE | grep 'UP' | wc -l)

  if [ $UP_COUNT -ge 1 ]
  then # $up_count >= 1 ("UP" 문자열이 있는지 검증)
      echo "> Health check 성공"
      break
  else
      echo "> Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다."
      echo "> Health check: ${RESPONSE}"
  fi

  if [ $RETRY_COUNT -eq 10 ]
  then
    echo "> Health check 실패. "
    exit 1
  fi

  echo "> Health check 연결 실패. 재시도..."
  sleep 10
done
exit 0

이후 작성된 2개의 쉘 스크립트를 각 hook 역할에 맞춰서 실행할 수 있도록 appspec.yml을 수정합니다.

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ec2-user/build # 인스턴스에서 파일이 저장될 위치

# ec2-user로 설정해야함
permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

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

scripts 디렉토리도 code-build의 대상이 되어 아티팩트에 같이 S3에 넣어질 수 있도록 buildspec.yml수정합니다.

artifacts:
  files:
    - appspec.yml # code-deploy를 위해
    - build/libs/*.jar
    - scripts/** # 추가!

이렇게 생성한 총 4개의 파일을 프로젝트 최상단 경로에 배치하면 됩니다.

👉🏼 8. 배포 시도
성공적으로 배포가 완료되었습니다!

EC2에 접속하면 build 디렉토리 안에 성공적으로 buildspec.yml에 적힌 아티팩트들이 다운로드 된 것을 알 수 있습니다.

📁 로그

성공적으로 배포되었는지 확인하기 위해서는 log를 보는것이 필수입니다.

AWS CodeDeploy 가이드에서는 다음과 같이 log데이터를 제공한다고 합니다.

# 배포 로그 확인
less /var/log/aws/codedeploy-agent/codedeploy-agent.log
# 스크립트 로그 확인
less /opt/codedeploy-agent/deployment-root/deployment-group-ID/deployment-ID/logs/scripts.log

자세한 확인 방법은 해당 링크를 참고해주세요.
CodeDeploy EC2/온프레미스 배포에 대한 로그 데이터 보기

❗ 오류

deploy.sh 빌드 스크립트가 실행되지 않는 오류

2023-04-26 12:16:26 WARN  [codedeploy-agent(2693)]: InstanceAgent::Plugins::CodeDeployPlugin::HookExecutor: Script at specified location: deploy.sh is not executable.  Trying to make it executable.
2023-04-26 13:02:27 WARN  [codedeploy-agent(2693)]: InstanceAgent::Plugins::CodeDeployPlugin::HookExecutor: Script at specified location: healthCheck.sh is not executable.  Trying to make it executable.

👉 해결방안1 : 권한 설정 오류

저와 동일한 오류가 발생하여 해결한 포스팅을 참조했습니다.

CodeDeployフックのベストプラクティス

해당 오류는 deploy.sh를 실행할 수 없어서 생긴 오류라고 진단합니다.
파일에 대한 권한을 755로 새로 지정해주어야 합니다.

  • 권한 부여는 8진수로 나타내는데, 7이면 111이므로 rwx가 다 가능하다는 의미
sudo chmod +x deploy.sh

해당 작업을 배포마다 수작업으로 추가하기엔 너무 번거로우니
codedeploy의 appspec.yml에 권한설정을 추가해줍니다.

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

하지만 위 포스팅에서는 permissions을 통해서 권한을 지정해도 상관없다고 합니다.

files.destination에 명시한 /home/ec2-user/build 경로에 복사합니다.
하지만, 실제로는 /deployment-archive/에 배포되며 (Amazon Linux의 deployment-root는 /opt/codedeploy-agent/deployment-root/) 배포후 실행되는 스크립트는 /home/ec2-user/build가 아닌 deployment-archive/hooks/deploy.sh 입니다.

하지만 이 이상으로 권한을 변경하는 방법을 찾지 못했고,
이전까지 잘 배포된 어플리케이션이고, 스크립트나 appspec 설정파일도 건들지 않았기 때문에 권한 문제가 원인이 아니라고 생각하게 되었습니다.

👉 해결방안2 : 파일 경로 혹은 파일 이름의 오타

파일 경로나 파일 이름이 오타가 생겼을 시 해당 hook에서 스크립트를 찾지 못해 오류가 발생할 수 있습니다.

하지만 이 역시 올바르게 되어있었습니다.

👉 해결방안3 : 어플리케이션 로그 확인

어플리케이션을 배포하는 도중에 springboot 자체를 실행하지 못할 경우 해당 오류가 발생할 수 있습니다.

저도 spring 배포 로그 파일을 확인한 결과 다음과 같이 오류가 발생한 것을 확인할 수 있었습니다.

13:48:39.134 [main] ERROR org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter - 

***************************
APPLICATION FAILED TO START
***************************

Description:

Config data resource 'class path resource [aws.yml]' via location 'classpath:aws.yml' does not exist

Action:

Check that the value 'classpath:aws.yml' is correct, or prefix it with 'optional:'

이는, 아까 위에서 추가했었던 aws.yml 파일을 인식하기 위해 classpath를 추가 설정하였는데
gitignore에 포함되어 있어서 EC2 인스턴스 내에서 인식하지 못해 발생한 오류입니다.

SpringBootApplication 클래스에서 classpath를 수정한 이후
정상적으로 배포된 것을 확인할 수 있었습니다.

🗣️ codedeploy에서 발생한 오류면 deploy-log와 spring log 둘 다 분석해보아야 합니다.

결론

이렇게 지속적인 배포 환경을 만들어보았는데
과정은 많이 어려웠지만 한번 만들고 나니 개발할 때 편리한 점을 많이 찾을 수 있었습니다.

스크립트 언어에 아직 익숙하지 않아서 구글링하며 많은 블로그들의 소스를 참고했습니다.

Pipeline을 통해서 배포를 진행하였기 때문에
기회가 된다면 Jenkins를 이용하여 CI/CD를 구축해볼 예정입니다.

참조

AWS CodeDeploy - aws-documentation
Github Action과 AWS CodeDeploy를 사용하여 Spring Boot 프로젝트 CI/CD 파이프라인 구축하기 - 해어린
/usr/share/ruby3.2/logger/log_device.rb:83:in `exist?': no implicit conversion of Array into String (TypeError) if File.exist?(path) - stackoverflow
3) AWS로 배포하기 시리즈 - 3. AWS Code Pipeline으로 배포하기 - 향로

profile
개발 노트

0개의 댓글