EC2의 프리티어 인스턴스는 t2.micro 사양일때 월 750간을 무료로 쓸 수 있다. 즉, 매월 1개의 인스턴스는 무료로 사용할 수 있다는 뜻이다. 하지만 지금 진행중인 프로젝트는 2개의 인스턴스를 필요로 한다. API 서버용으로 사용되는 인스턴스 1개와, 크롤링 작업을 위한 인스턴스 1개이다. 이처럼 2개의 인스턴스를 필요로하는 프로젝트이기 때문에, 불가피하게 비용이 발생할 수 밖에 없다.
하지만 나는 가난한 자취생이기 때문에 비용을 아껴보고자 한다. 그래서 생각해낸 아이디어는 "크롤러는 어차피 하루에 10분정도만 돌면된다.", "필요할 때만 인스턴스가 켜지고 작동되면 최대한 비용을 아낄 수 있겠다!" 였다.
참고로 중지상태의 EC2 인스턴스는 완전 무료상태에 있지는 않다, 어찌되었건 그 인스턴스의 내용물들이 AWS 데이터 센터에 저장되어있는 비용은 발생하기 때문이다. 하지만 인스턴스 사용료나 데이터 송수신 시 발생하는 비용은 절감할 수 있다. 상세 내용은 여기
1. EC2 인스턴스를 실행해보자
start_instance
메서드를 이용하여 인스턴스를 실행한다.describe_instance_status
메서드를 이용하여 인스턴스가 정상적으로 켜질때 까지 대기한다.DryRun=False
f로 두고 사용했다.import time
import boto3
def start_instance():
access_key = "AWS_ACCESS_KEY"
secret_key = "AWS_SECRET_KEY"
region = "ap-northeast-2"
instance_id = "AWS_INSTANCE_ID"
# boto3 EC2 클라이언트 인스턴스 생성
ec2_client = boto3.client(
"ec2",
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region
)
start_instance(instance_id, ec2_client)
instance_running = False
while instance_running is False:
instance_running = check_instance_status(instance_id, ec2_client)
time.sleep(20)
def start_instance(instance_id: str, client):
return client.start_instances(
InstanceIds=[instance_id], DryRun=False
)
def check_instance_status(instance_id: str, client):
response = client.describe_instance_status(
Filters=[
{
"Name": "instance-state-name",
"Values": ["running"]
}
],
InstanceIds=[instance_id],
DryRun=False,
IncludeAllInstances=True
)
return is_instance_running(response)
2. EC2 인스턴스로 명령어를 보내 shell script를 실행시키자
send_command
메서드를 이용하여 EC2인스턴스로 명령어를 보낸다.get_command_invocation
메서드를 이용하여 Systems Manager에 등록된 명령어의 실행이 완료될때까지 대기한다.AmazonSSMManagedInstanceCore
권한을 열어줘야한다. 그렇지 않으면 send_command에 입력된 instance_id에 접근권한이 없다는 에러메시지가 뜰 것이다.import time
import boto3
def run_command():
access_key = "AWS_ACCESS_KEY"
secret_key = "AWS_SECRET_KEY"
region = "ap-northeast-2"
instance_id = "AWS_INSTANCE_ID"
# boto3 SSM 클라이언트 인스턴스 생성
ssm_client = boto3.client(
"ssm",
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region
)
run_crawl_job = run_crawler(instance_id, ssm_client)
command_id = run_crawl_job.get("Command").get("CommandId")
command_status = "Pending"
while command_status == "Pending" or command_status == "InProgress" or command_status == "Delayed":
command_status = get_command_status(command_id, instance_id, ssm_client)
time.sleep(20)
return
def run_crawler(instance_id: str, client):
# 입력받은 EC2 인스턴스에서 실행하고자하는 명령어들
commands = ['cd /home/ec2-user/zfind-crawler/', 'sh run.sh']
response = client.send_command(
DocumentName="AWS-RunShellScript",
Parameters={"commands": commands},
InstanceIds=[instance_id],
CloudWatchOutputConfig={
'CloudWatchLogGroupName': 'zfind-crawler-log',
'CloudWatchOutputEnabled': True
}
)
return response
def get_command_status(command_id: str, instance_id: str, client):
response = client.get_command_invocation(
CommandId=command_id,
InstanceId=instance_id,
)
return response.get("Status")
3. 명령이 종료되면 인스턴스를 중지하도록 하자.
stop_instance
메서드를 이용하여 EC2 인스턴스를 중지한다.import boto3
def stop_instance():
access_key = "AWS_ACCESS_KEY"
secret_key = "AWS_SECRET_KEY"
region = "ap-northeast-2"
instance_id = "AWS_INSTANCE_ID"
# boto3 EC2 클라이언트 인스턴스 생성
ec2_client = boto3.client(
"ec2",
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region
)
ec2_client.stop_instances(
InstanceIds=[instance_id], DryRun=False
)
최종적으로 나는 아래와 같은 스크립트 코드를 짜서 AWS Lambda에 업로드했다.
import time
import boto3
def lambda_handler(event, context):
access_key = "AWS_ACCESS_KEY"
secret_key = "AWS_SECRET_KEY"
region = "ap-northeast-2"
instance_id = "AWS_INSTANCE_ID"
ec2_client = boto3.client(
"ec2",
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region
)
ssm_client = boto3.client(
"ssm",
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region
)
start_instance(instance_id, ec2_client)
instance_running = False
while instance_running is False:
instance_running = check_instance_status(instance_id, ec2_client)
run_crawl_job = run_crawler(instance_id, ssm_client)
command_id = run_crawl_job.get("Command").get("CommandId")
command_status = "Pending"
while command_status == "Pending" or command_status == "InProgress" or command_status == "Delayed":
command_status = get_command_status(command_id, instance_id, ssm_client)
time.sleep(20)
stop_instance(instance_id, ec2_client)
return
def start_instance(instance_id: str, client):
return client.start_instances(
InstanceIds=[instance_id], DryRun=False
)
def stop_instance(instance_id: str, client):
return client.stop_instances(
InstanceIds=[instance_id], DryRun=False
)
def check_instance_status(instance_id: str, client):
response = client.describe_instance_status(
Filters=[
{
"Name": "instance-state-name",
"Values": ["running"]
}
],
InstanceIds=[instance_id],
DryRun=False,
IncludeAllInstances=True
)
return is_instance_running(response)
def run_crawler(instance_id: str, client):
commands = ['cd /home/ec2-user/zfind-crawler/', 'sh run.sh']
response = client.send_command(
DocumentName="AWS-RunShellScript",
Parameters={"commands": commands},
InstanceIds=[instance_id],
CloudWatchOutputConfig={
'CloudWatchLogGroupName': 'zfind-crawler-log',
'CloudWatchOutputEnabled': True
}
)
return response
def is_instance_running(response: dict) -> bool:
return len(response.get("InstanceStatuses", [])) > 0
def get_command_status(command_id: str, instance_id: str, client):
response = client.get_command_invocation(
CommandId=command_id,
InstanceId=instance_id,
)
return response.get("Status")
Lambda에 업로드한 코드가 잘 작동하는지 테스트해보자. 아마 lambda에서 송신하여 작동시키는 ec2 인스턴스 프로그램이 3초안에 끝날만큼 빠르게 완료되지 않는다면, lambda는 타임아웃을 뱉을 것이다. 내가 만든 크롤러는 시작부터 종료까지 약 4분가량의 시간이 소요된다.
하지만 lambda의 기본 설정상 함수의 시작부터 종료까지 3초의 제한시간 내에 동작하도록 설정되어있을 것이다. Lambda에서 함수를 선택하고 구성 > 일반구성
으로 들어가보면 함수의 제한시간을 설정할 수 있다. 넉넉하게 15분으로 설정해두자. (최대가 15분이다.) 메모리나 임시스토리지도 설정할 수 있지만, 내가 짠 코드는 함수 자체에서 뭔가를 작업하는 것이 아니고, 단순히 boto3 SDK를 이용해 EC2 인스턴스로 명령에 관한 정보만 송수신하기 때문에 굳이 건들 필요는 없다.
이렇게 하고 다시 테스트를 해보면, 1. 종료된 EC2 인스턴스가 켜지고, 2. SSM으로 지정한 EC2 인스턴스에 명령을 송신하고, 3. 명령이 완료되면 인스턴스를 종료하는 코드가 정상 작동함을 확인 할 수 있다.
매우 간편하게 1번과 3번 과정은 EC2 대시보드에서 인스턴스의 상태가 running > stop 으로 바뀌는 것을 통해 확인해볼 수 있고, 2번 과정은 명령 송신후 해당 명령의 정상 동작 여부를 AWS SSM 콘솔에서 확인 할 수 있다.
먼저 AWS SSM 콘솔로 들어간 뒤 좌측 메뉴중 명령 실행
부분을 클릭하면 위의 lambda 코드에서 boto3의 ssm client로 작동을 지시한 명령을 확인 할 수 있다.
스케쥴링 테스트를 위해 Mon, 18, Jul 2022 06시부터 10시까지 주기적인 일정으로 명령이 실행된 것을 확인해볼 수 있다.
(이건 다음 포스팅에서 설명)
안녕하세요! 포스팅 잘 읽었습니다... 다만 저는 계속 문제가 생겨서 연이어 삽질을 하고 있는데용 ㅠ 도움 좀 요청드려도 될까요!?