2017년 8월 20일 일요일

[AWS]AWS Lambda에 Express 위에서 작동하는 React Application 업로드(배포) 하는 방법

AWS Lambda에 Express 위에서 작동하는 React Application 업로드(배포) 하는 방법

[How to upload(deploy) React application on Express to AWS Lambda]
(부제: aws-serverless-express 사용 방법)
주의! 본 글은 AWS Lambda와 API Gateway의 사용법은 구체적으로 다루지 않습니다. 이에 대해서는 사전에 익히고 보셔야 좋습니다.

최근 AWS에서는 Serverless Architecture를 지원해주기 위해 노력하고 있는 것 같습니다.
그 일환으로 aws-serverless-express를 이용해서 AWS Lambda에 Node.js 진영의 웹 프레임워크(Web Framework)인 Express 기반의 Web Application을 배포할 수 있게 했습니다.
또한, Serverless 기반의 App을 개발하고 배포하기 편리하게 하는 serverless application model(SAM) 을 공개했습니다.
SAM은 아직 Beta라서 그런지 안정적이지는 않은 것 같습니다. SAM을 이용하면 로컬에서 Lambda에 배포한 것 같이 테스트 할 수 있고 좋은데.. 조금만 더 기다려봐야할 것 같습니다.

이번 포스팅에서는 aws-serverless-express를 이용해서 React App을 AWS Lambda에 배포하는 방법을 정리해보려 합니다. 


1. 준비

Yarn과 Node.js는 일반적인 사항이라 따로 설치 방법을 정리하지는 않겠습니다.

1-1. React-Starter-Kit

본 포스팅에서는 React App의 뼈대를 잡기 위해서 React-Starter-Kit을 사용합니다. React-Starter-Kit은 Express기반으로 작성되어 있으며, Universal-Router를 이용해서 Server-side rendering을 지원합니다.
Terminal(또는 Powershell 등)에서 아래 명령을 수행하여 공식 Github에서 프로젝트를 내려 받습니다.
아래 명령에서 마지막에 있는 MyApp은 프로젝트 소스를 내려받을 폴더 이름입니다. 그러니 각자가 원하는 이름으로 지정하시면 됩니다.
git clone -o react-starter-kit -b master --single-branch https://github.com/kriasoft/react-starter-kit.git MyApp
프로젝트 폴더 내부로 이동해서 아래 명령으로 package.json에 명시된 dependencies를 설치합니다.
cd MyApp
yarn install

1-2. aws-serverless-express

aws-serverless-express는 lambda에서 express를 작동 시킬 수 있게 해주는 미들웨어 패키지입니다.
아래 명령으로 프로젝트에 모듈을 추가해줍니다.
yarn add aws-serverless-express

이제 준비는 되었습니다. 다음으로 소스코드의 수정 및 작성을 정리하겠습니다. 


2. Lambda에서 작동시키기 위한 소스 코드 수정 및 추가 작성

기존 Express Application은 listen() 함수를 이용해서 포트를 열고 클라이언트의 접속을 대기합니다.
하지만, Lambda에서는 클라이언트의 요청을 listen()으로 받는게 아니라 aws-serverless-express가 받아서 express로 전달합니다.
이를 위해서 코드의 수정이 필요합니다.

2-1. React-Starter-Kit의 소스 코드 수정

위에서 언급했듯이 listen() 함수가 필요가 없고, aws-serverless-express가 클라이언트 요청을 받도록 수정해줘야합니다.
이를 수정하기 위해서, src/server.js 에서 아래 부분을 주석처리하거나 삭제합니다.
const promise = models.sync().catch(err => console.error(err.stack));
if (!module.hot) {
  promise.then(() => {
    app.listen(config.port, () => {
      console.info(`The server is running at http://localhost:${config.port}/`);
    });
  });
}
그리고, aws-serverless-express 패키지를 import 합니다.
다른 패키지들이 import된 곳의 맨 마지막에 import하면 됩니다.
import awsServerlessExpressMiddleware from 'aws-serverless-express/middleware';
그리고 다른 미들웨어들이 작성되어 있는 곳에 aws-serverless-express 미들웨어도 추가해 줍니다.
//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(awsServerlessExpressMiddleware.eventContext());         // <--- 추가 됨.
app.use(express.static(path.resolve(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
아래 수정은 Lambda에서 제대로 작동하지 않아서 internal error를 발생시키는 패키지를 제거하는 것입니다.
아래 부분들을 찾아서 주석처리 해주시면 됩니다.
아래 패키지 들이 무조건 Lambda에서 사용을 못하는지는 아직 깊게 파보지 않아서 모르겠으나, 당장은 필요 없어서 일단 제거합니다.(앞으로 각 패키지들도 사용하는 방법을 연구해볼 예정입니다.)
import expressGraphQL from 'express-graphql';
import passport from './passport';
import models from './data/models';
import schema from './data/schema';

...

app.use(passport.initialize());

...

app.get(
  '/login/facebook',
  passport.authenticate('facebook', {
    scope: ['email', 'user_location'],
    session: false,
  }),
);
app.get(
  '/login/facebook/return',
  passport.authenticate('facebook', {
    failureRedirect: '/login',
    session: false,
  }),
  (req, res) => {
    const expiresIn = 60 * 60 * 24 * 180; // 180 days
    const token = jwt.sign(req.user, config.auth.jwt.secret, { expiresIn });
    res.cookie('id_token', token, { maxAge: 1000 * expiresIn, httpOnly: true });
    res.redirect('/');
  },
);

...

app.use(
  '/graphql',
  expressGraphQL(req => ({
    schema,
    graphiql: __DEV__,
    rootValue: { request: req },
    pretty: __DEV__,
  })),
);

2-2. lambda.js 코드 추가 작성

lambda.js는 React-Starter-Kit에 포함되어 있는 파일이 아닙니다.
Lambda와 프로젝트 파일들을 이어주는 접점이라고 생각하시면 됩니다.
파일은 프로젝트 폴더 최상위 위치에 저장해두면 나중에 계속 복사하면 되서 편합니다.
// lambda.js
'use strict';

const awsServerlessExpress = require('aws-serverless-express');
const app = require('./server');
const server = awsServerlessExpress.createServer(app.default);

exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);
여기서 주의할 점은, awsServerlessExpress.createServer()의 인자로 그냥 app이 아니라 app.default가 들어간다는 것입니다.
왜냐하면 빌드 된 server.js는 webpack에 의해서 여러 패키지가 하나로 합쳐져 있어서 export된 모듈이 많은데 그 중에서 원하는 것은 default 로 export된 모듈이기 때문입니다.


3. 프로젝트 빌드

이제 Lambda에 파일들을 배포하기 위해서 프로젝트를 빌드합니다.
가운데 -- 만 있는거 꼭 넣어줘야한다고 합니다.
yarn run build -- --release
빌드 명령을 수행하고 나면 build 폴더가 생성됩니다.
이 build 폴더에 바로 전에 작성한 lambda.js 파일을 복사해서 넣어줍니다.

그러고나서, build폴더 내부에서 yarn 명령으로 dependencies를 설치해줍니다.

cd build
yarn

4. build 폴더 압축(zip)

이제 build 폴더를 zip 파일로 압축해서 Lambda에 배포할 수 있게 준비합니다.
어떤 압축 프로그램을 해도 좋지만 확장자가 .zip으로 되어야하고,
압축 파일을 열었을 때 build 폴더가 보이는게 아니라 build 폴더의 내부 파일들이 최상위에 바로 보이게 압축해야합니다.

5. AWS Lambda에 zip 파일 업로드

AWS Lambda에 Function을 생성하고 코드를 압축 파일 업로드 방식으로 선택해서 방금 압축한 zip 파일을 업로드합니다.
그리고 Configuration(구성)에서 handler(핸들러)를 lambda.handler로 설정해줍니다.

6. API Gateway

API Gateway를 이용해서 Lambda에 올린 React App에 접속합니다.
API 구조는 아래 그림과 같이 구성합니다. 루트(/)에 Any method를 둔 것은 Web Application의 홈 화면을 접근하기 위해서이고, /{proxy+}는 루트 디렉토리 이외의 페이지에 접근하기 위해 필요합니다.


7. 확인

API Gateway 구성을 마쳤으면 할당된 URL을 이용해서 아래 주소로 접속해 봅니다.
https://{할당된 주소}/about
본 글에서 사용한 프로젝트에서는 몇몇 코드를 지워서 그런지 홈화면이 없고 Error 페이지가 나타납니다. 그래서 about 페이지에 접근하여 정상동작을 확인합니다.
about 페이지가 정상적으로 나타나면 성공입니다.

마무리

API Gateway에서 '사용자 지정 도메인 이름'을 사용하면 원하는 도메인으로 Lambda에 올린 Web Application에 접속할 수 있습니다. 어렵지 않으니 금방 하실거에요.
개인적으로 Lambda에 Web Application을 올릴 수 있는게 너무 좋습니다. EC2에서 서비스 한다면 가용성을 높이기 위해 다수의 EC2로 서비스하고 ELB로 로드밸런싱도 걸어줘야 할텐데, Lambda에 올리면 그런 수고가 덜어지니 너무 편합니다.
React Web App을 SSR 지원없이 서비스 한다면 S3에서 웹 호스팅으로 서비스 해도 될 테지만, Application 성격에 따라 SSR이 꼭 지원되야하면 Lambda가 너무 좋은 옵션인 것 같습니다.
앞으로도 Serverless로 새로 알게되는 정보들이 있으면 열심히 정리해보겠습니다.
감사합니다.

참고 링크

댓글 없음:

댓글 쓰기