1. 전체 아키텍처(Architecture) 다이어그램
%%{init: {"theme": "dark"}}%% graph LR subgraph AWS_Cloud["AWS Cloud"] direction TB CloudWatchEvent["CloudWatch Event Rule (3개월 Cron)"] LambdaFunc["AWS Lambda (Python)"] Lightsail["Lightsail (API Endpoints)"] CloudWatchLogs["CloudWatch Logs"] CloudWatchEvent --> LambdaFunc LambdaFunc -->|Lightsail API 호출| Lightsail LambdaFunc -->|CloudWatch Logs 기록| CloudWatchLogs end subgraph IAM_Section["IAM"] IAMRole["IAM Role (Lambda 실행 역할)"] end IAMRole -.->|AssumeRole| LambdaFunc subgraph Lightsail_Resources["AWS Lightsail 리소스"] LightsailInstance["원본 Lightsail 인스턴스"] Snapshot["Instance Snapshot"] NewInstance["새 Lightsail 인스턴스"] StaticIP["고정 IP"] DNSZone["DNS Zone & A Record"] end LightsailInstance -.->|Snapshot 생성| Snapshot Snapshot -.->|스냅샷 기반 인스턴스 생성| NewInstance StaticIP -.->|재연결| NewInstance DNSZone -.->|A 레코드 업데이트| NewInstance NewInstance -.->|방화벽 규칙 적용| LambdaFunc LightsailInstance -.->|삭제| LambdaFunc %% 차분한 색상 스타일 style AWS_Cloud fill:#232D3B,stroke:#455A64,stroke-width:2px style IAM_Section fill:#232D3B,stroke:#607D8B,stroke-width:2px style Lightsail_Resources fill:#232D3B,stroke:#388E3C,stroke-width:2px style CloudWatchEvent fill:#375A7F,stroke:#222 style LambdaFunc fill:#5D6470,stroke:#222 style Lightsail fill:#466B7F,stroke:#222 style CloudWatchLogs fill:#7C7362,stroke:#222 style IAMRole fill:#607D8B,stroke:#222 style LightsailInstance fill:#3D5A53,stroke:#222 style Snapshot fill:#44476A,stroke:#222 style NewInstance fill:#495D5B,stroke:#222 style StaticIP fill:#3E4852,stroke:#222 style DNSZone fill:#676553,stroke:#222
2. Process Flow Chart
%%{init: {"theme": "dark"}}%% graph TD A["Start: CloudWatch Event Trigger (3개월 주기)"] --> B{Lambda Function 실행} subgraph "Lambda Function (Python)" B --> C["1\. 현재 인스턴스 스냅샷 생성"] C --> D{스냅샷 'Available' <br>상태 대기} D --> E["2\. 새 인스턴스 생성 (스냅샷 기반)"] E --> F{새 인스턴스 'Running' <br>상태 대기} F --> G["3\. 고정 IP 재연결"] G --> H["4\. DNS A 레코드 업데이트"] H --> I["5\. Lightsail 방화벽 규칙 적용"] I --> J["6\. 이전 인스턴스 삭제"] J --> K["End: 프로세스 완료"] end %% 시작/종료만 약간 강조, 나머지는 한 계열로 style A fill:#232D3B,stroke:#9CCC65,stroke-width:2px,color:#E1F5FE style K fill:#232D3B,stroke:#FF8A65,stroke-width:2px,color:#FFCCBC style B fill:#3D4450,stroke:#546E7A,stroke-width:1px,color:#CFD8DC style C fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC style D fill:#4B5157,stroke:#263238,stroke-width:1px,color:#ECEFF1 style E fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC style F fill:#4B5157,stroke:#263238,stroke-width:1px,color:#ECEFF1 style G fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC style H fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC style I fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC style J fill:#37474F,stroke:#37474F,stroke-width:1px,color:#CFD8DC
다이어그램 설명
- A[Start: CloudWatch Event Trigger (3개월 주기)]: 자동화 프로세스의 시작점입니다. CloudWatch Events는 설정된 Cron 표현식(예: 3개월마다)에 따라 Lambda 함수를 주기적으로 트리거합니다.
- B{Lambda Function 실행}: 트리거된 AWS Lambda 함수가 실행됩니다. 이 함수 내부에 모든 자동화 로직이 Python 코드로 구현되어 있습니다.
- C[1. 현재 인스턴스 스냅샷 생성]: Lambda 함수가 먼저 Lightsail API를 호출하여 현재 운영 중인 인스턴스의 스냅샷을 생성합니다.
- D{스냅샷 ‘Available’ 상태 대기}: 스냅샷 생성이 완료되고 사용 가능한 상태(
available
)가 될 때까지 Lambda 함수가 대기합니다. (실제 코드에서는wait_for_resource_state
함수 역할) - E[2. 새 인스턴스 생성 (스냅샷 기반)]: 사용 가능한 스냅샷을 기반으로 새 Lightsail 인스턴스를 생성합니다. 이때 인스턴스 사양, 키 페어 등을 지정합니다.
- F{새 인스턴스 ‘Running’ 상태 대기}: 새로 생성된 인스턴스가 완전히 부팅되어 실행 중(
running
) 상태가 될 때까지 Lambda 함수가 대기합니다. - G[3. 고정 IP 재연결]: 기존 인스턴스에 연결되어 있던 **고정 IP(Static IP)**를 새로 생성된 인스턴스에 재연결합니다. 이 과정에서 Lightsail이 자동으로 기존 인스턴스에서 IP를 분리합니다.
- H[4. DNS A 레코드 업데이트]: 재연결된 고정 IP의 주소를 가져와서, Lightsail DNS Zone에 설정된 도메인의 A 레코드를 새로운 IP 주소로 업데이트합니다.
- I[5. Lightsail 방화벽 규칙 적용]: 미리 정의된 방화벽(Ports) 규칙을 새 인스턴스에 다시 적용합니다.
- J[6. 이전 인스턴스 삭제]: 모든 전환 및 설정이 성공적으로 완료되었음을 확인한 후, 더 이상 필요 없는 이전(원본) 인스턴스를 삭제합니다.
- K[End: 프로세스 완료]: 자동화 프로세스가 성공적으로 마무리됩니다.
3. 컴포넌트 상호작용(Sequence Diagram)
%%{init: {"theme": "dark"}}%% sequenceDiagram participant CW as CloudWatch Events participant Lambda as Lambda Function participant Lightsail as AWS Lightsail participant DNS as Lightsail DNS participant IAM as IAM Role CW->>Lambda: (3개월마다) Lambda Trigger Lambda->>IAM: AssumeRole (권한 위임) Lambda->>Lightsail: create_instance_snapshot() Lightsail-->>Lambda: Snapshot 생성 시작 Lambda->>Lightsail: get_instance_snapshot() (대기 루프) Lightsail-->>Lambda: Snapshot 'available' 상태 반환 Lambda->>Lightsail: create_instances_from_snapshot() Lightsail-->>Lambda: 새 인스턴스 생성 시작 Lambda->>Lightsail: get_instance() (대기 루프) Lightsail-->>Lambda: 인스턴스 'running' 상태 반환 Lambda->>Lightsail: attach_static_ip() Lightsail-->>Lambda: 고정 IP 연결 완료 Lambda->>Lightsail: get_static_ip() (새 Public IP 조회) Lightsail-->>Lambda: 새 IP 주소 반환 Lambda->>DNS: get_domain_entries() (A 레코드 조회) DNS-->>Lambda: A 레코드 ID 반환 Lambda->>DNS: update_domain_entry() (A 레코드 새 IP로 업데이트) DNS-->>Lambda: A 레코드 업데이트 성공 Lambda->>Lightsail: put_instance_public_ports() (방화벽 규칙 적용) Lightsail-->>Lambda: 방화벽 적용 완료 Lambda->>Lightsail: delete_instance() (이전 인스턴스 삭제) Lightsail-->>Lambda: 인스턴스 삭제 완료
4. IAM 역할 및 권한 구조
%%{init: {"theme": "dark"}}%% graph LR subgraph IAM_ROLE["Lambda Execution Role (IAM)"] Policy1[CloudWatch Logs 권한] Policy2[Lightsail 인스턴스/스냅샷/네트워크/API 권한] end Policy1 -- 포함 --> LambdaRole Policy2 -- 포함 --> LambdaRole LambdaRole -- AssumeRole --> LambdaFunction %% 차분한 색상 스타일 style IAM_ROLE fill:#232D3B,stroke:#81C784,stroke-width:2px style Policy1 fill:#3D4450,stroke:#546E7A,stroke-width:1px,color:#B0BEC5 style Policy2 fill:#3D4450,stroke:#388E3C,stroke-width:1px,color:#B0BEC5 style LambdaRole fill:#37474F,stroke:#607D8B,stroke-width:1px,color:#CFD8DC style LambdaFunction fill:#455A64,stroke:#607D8B,stroke-width:1px,color:#CFD8DC
5. CloudWatch Event Rule 다이어그램 (배경)
%%{init: {"theme": "dark"}}%% flowchart LR CRON[3개월마다 실행되는 Cron Rule] LambdaTarget[Lambda Function Target] TRIGGER[실행 트리거] CRON --> LambdaTarget LambdaTarget --> TRIGGER style CRON fill:#3D4450,stroke:#42A5F5,stroke-width:1.5px,color:#B0BEC5 style LambdaTarget fill:#3D4450,stroke:#81C784,stroke-width:1.5px,color:#CFD8DC style TRIGGER fill:#37474F,stroke:#FF8A65,stroke-width:1.5px,color:#FFF3E0
6. 상세 설명
주어진 요구사항은 AWS Lightsail 스냅샷 기반의 인스턴스 교체 자동화에 대한 것으로, 상당히 복잡하고 여러 AWS 서비스의 연동을 필요로 합니다. Lightsail, Lambda, CloudWatch Events, IAM, 그리고 Python(Boto3)을 사용하여 구현할 수 있습니다.
주의사항:
- 생산 환경 적용 전 철저한 테스트 필수: 이 코드는 예시이며, 실제 운영 환경에 적용하기 전에 충분한 테스트(특히 에러 핸들링, 롤백, 타임아웃 등)를 거쳐야 합니다.
- 권한 설정: IAM Role 설정이 매우 중요합니다. Lambda 함수에 필요한 Lightsail API 호출 권한을 정확히 부여해야 합니다.
- 변수 설정: 코드 내의 플레이스홀더 변수 (
YOUR_INSTANCE_NAME
,YOUR_BUNDLE_ID
,YOUR_AVAILABILITY_ZONE
,YOUR_KEY_PAIR_NAME
,YOUR_STATIC_IP_NAME
,YOUR_DOMAIN_NAME
,YOUR_DNS_ENTRY_ID
,YOUR_PORTS_CONFIG
)는 반드시 실제 환경에 맞게 수정해야 합니다. - Lightsail Region: 모든 작업은 동일한 Lightsail 리전에서 이루어져야 합니다. (예:
ap-northeast-2
) - 비용: Lambda 함수 실행, 스냅샷 보관, 새 인스턴스 생성 및 잠시 동안의 기존 인스턴스 유지로 인해 비용이 발생합니다.
전체 자동화 프로세스 개요 (Steps)
- CloudWatch Events Rule: 3개월마다 Lambda 함수를 트리거하도록 스케줄 설정.
- Lambda Function (Python):
- 단계 1: 인스턴스 스냅샷 생성
- 지정된 원본 인스턴스의 스냅샷을 생성합니다.
- 스냅샷 이름은 날짜 정보를 포함하여 고유하게 만듭니다.
- 단계 2: 새 인스턴스 생성
- 생성된 스냅샷을 기반으로 새 Lightsail 인스턴스를 생성합니다.
- 새 인스턴스의 사양(번들 ID), 가용 영역, 키 페어 등을 지정합니다.
- 중요: 새 인스턴스가
running
상태가 될 때까지 기다립니다.
- 단계 3: 고정 IP 재연결
- 기존에 원본 인스턴스에 연결되어 있던 고정 IP를 새 인스턴스에 연결합니다.
- Lightsail API는 이 과정에서 기존 인스턴스에서 자동으로 IP를 분리하고 새 인스턴스에 연결합니다.
- 단계 4: DNS 레코드 업데이트
- 재연결된 고정 IP의 실제 IP 주소를 가져옵니다.
- Lightsail DNS Zone에 등록된 해당 도메인의 A 레코드를 새 IP 주소로 업데이트합니다.
- 참고:
YOUR_DNS_ENTRY_ID
를 미리 파악하거나get_domain_entries
를 통해 동적으로 찾아야 합니다.
- 참고:
- 단계 5: Lightsail 방화벽 규칙 적용
- 사전에 정의된 (또는 기존 인스턴스에서 가져온) 방화벽 규칙을 새 인스턴스에 적용합니다.
- 단계 6: 이전 인스턴스 삭제
- 모든 네트워크 설정 및 복제가 성공적으로 완료되었음을 확인한 후, 기존(원본) 인스턴스를 삭제합니다.
- 단계 1: 인스턴스 스냅샷 생성
1. IAM Role (Lambda 실행 역할) 설정
Lambda 함수가 Lightsail API를 호출하고 CloudWatch Logs에 로그를 기록할 수 있도록 다음과 같은 권한을 가진 IAM 역할을 생성해야 합니다.
- Policy Name:
LightsailInstanceRotationPolicy
(예시) - Permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:::"
},
{
"Effect": "Allow",
"Action": [
"lightsail:CreateInstanceSnapshot",
"lightsail:GetInstanceSnapshots",
"lightsail:DeleteInstanceSnapshot", # (옵션) 오래된 스냅샷 정리 시 "lightsail:CreateInstancesFromSnapshot",
"lightsail:GetInstanceState",
"lightsail:AllocateStaticIp", # (옵션) 고정 IP가 없을 경우 새로 할당 "lightsail:GetStaticIps",
"lightsail:AttachStaticIp",
"lightsail:ReleaseStaticIp", # (옵션) 사용하지 않는 고정 IP 해제 "lightsail:GetDomains",
"lightsail:GetDomainEntries",
"lightsail:UpdateDomainEntry",
"lightsail:GetInstance",
"lightsail:GetInstances",
"lightsail:PutInstancePublicPorts",
"lightsail:DeleteInstance"
],
"Resource": ""
}
]
}
- Trust Policy:
JSON{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
2. Lambda Function Code (Python 3.9+)
이 코드는 단일 Lambda 함수로 모든 단계를 순차적으로 실행합니다. 실제 운영에서는 Step Functions로 분리하는 것이 더 견고하지만, 여기서는 요청에 따라 하나의 함수로 구현합니다.
Python
import boto3
import datetime
import time
# --- 설정 변수 (반드시 YOUR_ 로 시작하는 부분은 실제 값으로 변경하세요!) ---
# Lambda 환경 변수로 관리하는 것을 권장합니다.
SOURCE_INSTANCE_NAME = 'YOUR_INSTANCE_NAME' # 원본 Lightsail 인스턴스 이름
NEW_INSTANCE_BUNDLE_ID = 'medium_2_0' # 새 인스턴스의 사양 (예: 2GB RAM, 1vCPU)
# 'nano_2_0', 'small_2_0', 'medium_2_0', 'large_2_0', 'xlarge_2_0', '2xlarge_2_0'
AVAILABILITY_ZONE = 'ap-northeast-2a' # Lightsail 인스턴스와 동일한 가용 영역
# 'ap-northeast-2a', 'ap-northeast-2b', 'ap-northeast-2c' 등
KEY_PAIR_NAME = 'YOUR_KEY_PAIR_NAME' # 인스턴스에 연결할 키 페어 이름 (Lightsail 콘솔에서 생성)
STATIC_IP_NAME = 'YOUR_STATIC_IP_NAME' # 기존 인스턴스에 연결된 고정 IP 이름
DOMAIN_NAME = 'YOUR_DOMAIN_NAME.com' # 도메인 이름 (예: example.com)
SUBDOMAIN_NAME = '@' # 서브도메인 이름 ('@'는 루트 도메인, 'www' 등)
# 주의: DNS_ENTRY_ID는 Lightsail API로 찾아야 합니다. 아래 코드에서 동적으로 찾습니다.
# 만약 단일 A 레코드만 있다면 아래 코드가 작동할 가능성이 높습니다.
# 여러 A 레코드 중 특정 레코드만 업데이트하려면 ID를 명시해야 합니다.
# 새 인스턴스에 적용할 방화벽 포트 규칙 (예시: SSH, HTTP, HTTPS)
# 모든 포트 (0-65535)를 열고 싶다면 'fromPort': 0, 'toPort': 65535 로 설정
PUBLIC_PORT_RULES = [
{'protocol': 'TCP', 'fromPort': 22, 'toPort': 22, 'accessFrom': 'Anywhere (::/0)', 'accessType': 'Public'},
{'protocol': 'TCP', 'fromPort': 80, 'toPort': 80, 'accessFrom': 'Anywhere (::/0)', 'accessType': 'Public'},
{'protocol': 'TCP', 'fromPort': 443, 'toPort': 443, 'accessFrom': 'Anywhere (::/0)', 'accessType': 'Public'}
]
# --- Lambda 핸들러 함수 ---
def lambda_handler(event, context):
lightsail_client = boto3.client('lightsail')
current_time = datetime.datetime.now()
timestamp = current_time.strftime('%Y%m%d%H%M%S')
# 새 인스턴스 이름 및 스냅샷 이름 정의
snapshot_name = f"{SOURCE_INSTANCE_NAME}-snapshot-{timestamp}"
new_instance_name = f"{SOURCE_INSTANCE_NAME}-new-{timestamp}"
print(f"--- Lightsail Instance Rotation Process Started ---")
print(f"Source Instance: {SOURCE_INSTANCE_NAME}")
print(f"New Instance Name: {new_instance_name}")
print(f"Snapshot Name: {snapshot_name}")
try:
# --- 1. 인스턴스 스냅샷 생성 ---
print(f"\n[Step 1/6] Creating snapshot '{snapshot_name}' for instance '{SOURCE_INSTANCE_NAME}'...")
lightsail_client.create_instance_snapshot(
instanceSnapshotName=snapshot_name,
instanceName=SOURCE_INSTANCE_NAME
)
print(f"Snapshot creation initiated. Waiting for snapshot to be available...")
# 스냅샷이 'available' 상태가 될 때까지 대기
# 이 과정이 가장 오래 걸릴 수 있으므로, 충분한 타임아웃 고려
wait_for_resource_state(lightsail_client, 'instanceSnapshot', snapshot_name, 'available')
print(f"Snapshot '{snapshot_name}' is now available.")
# --- 2. 새 인스턴스 생성 ---
print(f"\n[Step 2/6] Creating new instance '{new_instance_name}' from snapshot '{snapshot_name}'...")
lightsail_client.create_instances_from_snapshot(
instanceNames=[new_instance_name],
instanceSnapshotName=snapshot_name,
bundleId=NEW_INSTANCE_BUNDLE_ID,
availabilityZone=AVAILABILITY_ZONE,
keyPairName=KEY_PAIR_NAME,
tags=[{'key': 'AutoCreated', 'value': 'True'},
{'key': 'OriginalInstance', 'value': SOURCE_INSTANCE_NAME}]
)
print(f"New instance creation initiated. Waiting for instance '{new_instance_name}' to be 'running'...")
# 새 인스턴스가 'running' 상태가 될 때까지 대기
wait_for_resource_state(lightsail_client, 'instance', new_instance_name, 'running')
print(f"New instance '{new_instance_name}' is now running.")
# --- 3. 고정 IP 재연결 ---
print(f"\n[Step 3/6] Attaching static IP '{STATIC_IP_NAME}' to new instance '{new_instance_name}'...")
# attach_static_ip는 자동으로 기존 인스턴스에서 분리하고 새 인스턴스에 연결합니다.
lightsail_client.attach_static_ip(
staticIpName=STATIC_IP_NAME,
instanceName=new_instance_name
)
print(f"Static IP '{STATIC_IP_NAME}' attached to '{new_instance_name}' successfully.")
# 새로 연결된 고정 IP의 실제 IP 주소 가져오기
static_ip_info = lightsail_client.get_static_ip(staticIpName=STATIC_IP_NAME)
new_public_ip = static_ip_info['staticIp']['ipAddress']
print(f"New Public IP address is: {new_public_ip}")
# --- 4. DNS 레코드 업데이트 ---
print(f"\n[Step 4/6] Updating DNS record for '{SUBDOMAIN_NAME}.{DOMAIN_NAME}' to '{new_public_ip}'...")
# 기존 DNS Entry ID 찾기 (동적으로 찾을 수 없는 경우 하드코딩 필요)
domain_entries = lightsail_client.get_domain_entries(domainName=DOMAIN_NAME)['domainEntries']
dns_entry_id = None
for entry in domain_entries:
if entry.get('name') == SUBDOMAIN_NAME and entry.get('type') == 'A':
dns_entry_id = entry['id']
break
if not dns_entry_id:
raise Exception(f"Could not find existing A record for '{SUBDOMAIN_NAME}' in domain '{DOMAIN_NAME}'. "
"Please ensure the DNS entry exists or provide its ID.")
lightsail_client.update_domain_entry(
domainName=DOMAIN_NAME,
domainEntry={
'id': dns_entry_id,
'name': SUBDOMAIN_NAME,
'type': 'A',
'target': new_public_ip
}
)
print(f"DNS record updated for '{SUBDOMAIN_NAME}.{DOMAIN_NAME}' to '{new_public_ip}' successfully.")
# --- 5. Lightsail 방화벽 규칙 적용 ---
print(f"\n[Step 5/6] Setting public port rules for new instance '{new_instance_name}'...")
lightsail_client.put_instance_public_ports(
instanceName=new_instance_name,
portInfos=PUBLIC_PORT_RULES
)
print(f"Public port rules applied to '{new_instance_name}' successfully.")
# --- 6. 이전 인스턴스 삭제 ---
print(f"\n[Step 6/6] Deleting old instance '{SOURCE_INSTANCE_NAME}'...")
lightsail_client.delete_instance(instanceName=SOURCE_INSTANCE_NAME)
print(f"Old instance '{SOURCE_INSTANCE_NAME}' deletion initiated.")
# (옵션) 사용하지 않는 오래된 스냅샷 정리 (예: 3개 이상의 스냅샷이 있을 경우 가장 오래된 것 삭제)
# print("\n[Optional] Cleaning up old snapshots...")
# snapshots = lightsail_client.get_instance_snapshots(instanceName=SOURCE_INSTANCE_NAME)['instanceSnapshots']
# sorted_snapshots = sorted(snapshots, key=lambda x: x['createdAt'], reverse=True)
# if len(sorted_snapshots) > 3:
# for old_snap in sorted_snapshots[3:]:
# print(f"Deleting old snapshot: {old_snap['name']}")
# lightsail_client.delete_instance_snapshot(instanceSnapshotName=old_snap['name'])
except Exception as e:
print(f"\n--- ERROR during Lightsail Instance Rotation ---")
print(f"An error occurred: {e}")
# 실패 시 롤백 또는 알림 (SNS 등) 로직 추가 필요
raise # Lambda 실행 실패를 알림
print(f"\n--- Lightsail Instance Rotation Process Completed Successfully ---")
# --- 헬퍼 함수: 리소스 상태 대기 ---
def wait_for_resource_state(client, resource_type, resource_name, desired_state, max_attempts=120, wait_interval=5):
"""
Lightsail 리소스(스냅샷, 인스턴스)가 특정 상태가 될 때까지 기다립니다.
Args:
client: boto3 Lightsail 클라이언트
resource_type: 'instanceSnapshot' 또는 'instance'
resource_name: 대상 리소스의 이름
desired_state: 목표 상태 (예: 'available', 'running')
max_attempts: 최대 시도 횟수
wait_interval: 각 시도 사이의 대기 시간(초)
"""
print(f" Waiting for {resource_type} '{resource_name}' to reach '{desired_state}' state...")
for attempt in range(max_attempts):
try:
if resource_type == 'instanceSnapshot':
response = client.get_instance_snapshot(instanceSnapshotName=resource_name)
current_state = response['instanceSnapshot']['state']
elif resource_type == 'instance':
response = client.get_instance(instanceName=resource_name)
current_state = response['instance']['state']['name'] # 인스턴스는 state.name 속성
else:
raise ValueError("Unsupported resource_type.")
if current_state == desired_state:
return True
elif current_state == 'failed' or current_state == 'error': # 스냅샷 또는 인스턴스 생성 실패 시
raise Exception(f"{resource_type} '{resource_name}' entered '{current_state}' state.")
print(f" Current state: {current_state}. Retrying in {wait_interval} seconds...")
time.sleep(wait_interval)
except client.exceptions.NotFoundException:
# 리소스가 아직 생성 중이거나 조회되지 않는 경우
print(f" {resource_type} '{resource_name}' not found yet. Retrying...")
time.sleep(wait_interval)
except Exception as e:
print(f" Error while waiting for {resource_type} state: {e}")
raise
raise TimeoutError(f"Timeout waiting for {resource_type} '{resource_name}' to reach '{desired_state}' state.")
3. CloudWatch Events Rule (스케줄 설정)
AWS Console에서 CloudWatch 서비스로 이동하여 EventBridge (CloudWatch Events)
-> 규칙
-> 규칙 생성
을 통해 설정합니다.
- 이름:
LightsailInstanceRotationScheduler
(예시) - 설명: 3개월마다 Lightsail 인스턴스를 스냅샷으로 교체하는 자동화
- 규칙 유형:
일정
- 일정 패턴:
Cron 표현식
- 3개월마다 실행 (매월 1일 0시 0분 UTC 기준):
0 0 1 */3 ? *
0
: 분 (0분)0
: 시 (0시)1
: 일 (1일)*/3
: 월 (3개월마다)?
: 요일 (특정하지 않음)*
: 연도 (매년)- 주의: CloudWatch Events는 UTC 시간을 사용합니다. KST는 UTC+9이므로, 만약 KST로 0시 0분에 실행하고 싶다면, UTC 15시 0분에 맞춰
0 15 1 */3 ? *
로 설정해야 합니다.
- 3개월마다 실행 (매월 1일 0시 0분 UTC 기준):
- 대상:
Lambda 함수
선택 후, 위에서 생성한 Lambda 함수를 선택합니다.
4. Lightsail 콘솔에서 설정
- 원본 인스턴스:
YOUR_INSTANCE_NAME
에 해당하는 Lightsail 인스턴스가 존재해야 합니다. - 키 페어:
YOUR_KEY_PAIR_NAME
에 해당하는 키 페어가 Lightsail 콘솔에 생성되어 있어야 합니다. (인스턴스 SSH 접속용) - 고정 IP:
YOUR_STATIC_IP_NAME
에 해당하는 고정 IP가 생성되어 있고,YOUR_INSTANCE_NAME
에 연결되어 있어야 합니다. - DNS 영역:
YOUR_DOMAIN_NAME.com
에 해당하는 DNS 영역이 Lightsail에 생성되어 있고,SUBDOMAIN_NAME
(예:@
)에 해당하는 A 레코드가 존재해야 합니다. (코드에서dns_entry_id
를 찾기 위함) - 방화벽 포트:
PUBLIC_PORT_RULES
변수에 지정된 포트들이 실제로 필요한 포트인지 확인해야 합니다.
배포 순서
- IAM Role 생성 (위에 명시된 권한 포함).
- Lambda 함수 생성:
Python 3.9
이상 런타임 선택.- 함수 이름 지정 (예:
LightsailInstanceRotator
). - 실행 역할로 1단계에서 생성한 IAM Role 선택.
- 코드 편집기에 위의 Python 코드 복사 붙여넣기.
- 설정 변수 (YOUR_로 시작하는 부분)를 실제 값으로 수정.
- Lambda 함수 핸들러를
main.lambda_handler
(기본값)로 설정. - 타임아웃 설정: 스냅샷 생성 및 인스턴스 대기 시간이 길 수 있으므로, Lambda 함수의 타임아웃을 **최소 5분 이상 (권장 10분 이상)**으로 충분히 늘립니다. (기본값 3초는 부족합니다.)
- 메모리도 필요에 따라 256MB 이상으로 늘려줍니다.
- CloudWatch Events Rule 생성: 2단계에서 생성한 Lambda 함수를 대상으로 지정하고, 원하는 스케줄(3개월마다)을 설정합니다.
- 최초 실행 테스트: 스케줄에 따라 자동 실행되기 전에, Lambda 콘솔에서
테스트
버튼을 눌러 수동으로 한 번 실행하여 전체 프로세스가 정상 작동하는지 확인하는 것이 매우 중요합니다. (단, 이 경우 인스턴스가 즉시 교체되므로 주의!)
이 자동화 프로세스는 Lightsail 인스턴스의 유지보수 및 업그레이드를 자동화하는 강력한 도구가 될 수 있습니다.