2025. 1. 23. 21:53ㆍProject Log/학부 졸업프로젝트
LocalStack이란?
LocalStack은 클라우드 서비스 에뮬레이터로서, 노트북에 있는 하나의 컨테이너에서 실행된다. 원격 클라우드 제공자에게 연결하지 않고도, AWS 애플리케이션 또는 Lambda를 로컬 머신에서 실행할 수 있다.
바로 AWS 서비스를 활용해서 개발하면 비용 문제도 발생하고, 온프레미스 환경에서 서버를 비롯한 각종 코드를 작성했다가 클라우드 서비스로 옮기는 시간이 걸릴 것 같아서 LocalStack을 사용하기로 했다. 깃허브에 Contributor가 엄청 많았다.
- Github 링크: https://github.com/localstack/localstack
- LocalStack Docs: https://docs.localstack.cloud/overview/
LocalStack 설치 과정
(macOS) LocalStack CLI를 설치한다. LocalStack의 버전을 확인하고 실행한다.
brew install localstack/tap/localstack-cli // 설치
localstack --version // 버전 확인
localstack start -d // 실행 (-d 옵션: 백그라운드에서 실행)
실행하면 아래와 같이 출력된다.
localstack-cli가 실행되면서, 도커 이미지와 컨테이너가 자동 생성된다. 공식 docker-compose.yml 파일에 각종 설정을 추가해서 수동으로 빌드하는 방법도 있는데, 잘못된 구성이 생길 수도 있고 관리 면에서 localstack-cli를 사용하는 편이 낫겠다고 판단했다.
LocalStack Dashboard 사이트(https://app.localstack.cloud/dashboard)에 회원가입하고 로그인한다. 나는 Github 계정으로 가입하고, Hobby Subscription을 선택했다.
Workspace > Auth Tokens 탭에서 개인용 Auth Token을 복사한다.
아래 커맨드를 실행하여 인증 토큰을 설정해 준다. license.json 파일이 표시되는 걸 볼 수 있다.
localstack auth set-token <Personal_Auth_Token>
localstack start -d
LocalStack Instances > Default Instance > Status 탭에서 사용할 수 있는 서비스들을 확인한다. Pro가 적힌 것은 유료 이용자만 사용할 수 있다. 내가 필요로 하는 API Gateway, Lambda, EventBridge는 무료 이용자도 사용할 수 있다.
커맨드로 이용할 수 있는 서비스를 확인할 수도 있다. 아래 커맨드는 Pro 서비스 유무는 알려주지 않는다.
localstack % localstack status services
'공식 Docs > User Guides > Local AWS Services > 원하는 서비스 선택' 탭에서 각 서비스의 상세한 사용 방법을 알아볼 수 있다. Lambda, API Gateway, EventBridge, EC2 사용 방법을 알아보기로 했다.
🟧 LocalStack Lambda 사용 방법
LocalStack으로 Lambda API 생성, 배포, 테스트를 할 수 있다.
awscli-local 설치 및 람다 함수 생성
index.js 파일에 람다 함수를 만든다.
exports.handler = async (event) => {
let body = JSON.parse(event.body)
const product = body.num1 * body.num2;
const response = {
statusCode: 200,
body: "The product of " + body.num1 + " and " + body.num2 + " is " + product,
};
return response;
};
index.js 파일을 function.zip 파일로 압축한다.
zip function.zip index.js
awscli와 awscli-local을 설치한다.
brew install awscli
brew install awscli-local
awslocal --version
람다 함수를 생성한다.
- lambda create-function: 함수 생성
- --function-name: 람다 함수 이름 지정
- --runtime: 함수 실행 환경 설정
- --zip-file: 람다 함수 코드의 압축 파일
- --handler: 핸들러 지정 (index.handler는 index.js 파일의 handler 함수를 의미함)
- --role: 함수가 실행되는 동안 사용할 IAM 역할 지정
- --tags: 리소스 식별을 위한 태그 추가 (선택 사항)
awslocal lambda create-function \
--function-name localstack-lambda-url-example \
--runtime nodejs18.x \
--zip-file fileb://function.zip \
--handler index.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--tags '{"_custom_id_":"my-custom-subdomain"}'
람다 함수가 정상적으로 생성되면 아래와 같이 함수 상세 정보가 출력된다. 대시보드에서도 추가된 함수를 볼 수 있다.
람다 함수 호출
람다 함수를 호출(invoke)한다. payload로 전달한 값이 람다 함수 내의 로직으로 처리된 후, 응답을 반환하고 output.txt에 저장되었다. 대시 보드에서도 로그 스트림을 확인할 수 있다.
- lambda invoke: 람다 함수 실행을 트리거
- --cli-binary-format raw-in-base64-out: 데이터를 바이너리 형식으로 처리할 때 사용하는 옵션. 입력 데이터는 원시 형식, 출력 데이터는 base64로 인코딩된 값
- --payload: 람다 함수에 전달할 입력 데이터
- output.txt: 람다 함수의 실행 결과를 저장
awslocal lambda invoke --function-name localstack-lambda-url-example \
--payload '{"body": "{\"num1\": \"10\", \"num2\": \"10\"}" }' output.txt
람다 함수 URL 생성 및 POST 요청
람다 함수에 HTTP API URL을 할당하여, HTTP 요청으로 람다 함수를 호출할 수 있게 만든다.
먼저 람다 함수 URL을 생성한다.
- lambda create-function-url-config: 람다 함수에 HTTP URL 생성
- --function-name: URL을 생성할 함수 이름 지정
- --auth-type: 람다 함수 호출 시 인증 방식 설정
- --qualifier: 람다 함수 버전 별칭 추가
awslocal lambda create-function-url-config \
--function-name localstack-lambda-url-example \
--auth-type NONE
정상적으로 URL이 생성되었다. 람다 함수에 추가했던 _custom_id_ 태그(my-custom-subdomain)가 URL에 포함되어 있다.
람다 함수에 추가한 URL을 삭제할 수도 있다.
awslocal lambda delete-function-url-config --function-name localstack-lambda-url-example
생성한 URL에 HTTP POST 요청을 보내서 람다 함수를 트리거한다. <xxxxxxxx>는 실제 URL에 맞게 수정한다.
curl -X POST \
'http://<xxxxxxxx>.lambda-url.us-east-1.localhost.localstack.cloud:4566/' \
-H 'Content-Type: application/json' \
-d '{"num1": "10", "num2": "10"}'
- curl -X POST: HTTP 요청을 POST로 설정
- -H 'Content-Type: application/json': 헤더 설정 옵션 (본문 데이터 형식이 json임을 알림)
- -d: POST 요청의 데이터 설정
🔑 LocalStack API Gateway 사용 방법
LocalStack API Gateway를 통해 REST, HTTP, WebSocket API를 생성, 배포, 관리할 수 있다. API를 통해 람다 또는 EC2와 같은 백엔드 서비스의 데이터 및 비즈니즈 로직, 기능에 접근할 수 있다.
Lambda proxy integration을 통해 람다 함수를 API Gateway와 연결한다. API Gateway과 GET 요청을 보내면, 람다 함수가 호출되어 응답을 반환하는 작업을 테스트할 것이다.
람다 함수 생성
lambda.js 파일에 람다 함수를 작성한다.
'use strict'
const apiHandler = (payload, context, callback) => {
callback(null, {
statusCode: 200,
body: JSON.stringify({
message: 'Hello from Lambda'
}),
});
}
module.exports = {
apiHandler,
}
lambda.js 파일을 lambda_function.zip으로 압축한다.
zip lambda_function.zip lambda.js
람다 함수를 생성한다. 옵션의 의미는 앞선 람다 사용 방법에서 다루었으니 생략한다.
awslocal lambda create-function \
--function-name apigw-lambda \
--runtime nodejs16.x \
--handler lambda.apiHandler \
--memory-size 128 \
--zip-file fileb://lambda_function.zip \
--role arn:aws:iam::111111111111:role/apigw
REST API 생성
새로운 REST API를 생성한다.
- apigateway create-rest-api: API Gateway의 REST API를 생성
- --name: REST API 이름 지정
awslocal apigateway create-rest-api --name 'API Gateway Lambda integration'
지정한 이름대로 REST API가 생성된 것을 출력에서 확인할 수 있다.
리소스 요청 및 생성
API를 위한 리소스를 요청한다.
- apigateway get-resources: 리소스 요청
- <REST_API_ID>: 이전 단계에서 생성한 REST API의 id
awslocal apigateway get-resources --rest-api-id <REST_API_ID>
출력된 root resource의 id를 복사해 놓는다.
새로운 리소스를 생성한다.
- apigateway create-resource: 리소스 생성
- <REST_API_ID>: REST API의 id
- <PARENT_ID>: 이전 단계에서 복사한 root resource의 id
awslocal apigateway create-resource \
--rest-api-id <REST_API_ID> \
--parent-id <PARENT_ID> \
--path-part "{somethingId}"
출력된 새로운 리소스의 id를 복사해 놓는다.
GET 함수 추가 및 통합
리소스에 GET 메서드를 추가한다.
- apigateway put-method: 메서드 추가
- <REST_API_ID>: REST API의 id
- <RESOURCE_ID>: 새로운 리소스의 id
- --http-method: 추가할 HTTP 메서드 종류
- --request-parameters: API Gateway가 요구하는 필수 파라미터 정의
- --authorization-type: API 호출 시 인증 타입
awslocal apigateway put-method \
--rest-api-id <REST_API_ID> \
--resource-id <RESOURCE_ID> \
--http-method GET \
--request-parameters "method.request.path.somethingId=true" \
--authorization-type "NONE"
새로운 Integration을 생성한다.
- apigateway put-integration: API Gateway와 특정 리소스의 통합 추가
- --type: 통합 유형 (AWS_PROXY는 람다 프록시 통합을 의미함)
- --integration-http-method: 람다 함수 호출 시 사용할 HTTP 메서드 종류 지정
- --uri: API Gateway가 람다 함수를 호출할 때 사용하는 엔드포인트 지정
- --passthrough-behavior WHEN_NO_MATCH: 요청된 데이터가 매핑되지 않아도 그대로 람다 함수에 전달
awslocal apigateway put-integration \
--rest-api-id <REST_API_ID> \
--resource-id <RESOURCE_ID> \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:apigw-lambda/invocations \
--passthrough-behavior WHEN_NO_MATCH
API Gateway와 람다 함수가 성공적으로 통합되었다. GET 요청이 API Gateway를 통해 람다로 전달되고, 람다 함수의 응답이 반환될 수 있다.
배포 및 GET 요청
create-deployment를 통해 REST API를 배포한다.
- apigateway create-development: API Gateway의 배포를 생성
- <REST_API_ID>: REST API의 id
- --stage: 배포된 API의 환경 (dev 개발 / test 테스트 / prod 프로덕션)
awslocal apigateway create-deployment \
--rest-api-id <REST_API_ID> \
--stage-name dev
배포가 잘 진행되었다.
curl -X GET을 통해 HTTP REST 클라이언트가 API 엔드포인트를 호출(invoke)한다.
curl -X GET http://<REST_API_ID>.execute-api.localhost.localstack.cloud:4566/dev/test
LocalStack 대시보드에서도 방금 설정한 내용들을 확인할 수 있다.
⏱️ LocalStack EventBridge 사용 방법
EventBridge는 다양한 AWS 서비스와 애플리케이션 간의 이벤트를 발견하고 통신할 수 있게 한다. 이벤트를 등록, 추적, 해결하고, 규칙을 적용하여 이벤트를 특정 대상으로 라우팅할 수 있다. ID 기반 정책과 리소스 기반 정책을 사용할 수 있다.
람다 함수 생성
event_lambda.js 파일에 람다 함수를 작성한다.
- event: 람다 함수에 전달된 입력 데이터
- context: 람다의 실행 환경 정보
- callback: 람다 실행 종료를 알리고 결과 반환
'use strict';
exports.handler = (event, context, callback) => {
console.log('LogScheduledEvent');
console.log('Received event:', JSON.stringify(event, null, 2));
callback(null, 'Finished');
};
event_lambda.js를 event_function.zip으로 압축한다.
zip event_function.zip event_lambda.js
람다 함수를 생성한다. 옵션의 의미는 앞선 람다 사용 방법에서 다루었으니 생략한다. zip-file 및 handler 옵션에 알맞은 파일명을 전달하도록 주의한다.
awslocal lambda create-function \
--function-name events-example \
--runtime nodejs16.x \
--zip-file fileb://event_function.zip \
--handler event_lambda.handler \
--role arn:aws:iam::000000000000:role/cool-stacklifter
EventBridge Rule 생성
EventBridge Rule을 생성한다.
- events put-rule: EventBridge Rule 생성
- --name: 규칙의 이름 지정
- --schedule-expression: 규칙 실행 주기 설정
awslocal events put-rule \
--name my-scheduled-rule \
--schedule-expression 'rate(2 minutes)'
RuleArn을 출력한다.
람다 함수에 EventBridge 규칙이 트리거될 수 있도록 권한을 추가한다.
- lambda add-permission: 람다 함수에 권한 추가
- --function-name: 권한을 추가할 람다 함수 이름
- --statement-id: 권한 추가 요청에 대한 권한 식별자 지정
- --action: 이벤트 발생 시 람다 함수 호출 권한 부여
- --principal: 권한을 요청하는 서비스 (events.amazonaws.com = EventBridge)
- --source: 권한을 부여하는 이벤트 규칙 ARN 지정
awslocal lambda add-permission \
--function-name events-example \
--statement-id my-scheduled-event \
--action 'lambda:InvokeFunction' \
--principal events.amazonaws.com \
--source-arn arn:aws:events:us-east-1:000000000000:rule/my-scheduled-rule
설정한 권한 정보들이 출력된다. 리소스의 ARN을 복사해 놓는다.
람다 함수를 Target으로 설정
targets.json 파일을 만들고, 위에서 복사한 람다 함수의 ARN 정보를 입력한다.
[
{
"Id": "1",
"Arn": "arn:aws:lambda:us-east-1:000000000000:function:events-example"
}
]
EventBridge Rule에 람다 함수를 타겟으로 추가한다.
- events put-targets: EventBridge Rule에 타겟을 추가
- --rule: EventBridge 규칙 이름
- --targets: 이벤트 발생 시 실행될 타겟 정보
awslocal events put-targets \
--rule my-scheduled-rule \
--targets file://targets.json
실패한 항목이 없다는 의미로, 성공적으로 타겟이 추가되었다.
람다 호출 검증
람다 함수가 제대로 호출되었는지 검증하는 방법이 여러 가지 있다.
1. CloudWatch 로그 그룹 확인
awslocal logs describe-log-groups
2. 특정 람다 함수의 로그 스트림 확인
awslocal logs describe-log-streams \
--log-group-name /aws/lambda/events-example
3. LocalStack 로그 확인
localstack logs
🕋 LocalStack EC2 사용 방법
Elastic Compute Cloud(EC2)는 확장 가능하고 유연한 가상 컴퓨팅 리소스를 제공한다. EC2는 사용자가 가상 머신을 생성하고 관리할 수 있게 한다. 이 가상 머신을 인스턴스라고 한다.
키페어 생성 및 임포트
키페어를 생성한다.
- ec2 create-key-pair: EC2 키 페어 생성
- --key-name: 키 이름 지정
- --query: 특정 부분만 출력하도록 쿼리 (ex. KeyMaterial)
- --output: 출력의 형식 지정
- tee: key.pem 파일에 내용을 저장하면서 화면에도 출력하게 함
awslocal ec2 create-key-pair \
--key-name my-key \
--query 'KeyMaterial' \
--output text | tee key.pem
key.pem의 권한을 변경한다.
chmod 400 key.pem
보안 그룹에 규칙 추가
기본(default) 보안 그룹에 인바운드 트래픽 규칙을 추가한다.
- ec2 authorize-security-group-ingress: 보안 그룹에 인바운드 트래픽 규칙 추가
- --group-id: 보안 그룹 지정
- --protocol: 프로토콜 종류 지정
- --port: 규칙을 추가할 포트 지정
- --cidr 0.0.0.0/0: 모든 IP 주소에서 접근 허용
awslocal ec2 authorize-security-group-ingress \
--group-id default \
--protocol tcp \
--port 8000 \
--cidr 0.0.0.0/0
EC2 인스턴스 실행
Security Group ID를 확인한다.
awslocal ec2 describe-security-groups
인스턴스가 시작되면 자동으로 웹 서버를 실행하는 스크립트 user_script.sh를 작성한다.
#!/bin/bash -xeu
apt update
apt install python3 -y
python3 -m http.server 8000
EC2 인스턴스를 실행한다.
- ec2 run-instances: EC2 인스턴스를 실행
- --image-id: AMI ID 지정
- --count: 실행할 인스턴스 수 지정
- --instance-type: 인스턴스 종류 지정
- --key-name: SSH 키 이름
- --security-group-ids: 보안 그룹 ID
- --user-data: 인스턴스가 시작될 때 실행할 스크립트
awslocal ec2 run-instances \
--image-id ami-df5de72bdb3b \
--count 1 \
--instance-type t3.nano \
--key-name my-key \
--security-group-ids '<SECURITY_GROUP_ID>' \
--user-data file://./user_script.sh
파이썬 웹 서버 테스트
EC2 인스턴스의 IP address를 찾는다.
localstack logs
위에서 얻은 IP 주소를 사용해서 파이썬 웹 서버를 테스트한다.
curl 172.17.0.3:8000
이 부분은 왜 연결이 안 되는지 모르겠다.
SSH를 통한 연결
인스턴스의 IP 주소를 사용하여, EC2에 SSH 연결을 설정할 수 있다.
ssh -p 22 -i key.pem root@127.0.0.1
SSH를 통해 EC2 인스턴스에 접속했다.
느낀 점
LocalStack의 기본적인 사용법을 익히고, 4종류의 서비스를 테스트하기까지 하루 정도 시간이 소요되었다. CLI로 하루 종일 보니까 눈이 피로하기는 한데, 앞으로 구현할 기능들의 로컬 인프라를 잘 마련해 가고 있는 것 같아서 뿌듯하다. 비용이 들어가는 AWS 서비스들을 로컬 환경에서 테스트하게 했다니 정말 기발하고, 만들어주신 분들께 감사하다. 잘 써봐야겠다.
* 참고 사이트
- LocalStack을 활용하여 AWS 환경 구축: https://velog.io/@wonjun/LocalStack을-활용하여-AWS-환경-구축
- LocalStack Docs Lambda: https://docs.localstack.cloud/user-guide/aws/lambda/
- LocalStack Docs API Gateway: https://docs.localstack.cloud/user-guide/aws/apigateway/
- LocalStack Docs EventBridge: https://docs.localstack.cloud/user-guide/aws/events/
- LocalStack Docs EC2: https://docs.localstack.cloud/user-guide/aws/ec2/