상세 컨텐츠

본문 제목

[Node.js] 15장 AWS와 GCP로 배포하기

21-22/21-22 Node.js

by Kimpeep 2022. 1. 24. 13:01

본문

728x90
  • 배포를 위한 사전 작업
  • 배포(AWS, GCP)

1. 서비스 운영을 위한 패키지

(1) morgan과 express-session

미들웨어를 개발용에서 배포용으로 변경

1. app.js : morgan

//변경 전
app.use(morgan('dev'));

 

//변경 후
if (process.env.NODE_ENV === 'production') {
  app.use(morgan('combined'));
} else {
  app.use(morgan('dev'));
}
  • process.env.NODE_ENV : 배포 환경인지 개발 환경인지 판단할 수 있는 환경변수(.env파일에 넣을 수 없음-정적파일이기 때문 ->cross-env)
  • 배포환경일 때, morgan을 combined모드로 사용: 더 많은 사용자 정보를 로그로 남겨 추후 버그 해결에 유용
  • 개발환경일 때, morgna을 dev모드로 사용

2. app.js : express-session

//변경 전
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));

 

//변경 후
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;
}
app.use(session(sessionOption));
  • 배포 환경일 때, proxy와 cookie.secure를 true로 변경(https 적용 시에만)

(2) 시퀄라이즈

데이터베이스 배포 환경으로 변경 -> 시퀄라이즈 수정 필요
  • 비밀번호 하드 코딩
  • JSON 파일로 변수 사용 불가 (but, 시퀄라이즈는 js파일을 설정 파일로 사용 지원)

1. config/config.json 삭제 -> config/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,
  },
};
  • js파일이므로 dotenv모듈 사용 가능: 보안 규칙에 따라 password 외의 다른 정보들도 process.env로 변경 가능(username속성이나 host속성은 각각 아이디와 DB 서버 주소 역할을 하므로 숨기는게 좋음)
  • 데이터베이스 비밀번호 .env파일에 입력

(3) cross-env

process.env(환경변수) 동적으로 변경 가능
모든 운영체제에서 동일한 방법으로 환경 변수 변경 가능

1. package.json

//변경 전
{
	.....
    
    "scripts": {
        "start": "nodemon server",
        "test": "jest"
      },
      
    .....
}

 

//변경 후
{
	.....
    
    "scripts": {
        "start": "NODE_ENV=production PORT=80 node server", //동적 설정
   	"dev": "nodemon server", //
    	"test": "jest"
      },
      
    .....
}

서버실행을 위한 스크립트 두 개로 분류

  • npm start : 배포 환경에서 사용하는 스크립트, process.env 동적 설정 명령어(process.env.NODE_ENV가 production이 되고, process.env.PORT가 80이 됨)
  • npm run dev : 개발 환경에서 사용하는 스크립트

리눅스나 맥에서는 위 방식으로 process.env 동적 설정 가능하지만, 윈도우의 경우 불가능!

윈도우의 경우, 

  • cross-env 패키지 설치
  • package.json 파일 변경
npm i cross-env

 

//변경 후(윈도우)
{
	.....
    
    "scripts": {
        "start": "cross-env NODE_ENV=production PORT=80 node server", //동적 설정(윈도우)
   	"dev": "nodemon server", //
    	"test": "jest"
      },
      
    .....
}

(4) sanitize-html, csurf

  • sanitize-html : XSS(cross site scripting)공격에 대한 조치
  • csurf : CSRF(cross site request forgery)공격에 대한 조치
npm i sanitize-html
npm i csurf

1. sanitize-html

XSS
악의적인 사용자가 사이트에 스크립트를 삽입하는 공격,
악성 사용자가 게시글이나 댓글 등을 업로드할 때 자바스크립트가 포함된 태그를 올리면, 나중에 다른 사용자가 그 게시글이나 댓글을 볼 때 그 스크립트가 실행되어서 예기치 못한 동작을 하게 됨 -> 서버에서 사용자가 게시글 업로드할 때 스크립트 포함 여부를 검사하여 제거해야 함(공격성 스크립트 유형이 많으므로 라이브러리의 도움을 받는 것이 좋음)
const sanitizeHtml = require('sanitize-html');

const html = "<script>location.href = 'https://gilbut.co.kr'</script>";
console.log(sanitizeHtml(html); //''
  • 사용자가 업로드한 html을 sanitize-html 함수로 감싸면 허용하지 않는 태그나 스크립트 제거
  • 두 번째 인수로 허용할 부분에 대한 옵션 추가 가능, 옵션 목록은 공식 문서 참고

https://www.npmjs.com/package/sanitize-html

 

sanitize-html

Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis

www.npmjs.com

2. csurf

CSRF
사용자가 의도치 않게 공격자가 의도한 행동을 하게 만드는 공격,
ex)특정 페이지에 방문할 때 저절로 로그아웃되거나, 게시글이 써지는 현상을 유도할 수 있음, 은행과 같은 사이트에서는 다른 사람에게 송금하는 행동을 넣는 등 상황에 따라 크게 악용될 수 있음
이에 대한 조치로 내가 한 행동이 내가 한 것이 맞다는 점을 인증해야 함 -> 이때 CSRF 토큰 사용, csurf 패키지는 이를 쉽게 발급하거나 검증할 수 있도록 도움
//공식 문서에서 발췌한 예제
const csrf = require('csurf');
const csrfProtection = csrf({cookie: true});

app.get('/form', csrfProtection, (req, res) => {
	 res.render('csrf', {csrfToken: req.csrfToken()});
});

app.post('/form', csrfProtection, (req, res) =>{
	res.send('ok');
});
  • 익스프레스의 미들웨어 형식으로 동작
  • CSRF 토큰을 같이 제공 : 토큰은 req.csrfToken()으로 가져올 수 있음, 프론트엔드에 렌더링된 CSRF 토큰을 나중에 form을 제출할 때 데이터와 함께 제출하면 됨(상황에 따라 CSFR토큰을 적용하는 방법이 다르므로 공식문서 참조)
  • 위 예제에서는 cookie 사용 옵션을 설정하였으므로 cookie-parser 패키지도 연결되어 있어야 함

https://www.npmjs.com/package/csurf

 

csurf

CSRF token middleware

www.npmjs.com

 

(5) pm2

원활한 서버 운영을 위한 패키지
"개발할 때 nodemon을 쓴다면, 배포할 때는 pm2를 쓴다"

주요기능

  • 서버가 에러로 인해 꺼졌을 때 서버를 다시 켜줌
  • 멀티 프로세싱
pm2의 멀티 프로세싱
멀티 스레딩은 아니지만 멀티 프로세싱 지원으로 노드 프로세스 개수를 한 개 이상으로 늘릴 수 있음.
pm2를 사용하여 여러개의 프로세스를 생성하면 다른 코어들도 사용 가능.
클라이언트로부터 요청이 올 때 알아서 요청을 여러 노드 프로세스에 고르게 분배하여 하나의 프로세스가 받는 부하가 적어져 서비스 더 원할하게 운영 가능.
그러나, 멀티 스레딩이 아니므로 서버의 메모리 같은 자원을 공유하지 못한다는 단점이 있음 -> 멤캐시드, 레디스 서비스 사용으로 극복
pm2는 nodemon과 같이 콘솔에 입력하는 명령어
npm i pm2

1-1. package.json : 

{
	.....
    
    "scripts": {
        "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js", //node server
   	"dev": "nodemon server",
    	"test": "jest"
      },
      
    .....
}
  • start 스크립트: node server에서 pm2 start server.js로 변경(pm2로 스크립트 실행)

콘솔에 npm start 입력하면 바뀐 start 스크립트 명령어로 실행

npm start 실행 화면

pm2 실행하면 node나 nodemon 명령어와는 달리 노드 프로세스가 실행된 후에 콘솔에 다른 명령어 입력 가능,
pm2가 노드 프로세스를 백그라운드로 돌리므로 가능한 것임

백그라운드에서 돌고 있는 노드 프로세스를 확인하려면 콘솔에
npx pm2 list

npx pm2 list 실행 화면

pm2 관련 추가 명령어
npx pm2 logs : 프로세스 로그 확인(-err, --lines 옵션 추가 가능)
npx pm2 kill : pm2 프로세스 종료
npx pm2 reload all : 서버 재시작(다운타임이 거의 없이 서버가 재시작되어 좋음)
npx pm2 monit : 현재 프로세스 모니터링

*다운타임: 서버가 중지되어 클라이언트가 접속할 수 없는 시간

1-2. package.json : pm2의 클러스터링 모드 사용

{
	.....
    
    "scripts": {
        "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js -i 0", //
   	"dev": "nodemon server",
    	"test": "jest"
      },
      
    .....
}
  • start 스크립트: pm2 start server.js 를 pm2 start server.js -i 0 명령어로 변경(취향에 따라 pm2 start server.js -i -1 도 많이 쓰인다고 함)
pm2 start server.js -i 0 / pm2 start server.js -i -1 
-i 뒤에 생성하길 원하는 프로세스 개수 기입,
0은 현재 CPU코어 개수만큼 프로세스 생성,
-1은 프로세스를 CPU코어 개수보다 한 개 덜 생성 : 노드 외의 다른 작업을 할 수 있게 하기 위한 코어를 남김

npx pm2 kill 실행 화면
npm start 실행 화면(코어가 6개인 경우)

 

(6) winston

실제 서버를 운영할 때 console.log와 console.error를 대체하기 위한 모듈
console.log와 console.error를 개발 중에는 편리하지만, 실제 배포 시에는 console 객체의 메서드들이 언제 호출되었는지 파악하기 힘들 뿐만 아니라 서버가 종료되는 순간 로그들도 사라진다는 점에서 좋지 않음. 이를 방지하기 위해서는 로그를 파일이나 다른 데이터베이스에 저장해야 하는데, 이때 winston 사용

 

1. winston 설치

npm i winston

2. logger.js 생성

const { createLogger, format, transports } = require('winston'); //winston 패키지

const logger = createLogger({  //createLogger 메서드로 logger 생성&설정
  level: 'info',
  format: 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)
  • format : 로그의 형식(json, label, timestamp, printf, simple, combine 등)/ 기본적으로 JSON형식으로 기록하지만 로그 기록 시간을 표시하려면 timestamp를 쓰는 것이 좋음, combine은 여러 형식을 혼합해서 사용할 때 쓰임.
  • transports : 로그 저장 방식/ new transports.File은 파일로 저장(filename 설정 가능), new transports.Console은 콘솔에 출력, 여러 로깅 방식 사용 가능하고 이 메서드들에도 level과 format 설정 가능.
위 과정으로 생성 및 설정한 logger 객체는 다른 파일에서도 사용 가능,
info, warn, error 등의 메서드를 사용하면 해당 심각도가 적용된 로그가 기록됨.
//app.js
.......

const logger = require('./logger'); //logger 객체 불러와

.......

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  logger.info('hello');  //심각도 info에 대한 로그 기록
  logger.error(error.message);  //심각도 error에 대한 로그 기록
  next(error);
});

.......
  • combined.log 파일: 심각도 info 이상의 모든 로그 기록(info, error) 
  • error.log 파일: 심각도 error 단계의 로그만 기록
winston-daily-rotate-file 패키지
로그를 날짜별로 관리할 수 있게 해주는 패키지

 

(7) helmet, hpp

서버의 각종 취약점을 보완해주는 패키지,
익스프레스 미들웨어로서 사용할 수 있음,
그러나 이 패키지들을 사용한다고 해서 모든 취약점을 방어해주는 것은 아니므로 서버를 운영할 때는 주기적으로 취약점을 점검해야 함,
개발 환경에서는 사용할 필요가 없으므로 배포 환경일 때만 적용하면 됨
npm i helmet hpp
//app.js

........

const helmet = require('helmet');  //
const hpp = require('hpp');  //

........

if (process.env.NODE_ENV === 'production') {
  app.use(morgan('combined'));
  app.use(helmet());  //
  app.use(hpp());  //
} else {
  app.use(morgan('dev'));
}

........

 

(8) connect-redis

멀티 프로세스 간 세션 공유를 위해 레디스와 익스프레스를 연결해주는 패키지
레디스 redis
기존에는 로그인할 때 express-session의 세션 아이디와 실제 사용자 정보가 메모리에 저장되어, 서버가 종료되면 메모리가 날아가면서 접속자들의 로그인이 모두 풀려버림. 이를 방지하기 위해 세션 아이디와 실제 사용자 정보를 데이터베이스에 저장하는데, 이때 사용하는 데이터베이스가 레디스. 메모리 기반의 데이터베이스로 성능 우수.

1. connect-redis와 redis 데이터베이스 설치

npm i redis connect-redis

2. redislabs: 레디스를 호스팅해주는 서비스

https://redis.com/

 

Redis | The Real-time Data Platform

Build and run faster apps with the world’s leading real-time data platform.

redis.com

login 클릭
클라우드 선택
subscription 이름 입력 생성
subscription 생성 화면
데이터베이스 이름 입력 생성

  • public endpoint 복사 -< .env 파일(REDIS_HOST, REDIS_PORT)

public endpoint

  • redis password 복사 -> .env 파일(REDIS_PASSWORD)

redis password

  • app.js
..........

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

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

..........

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 }),
};

..........

이제, 세션 정보가 메모리 대신 레디스에 저장되어 로그인 후에 서버를 껐다 켜도 로그인이 유지됨!

 

(9) nvm, n

노드 버전을 업데이트하기 위한 패키지
윈도우에서는 nvm-installer 사용, 리눅스나 맥에서는 n패키지 사용

1. 윈도우

https://github.com/coreybutler/nvm-windows/releases

 

Releases · coreybutler/nvm-windows

A node.js version management utility for Windows. Ironically written in Go. - coreybutler/nvm-windows

github.com

위 페이지에 접속하여 nvm-setup.zip 다운, 압축 해제한 후 실행시켜 설치

관련 명령어
nvm list : 설치된 노드 버전 확인
nvm install [버전] : 새로운 버전 설치
nvm install latest : 최신 버전으로 설치
nvm use [버전] : 설치된 버전 사용

2. 맥, 리눅스

sudo npm i -g n
관련 명령어
n : 현재 설치된 노드 버전 확인, 설치된 버전 선택 시에도 사용
n [버전] :  입력한 버전 설치
n latest : 최신 버전 설치

3. 업그레이드 후 npm 충돌 시

Error: The module '모듈명' was compiled against a different Node.js version using NODE_MODULE_VERSION 이전 버전. This version of Node.js requires NODE_MODULE_VERSION 현재버전. Please try re-compiling or re-installing the module (for instance, using npm rebuild or npm install).
  • npm rebuild 명령어
  • node_modules 폴더 제거한 후 npm i 명령어로 패키지 재설치

 

2. 깃과 깃허브 사용하기

깃: 분산형 버전 관리 시스템(실무에서는 협업, 코드 롤백, 배포 자동화 등 다양한 용도로 사용)
깃허브: 깃으로부터 업로드한 소스 코드를 서버에 저장할 수 있는 원격 저장소(코드 공동 관리 가능)
.gitignore 파일
node_modules
uploads
*.log
coverage

실무에서는 .env파일도 추가(보안)

 

3. AWS 시작하기

https://aws.amazon.com/ko/

 

클라우드 서비스 | 클라우드 컴퓨팅 솔루션| Amazon Web Services

개발자, 데이터 사이언티스트, 솔루션스 아키텍트 또는 AWS에서 구축하는 방법을 배우는 데 관심이 있는 모든 사용자용 무료 온라인 교육 AWS 전문가가 구축한 500개 이상의 무료 디지털 교육 과정

aws.amazon.com

로그인 후 화면, lightsail 선택
인스턴스 생성
인스턴스 생성 화면, 블루프린트로 노드 선택
인스턴스 플랜 선택
인스턴스 목록 화면
인스턴스 화면

  • 인스턴스 화면의 퍼블릭IP로 접근 가능
  • 기본적으로 비트나미(bitnami)서버 실행

4. AWS에 배포하기

인스턴스 화면
SSH 화면-lightsail용 콘솔 실행

mysql 설치   7.2.3절 참조
sudo apt-get update
sudo apt-get install -y mysql-server
sudo mysql_secure_intallation
mysqladmin -u root -p password 비밀번호
git clone [url] : 깃허브 소스코드 내려받기
비트나미 아파치 서버 종료
cd /opt/bitnami
sudo ./ctlscript.sh stop apache
cd ~/node-deploy : 폴더 이동
npm i : npm 패키지 설치
npx sequelize db:create --env production : 시퀄라이즈로 mysql 데이터베이스 생성
sudo npm start : 서버 실행

pm2 실행 화면

이제 퍼블릭IP로 접속 가능

 

5. GCP 시작하기

https://console.cloud.google.com/

 

Google Cloud Platform

하나의 계정으로 모든 Google 서비스를 Google Cloud Platform을 사용하려면 로그인하세요.

accounts.google.com

새 프로젝트 생성
프로젝트 이름 입력 생성

GCP 계정 등록 화면으로 전환 > 정보 입력 > 계정 유형과 주소 입력 > 휴대폰 인증 > 카드 등록

탐색메뉴 &amp;amp;amp;gt; compute engin &amp;amp;amp;gt; vm인스턴스
인스턴스 생성
인스턴스 생성
인스턴스 목록

6. GCP에 배포하기

SSH 실행

SSH 화면

sudo su : 루트 계정으로 변경
노드와 mysql 설치(우분투 1.4.1.3절과 7.2.3절 참조)
git clone [url] : 깃허브 소스코드 내려받기

node-deploy 폴더 생성

혹시 비밀번호를 올바르게 설정했는데도 sequelize명령어에서 ERROR: Access denied for user 'root'@'localhost' 에러가 발생한다면, 
mysql 프롬프트에 다음 명령어 입력하여 비밀번호 변경
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '비밀번호';
cd node-deploy
sudo npm i
npx sequelize db:create --env production
sudo npm start

pm2 실행 화면
외부IP 확인

 

728x90

관련글 더보기