Hyun's Wonderwall

[AWS] EC2 서버 계속 꺼짐(상태 검사 실패) 해결법 - Lambda & EventBridge로 재시작 자동화하기! 본문

Study/Java, Spring

[AWS] EC2 서버 계속 꺼짐(상태 검사 실패) 해결법 - Lambda & EventBridge로 재시작 자동화하기!

Hyun_! 2024. 10. 7. 17:55

처음으로 서버 배포 담당을 맡은 때부터 지금까지, 항상 모든 프로젝트에서 백엔드 서버를 켜두면 서버가 오래가지 못하고 짧게는 몇 시간에서 길면 며칠 후 맛이 가고는 했다. 즉, EC2 인스턴스가 상태 검사 - 인스턴스 연결성 검사에 실패하는 일이 잦았다.

 

이 문제 상황에서 나는 주로 [인스턴스 재시작]을 시키거나 [인스턴스 중지] + (잠시 기다리고) + [인스턴스 시작]을 수동으로 진행시켜서 해결해왔었다. 많은 경우에 이 방식으로 해결되었지만 종종 해결이 안 되기도 해서... 오래 중지해 두어도 안 돌아오면 인스턴스를 새로 만들었었다. (AMI로 인스턴스 복제하는 법 몰랐을 때는 아예 nginx, docker 다 새로 깔았다...)

 

메모리 사용량이 높으면 인스턴스가 꺼진다는 말을 읽고 인스턴스 유형을 업그레이드 해보기도 했는데 (t3.small로) 마찬가지로 종종 꺼져서 크게 효과가 없었다. 나만 겪는 문제는 아닌 것 같은데, 검색했던 당시 자세한 해결법을 찾지 못했었다. (당시 찾은 최선이 중지+시작..)

 

인스턴스 연결성 검사가 실패할 때마다 수동으로 조치하는 것이 번거로웠고, 서비스를 실배포할 때는 이와 같은 문제가 생기면 안 돼서 해결책을 강구했다. 

구글링을 한 결과, 아래의 블로그를 발견하여 인스턴스 중지+시작이 매일 일정 시각에 이루어지도록 자동하는 것에 성공했다. 이후로는 서버가 꺼지는 일이 없었다!

https://blog.naver.com/zacra/223317309373

 

AWS 서버 자동 재시작으로 안정성 확보하기 대작전!

투자의 적인 뇌동매매를 피하고자 파이썬으로 자동매매를 하고 있습니다! 클래스101에서 주식/코인 자동매...

blog.naver.com



내 절차에 맞게 적용 과정을 다시 정리해보았다. 인스턴스를 하나 만들어놓은 상태에서 시작 바란다.

1. IAM - 정책 만들기

IAM 서비스 > 정책 탭 > [정책 생성]을 클릭하고, 권한 지정에서 [JSON]을 클릭해 아래의 JSON 코드를 복붙한다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Start*",
                "ec2:Stop*"
            ],
            "Resource": "*"
        }
    ]
}

 

[다음]을 누르고 "정책 이름"을 지어준다. (ex: MyEC2AutoRestart)

"이 정책에 정의된 권한"을 보면, 위에서 JSON 코드를 작성함으로써 이 정책에 CloudWatch Logs와 EC2 두가지 서비스에 대한 권한이 허용되었음을 알 수 있다.

[정책 생성]을 클릭해 정책 생성을 완료한다.


2. IAM - 역할 만들기

IAM > 역할 > [역할 생성]을 클릭한다.

"신뢰할 수 있는 엔터티"에 [AWS 서비스], "사용 사례"로 [Lambda]를 선택한다.

 

"권한 추가"의 검색바에서 위에서 자신이 생성한 정책을 검색해 추가한다.

 

"역할 이름"을 지정한다. 나는 편의상 정책과 같은 이름으로 지정했다.

[역할 생성]을 클릭해 역할 생성을 완료한다.


3. AWS Lambda - 중지 함수, 시작 함수 만들기

AWS Lambda 서비스로 EC2 인스턴스를 중지하는 함수 하나, 시작하는 함수 하나 이렇게 두 개의 함수를 만들고자 한다.

 

먼저 중지 함수부터 만들겠다.

Lambda > 함수 > [함수 생성]을 클릭한다.

중지 함수의 "함수 이름"을 짓고(ex. StopEC2Instances), "런타임"을 Python으로 선택.

"권한"에서 "기본 실행 역할 변경" 토글을 열어서 [기존 역할 사용]을 선택하고, 위에서 만든 역할(MyEC2AutoRestart)을 선택한다.


[함수 생성]을 클릭해 함수를 생성한다.

 

함수 정보 아래의 "코드"를 편집해 아래의 내용을 적는다. (lambda_function.py 파일을 편집)

(*코드에 ID를 직접 쓰지 않는 방법은 후술합니다.)

import boto3

region = 'eu-north-1' # 자신의 리전에 맞게 변경
instances = ['i-...'] # 대상 인스턴스 ID를 작성
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.stop_instances(InstanceIds=instances)
    print('stopped your instances: ' + str(instances))

 

[Deploy]를 클릭해서 배포한다.

 

이후 "구성 탭"에서 "제한 시간"을 10초로 변경한다.

여기까지 하면 중지 함수 작성이 완료된다.

 

시작 함수도 같은 방법으로 만들어준다.

시작 함수의 코드는 아래와 같이 적는다.

import boto3

region = 'eu-north-1' # 자신의 리전에 맞게 변경
instances = ['i-...'] # 대상 인스턴스 ID를 작성
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.start_instances(InstanceIds=instances)
    print('started your instances: ' + str(instances))

[Deploy]를 클릭해 배포하고, 제한 시간도 늘려준다.

 

각 함수에서 Test 버튼을 클릭해보면 작성한 ID의 인스턴스가 중지/시작된다.


4. AWS EventBridge - 일정 생성, 스케줄링

AWS EventBridge 서비스를 활용해 EC2 인스턴스가 특정 시각에 중지되고, 특정 시작에 시각되도록 스케줄링할 것이다.

따라서 중지하는 일정과 시작하는 일정(규칙) 두 개를 생성하고 언제 무엇을 진행할지 정하여야 한다.

 

중지 일정부터 만들겠다.

Amazon EventBridge > Scheduler > 일정 > [일정 생성]을 클릭한다.

일정 이름을 작성하고 (ex. StopEC2Instances)

일정 패턴에서 발생은 [반복 일정]을 선택,

시간대가 맞는지 한번 확인한다.

일정 유형은 [Cron 기반 일정]을 선택하고, 원하는 일정의 주기와 시각을 써넣는다.

나는 0 5 * * ? * 값을 입력해 매일 새벽 5시에 스케줄러가 동작하도록 했다.

대상은 [AWS Lambda Invoke]를 선택

위에서 만든 서버 중지 함수를 선택하고, [일정 검토 및 생성단계로 건너뛰기] 버튼을 클릭한다.

[일정 생성] 버튼을 클릭하면 일정 생성 완료.

 

시작 일정도 동일하게 만들면 되는데,

Cron 표현식의 "분"을 1,2로 써넣어

Stop하는 일정이 진행된 이후 5시 1분과 2분에 실행되도록 했다.

(두 번 진행하는 것은 참고한 블로그를 따라, 5시 1분의 시점에서 인스턴스의 중지가 끝나지 않아서 Start 일정이 시작하지 못하는 경우를 고려했다. 1분 또는 2분으로만 적으셔도 무관할 것 같다.)


5. 부록 - 페이로드로 instance들 ID 넘겨주기

함수 코드를 수정하는 대신, 인스턴스 ID를 따로 넣어주고 싶은 경우 아래와 같이 진행하면 된다.

(이것은 원본 블로그에서 더 나아가서 내가 추가로 학습한 내용!)

 

기존의 Lambda 함수 StartEC2Instances, StopEC2Instances의 코드들을 아래와 같이 수정할 수 있다.

상태코드와 메시지 출력은 디버깅 용으로 추가한 것이어서,

위의 코드에서 instances = event.get('InstanceIds', [])만 변경해도 무방하다.

 

[ Lambda 함수 StartEC2Instances의 코드 ]

import boto3

region = 'eu-north-1'
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    try:
        # Extract instance IDs from the event
        instances = event.get('InstanceIds', [])
        
        if not instances:
            return {
                'statusCode': 400,
                'body': 'No instance IDs provided'
            }
        
        # Start the instances
        response = ec2.start_instances(InstanceIds=instances)
        
        # Log the started instances
        print('Started your instances: ' + str(instances))
        
        return {
            'statusCode': 200,
            'body': f'Started instances: {str(instances)}'
        }
    except Exception as e:
        print(f'Error: {str(e)}')
        return {
            'statusCode': 500,
            'body': f'Error starting instances: {str(e)}'
        }


[ Lambda 함수 StopEC2Instances의 코드 ]

import boto3

region = 'eu-north-1'
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    try:
        # Extract instance IDs from the event
        instances = event.get('InstanceIds', [])
        
        if not instances:
            return {
                'statusCode': 400,
                'body': 'No instance IDs provided'
            }
        
        # Stop the instances
        response = ec2.stop_instances(InstanceIds=instances)
        
        # Log the stopped instances
        print('Stopped your instances: ' + str(instances))
        
        return {
            'statusCode': 200,
            'body': f'Stopped instances: {str(instances)}'
        }
    except Exception as e:
        print(f'Error: {str(e)}')
        return {
            'statusCode': 500,
            'body': f'Error stopping instances: {str(e)}'
        }

 

두 람다 함수 모두에서 Test 옆의 토글을 클릭해 테스트 이벤트를 만들거나, 저장했던 이벤트를 편집한다.

이곳의 "이벤트 JSON"에 (통신 시 사용하는 RequestBody처럼) argument를 적을 수 있다.

대괄호 안에 자신의 인스턴스 id(들)을 컴마로 구분해서 적어준다. 나는 하나의 인스턴스만 쓰고 있으므로 한 개만 적었다.

{
  "InstanceIds": ["i-..."]
}

 

이제 Test 버튼을 클릭하면 이벤트 JSON에 적은 인스턴스 ID들 문자열 리스트를 가져와 함수가 실행됨을 확인할 수 있다.

 

위에서 한 것은 테스트 이벤트였으므로 실제 일정에도 적용해주자.

EventBridge로 돌아가서 두 일정을 각각 편집하는데,

Lambda Invoke를 선택한 다음의 "페이로드" 공간에 동일하게 작성해주면 된다.