[Pet-Hub] Blue/Green 배포시 런타임 에러로 인한 서버다운 문제해결

DevSeoRex·2023년 5월 19일
8
post-thumbnail

🤫 과연 무중단 배포가 맞는가?

3개의 포스팅을 걸쳐 무중단 배포를 위한 인프라를 구축 하였습니다.
지금 구축한 배포 시스템에는 무중단 배포를 위한 가장 중요한 한가지 조건이 있습니다.

배포 스크립트를 보면 blue/green중에 한개를 실행하게 되면, 실행중인 애플리케이션은 반드시 down 되도록 스크립트가 작성되어 있습니다.

이것이 왜 문제가 될까 생각하실 수 있습니다.
왜냐면.. 저도 별로 알고 싶지 않았습니다.

왜 문제가 되는지 지금부터 살펴보겠습니다.

Github Actions를 이용한 배포 자동화 시스템은 컴파일타임, 즉 빌드를 진행할때 문제가 생기면 배포가 되지 않고 job이 실패합니다.

한번 확인해보겠습니다.

컴파일 타임에 오류가 날 수 있도록 코드에 문법 오류를 내고 job을 수행하도록 해보겠습니다.


빌드가 실패하게 되면서 배포와 관련된 프로세스들이 모두 실행되지 않고 배포를 진행하지 않게됩니다.
컴파일 타임에 발생하는 문제는 Github Actions에서 충분히 잡아내서 배포를 막을 수 있습니다.

그렇다면 서버가 실행될때 발생하는 런타임에러를 낸다면 어떻게 될까요?
현재 EC2 서버에는 spring-blue가 실행중입니다. 이 상황에서 green 버전에 런타임 에러를 낼 수 있는 코드를 넣어두고 배포를 시도해보겠습니다.


런타임 에러가 있는 코드를 넣어서 push 했는데도 불구하고 배포는 성공적으로 진행 되었습니다.


green이 성공적으로 컨테이너에서 실행되고 있지만, 실행과 동시에 런타임 에러를 만나게 되고 blue 컨테이너까지 종료시키면서 모든 spring 서버가 종료되는 문제를 겪게 되었습니다.

🥺 어떻게 개선해야 할까?

가장 먼저 기존 배포 스크립트부터 수정이 필요했습니다.

# green이 실행중이면 blue up
# EXIST_BLUE 변수가 비어있는지 확인
if [ -z "$EXIST_BLUE" ]; then

  # 로그 파일(/home/ec2-user/deploy.log)에 "blue up - blue 배포 : port:8081"이라는 내용을 추가
  echo "blue 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

	# docker-compose.blue.yml 파일을 사용하여 spring-blue 프로젝트의 컨테이너를 빌드하고 실행
	sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build

  # 30초 동안 대기
  sleep 30

  # /home/ec2-user/deploy.log: 로그 파일에 "green 중단 시작"이라는 내용을 추가
  echo "green 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

기존 배포 스크립트의 일부 내용입니다.
spring-blue가 현재 실행중이지 않다면, spring-blue를 실행하고 30초 뒤에 spring-green을 종료하는 방식입니다. spring-green이 실행 중에 런타임 에러가 발생할경우 두개 다 종료되는 문제가 발생하여 스크립트를 보완하였습니다.

#!/bin/bash

# 작업 디렉토리를 /home/ec2-user/app으로 변경
cd /home/ec2-user/app

# 환경변수 DOCKER_APP_NAME을 spring으로 설정
DOCKER_APP_NAME=spring

# 실행중인 blue가 있는지 확인
# 프로젝트의 실행 중인 컨테이너를 확인하고, 해당 컨테이너가 실행 중인지 여부를 EXIST_BLUE 변수에 저장
EXIST_BLUE=$(sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)

# 배포 시작한 날짜와 시간을 기록
echo "배포 시작일자 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

# green이 실행중이면 blue up
# EXIST_BLUE 변수가 비어있는지 확인
if [ -z "$EXIST_BLUE" ]; then

  # 로그 파일(/home/ec2-user/deploy.log)에 "blue up - blue 배포 : port:8081"이라는 내용을 추가
  echo "blue 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

	# docker-compose.blue.yml 파일을 사용하여 spring-blue 프로젝트의 컨테이너를 빌드하고 실행
	sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build

  # 30초 동안 대기
  sleep 30
  
  # blue가 문제 없이 배포 되었는지 현재 실행여부를 확인한다
  BLUE_HEALTH=$(sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)

  # blue가 현재 실행중이지 않다면 -> 런타임 에러 또는 다른 이유로 배포가 되지 못한 상태
  if [ -z "$BLUE_HEALTH" ]; then
    # slack으로 알람을 보낼 수 있는 스크립트를 실행한다.
    sudo ./slack_blue.sh
  # blue가 현재 실행되고 있는 경우에만 green을 종료
  else

    # /home/ec2-user/deploy.log: 로그 파일에 "green 중단 시작"이라는 내용을 추가
    echo "green 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

    # docker-compose.green.yml 파일을 사용하여 spring-green 프로젝트의 컨테이너를 중지
    sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down

    # 사용하지 않는 이미지 삭제
    sudo docker image prune -af

    echo "green 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
  fi

# blue가 실행중이면 green up
else
	echo "green 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
	sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d --build

  sleep 30

  GREEN_HEALTH=$(sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml ps | grep Up)

  if [ -z "$GREEN_HEALTH" ]; then

      sudo ./slack_green.sh
  else

      # /home/ec2-user/deploy.log: 로그 파일에 "blue 중단 시작"이라는 내용을 추가
      echo "blue 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

      # docker-compose.blue.yml 파일을 사용하여 spring-green 프로젝트의 컨테이너를 중지
      sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down

      # 사용하지 않는 이미지 삭제
      sudo docker image prune -af

      echo "blue 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
  fi
fi

blue 또는 green을 배포할때 배포 후 스레드를 30초 멈추고 현재 배포한 애플리케이션이 실행중인지 확인 한 후에 실행중일때만 이전 애플리케이션을 down 시키도록 조건을 추가 하였습니다.

🤨 어떻게 관리자가 에러를 알 수 있을까?

이렇게 배포가 비정상적으로 될 경우 애플리케이션이 다운 되는 것 까지는 막아냈습니다.
그렇다면 관리자가 이 에러를 보고 애플리케이션을 수정해서 다시 배포하는 과정을 진행해야 하는데 관리자에게 어떻게 알림을 보낼 것인지 고민해보았습니다.

리눅스 시스템에서 메일을 발송하는 방법이 있지만 이 방법은 이메일을 계속 보고 있지 않고, 또 슬랙이나 다른 업무 툴과 연관성이 떨어져 주시하지 않고 있다는 단점이 있어서 슬랙으로 알람을 받기로 결정하였습니다.

shell script에서 slack-webhook을 이용해 알람을 보내려면 설정이 필요합니다.

  • 알람을 받을 채널에서 앱추가로 메뉴로 진입합니다.

  • 수신 웹 후크를 설치해줍니다.

  • 이용할 채널을 지정하고 앱을 생성하고, 웹후크 URL을 복사해둡니다.

    웹후크 URL을 이용해 메시지를 보내기때문에 잘 저장해두어야 합니다.

    🦄 web-hook-url은 github나 온라인 공간에 유출되어서는 안됩니다. 유출될 경우 slack에서 감지하여 기존 URL을 무효화시키고 새로운 URL을 메일로 보내줍니다.

  • 앱의 이름과 사진을 지정할 수 있습니다.

배포 스크립트와 슬랙 설정을 마쳤으니 각 오류 케이스마다 사용할 슬랙 스크립트를 작성하겠습니다.

🐯 Slack 스크립트 작성과 배포 테스트

배포 스크립트 안에 슬랙 메시지를 보내는 것을 포함시켰더니, github에 webhook-url을 노출하게 되어 토큰이 무효화 되는 문제가 발생하게 되었습니다.

따라서 저는 EC2 서버에 접속해서 app 경로에 slack_green.sh와 slack_blue.sh를 만들어주겠습니다.

  • EC2 서버에 접속해서 app 폴더로 이동 후 아래의 명령어를 입력합니다.
// slack_green.sh & slack_blue.sh 생성
$ sudo touch slack_green.sh
$ sudo touch slack_blue.sh

// slack_green.sh & slack_blue.sh 권한 부여
$ sudo chmod 735 slack_green.sh
$ sudo chmod 735 slack_blue.sh
  • slack_green.sh와 slack_blue.sh를 문서 편집기로 열어서 내용을 채워줍니다.
// 문서 편집기로 파일 열기
$ sudo vi slack_green.sh

...(수정)

리눅스에서 문서 편집기로 파일을 수정하는 것은 따로 설명하지 않겠습니다.

  • slack_green.sh
#!/bin/bash

# slack-web-hook URL 셋팅
slack_web_hook="설정시 저장해둔 slack-webHook-url"

# 배포 중 문제가 발생했다는 내용의 로그를 남겨준다.
echo "green 배포 중 문제 발생 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
echo "관리자 알람 발송 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

# 슬랙으로 보낼 메시지를 변수에 저장해준다.
json="{ \"text\": \"green  배포 중 문제가 발생하여 배포가 비정상 중단되었으니 확인 부탁드립니다 -> 문제 발생 시각: $(date '+%Y-%m-%d %H:%M:%S')\" }"

# 변수에 메시지가 잘 입력 되었는지 콘솔창에 출력해본다.
echo "json: $json"


# 슬랙으로 메시지를 발송한다.
curl -X POST -H 'Content-type: application/json' --data "$json" "$slack_web_hook"

# 슬랙 알람 발송이후 배포 비정상종료 로그를 남겨준다.
echo "관리자 알람 발송완료, 배포 비정상종료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
  • slack_blue.sh
#!/bin/bash

# slack-web-hook URL 셋팅
slack_web_hook="설정시 저장해둔 slack-webHook-url"

# 배포 중 문제가 발생했다는 내용의 로그를 남겨준다.
echo "blue 배포 중 문제 발생 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
echo "관리자 알람 발송 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

# 슬랙으로 보낼 메시지를 변수에 저장해준다.
json="{ \"text\": \"blue  배포 중 문제가 발생하여 배포가 비정상 중단되었으니 확인 부탁드립니다 -> 문제 발생 시각: $(date '+%Y-%m-%d %H:%M:%S')\" }"

# 변수에 메시지가 잘 입력 되었는지 콘솔창에 출력해본다.
echo "json: $json"


# 슬랙으로 메시지를 발송한다.
curl -X POST -H 'Content-type: application/json' --data "$json" "$slack_web_hook"

# 슬랙 알람 발송이후 배포 비정상종료 로그를 남겨준다.
echo "관리자 알람 발송완료, 배포 비정상종료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log

slack_blue.sh와 slack_green.sh는 알림 메시지의 blue와 green이라는 키워드만 다를 뿐, 나머지 내용은 전부 동일합니다.

이제 정말 런타임 에러가 발생하는 코드를 배포했을때 잘 방어하는지 확인해보겠습니다.

현재 blue 버전이 실행중인 상태입니다. 새 버전 배포를 진행하면 green 버전이 같이 실행되고, green 버전에 문제가 없는 것이 확인 되어야 blue 버전이 down 됩니다.

이전과 같이 런타임 에러가 날 수 있는 코드를 깃허브에서 push 해보겠습니다.

🦊 @Query 애너테이션으로 정의한 메서드는 서버가 시작될때 전부 JPQL를 파싱해 두기 때문에 런타임에 문법 오류가 있을경우 서버가 다운되게 됩니다.


런타임 에러가 나겠지만 배포는 정상적으로 된 것을 볼 수 있습니다.

green 버전을 배포 시도하였으나 런타임 에러로 인해 서버가 종료되었고 blue는 그대로 잘 동작하고 있는 것을 확인할 수 있습니다.

배포 로그를 확인해보겠습니다.

예상대로 green을 배포하고 30초 뒤에 상태를 확인해서 문제가 있다면 blue를 down 시키지 않는 것을 확인했습니다. 그렇다면 슬랙 알람은 잘 왔는지 확인해보겠습니다.


슬랙 알람도 잘 도착해 있는 것을 확인할 수 있습니다.

🚀 배포는 긴장의 연속..!!

배포를 진행하면서 런타임 에러가 나지 않는 경우에는 무중단 배포가 잘 되고 있었는데 어느날 런타임 문제가 생겨서 서버에서 작동중인 애플리케이션이 없으니 문제가 생기고 있었습니다.

이 부분을 어떻게 해결할까 고민하고 있었고, 슬랙 알람을 이용한 방법으로 문제를 해결했습니다.
이번 배포 자동화 경험을 통해 안전하고 변화에 빠르게 대응할 수 있는 인프라를 만들어가고 싶다는 또다른 욕심이 생겨버렸습니다.

긴 게시글 함께해주셔서 감사합니다.

🙇

2개의 댓글

comment-user-thumbnail
2023년 5월 31일

사랑해요 형

1개의 답글