Serverless Framework를 사용하여 Express/Fastify 앱을 AWS Lambda에 배포하기
Min-jun Kim
Dev Intern · Leapcell

소개
오늘날 빠르게 변화하는 클라우드 네이티브 환경에서 개발자들은 애플리케이션을 더욱 효율적이고 비용 효율적이며 확장 가능하게 구축하고 배포할 방법을 끊임없이 모색하고 있습니다. 기존 서버 기반 배포는 종종 인프라, 확장 및 패치를 관리하는 오버헤드를 동반합니다. 특히 AWS Lambda를 사용한 서버리스 컴퓨팅은 서버 관리를 추상화하여 개발자가 애플리케이션 로직에만 집중할 수 있도록 함으로써 매력적인 대안을 제공합니다. 하지만 기존 모놀리식 또는 마이크로서비스 기반 Express 또는 Fastify 애플리케이션을 서버리스 아키텍처로 마이그레이션하는 것은 daunting해 보일 수 있습니다. 좋은 소식은 전체 코드베이스를 항상 다시 작성할 필요는 없다는 것입니다. 이 글에서는 Serverless Framework를 사용하여 Express 또는 Fastify 애플리케이션을 AWS Lambda에 쉽게 배포하고, 익숙한 JavaScript 프레임워크를 포기하지 않고 서버리스의 이점을 활용하는 방법을 살펴봅니다.
서버리스 변환 이해하기
배포 프로세스를 자세히 살펴보기 전에, 기존 웹 애플리케이션이 서버리스 패러다임에 어떻게 적합한지 이해하는 데 중요한 몇 가지 핵심 개념을 명확히 하겠습니다.
핵심 용어
- AWS Lambda: 서버를 프로비저닝하거나 관리할 필요 없이 코드를 실행할 수 있는 컴퓨팅 서비스입니다. 소비하는 컴퓨팅 시간에 대해서만 비용을 지불합니다.
- API Gateway: 개발자가 모든 규모에서 API를 쉽게 생성, 게시, 유지 관리, 모니터링 및 보안할 수 있도록 하는 완전 관리형 서비스입니다. Lambda에서 실행되는 애플리케이션의 "정문" 역할을 합니다.
- Serverless Framework: AWS를 포함한 다양한 클라우드 공급자에서 서버리스 애플리케이션을 구축, 배포 및 관리하는 데 도움이 되는 오픈 소스 CLI 도구입니다. 서버리스 리소스의 정의 및 배포를 간소화합니다.
- Lambda 프록시 통합: API Gateway에서 특정 통합 유형으로, API Gateway가 요청 전체를 백엔드 Lambda 함수로 그대로 전달하고 Lambda 응답 전체를 클라이언트로 그대로 반환할 수 있도록 합니다. 이는 Express 또는 Fastify와 같이 HTTP 요청 및 응답 객체에 직접 액세스해야 하는 프레임워크에 중요합니다.
serverless-http
: HTTP 프레임워크(Express, Fastify 등)와 AWS Lambda의 이벤트 객체 간의 다리 역할을 하는 경량 Node.js 모듈입니다. Lambda 이벤트 구조를 이러한 프레임워크에서 예상하는 표준 Node.jshttp.IncomingMessage
및http.ServerResponse
객체와 일치하도록 조정합니다.
작동 방식
기본 아이디어는 Express 또는 Fastify 애플리케이션을 AWS Lambda 함수 내에 "래핑"하는 것입니다. API Gateway를 통해 요청이 들어올 때, 간단한 이벤트로 Lambda 함수를 직접 호출하는 대신 API Gateway를 Lambda 프록시 통합으로 구성합니다.
이는 API Gateway가 원시 HTTP 요청 세부 정보(헤더, 본문, 메서드, 경로, 쿼리 매개변수)를 JSON 객체로 Lambda 함수에 보낸다는 것을 의미합니다. Lambda 함수 내에서 serverless-http
라이브러리가 중요한 역할을 합니다. 이 라이브러리는 API Gateway 프록시 이벤트를 가로채고, 이를 http.IncomingMessage
및 http.ServerResponse
객체로 변환한 다음, 이를 Express 또는 Fastify 애플리케이션의 요청 핸들러에 전달합니다. 애플리케이션은 평소와 같이 요청을 처리합니다. 애플리케이션에서 응답을 생성하면, serverless-http
가 이를 캡처하여 API Gateway에서 예상하는 형식( statusCode
, headers
및 body
가 있는 JSON 객체)으로 다시 변환하여 API Gateway로 반환하고, API Gateway는 이를 다시 클라이언트로 보냅니다.
이 아키텍처를 통해 기존 Express/Fastify 로직을 거의 수정하지 않고 실행할 수 있으며, Lambda의 자동 확장, 실행당 지불 모델 및 운영 오버헤드 감소의 이점을 누릴 수 있습니다.
실제 배포 예시
Express 애플리케이션을 사용하여 예시를 살펴보겠습니다. Fastify의 경우도 매우 유사합니다.
1. 프로젝트 설정
먼저 새 Node.js 프로젝트를 초기화합니다.
mkdir express-lambda-app cd express-lambda-app npm init -y
2. 필수 패키지 설치
Express와 serverless-http
를 설치합니다. serverless
를 전역 또는 로컬로 설치할 수도 있습니다.
npm install express serverless-http npm install -g serverless # 또는 npm install --save-dev serverless
3. Express 애플리케이션 생성 (app.js
)
Express 애플리케이션 로직이 포함된 app.js
파일을 만듭니다.
// app.js const express = require('express'); const app = express(); const port = 3000; // Lambda에서는 이 포트를 사용하지 않지만, 로컬 테스트에 유용합니다. app.use(express.json()); // application/json 파싱용 app.get('/', (req, res) => { res.send('Hello from Express on Lambda!'); }); app.get('/users/:id', (req, res) => { const userId = req.params.id; res.json({ message: `Fetching user with ID: ${userId}` }); }); app.post('/data', (req, res) => { const data = req.body; res.json({ message: 'Data received', data: data }); }); // 로컬 테스트용 if (process.env.NODE_ENV !== 'production') { app.listen(port, () => { console.log(`Local Express app listening at http://localhost:${port}`); }); } module.exports = app;
4. Lambda 핸들러 생성 (handler.js
)
이 파일에는 serverless-http
가 사용하는 실제 Lambda 함수가 포함됩니다.
// handler.js const serverless = require('serverless-http'); const app = require('./app'); // Express 앱을 serverless-http로 래핑 module.exports.handler = serverless(app);
5. Serverless 구성 (serverless.yml
)
이것이 서버리스 배포의 핵심입니다. serverless.yml
파일을 만듭니다.
# serverless.yml service: express-lambda-app frameworkVersion: '3' provider: name: aws runtime: nodejs18.x # 적합한 Node.js 런타임 선택 region: us-east-1 # 선호하는 AWS 리전 memorySize: 128 # 최소 메모리 크기 stage: dev # 배포 스테이지 (예: dev, prod) environment: # Lambda 함수 환경 변수 NODE_ENV: production apiGateway: # API Gateway가 Lambda에 원시 요청을 전달하는 데 중요한 설정 minimumCompressionSize: 1024 # 1KB보다 큰 응답에 대한 gzip 압축 활성화 functions: api: handler: handler.handler # handler.js와 해당 'handler' 내보내기를 가리킴 events: - http: # API Gateway HTTP 엔드포인트를 정의함 path: / # 루트 경로 요청을 처리함 method: any # 모든 HTTP 메서드(GET, POST, PUT, DELETE 등)를 처리함 cors: true # 이 엔드포인트에 대한 CORS 활성화 - http: path: /{proxy+} # 모든 하위 경로(예: /users, /users/123, /data)를 처리함 method: any cors: true
serverless.yml
설명:
service
: 서버리스 서비스의 이름입니다.frameworkVersion
: 사용되는 Serverless Framework 버전을 지정합니다.provider
: 클라우드 공급자(이 경우 AWS)를 구성합니다.runtime
: Lambda 함수에 대한 Node.js 버전입니다.region
: 리소스가 배포될 AWS 리전입니다.memorySize
: Lambda 함수 할당 메모리입니다.stage
: 배포 스테이지입니다.environment
: Lambda 내에서 액세스할 수 있는 환경 변수입니다.apiGateway
: API Gateway별 구성입니다.minimumCompressionSize
는 성능에 좋습니다.
functions
: AWS Lambda 함수를 정의합니다.api
: Lambda 함수의 이름입니다.handler
: 파일 및 내보낸 함수(예:handler.js
의handler
내보내기)를 지정합니다.events
: Lambda를 API Gateway HTTP 엔드포인트에 연결하는 곳입니다.- 두 개의
http
이벤트는 중요합니다. 하나는 루트 경로/
에 대한 것이고 다른 하나는 후속 경로/{proxy+}
에 대한 것입니다. 이를 통해 모든 API Gateway 요청이 단일 Lambda 함수로 라우팅되고 Express 앱에서 처리되도록 합니다.proxy+
는 기본 경로 이후 모든 경로를 캡처하는 와일드카드입니다. method: any
는 Express 앱이 모든 HTTP 메서드를 처리할 수 있도록 합니다.cors: true
는 교차 원본 요청 공유를 활성화하여 종종 프런트엔드 애플리케이션에 필수적입니다.
- 두 개의
6. 애플리케이션 배포
마지막으로 Serverless Framework CLI를 사용하여 애플리케이션을 배포합니다.
serverless deploy
CLI는 코드를 패키지하고 필요한 AWS 리소스(Lambda 함수, API Gateway 엔드포인트, IAM 역할)를 생성하며 성공적으로 배포되면 API Gateway 엔드포인트 URL을 제공합니다.
7. 애플리케이션 테스트
배포 후 제공된 API Gateway URL을 통해 Express 애플리케이션에 액세스할 수 있습니다.
예를 들어, URL이 https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
인 경우:
GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
는 "Hello from Express on Lambda!"를 반환합니다.GET https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/123
은{"message": "Fetching user with ID: 123"}
을 반환합니다.POST https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/data
에{"name": "Alice"}
와 같은 JSON 본문을 사용하면{"message": "Data received", "data": {"name": "Alice"}}
를 반환합니다.
Fastify 애플리케이션 예시
Fastify의 프로세스는 거의 동일합니다.
1. 필수 패키지 설치
npm install fastify serverless-http # 그리고 이전과 같이 serverless
2. Fastify 애플리케이션 생성 (fastify-app.js
)
// fastify-app.js const fastify = require('fastify'); const app = fastify({ logger: true }); // 더 나은 개발 경험을 위해 로거 활성화 app.get('/', async (request, reply) => { return { message: 'Hello from Fastify on Lambda!' }; }); app.get('/users/:id', async (request, reply) => { const userId = request.params.id; return { message: `Fetching user with ID: ${userId}` }; }); app.post('/data', async (request, reply) => { const data = request.body; return { message: 'Data received', data: data }; }); // 로컬 테스트용 if (process.env.NODE_ENV !== 'production') { app.listen({ port: 3000 }, (err, address) => { if (err) { app.log.error(err); process.exit(1); } console.log(`Local Fastify app listening at ${address}`); }); } module.exports = app;
3. Lambda 핸들러 생성 (fastify-handler.js
)
// fastify-handler.js const serverless = require('serverless-http'); const app = require('./fastify-app'); // Fastify 앱을 serverless-http로 래핑 (먼저 초기화되었는지 확인) // 참고: serverless-http는 앱을 생성하는 함수가 아닌 Fastify 인스턴스를 필요로 합니다. // 또한 serverless-http에 전달하기 전에 Fastify의 ready 약속을 await해야 합니다. const handler = async (event, context) => { await app.ready(); // Fastify 플러그인이 로드되었는지 확인 const serverlessHandler = serverless(app); return serverlessHandler(event, context); }; module.exports.handler = handler;
Fastify 관련 중요 참고 사항: serverless-http
는 이미 초기화된 애플리케이션 인스턴스를 예상합니다. Fastify의 경우, serverless-http
가 요청 처리를 시도하기 전에 모든 플러그인이 로드되었는지 확인하려면 일반적으로 await app.ready()
를 호출해야 합니다. 이는 serverless(app)
를 async
함수로 래핑하여 처리됩니다.
4. Fastify용 Serverless 구성 (serverless.yml
)
serverless.yml
에서 handler
경로만 업데이트하면 됩니다.
# serverless.yml (Fastify 발췌) functions: api: handler: fastify-handler.handler # fastify-handler.js와 해당 'handler' 내보내기를 가리킴 events: - http: path: / method: any cors: true - http: path: /{proxy+} method: any cors: true
그런 다음 이전과 같이 serverless deploy
를 실행합니다.
애플리케이션 시나리오 및 모범 사례
이 접근 방식은 다음과 같은 경우에 이상적입니다.
- 기존 웹 애플리케이션 마이그레이션: 전체 재작성이 필요 없습니다.
- 기존 프레임워크로 API 구축: 서버리스 환경에서 Express 또는 Fastify의 강력한 기능을 활용합니다.
- 빠른 프로토타이핑: 서버를 관리하지 않고 API를 빠르게 배포합니다.
- 마이크로서비스: 각 마이크로서비스는 Lambda에서 별도의 Express/Fastify 앱으로 배포될 수 있습니다.
모범 사례:
- Lambda 콜드 스타트 염두에 두기:
serverless-http
는 효율적이지만, 복잡한 Express/Fastify 앱은 여전히 콜드 스타트 페널티를 발생시킬 수 있습니다. 성능이 중요한 엔드포인트의 경우 프로비저닝된 동시성을 고려합니다. - 종속성 최적화: Serverless Framework 플러그인과 함께
webpack
또는esbuild
와 같은 도구를 사용하여 필요한 종속성만 번들링하여 배포 패키지 크기와 콜드 스타트 시간을 줄입니다. - 상태 관리: Lambda 함수는 상태가 없음을 기억하십시오. 영구 데이터 저장을 위해 DynamoDB, RDS, S3 또는 ElastiCache와 같은 외부 서비스를 사용합니다.
- 로깅 및 모니터링: 로그 및 메트릭에 AWS CloudWatch를 활용합니다.
- 오류 처리: Express/Fastify 앱 내에서 강력한 오류 처리를 구현하고 분산 추적을 위해 AWS X-Ray와 같은 서비스를 고려합니다.
- 환경 변수:
serverless.yml
의environment
섹션 또는 AWS Secrets Manager/Parameter Store를 민감한 구성에 사용합니다.
결론
Serverless Framework와 serverless-http
를 사용하여 Express 또는 Fastify 애플리케이션을 AWS Lambda에 배포하는 것은 서버리스 채택을 위한 강력한 경로를 제공합니다. 이를 통해 개발자는 선호하는 JavaScript 웹 프레임워크를 포기하지 않고 Lambda의 확장성, 비용 효율성 및 운영 단순성을 활용할 수 있습니다. 통합 메커니즘을 이해하고 모범 사례를 따르면 최소한의 마찰로 기존 또는 새로운 서버리스 웹 애플리케이션을 성공적으로 전환할 수 있습니다. 이 접근 방식은 개발자가 인프라 관리가 아닌 기능 구축에 집중할 수 있도록 합니다.