Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

무중단배포 중단 문제 해결 #794

Open
wants to merge 10 commits into
base: develop-backend
Choose a base branch
from
Open

Conversation

ay-eonii
Copy link
Contributor

PR의 목적이 무엇인가요?

무중단배포인줄 알았으나 한 서버에 배포를 진행하는 30초 동안 (1/2확률로..) 다운타임이 발생합니다.
트래픽이 다른 서버로 보내지면 정상 작동하지만 배포 중 서버로 보내지면 응답을 받을 수 없습니다.
자세한 사항은 모우다 팀 블로그: 랜덤중단배포를 무중단배포로 개선하기에 작성했습니다.

이슈 ID는 무엇인가요?

설명

질문 혹은 공유 사항 (Optional)

@ay-eonii ay-eonii added BE 백엔드 관련 이슈입니다. ⚙ 환경설정 chore (빌드 스크립트 등 환경 설정) labels Oct 30, 2024
Copy link
Contributor

@pricelees pricelees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼬르륵

Copy link
Contributor

@Mingyum-Kim Mingyum-Kim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테니 고민 많이 한 게 느껴지네요 👏👏 정리 잘해주셔서 이해가 쉬웠어요.

Comment on lines +36 to +49
ATTEMPTS=0
MAX_ATTEMPTS=3
until [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; do
RESPONSE=$(curl --write-out '%{http_code}' --silent --output /dev/null http://localhost:8080/health)
if [ $RESPONSE -eq 200 ]; then
echo "Prod1 instance is healthy."
exit 0
fi
echo "Health check failed, attempt $((ATTEMPTS+1))/$MAX_ATTEMPTS. Retrying in 5 seconds..."
ATTEMPTS=$((ATTEMPTS+1))
sleep 5
done
echo "Prod1 instance deployment failed after $MAX_ATTEMPTS attempts."
exit 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로컬에서 헬스체크를 하고 있네여
여러 번 헬스체크를 하는 이유가 궁금합니당

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트 규모가 좀 더 커지면 25초 보다 오래 걸릴 가능성이 있다고 판단했습니다. 규모에 따라 대기 시간을 계속 조절하는 것이 불편할 . 것같아 25초 이후로 5초마다 헬스체크를 합니다. 한번이라도 성공하면 그 이후로 진행하지는 않아용

@@ -64,3 +64,4 @@ server:
tomcat:
mbeanregistry:
enabled: true
shutdown: graceful
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 무슨 설정인가요? 우아하게 서버를 내리는건가요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@테니의 테코톡

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제 테코톡을 다 잊으셨군요

요청 처리중에 서버를 내리려고 하면 요청을 마저 처리하고 다운시키는 설정입니닷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 POST /termination을 보낸 뒤 20초를 대기하는 것 같은데요, 20초라는 특정 시간을 대기할 게 아니라 등록 취소 지연(deregistration delay) 시간값과 graceful shutdown의 timeout값을 동일하게 설정해도 될 것 같아요 ~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graceful shutdown timeout은 deploy.sh 를 실행하여 프로세스를 종료했을 때 대기하는 시간이고, 20초는 /termination 요청(prepare-deploy.sh 실행) 후 대기하는 시간인데요! 이 두 가지가 어떤 관계를 가져서 동일하게 설정하도록 의견을 주신 걸까요 ??

graceful shutdown downtime은 외부 API를 사용하는 등 응답까지 시간이 오래걸리는 API를 기준으로 설정하는 게 좋을 것 같은데, 어떻게 생각하시나요 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graceful shutdown timeout은 deploy.sh 를 실행하여 프로세스를 종료했을 때 대기하는 시간이고, 20초는 /termination 요청(prepare-deploy.sh 실행) 후 대기하는 시간인데요! 이 두 가지가 어떤 관계를 가져서 동일하게 설정하도록 의견을 주신 걸까요 ??

graceful shutdown downtime은 외부 API를 사용하는 등 응답까지 시간이 오래걸리는 API를 기준으로 설정하는 게 좋을 것 같은데, 어떻게 생각하시나요 ?

현재 배포 순서를 보면,

  1. prepare-deploy에서 /termination api 호출 후 헬스 체크에 실패하도록 지정
  2. 20초 대기(sleep 20) -> ELB가 트래픽을 보내지 않음.
  3. deploy.sh 실행
  4. deploy에서는 clone, pull, build 등의 작업 뒤 기존 springboot 서버를 종료한 뒤 새로운 springboot 애플리케이션을 실행
    이 순서로 이어지네요.

지금 구조대로라면, prepare-deploy가 끝나고 deploy.sh 가 실행되는 시점에서는 ELB가 해당 인스턴스로 트래픽을 보내지 않을텐데 graceful shutdown을 지정하는 것이 크게 의미가 없는 것 같아요 ~

그래서,, 제가 생각하는 graceful shutdown을 제대로 확인하는 방법은

  1. 배포를 시작하기 전에 /termination을 호출하지 않고 해당 인스턴스를 elb에서 deregister한 뒤 springboot 종료
  2. deregistration delay를 graceful shutdown timeout과 동일하게 설정하면, springboot가 종료될 때 까지는 요청을 처리
  3. springboot가 종료되면(= 8080포트를 사용하는 프로세스가 없으면) 그때 springboot 배포 시작

이 방법으로 하면 prepare-deploy에서 sleep 20 을 호출할 필요가 없이 springboot를 종료하고 새로운 버전의 애플리케이션을 빌드하는 사이에 springboot가 정상 종료될 것 같아요~(경험상 우리 프로젝트에서의 gradle 빌드가 20~30 초 정도 걸림..)

일단 이 부분은 개인적인 추측이고, 제 개인 서버에서 돌려보고 있으니 완료되면 같이 확인해보시죠!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 구조대로라면, prepare-deploy가 끝나고 deploy.sh 가 실행되는 시점에서는 ELB가 해당 인스턴스로 트래픽을 보내지 않을텐데 graceful shutdown을 지정하는 것이 크게 의미가 없는 것 같아요 ~

요건 맞아요. 사실 graceful shutdown 학습한김에 써봤습니다 !! 하핫

배포를 시작하기 전에 /termination을 호출하지 않고 해당 인스턴스를 elb에서 deregister한 뒤 springboot 종료

이건 수동으로(aws에 들어가서 직접) 한다는 의미인가용?

deregistration delay를 graceful shutdown timeout과 동일하게 설정하면, springboot가 종료될 때 까지는 요청을 처리

  • 위에서 말씀하신 elb에서 deregister가 수동이라면 deregistration delay가 발생하나요?
  • 수동이든 자동이든 deregistration delay이 발생하는 경우, graceful shutdown을 설정하더라도 대기하는 동안 springboot를 종료하면 처리하지 못하는 트래픽이 생길것 같아요
    • graceful shutdown이 20초를 무조건 대기하는 것이 아니라, 처리중인 요청이 있다면 20초까지는 처리를 위해 종료를 지연하는 것으로 알고있어요. (종료 명령 후 요청이 들어오는 것은 처리하지 무시하구요) 그래서 처리중인 요청이 없다면 바로 종료하여 deregistration delay동안 유입되는 트래픽을 처리하지 못할 것 같아요.

혹시 제가 잘못 이해한 부분이 있다면 말씀해주세옹 ..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 구조대로라면, prepare-deploy가 끝나고 deploy.sh 가 실행되는 시점에서는 ELB가 해당 인스턴스로 트래픽을 보내지 않을텐데 graceful shutdown을 지정하는 것이 크게 의미가 없는 것 같아요 ~

요건 맞아요. 사실 graceful shutdown 학습한김에 써봤습니다 !! 하핫

배포를 시작하기 전에 /termination을 호출하지 않고 해당 인스턴스를 elb에서 deregister한 뒤 springboot 종료

이건 수동으로(aws에 들어가서 직접) 한다는 의미인가용?

deregistration delay를 graceful shutdown timeout과 동일하게 설정하면, springboot가 종료될 때 까지는 요청을 처리

  • 위에서 말씀하신 elb에서 deregister가 수동이라면 deregistration delay가 발생하나요?

  • 수동이든 자동이든 deregistration delay이 발생하는 경우, graceful shutdown을 설정하더라도 대기하는 동안 springboot를 종료하면 처리하지 못하는 트래픽이 생길것 같아요

    • graceful shutdown이 20초를 무조건 대기하는 것이 아니라, 처리중인 요청이 있다면 20초까지는 처리를 위해 종료를 지연하는 것으로 알고있어요. (종료 명령 후 요청이 들어오는 것은 처리하지 무시하구요) 그래서 처리중인 요청이 없다면 바로 종료하여 deregistration delay동안 유입되는 트래픽을 처리하지 못할 것 같아요.

혹시 제가 잘못 이해한 부분이 있다면 말씀해주세옹 ..

거창한게 아니라 deregister는 cli를 인스턴스에 설치해서 명령어로 가능해요~ 해당 부분을 스크립트에 넣으면 될 것 같구요

자세한건 다음에 얘기해보시죠~~

Copy link
Contributor

@pricelees pricelees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

찐 리뷰 드려요~ 반영할건 그렇게 많지 않아요!
답글 남겨주시면 바로 Approve 하겠습니다 !

Comment on lines +79 to +92
ATTEMPTS=0
MAX_ATTEMPTS=3
until [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; do
RESPONSE=$(curl --write-out '%{http_code}' --silent --output /dev/null http://localhost:8080/health)
if [ $RESPONSE -eq 200 ]; then
echo "Prod2 instance is healthy."
exit 0
fi
echo "Health check failed, attempt $((ATTEMPTS+1))/$MAX_ATTEMPTS. Retrying in 5 seconds..."
ATTEMPTS=$((ATTEMPTS+1))
sleep 5
done
echo "Prod2 instance deployment failed after $MAX_ATTEMPTS attempts."
exit 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prod1과 prod2가 인스턴스가 다르긴 하지만 동일한 코드로 배포하는 만큼 prod1에서 헬스체크가 완료되었으면 prod2에서는 별도로 진행하지 않아도 될 것 같아요 ~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인스턴스가 다르기 때문에 헬스체크가 있어도 좋다고 생각해요 (필수는 아니어도 되지만!) 배포 후 LB나 로그를 확인하는 것보다 actions에서 한번에 확인할 수 있다는 장점이 있을 것 같아요

ATTEMPTS=0
MAX_ATTEMPTS=3
until [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; do
RESPONSE=$(curl --write-out '%{http_code}' --silent --output /dev/null http://localhost:8080/health)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 별도로 만든 헬스체크용 api를 만들어서 사용하고 있는데요, 최근에 추가된 spring actuator를 활용하도록 할 수도 있을 것 같아요 ~

@@ -64,3 +64,4 @@ server:
tomcat:
mbeanregistry:
enabled: true
shutdown: graceful
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 POST /termination을 보낸 뒤 20초를 대기하는 것 같은데요, 20초라는 특정 시간을 대기할 게 아니라 등록 취소 지연(deregistration delay) 시간값과 graceful shutdown의 timeout값을 동일하게 설정해도 될 것 같아요 ~


@PostMapping("/termination")
public ResponseEntity<Void> terminate(HttpServletRequest request) {
String remoteHost = request.getRemoteHost();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 값이 인스턴스 내부에서의 요청인 경우에만 Localhost로 지정됨을 보장하나요?
nginx 설정에 따라 외부 요청도 localhost로 들어올 수 있는 것 같은데요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 고려하지 못한 상황이네요! 한 번 알아보도록 하겠슴다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 고려하지 못한 상황이네요! 한 번 알아보도록 하겠슴다

알아보셨나요? ㅋㅋ

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알아봤는데 nginx 설정을 저희가 바꾸지 않는 이상 localhost가 들어올 수 있나용 ? 잘 모르겠습니다 ㅎ .. ㅎ

Comment on lines +33 to +36
if (HOST_IPV6.equals(remoteHost) || HOST_IPV4.equals(remoteHost) || HOST_NAME.equals(remoteHost)) {
isTerminating.set(true);
return ResponseEntity.ok().build();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

애플리케이션에서 막는 것도 좋지만, 외부 요청을 확실하게 막으려면 nginx 설정에 해당 api에 대한 요청을 처리하는 코드를 추가하는게 좋을 것 같아요~

location /termination {
              return 403(또는 404);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 의견이네요!! 반영하겠슴다1

Copy link
Contributor

@pricelees pricelees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테니 ~
커멘트에 남긴 deregister 관련해서 조금 더 확인해보고 싶긴 한데, 일단 해당 워크플로 자체가 문제가 있진 않아보여 Approve 할게요 !

@@ -64,3 +64,4 @@ server:
tomcat:
mbeanregistry:
enabled: true
shutdown: graceful
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graceful shutdown timeout은 deploy.sh 를 실행하여 프로세스를 종료했을 때 대기하는 시간이고, 20초는 /termination 요청(prepare-deploy.sh 실행) 후 대기하는 시간인데요! 이 두 가지가 어떤 관계를 가져서 동일하게 설정하도록 의견을 주신 걸까요 ??

graceful shutdown downtime은 외부 API를 사용하는 등 응답까지 시간이 오래걸리는 API를 기준으로 설정하는 게 좋을 것 같은데, 어떻게 생각하시나요 ?

현재 배포 순서를 보면,

  1. prepare-deploy에서 /termination api 호출 후 헬스 체크에 실패하도록 지정
  2. 20초 대기(sleep 20) -> ELB가 트래픽을 보내지 않음.
  3. deploy.sh 실행
  4. deploy에서는 clone, pull, build 등의 작업 뒤 기존 springboot 서버를 종료한 뒤 새로운 springboot 애플리케이션을 실행
    이 순서로 이어지네요.

지금 구조대로라면, prepare-deploy가 끝나고 deploy.sh 가 실행되는 시점에서는 ELB가 해당 인스턴스로 트래픽을 보내지 않을텐데 graceful shutdown을 지정하는 것이 크게 의미가 없는 것 같아요 ~

그래서,, 제가 생각하는 graceful shutdown을 제대로 확인하는 방법은

  1. 배포를 시작하기 전에 /termination을 호출하지 않고 해당 인스턴스를 elb에서 deregister한 뒤 springboot 종료
  2. deregistration delay를 graceful shutdown timeout과 동일하게 설정하면, springboot가 종료될 때 까지는 요청을 처리
  3. springboot가 종료되면(= 8080포트를 사용하는 프로세스가 없으면) 그때 springboot 배포 시작

이 방법으로 하면 prepare-deploy에서 sleep 20 을 호출할 필요가 없이 springboot를 종료하고 새로운 버전의 애플리케이션을 빌드하는 사이에 springboot가 정상 종료될 것 같아요~(경험상 우리 프로젝트에서의 gradle 빌드가 20~30 초 정도 걸림..)

일단 이 부분은 개인적인 추측이고, 제 개인 서버에서 돌려보고 있으니 완료되면 같이 확인해보시죠!


@PostMapping("/termination")
public ResponseEntity<Void> terminate(HttpServletRequest request) {
String remoteHost = request.getRemoteHost();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 고려하지 못한 상황이네요! 한 번 알아보도록 하겠슴다

알아보셨나요? ㅋㅋ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BE 백엔드 관련 이슈입니다. ⚙ 환경설정 chore (빌드 스크립트 등 환경 설정)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants