brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Jan 03. 2021

ASG의 Lifecycle Hook 활용하기

Journey to AWS

Autoscaling Group (이하 ASG) 을 이용해서 EC2 인스턴스의 수를 동적으로 조절하는 것은 AWS 기반으로 서비스를 운영하는 곳에서는 일상적으로 사용하고 있는 기능 중 하나일 겁니다. 하지만 ASG를 이용해서 인스턴스의 수를 늘리고 줄이고 하다 보면 서비스에 영향을 주는 경우가 간혹 있습니다. 특히 Scale in 시에 이슈가 발생하는 경우가 많은데, 인스턴스 내에서 동작 중인 애플리케이션이 정상적으로 종료되지 못하고 인스턴스가 삭제되기 때문입니다. 그래서 이번 글에서는 ASG에서 제공하는 Lifecycle Hook을 활용해서 인스턴스 종료를 Graceful 하게 하는 과정에 대해서 다뤄 보려고 합니다.


ASG Lifecycle 살펴보기


먼저 ASG Lifecycle이 무엇인지 살펴보겠습니다. ASG를 통해 생성되는 인스턴스들은 아래와 같은 Lifecycle을 가지고 있습니다.

ASG에 의해 생성된 EC2의 Lifecycle
출처 : https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html

ASG가 인스턴스를 증가시키는 이벤트는 Scale out 이벤트이며, 이 때는 Pending 상태가 됩니다. 그 후 인스턴스가 부트스트랩 과정을 마치고 나면 InService 상태가 됩니다. 그리고 인스턴스가 축소되는 이벤트는 Scale in 이벤트이며, Terminating 상태를 거쳐 Terminated 상태가 됩니다. ASGLifecycle Hook은 이 과정 중 Scale inScale out 과정에 설정할 수 있습니다. 먼저 Scale out 이벤트에 걸게 된다면 인스턴스는 Pending 이후에 Pending:Wait 상태와 Pending:Proceed 상태를 거치게 됩니다. Scale in 이벤트에 걸게 된다면 인스턴스는 Terminating:Wait 상태와 Terminating:Proceed 상태를 거치게 됩니다.  

그래서 인스턴스가 생성된 후 부트 스트랩 이후에 뭔가 추가적으로 작업해야 할 것이 있다면 EC2_INSTANCE_LAUNCHING 이벤트에, 인스턴스가 삭제될 때 추가적으로 작업해야 할 것이 있다면 EC2_INSTANCE_TERMINATING 이벤트에 Lifecycle Hook을 설정해 주면 됩니다. 


이번 글에서는 이 중에서도 EC2_INSTANCE_TERMINATING 이벤트에 Lifecycle Hook을 설정해서 인스턴스가 삭제되기 전에 애플리케이션이 확실하게 종료될 수 있도록 구성해 보겠습니다.


ASG Lifecycle Hook의 처리 순서


ASGLifecycle Hook 이벤트는 여러 가지 방법으로 전달받을 수 있지만 이번 글에서는 Cloudwatch Event Bridge를 통해서 받아 보겠습니다. 

Lifecycle Hook은 SQS, SNS를 통해서도 전달받을 수 있습니다. (https://docs.aws.amazon.com/autoscaling/ec2/userguide/configuring-lifecycle-hook-notifications.html)
Lifecycle Hook 처리 순서

ASG에서 설정한 Lifecycle 이벤트가 발생하면 Cloudwatch Event Bridge (이하 Event Bridge) 를 통해 이벤트가 생성되고 Lambda 함수가 실행됩니다. 실행된 Lambda 함수는 SSM 의 Run Command를 이용해서 Lifecycle 이벤트의 대상이 된 인스턴스에 주어진 명령을 실행합니다. 그리고 정상적으로 실행이 완료되면 Lambda 함수가 ASG 에게 Lifecycle Hook에 의해 진행된 작업이 완료되었음을 알려 줍니다. 

작업 완료를 알려 주는 것은 매우 중요한데, 그렇게 하지 않으면 Lifecycle Hook의 Heartbeat timeout에 설정한 시간만큼 기다리게 되기 때문입니다. 

Lambda 함수의 구조


먼저 Lambda 함수부터 살펴보겠습니다. 아래는 Lambda 함수의 뼈대가 되는 부분입니다. AWS 세션을 획득한 후 EventBridge를 통해 전달받은 이벤트를 확인하고 애플리케이션을 중지시키도록 SSMRun Command를 호출 한 뒤 Lifecycle Hook Action 이 완료되었음을 알립니다. 

TerminationHandler 함수

핵심이 되는 부분 중에 하나는 EventBridge를 통해 전달받은 이벤트를 함수 내부에서 사용할 수 있도록 구조체로 변경하는 부분입니다. 그리고 이 이벤트는 TerminationDetail 이라는 구조체로 정의되어 있습니다.

TerminationDetail 구조체

이렇게 이벤트의 내용을 구조체로 변환한 다음 stopApplication 이라는 함수를 호출합니다. 이 함수는 아래와 같이 구성되어 있습니다. 

stopApplication 함수는 크게 두 부분으로 나뉘어 있습니다. 먼저 SSM RunCommand를 호출하는 부분과 호출된 RunCommand가 정상적으로 종료되었는지를 확인하는 부분입니다.

RunCommand를 호출하는 부분

ASGLifecycle 대상이 된 인스턴스 ID와 실행해야 하는 명령을 인자로 넘겨주어서 AWS-RunShellScript 방식으로 RunCommand를 실행합니다. 이때 넘겨주는 명령은 systemctl stop application 이라는 명령입니다. 그럼 인스턴스는 종료되기 전에 systemctl stop application 이라는 명령을 실행하게 되고 애플리케이션을 정상적으로 종료할 수 있게 됩니다. 

RunCommand의 정상적인 종료를 확인하는 부분

그리고 생성된 RunCommand가 정상적으로 종료되었는지를 확인합니다. RunCommand가 실행되고 API를 통해 상태를 확인하기 위해서는 약간의 시간이 소요되기 때문에 강제로 Sleep 을 주어 텀을 주어야 합니다.


serverless를 이용한 배포


Lambda 함수를 만들었으면 사용할 수 있도록 배포해야 합니다. 배포는 serverless 프레임워크를 사용해 보겠습니다. (https://www.serverless.com/)

serverless.yml 파일 내용

함수의 이름은 asg-termination-handler라고 지어줍니다. serverless.yml 파일의 핵심은 하단 events 부분인데요, EventBridge를 정의하기 위한 부분입니다. 

event 하단에 있는 source와 detail-type은 이미 정의되어 있는 부분이기 때문에 동일하게 맞춰 주어야 합니다. 
출처 : https://docs.aws.amazon.com/autoscaling/ec2/userguide/configuring-lifecycle-hook-notifications.html 

만약 Scale in 이 아닌 Scale out 시에 걸고 싶으면 detail-typeEC2 Instance-launch Lifecycle Action 로 변경해 주면 됩니다.

또한 Lifecycle Hook을 처리하기 위한 Lambda 함수는 일반적인 Lambda 함수 실행 권한 외에도 ASGSSM과 관련된 권한이 더 필요합니다. 그래서 테라폼을 이용해서 롤을 먼저 만들어 준 다음에 serverless.yml 파일에 role을 명시해서 추가해 줍니다.

추가가 필요한 IAM Policy들

이후 serverless 프레임워크로 배포하면 Lifecycle Hook 이벤트를 받아서 처리하기 위한 준비는 끝이 납니다.


ASG에 Lifecycle Hook 설정하기


이제 마지막으로 ASGLifecycle Hook 을 설정합니다. AWS 콘솔에서 EC2 -> Auto Scaling Gropus -> Instance Management 탭에 들어가면 아래와 같이 Lifecycle hooks를 설정할 수 있는 콘솔이 나옵니다.

Lifecycle hooks 콘솔

Create lifecycle hook 버튼을 클릭하면 아래와 같은 입력창이 나옵니다. 우리가 구현한 것은 Scale in 시의 작업이기 때문에 이름은 자유롭게 지어주되 Lifecycle transition 만 Instance terminate로 설정해 줍니다.

Lifecycle Hook 생성하기

Heartbeat timeoutLifecycle Hook Action 의 유지 시간인데, 앞에서 언급했던 것처럼 completion 처리를 별도로 해주지 않으면 액션 처리를 위한 람다 함수가 종료되고도 Heartbeat timeout에 설정한 시간만큼 기다리게 됩니다. 만약 Lifecycle Hook 이벤트를 받은 후 진행하는 작업이 오래 걸린다면 충분히 크게 주어서 안정적으로 진행되도록 해주어야 합니다. 이 값의 최대 값은 7200초입니다.

설정이 완료된 후 ASG를 통해서 Scale in 해 보면 아래와 같이 인스턴스들이 Terminating:Waiting 상태를 거쳐 Terminating:Proceed 상태로 변경되는 것을 볼 수 있습니다.

Lifecycle 변화

그리고 SSMRunCommand 콘솔로 가보면 아래와 같이 정상적으로 RunCommand가 실행된 것도 볼 수 있습니다.

RunCommand 실행 완료 화면

마치며


지금까지의 과정을 통해서 ASGLifecycle Hook에 대해서 살펴봤습니다. Lifecycle HookASG에 의해 인스턴스가 늘어나거나 줄어들 때 이벤트를 발생시켜서 사용자가 별도의 원하는 작업을 수행할 수 있게 해 줍니다. 이번 글에서는 간단하게 애플리케이션을 종료시키는 로직을 구성해 봤지만 Heartbeat timeout 이내에 가능한 작업이라면 어떤 작업도 할 수 있습니다. 예를 들어 인스턴스의 데이터를 다른 곳으로 옮긴다거나 하는 형태의 복잡한 작업도 가능합니다. 

ASG의 기본 기능으로만으로는 유연한 인프라를 구성하는 것에 부족함을 느끼시는 분들에게 Lifecycle Hook이 도움이 되었으면 좋겠습니다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari