Node.js 교과서 : 15. AWS에 배포해 보기

전지연·2025년 12월 23일

GDGoC_2025

목록 보기
16/16
post-thumbnail

배포 전 주의사항

morgan과 express-session

Nordbird 앱의 익스프레스 미들웨어 중 일부, express-session을 개발용에서 배포용으로 설정

lecture-api/app.js


...

const helmet = require('helmet');
const hpp = require('hpp');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);

dotenv.config();

...

app.enable('trust proxy');   // proxy 서버 사용 시 있으면 좋음
nunjucks.configure('views', {
  express: app,
  watch: true,
});

sequelize.sync({ force: false })
  .then(() => {
    console.log('데이터베이스 연결 성공');
  })
  .catch((err) => {
    console.error(err);
  });

// 추가
if (process.env.NODE_ENV === 'production') {
  app.enable('trust proxy');
  app.use(morgan('combined'));
  app.use(helmet( contentSecurityPolicy: false ));
  app.use(hpp());
} else {
  app.use(morgan('dev'));
}

app.use(express.static(path.join(__dirname, 'public')));

...

app.use(cookieParser(process.env.COOKIE_SECRET));

const sessionOption = {
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
};

if (process.env.NODE_ENV === 'production') {  // 배포 모드인 경우
  sessionOption.proxy = true;
  // sessionOption.cookie.secure = true;   // http 적용 시엔 주석 해제
}
app.use(session(sessionOption));
app.use(passport.initialize());

...
  • process.env.NODE_ENV : 배포 환경인지 개발 환경인지 판단하는 환경 변수
    • combined가 더 log가 자세함
  • 배포 환경에선 morgan('combined'), 개발 환경에선 morgan('dev') 모드로 사용할 수 있게 설정
  • express-session : 배포 모드일 때 sessionOption을 따로 설정해 주기 위해 변수로 분리
    • 배포 환경에서 proxy와 cookie.secure를 true로 설정
      (proxy와 cookie.secure가 true인 경우는 https 적용을 위해 노드 서버 앞에 다른 서버 뒀을 경우에만)

시퀄라이즈

json은 변수 사용 불가(process.env 사용 불가) -> config.json을 config.js로 변경한다.

config.js

require('dotenv').config();

module.exports = {
    development: {
        username: 'root',
        password: process.env.SEQUELIZE_PASSWORD,
        database: 'nodebird',
        host: '127.0.0.1',
        dialect: 'mysql',
    },
    test: {
        username: "root",
        password: process.env.SEQUELIZE_PASSWORD,
        database: 'nodebird_test',
        host: '127.0.0.1',
        dialect: 'mysql',
    },
    production: {
        username: "root",
        password: process.env.SEQUELIZE_PASSWORD,
        database: 'nodebird',
        host: '127.0.0.1',
        dialect: 'mysql',
        logging: false,
    },
};
  • dotenv : 숨기고 싶은 정보를 process.env를 사용해서 가릴 수 있다.
    (예제에서는 password만 숨김 처리)
  • production 모드 시에는 프로그램 실행 시 쿼리문이 노출되지 않아야 함
    -> logging모드 false처리

.env

COOKIE_SECRET=nodebirdsecret
JWT_SECRET=nodebirdjwt
SEQUELIZE_PASSWORD=dbpassword

package.json 수정

{
  ...
  
  "scripts": {
    "start": "NODE_ENV=production PORT=80 node server",
    "dev": "nodemon server",
    "test": "jest"
  },
  
  ...
}
  • npm start : 배포 환경에서의 스크립트
    • NODE_ENV = production 버전
    • 포트 번호 = 80
  • npm run dev : 개발 환경에서 사용하는 스크립트

cross-env
: 동적으로 process.env(환경 변수)를 변경
( + 모든 운영체제에서 동일한 바식으로 환경 변수 변경)
위의 package.json에서 process.env를 설정할 수 없기 때문에 cross-env를 설치해서 사용한다.

npm i cross-env

package.json은 다음과 같이 수정한다.

"start": "cross-env NODE_ENV=production PORT=80 node server",

sanitize-html, csurf

sanitize-html, csurf 패키지는 각각 XSS, CSRF공격을 막기 위한 페이지이다.

  • XSS(Cross Site Scripting)
    : 악의적인 사용자가 사이트에 스크립트를 삽입하는 공격
    악성 사용자가 게시글/댓글 등을 업로드할 때 악성 스크립트를 삽입해 놓은 후, 다른 사용자가 해당 게시글/댓글을 클릭했을 때 악성 스크립트가 실행되도록 유도
    해결책 : 서버에서 게시글/댓글 등이 업로드되는 경우 스크립트가 포함되어 있는지 검사(sanitize-html 라이브러리 사용)

  • CSRF(Cross Site Request Forgery)
    : 사용자가 의도치 않게 공격자가 의도한 행동을 하게 만드는 공격
    저절로 로그아웃/게시글 작성, 혹은 다른 사람에게 송금하는 공격적 행동까지 유도 가능
    해결책 : 내가 한 행동이 내가 한 것이 맞다는 사실 인증 필요
    -> CSRF 토큰 사용(csurf 패키지)

pm2 사용하기

pm2 : 에러로 서버가 꺼진 경우 다시 켜주는 역할

  • 멀티 프로세싱 지원(클러스터링)
    -> 노드 프로세스 수를 1개 이상으로 늘릴 수 있음
    -> 요청을 프로세스들에 고르게 분배
  • but 프로세스 간 서버의 메모리 같은 자원 공유 불가
    => 멤케시드(Memacached), 레디스(Redis) 같은 메모리 DB 사용

패키지 설치

npm i pm2

pm2 -> 콘솔에 입력하는 명령어

package.json 수정
(nodemon 대신 pm2 사용)

  • pm2의 클러스터링 모드(1코어 모드로 쓰고 싶으면 -i 0을 삭제
  • -i 뒤에는 생성되길 원하는 프로세스 개수 기입(0은 현재 코어 개수만큼, -1은 프로세스를 CPU 코어 개수보다 한 개 덜 생성)

pm2는 노드 프로세스를 백그라운드로 실행 -> 콘솔에 다른 명령어 사용 가능
백그라운드의 노드 프로세스 확인 방법

npx pm2 list

npx pm2 list 실행한 콘솔

노드 프로세스 종료

npx pm2 kill

서버 재시작

npx pm2 reload all

켜진 프로세스 모니터링

npx pm2 monit

winston 사용하기

: console.log, console.error 는 메모리 기반 -> 서버 끄면 휘발
대체 모듈 : winston
=> 로그를 파일이나 다른 데이터베이스에 저장하는데 사용

패키지 설치

npm i winston

logger.js

const { createLogger, format, transports } = require('winston')

const logger = createLogger ({
    level: 'info',
    format.json(),
    transports: [
        new transports.File({ filename: 'combined_log'}),
        new transports.File({ filename: 'error.log', level: 'error' }),
    ],
});

if (process.env.NODE_ENV !== 'production') {
    logger.add(new transports.Console({ format: format.simple() }));
}

module.exports = logger;

logger에 대한 설정

  • level : 로그의 심각도
    { error, warn, info, verbose, debug, silly } <- error가 가장 심각
  • format : 로그의 형식
    { json, label, timestamp, printf, simple, combine }
    combine은 여러 형식 혼합
  • transports : 로그 저장 방식
    ex) new transports.File : 파일로 저장
    • File외에도 Console도 사용 가능(콘솔에 출력한다는 뜻)

logger 객체 적용

app.js

...

const logger = require('./logger');

...

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  logger.info('hello');
  logger.error(error.message);
  next(error);
});

...

helmet, hpp

: 서버의 각종 취약점을 보완해주는 패키지, 익스프레스 미들웨어로서 사용 가능

패키지 설치

npm i helmet hpp

app.js 수정

배포 환경에서나 적용

...

if (process.env.NODE_ENV === 'production') {
  app.enable('trust proxy');
  app.use(morgan('combined'));
  app.use(helmet({
    contentSecourityPolicy: false,
    crossOriginEmbedderPolicy: false,
    crossOriginResourcePolicy: false,
  }));
  app.use(hpp());
} else {
  app.use(morgan('dev'));
}

...
  • 보안 규칙 빡세면 서비스 작동 안할수도 -> 적당히 helmet의 필요 없는 옵션은 해제

redis에 세션 저장하기

기존에는 로그인할 때 사용한 express-session의 세션 id와 실제 사용자 정보가 모두 메모리에 저장되어 있었기 때문에 서버 종료되면 모두 날아감
-> Redis라는 별도 메모리 기반 데이터베이스에 정보를 저장

connect-redis

: 멀티 프로세션 간 세션 공유를 위해 Redis와 express를 연결해주는 패키지

redis라는 DB는 따로 설치가 필요

패키지 설치
이건 mysql같은 드라이브에 가까움

npm i redis connect-redis

진짜 Redis 설치
1. Redis 공식 홈페이지에 로그인
2. DB 정보를 입력한다.

3. Public endpoint와 Default user password를 각각 찾아 ENV의 REDIS_HOST, REDIS_PASSWORD에 입력한다.

  1. HOST의 맨 마지막 PORT를 지우고 따로 REDIS_PORT를 작성한다.

app.js

...

const redis = require('redis');
const RedisStore = require('connect-redis')(session);
const logger = require('./logger');

dotenv.config();

const redisClient = redis.createClient({
  url:`redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
  password: process.env.REDIS_PASSWORD,
  legacyMode: true,
})

...

app.use(cookieParser(process.env.COOKIE_SECRET));

// 수정
const sessionOption = {
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
  store: new RedisStore({ client: redisClient }),
};

...

express-rate-limit
express-rate-limit도 메모리에 사용량 기록
-> Redis에 기록하면 좋음
(rate-limit-redis + express-rate-limit 같이 사용하면 됨)

AWS에 배포하기

Git과 Github 사용하기

이전에 먼저 git과 github 설치 필요

github에 node-deploy 리포지토리 하나 만든다.

프로젝트를 Nodebird에 올린다.

AWS 시작

  1. AWS 웹 사이트에 접속해 회원 가입한다.

  2. 리전 설정(한국으로)

  3. lightsail 검색

  4. 인스턴스 생성

  5. Node.js 선택

  6. 서버 생성된 것을 확인

  7. 인스턴스로 들어간 후 'ssh를 사용하여 연결' 클릭

  8. 새로 뜬 SSH 창에 다음과 같은 명령어를 차례대로 입력

  9. 무엇을 설치할 지 물어보면 MySQL Server & Cluster(Currently selected: mysql-8.0)으로 설치한다.

  10. 다음과 같은 명령어를 차례로 입력한다.

sudo apt update
sudo apt-get install -y mysql-server
  1. mysql-server 설치 중 비밀번호 설정 화면이 나타나면 NordBird 실습 시 사용한 MySQL 비밀번호를 입력하는 것이 좋음

  2. Use Legacy Authentication Method 선택

  3. MySQL에 접속해 정상 작동하는지 확인

sudo mysql -u root -p
  1. 이제 git clone 명령어를 사용해 깃허브에 올렸던 소스 코드 내려받는다.
git clone https://--/node-deploy

레포지토리가 public이냐 private이냐에 따라 clone 방식이 달라진다.

  • public인 경우 : 비밀번호 필요X
  • private인 경우 : username, password에 나오는데 password에는 깃허브 계정에서 발급한 토큰을 넣어야 한다.
    (자세한 건 여기 참고)

node-deploy 폴더가 생성됨을 확인

  1. npm ci로 node_modules를 설치한다.(npm i는 사용x)
  1. 안에 .env 파일을 수동으로 작성해 준다.

  2. 서버 실행 전 Lightsail에선 기본적으로 비트나미 아파치 서버가 켜져 있음 -> 종료

cd /opt/bitnami
sudo ./ctlscript.sh stop apache

서버 실행 확인 -> sudo pm2 list

이래도 서버가 실행되지 않았다면 sudo pm2 logs --err 명령어 입력해서 어떤 에러 가 발생했는지 확인
-> 에러 해결 후 sudo pm2 reload all로 재시작

추후 소스 코드 수정해서 업데이트한 내용으로 배포 -> git pull 사용

git pull
sudo pm2 reload all
  1. http://퍼블릭IP로 접속

0개의 댓글