티스토리 뷰

책/nodejs 교과서

15. AWS와 GCP로 배포하기

안양사람 2021. 3. 14. 01:13
728x90
SMALL

서비스 운영을 위한 패키지

NodeBird 수정

 

app.js

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


const sessionOption = {
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: true,
  },
};
if (process.env.NODE_ENV === 'production') {
  sessionOption.proxy = true;
  // sessionOption.cookie.secure=true;
}
app.use(session(sessionOption));

 

morgan모드 if else문으로 바꾸고

배포 환경일 때는 proxy와 cookie.secure를 true로 바꾼다. 하지만 무조건 이렇게 적용해야 하는 것은 아니다. proxy를 true로 적용해야 하는 경우는 https 적용을 위해 노드 서버 앞에 다른 서버를 두었을 때이다. cookie.secure도 https를 적용할 때만 true로 바꾼다.

 

시퀄라이즈

시퀄라이즈에서 가장 큰 문제는 비밀번호가 하드 코딩되어 있다는 것이며, JSON 파일이므로 변수를 사용할 수 없다. 다행히 시퀄라이즈는 js파일을 설정 파일로 쓸수있다. 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',
    logging: false,
  },
  test: {
    username: 'root',
    password: process.env.SEQUELIZE_PASSWORD,
    database: 'nodebird_test',
    host: '127.0.0.1',
    dialect: 'mysql',
    logging: false,
  },
  production: {
    username: 'root',
    password: process.env.SEQUELIZE_PASSWORD,
    database: 'database_production',
    host: '127.0.0.1',
    dialect: 'mysql',
  },
};

 

corss-env

corss-env 패키지를 사용하면 동적으로 process.env(환경 변수)를 변경할 수 있다. 또한, 모든 운영체제에서 동일한 방법으로 환경 변수를 변경할 수 있게 된다.

package.json

  "scripts": {
    "start": "NODE_ENV=production PORT=80 node server",
    "dev": "nodemon server",

 

npm start는 배포 환경, npm run dev는 개발 환경에서 사용

npm start 시에 실행되는 명령어는 process.env를 동적으로 설정하는 방법.(process.env.NODE_ENV, process.env.PORT)

 

그런데 윈도우에서는 안돼

npm i cross-env

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

 

sanitize-html csurf

각각 XSS(Cross Site Scripting), CSRF(Cross Site Request Forgery) 공격을 막기 위한 패키지

npm i sanitize-html    npm i csurf

 

XSS는 악의적인 사용자가 사이트에 스크립트를 삽입하는 공격

사용법

사용자가 업로드한 HTML을 sanitize-html 함수로 감싸면 허용하지 않는 태그나 스크립트는 제거된다. 두번째 인수로 허용할 부분에 대한 옵션이 있다.(공식문서 참조)

const sanitizeHtml=require('sanitie-html');

const html="<script>location.href='https://gilbut.co.kr'</script>";
console.log(sanitizeHtml(html)); // ''

 

CSRF는 사용자가 의도치 않게 공격자가 의도한 행동을 하게 만드는 공격이다. 예를들어 특정 페이지에 방문할 때 저절로 로그아웃, 게시글 써지는 현상 유도

이 공격을 막으려면 내가 한 것이 맞다는 점을 인증해야 한다. 이때 CSRF 토큰이 사용되고, csuf 패키지는 이 토큰을 쉽게 발급하거나 검증할 수 있도록 돕는다.

예제

익스프레스의 미들웨어 형식으로 동작하며 form 같은 것을 렌더링할 때 CSRF 토큰을 같이 제공한다. 현재 cookie를 사용하는 것으로 옵션을 설정했으므로 cookie-parse 패키지도 연결되어 있어야 한다.

csrf 토큰을 form을 제출할 때 데이터와 함께 제출하면 된다.

const csrf = require('csrf');
const app = require('./app');
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');
});

 

pm2

npm i pm2

개발할 때 nodemon을 쓴다면, 배포할 때는 pm2를 쓴다는 말이 있을 정도로 유용하다. 가장 큰 기능은 서버가 에러로 인해 꺼졌을 때 서버를 다시 켜주는 것

또 하나의 중요한 기능은 멀티 프로세싱. 멀티 스레딩은 아니지만 멀티 프로세싱을 지원하여 노드 프로세스 개수를 한 개 이상으로 늘릴 수 있다. 기본적으로는 CPU 코어를 하나만 사용하는데, pm2를 사용해서 프로세스를 여러 개 만들면 다른 코어들까지 사용할 수 있다. 클라이언트로부터 요청이 올 때 알아서 요청을 여러 노드 프로세스에 고르게 분배한다. 하나의 프로세스가 받는 부하가 적어지므로 서비스를 덛 원할하게 운영할 수 있다.

단점도 있다. 멀티 스레딩이 아니므로 서버의 메모리 같은 자원을 공유하지 못한다. 지금까지 세션을 메모리에 저장했는데, 메모리를 공유하지 못해서 프로세스 간에 세션이 공유되지 않게 된다. 로그인 후 새로고침을 해서 세션 메모리가 있는 프로세스로 요청이 가면 로그인된 상태, 세션 메모리가 없는 프로세스로 요청이 가면 로그인되지 않은 상태가 되는것이다.

이를 극복하려면 세션을 공유해야 한다. 이를 위해 주로 맴캐시드나 레드스 같은 서비스를 사용한다.

  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js",

 

note. 리눅스나 맥에서 1024번 이하의 포트를 사용하려면 관리자 권한 필요. sudo를 앞에 붙이자 sudo npm start

 

npx pm2 list

npx pm2 logs

에러 로그만 보고 싶으면 npx pm2 logs =err

출력 줄 수를 바꾸고 싶다면 --lines 숫자 옵션

npx pm2 kill

npx pm2 reload all

 

노드의 cluster 모듈처럼 클러스터링을 가능하게 하는 pm2의 클러스터링 모드를 사용해보자

  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js -i 0",

-i 뒤에 생성하길 원하는 프로세스 개수를 기입하면 된다. -은 현재 CPU 코어 개수만큼 프로세스 생성

-1은 프로세스를 cpu 코어 개수보다 한 개 덜 생성

 

&&로 여러 명령어 연달아 실행

$ npx pm2 kill && npm start

┌─────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name      │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ server    │ default     │ 0.0.1   │ cluster │ 28636    │ 9s     │ 0    │ online    │ 0%       │ 69.4mb   │ User     │ disabled │
│ 1   │ server    │ default     │ 0.0.1   │ cluster │ 24980    │ 8s     │ 0    │ online    │ 0%       │ 69.2mb   │ User     │ disabled │
│ 2   │ server    │ default     │ 0.0.1   │ cluster │ 23344    │ 8s     │ 0    │ online    │ 0%       │ 69.8mb   │ User     │ disabled │
│ 3   │ server    │ default     │ 0.0.1   │ cluster │ 23920    │ 8s     │ 0    │ online    │ 0%       │ 69.4mb   │ User     │ disabled │
│ 4   │ server    │ default     │ 0.0.1   │ cluster │ 25492    │ 7s     │ 0    │ online    │ 0%       │ 69.4mb   │ User     │ disabled │
│ 5   │ server    │ default     │ 0.0.1   │ cluster │ 9164     │ 7s     │ 0    │ online    │ 0%       │ 69.5mb   │ User     │ disabled │
│ 6   │ server    │ default     │ 0.0.1   │ cluster │ 16688    │ 6s     │ 0    │ online    │ 0%       │ 69.7mb   │ User     │ disabled │
│ 7   │ server    │ default     │ 0.0.1   │ cluster │ 12640    │ 5s     │ 0    │ online    │ 0%       │ 69.7mb   │ User     │ disabled │
│ 8   │ server    │ default     │ 0.0.1   │ cluster │ 7328     │ 5s     │ 0    │ online    │ 0%       │ 69.5mb   │ User     │ disabled │
│ 9   │ server    │ default     │ 0.0.1   │ cluster │ 21236    │ 4s     │ 0    │ online    │ 0%       │ 69.5mb   │ User     │ disabled │
│ 10  │ server    │ default     │ 0.0.1   │ cluster │ 12160    │ 4s     │ 0    │ online    │ 1.6%     │ 69.3mb   │ User     │ disabled │
│ 11  │ server    │ default     │ 0.0.1   │ cluster │ 27184    │ 3s     │ 0    │ online    │ 47.6%    │ 69.6mb   │ User     │ disabled │
└─────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

 

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: 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;

 

level은 로그의 심각도. error, warn, info, verbose, debug, silly가 있다.(error가 가장 심각)

info를 고르면 info보다 심한 단계의 로그도 함께 기록

format은 로그의 형식. json, label, timestamp, printf, simple, combine 등의 다양한 형식.

기본적으로는 JSON, 로그 기록 시간을 표시하려면 timestamp가 좋아. combine은 여러 형식 혼합할때(공식문서 참조)

transports는 로그 저장 방식. new transports.File은 파일로 저장, new transports.Console은 콘솔에 출력. 이 메서드들도 level, format 등을 설정할 수 있다.

 

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

npm run dev

/abcd에 들어가면 combined.log, error.log에 내용 추가된것 보여

 

helmet, hpp

서버의 각종 취약점을 보완해주는 패키지, 익스프레스 미들웨어로 사용. 배포 환경에만 적용

npm i helmet hpp

if (process.env.NODE_ENV === 'production') {
  app.use(morgan('combined'));
  app.use(helmet({ contentSecurityPolicy: false }));
  app.use(hpp());

 

자세한 내용은 공식문서 참조

 

connect-redis

멀티 프로세스 간 세션 공유를 위해 레디스와 익스프레스를 연결해주는 패키지. 기존에는 로그인할 때 express-session의 세션 아이디와 실제 사용자 정보가 메모리에 저장된다.

따라서 서버가 종료되어 메모리가 날라가면 접속자들의 로그인이 모두 풀려버린다. 이를 방지하기 위해 세션 아이디와 실제 사용자 정보를 데이터베이스에 저장한다. 이때 사용하는 데이터베이스가 레디스이다.

 

npm i redis connect-redis

 

레디스를 사용하려면 레디스 데이터베이스를 설치해야 하지만 호스팅해주는 서비스를 쓰는 것이 편해. redislabs

가입하고 Endpoint와 Redis Password를 .env에 붙여 넣자. 

 

.env

REDIS_HOST=redis-18954.c92.us-east-1-3.ec2.cloud.redislabs.com
REDIS_PORT=18954
REDIS_PASSWORD=JwTwGgKM4P0OFGStgQDgy2AcXvZjX4dc

 

app.js

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

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

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

 

note

10장에서 사용하는 express-rate-limit 패키지도 사용량을 메모리에 기록하므로 서버를 재시작하면 사용량이 초기화된다. 따라서 이것도 레디스에 기록하는 것이 좋다. rate-limit-redis라는 패키지와 express-rate-limit 패키지를 같이 사용하면 된다.

 

nvm, n

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

 

윈도

nvm list로 설치된 노드 버전 확인

새로운 버전을 설치하고 싶다면 nvm install [버전] ( ex) nvm install 14.1.0, nvm install latrest)

설치된 버전 사용하고 싶다면 num use 14.1.0

 

맥, 리눅스

sudo npm i -g n

이제 n으로 현재 설치된 노드 버전을 볼 수 있다.

새로운 버전을 설치하고 싶다면 n 버전(n 14.1.0)

 

업그레이드 후 npm 충돌

이럴때는 npm rebuild 명령어로 해결

만약 해결되지 않으면 node_modules 제거후 npm i 명령어로 패키지 다시 설치

 

깃과 깃 허브 사용하기

git --version으로 버전확인

.gitignore

node_modules
uploads
*.log
coverage
.env

AWS 시작하기

EC2말고 Lightsail ㄱㄱ

 

SSH를 사용하여 연결

git clone ~~

 

서버 실행하기 전에 Lightsail에서는 기본적으로 비트나미 아파치 서버가 켜져 있다. 노드 서버와 같이 쓸 수 없으므로 아파치 서버를 종료해야 한다.

cd /opt/bitnami

sudo ./ctlscript.sh stop apache

 

cd ~/nodebook-nodebird

.env 파일 만들고  vi .env

npx sequelize db:create --env production

sudo nppm start

 

note 웹 서버와 DB 서버의 분리

서버 하나에 문제가 생겼을 때, 다른 서버에 영향을 미치는 것을 막기 위해서 분리하자

AWS는 EC2라는 컴퓨팅 서비스와 MYSQL 전용 서비스인 RDS 따로 운영.

GCP 시작하기

sudo su

원활한 진행을 위해 먼저 루트 계정으로 변경

git clone ~~

 

비밀번호를 올바르게 설정했는데도 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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90
LIST
댓글
공지사항