Lambda로 RDS 시작 및 중지 설정
아직 개발단계인데 AWS 비용이 너무 많이 나와서 RDS를 사용할 때 시작하고, 사용하지 않을 때는 중지하는 lambda 함수를 만들어봤다.
1. 함수 생성
먼저, 두 개의 RDS 인스턴스를 시작하고 종료하는 Python 코드로 람다를 생성해 보자.
우선 AWS Lambda 콘솔창으로 이동한다.
https://aws.amazon.com/ko/lambda/
1-1. 함수 생성
- 함수를 생성할 때 '새로 작성'이 기본값으로 선택되어 있다. 그 화면에서 "함수 이름"과 "런타임"을 선택한다.
- "런타임"은 Python 3.8로 선택한다.
- "아키텍처"는 x86_64로 선택한다.
- 고급 설정은 아무것도 선택하지 않았다. 그리고 "함수 생성"을 클릭하면 함수가 생성된다.
💡 AWS Lambda에서 JAVA 에디터를 제공하지 않는 이유는 Java가 컴파일 언어라는 특성 때문이다.
Lambda에 업로드된 Java 코드는 이미 컴파일된 상태이므로, AWS Lambda 콘솔에서 직접 소스 코드를 보거나 편집하는 것이 불가능하다.
1-2. 생성 완료
- 아래는 생성이 완료된 목록 화면이다.
- 아래는 생성한 함수의 상세 화면이다.
2. 코드 입력
- 스크롤을 내려서 "코드" 탭에서 'lambda_function.py' 창에 코드를 입력한다.
코드 내용
우리는 이 코드에서 AWS의 RDS(Relational Database Service) 인스턴스를 시작하거나 정지하는 기능을 작성할 것이다.
코드의 각 부분을 설명해 보겠다.
모듈 임포트
import boto3
import os
- boto3: AWS 서비스와 상호 작용하기 위한 AWS SDK for Python이다. 이를 통해 RDS 서비스를 제어할 수 있다.
- os: 환경 변수에 접근하기 위한 모듈이다.
Lambda 핸들러 함수 정의
def lambda_handler(event, context):
- lambda_handler는 AWS Lambda가 호출하는 기본 함수다.
- event: 이벤트 데이터를 포함하는 객체로, Lambda 함수가 트리거 될 때 전달된다.
- context: 런타임 정보를 포함하는 객체로, Lambda 실행 환경에 대한 정보를 제공한다.
RDS 클라이언트 생성
rds = boto3.client('rds')
- boto3.client('rds')를 호출하여 RDS 서비스와 상호 작용할 수 있는 클라이언트 객체를 생성한다.
환경 변수에서 RDS 인스턴스 이름 읽기
instances = os.environ['RDS_INSTANCES'].split(',')
- 환경 변수 RDS_INSTANCES에서 RDS 인스턴스의 이름 목록을 읽어온다. 이 이름들은 쉼표로 구분되어 있다.
- split(',') 메서드를 사용하여 문자열을 쉼표 기준으로 분리하고, 결과를 리스트로 변환한다.
- 여기서 사용할 환경 변수는 아래서 추가해 주도록 하겠다.
이벤트에 따른 RDS 인스턴스 시작 또는 정지
if event.get('action') == 'start':
for instance in instances:
rds.start_db_instance(DBInstanceIdentifier=instance)
elif event.get('action') == 'stop':
for instance in instances:
rds.stop_db_instance(DBInstanceIdentifier=instance)
- event 객체에서 'action' 키의 값을 확인하여 'start' 또는 'stop'인지 결정한다.
- 'start'인 경우, 모든 지정된 RDS 인스턴스를 순회하며 start_db_instance 메서드를 호출하여 인스턴스를 시작한다.
- 'stop'인 경우, stop_db_instance 메서드를 호출하여 인스턴스를 정지한다.
응답 반환
return {
'statusCode': 200,
'body': f'Performed {event.get("action")} action on RDS instances: {instances}'
}
- 함수는 statusCode와 body를 포함한 응답 객체를 반환한다.
- statusCode는 HTTP 상태 코드를 나타낸다. 여기서는 200을 사용하여 성공을 나타낸다.
- body는 수행된 작업과 영향을 받은 RDS 인스턴스 목록을 문자열로 포함한다.
이 코드는 AWS Lambda를 통해 RDS 인스턴스를 유연하게 관리할 수 있게 해 주며, 특히 환경 변수와 이벤트 데이터를 사용하여 여러 인스턴스에 대한 제어를 손쉽게 구성할 수 있다.
(전체 코드)
import boto3
import os
def lambda_handler(event, context):
# RDS 클라이언트 생성
rds = boto3.client('rds')
# 환경 변수에서 RDS 인스턴스 이름 읽기
instances = os.environ['RDS_INSTANCES'].split(',')
if event.get('action') == 'start':
for instance in instances:
# RDS 인스턴스 시작
rds.start_db_instance(DBInstanceIdentifier=instance)
elif event.get('action') == 'stop':
for instance in instances:
# RDS 인스턴스 종료
rds.stop_db_instance(DBInstanceIdentifier=instance)
return {
'statusCode': 200,
'body': f'Performed {event.get("action")} action on RDS instances: {instances}'
}
❓ 근데 RDS 인스턴스를 매번 시작하고 정지할 때마다 안에 데이터가 삭제될까 봐 걱정할 수 있지만 RDS 인스턴스를 끄고 킬 때 데이터는 삭제되지 않는다.
RDS 인스턴스의 데이터는 Amazon EBS(Elastic Block Store) 볼륨에 저장되기 때문에, 인스턴스를 정지하고 나중에 다시 시작해도 데이터는 유지된다. RDS 인스턴스를 정지한 후 다시 시작하면, EBS 볼륨에 저장된 모든 데이터가 그대로 유지된다. 따라서, 데이터베이스의 데이터, 설정, 로그 등은 안전하게 보존된다. 그리고 인스턴스를 정지하기 전에 자동 백업과 DB 스냅샷이 유지되며, 이를 통해 데이터의 안전성을 추가적으로 보장할 수 있다.
따라서, RDS 인스턴스를 정지했다가 다시 시작해도 데이터는 안전하게 EBS 볼륨에 보존되며, 이전 상태로 복구된다. 데이터베이스 관련 작업을 할 때에는 항상 백업과 복구 전략을 마련해 두는 것이 좋다.
그리고 코드를 작성하다 보면 아래처럼 "Changes not deployes"라는 문구가 나오는데 이는 아직 코드가 함수에 적용이 안되었다는 뜻이니 "Deploy" 버튼을 눌러서 저장해 주자.
그럼 함수가 업데이트되었다는 알림이 뜬다.
💡 AWS Lambda에서 함수를 "배포"한다는 것은 새로운 코드 또는 업데이트된 코드를 Lambda 함수에 적용하고, 이 코드가 AWS Lambda 인프라에서 실행될 수 있도록 만드는 과정을 의미한다.
AWS Lambda에서의 배포 과정은 다음과 같다:
- 코드 작성 또는 업데이트: Lambda 함수의 코드를 작성하거나 기존 코드를 수정한다.
- 변경 사항 저장: 작성 또는 수정된 코드를 Lambda 함수에 저장한다. 이 단계에서 AWS는 코드를 Lambda 실행 환경에 적용한다.
- 배포 버전 생성 (선택적): 코드 변경 사항을 특정 버전으로 배포할 수 있다. 이를 통해 코드의 특정 버전을 관리하고, 필요에 따라 다양한 버전을 실행할 수 있다.
- 테스트 및 검증: 코드 변경이 정상적으로 적용되었는지를 테스트하고 검증한다. AWS Management Console, AWS CLI 또는 AWS SDK를 사용하여 이를 수행할 수 있다.
- 함수 실행: 변경 사항이 적용된 함수가 이제 준비되었으며, 트리거나 직접 호출을 통해 실행될 수 있다.
즉, "배포"라는 용어는 이러한 전체 프로세스를 포괄적으로 설명하는 데 사용된다. 이는 코드의 개발, 테스트 및 라이브 환경으로의 전환을 포함한 일련의 단계를 의미한다.
3. 환경 변수 설정
위 코드에서 작성한 RDS_INSTANCES 환경 변수를 설정해 보자.
- "구성" 탭으로 이동해서 "환경 변수"를 클릭한다. 그리고 "편집"을 클릭한다.
- 그럼 "환경 변수 편집" 화면이 나온다. 여기에 python 코드에서 작성한 "RDS_INSTANCES"를 키로 적어주고 값에는 내가 이 함수를 적용할 RDS 인스턴스명을 적어주면 된다.
- 공백 없이 , 로 바로 이어서 작성해 준다.
- 값에는 DB 식별자가 들어간다. 이 값을 모르겠다면 [RDS > 데이터베이스]에서 확인 가능하다.
- 우리 같은 경우에는 recipia-member와 recipia-recipe가 된다.
- 환경 변수 편집 화면에서 "저장"을 클릭하면 아래처럼 환경 변수 세팅이 완료된 모습을 볼 수 있다.
💡 AWS Lambda에서 코드를 입력하고 배포한 후, 환경 변수를 설정하거나 변경할 때는 다시 배포할 필요가 없다.
환경 변수는 Lambda 함수의 구성 일부로, 이를 변경하는 것은 함수의 코드 자체를 변경하는 것과는 다르다. 환경 변수를 추가하거나 수정하고 저장하면, AWS Lambda는 자동으로 이러한 변경 사항을 적용한다.
따라서, 코드 변경이 아닌 환경 변수만 수정하는 경우에는 새로운 코드 배포 과정을 거칠 필요가 없다.
4. 테스트 생성
테스트를 생성해 보자. 아래 "Test" 버튼을 클릭한다.
맨 처음에 테스트 누르면 진행할 테스트가 없기 때문에 아래처럼 팝업 형태로 뜬다.
여기서 '이벤트 이름'과 '이벤트 JSON' 코드를 작성한다.
{
"action": "stop"
}
AWS Lambda에서 테스트 이벤트를 구성할 때 'hello-world' 템플릿을 선택하는 것은 기본적이고 간단한 테스트 이벤트를 설정하는 방법이다. 'hello-world' 템플릿은 Lambda 함수를 테스트하기 위해 가장 기본적인 형태의 입력 데이터를 제공한다.
하지만, 이 템플릿에 제공된 데이터를 사용하는 대신에, 직접 이벤트 JSON 데이터를 수정하거나 새로 작성할 수 있다. 이렇게 하면 Lambda 함수는 사용자가 제공한 JSON 데이터를 입력으로 받아 처리하게 된다.
따라서, 이 테스트에서 나는 기본 템플릿이 아니라, stop 액션을 감지하고 그에 따른 로직(RDS 인스턴스를 정지하는 로직)을 수행할 것이다.
테스트를 저장하면 아래와 같이 테스트 이벤트가 저장되었다는 알림이 나온다.
5. 테스트 진행
테스트를 만들었으니 테스트를 진행해 보자. 코드 소스에서 "Test" 버튼을 클릭하니까 에러가 나왔다.
Test Event Name
stop-event-test
Response
{
"errorMessage": "An error occurred (AccessDenied) when calling the StopDBInstance operation: User: arn:aws:sts::#$%:assumed-role/rds-start-stop-role-2uas21jd/rds-start-stop is not authorized to perform: rds:StopDBInstance on resource: arn:aws:rds:ap-northeast-2:#$%:db:recipia-member because no identity-based policy allows the rds:StopDBInstance action",
"errorType": "ClientError",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 18, in lambda_handler\n rds.stop_db_instance(DBInstanceIdentifier=instance)\n",
" File \"/var/runtime/botocore/client.py\", line 530, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/runtime/botocore/client.py\", line 960, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
Function Logs
START RequestId: 0cd73205-b3bc-41d5-bd3b-46712474ee3c Version: $LATEST
[ERROR] ClientError: An error occurred (AccessDenied) when calling the StopDBInstance operation: User: arn:aws:sts::#$#$:assumed-role/rds-start-stop-role-2uas21jd/rds-start-stop is not authorized to perform: rds:StopDBInstance on resource: arn:aws:rds:ap-northeast-2:#$#$:db:recipia-member because no identity-based policy allows the rds:StopDBInstance action
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 18, in lambda_handler
rds.stop_db_instance(DBInstanceIdentifier=instance)
File "/var/runtime/botocore/client.py", line 530, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/var/runtime/botocore/client.py", line 960, in _make_api_call
raise error_class(parsed_response, operation_name)END RequestId: 0cd73205-b3bc-41d5-bd3b-46712474ee3c
REPORT RequestId: 0cd73205-b3bc-41d5-bd3b-46712474ee3c Duration: 1645.02 ms Billed Duration: 1646 ms Memory Size: 128 MB Max Memory Used: 71 MB Init Duration: 269.57 ms
Request ID
0cd73205-b3bc-41d5-bd3b-46712474ee3c
표시된 에러 메시지에 따르면, AWS Lambda 함수가 RDS 인스턴스를 정지(StopDBInstance) 시도할 때 AccessDenied 오류가 발생했다. 이는 Lambda 함수가 실행될 때 사용되는 IAM 역할(rds-start-stop-role-2uas21jd)에 RDS 인스턴스를 정지할 수 있는 권한이 없음을 의미한다.
일단 기본적인 권한은 클라우드 워치에 로그를 남기는 권한만 있다. 권한을 추가해 보자.
6. 권한 추가
다시 "구성" 탭으로 와서 "권한"을 클릭한다. 여기서 [역할 이름]을 클릭한다.
이동된 화면에서 스크롤을 내리면 "권한" 탭이 보인다. 여기서 [권한 추가 > 인라인 정책 생성]을 클릭해 준다.
권한 지정하는 화면에서 "JSON"을 클릭하고 보이는 에디터에 RDS 인스턴스를 시작하고 정지할 수 있는 권한(rds:StartDBInstance, rds:StopDBInstance)을 포함하는 정책을 추가해 준다.
작성을 완료하고 아래 "다음"을 클릭한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"rds:StartDBInstance",
"rds:StopDBInstance"
],
"Resource": "*"
}
]
}
검토 및 생성 페이지에서 정책 이름을 적어주고 [정책 생성]을 클릭해 준다.
아래는 해당 역할에 방금 생성한 정책이 추가된 모습이다.
7. 다시 람다로 돌아와서 테스트 진행
다시 람다 화면으로 돌아와서 "Test"를 클릭하면 아래와 같이 성공 로그가 보인다.
Lambda 함수 로그와 응답을 기반으로 볼 때, Lambda 함수가 성공적으로 실행되었고, 지정된 RDS 인스턴스들('recipia-member', 'recipia-recipe')에 대해 'stop' 액션을 수행했다고 볼 수 있다.
Response
- "statusCode": 200 - HTTP 상태 코드 200은 요청이 성공적으로 처리되었음을 나타낸다.
- "body": "Performed stop action on RDS instances: ['recipia-member', 'recipia-recipe']" - 이 메시지는 Lambda 함수가 'stop' 작업을 두 RDS 인스턴스에 대해 수행했음을 나타낸다.
Function Logs
- 로그는 특별한 오류 메시지 없이 함수의 시작과 종료를 보여주며, 메모리 사용량과 실행 시간도 정상 범위 내에 있다.
이러한 응답은 함수가 정상적으로 실행되었고, 예상대로 RDS 인스턴스들에 대한 'stop' 작업을 수행했음을 나타낸다. 하지만 실제 RDS 인스턴스들이 정지되었는지 확인하기 위해서는 AWS RDS 관리 콘솔에 로그인하여 인스턴스들의 상태를 직접 확인해야 한다.
AWS RDS 콘솔에서 각 인스턴스의 상태가 'stopping' 또는 'stopped'으로 변경되었다면, 이는 Lambda 함수가 실제로 인스턴스를 정지시켰음을 나타낸다.
이제 실제로 [RDS > 데이터베이스] 화면에서 확인해 보자.
두 데이터베이스 모드 '중지 중'에서 '일시적으로 중지됨'으로 바뀌는 모습을 볼 수 있다.
8. starat 하는 테스트 생성
이전에는 중지시키는 테스트를 작성해 봤으니, 다시 실행시키는 테스트를 작성해 보겠다.
다시 람다 화면으로 돌아와서 [테스트] 탭으로 이동해서 새로 생성하고 [테스트]를 클릭한다. 테스트를 누르면 바로 테스트가 시작된다.
테스트가 완료되고 성공했으면 다시 RDS 데이터베이스를 확인해 보자
이렇게 테스트가 성공적으로 완료된 모습을 확인할 수 있다.
9. 테스트 실행
기존에 만들어놨던 테스트를 골라서 실행할 수 있는데 그 방법은 아래 'Test' 버튼 우측 ▼ 이 버튼을 클릭해서 실행할 테스트를 선택하고 'Test'를 클릭하면 된다.
10. 마무리
이렇게 간단하게 람다를 적용시켜 봤다. 아무래도 RDS 비용이 비싸다 보니 한 달에 청구되는 비용이 너무 많아졌다. 그래서 일단 개발할 때는 켜놓고 개발하지 않고 자는 시간 몇 시간이라도 데이터베이스를 꺼놓으면 좀 절약될까 싶어서 적용해 봤다.
아직 테스트로써만 람다를 사용했지만 더 손쉽게 RDS를 껐다 켜고 할 수 있게 트리거도 적용해야겠다.
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)
'AWS' 카테고리의 다른 글
DynamoDB 테이블 생성하기 (1) | 2024.02.26 |
---|---|
[AWS] AWS ALB 헬스 체크(상태 검사) 간격 및 경로 수정을 통한 서비스 최적화 (0) | 2023.11.22 |
[AWS] ECS에서 Zipkin을 통한 스프링 부트 서비스 트레이싱 구축하기 (0) | 2023.11.19 |
[AWS] SNS, SQS 연동하기 (2) - Spring Boot 3.X.X 버전과 SNS, SQS 연동하기 (2) | 2023.11.06 |
[AWS] SNS, SQS 연동하기 (1) - SNS, SQS 생성하기 (0) | 2023.11.06 |