본문으로 바로가기

🎩 코드 배포용으로 전환하기

 

 

개발용으로 세팅해놓았던 미들웨어를 배포용으로 전환하기

.env에 NODE_ENV="development"를 "production"으로 변환하면 작동할 수 있게끔 만들자.

 

- morgan 수정

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

 

- express-session 수정

const sessionOption = {
  secret: process.env.COOKIE_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
    httpOny: true,
    secure: false
  }
};
if (process.env.NODE_ENV === "production") {
  // proxy 서버를 사용한다면 true값을 주자.
  sessionOption.proxy = true;
  // Secure는 https로 통신하는 경우만 웹브라우저가 쿠키를 서버로 전송하는 옵션입니다.
  // 여기서는 https를 사용하지 않으므로 주석 처리합니다.
  // sessionOption.cookie.secure = true;
}
app.use(session(sessionOption));

 

- 하드 코딩된 부분 .env로 처리하기

 

  코드 전반을 살피며 포트 번호나 비밀번호가 하드 코딩되었다면 env로 넣어야 합니다. 저는 살펴보니 MySQL의 config.json의 비밀번호 부분이 하드 코딩되어 있어서 이를 env로 넣었습니다. json에서 .env를 사용하는 방법은 파일명을 json에서 js로 수정한 후 일반 js 폴더 처럼 사용하면 됩니다.

  원하는 만큼한 .env를 통해 숨기면 되지만 유저 네임, 비밀번호만큼은 숨기는 것이 좋습니다.

  여기서 팁이 있다면, 배포에서는 "logging":false를 하는 것이 좋습니다. 무슨 쿼리문을 작성했는지를 통해 DB를 유추할 수 있기 때문입니다.

 

import dotenv from "dotenv";
dotenv.config();

export default {
  development: {
    username: "process.env.SEQUELIZE_USERNAME",
    password: process.env.SEQUELIZE_PWD,
    database: "twitter",
    host: process.env.HOST,
    dialect: "mysql",
    operatorsAliases: false,
    logging: true
  },
  test: {
    username: "process.env.SEQUELIZE_USERNAME",
    password: process.env.SEQUELIZE_PWD,
    database: "database_test",
    host: process.env.HOST,
    dialect: "mysql",
    operatorsAliases: false
  },
  production: {
    username: "process.env.SEQUELIZE_USERNAME",
    password: process.env.SEQUELIZE_PWD,
    database: "database_production",
    host: process.env.HOST,
    dialect: "mysql",
    operatorsAliases: false,
    logging: false
  }
};

 

- pakage.json의 script 부분 수정 + cross-env + pm2

 

  React.js에서 배포할 때 npm run build를 입력해 본 경험이 있을 것이다. 그와 같이 배포용으로 script를 수정해야 한다.

 

// 전의 코드

"scripts": {
  "start": "nodemon --exec babel-node app.js"
}

// 수정 후 코드

"scripts": {
  "dev": "nodemon --exec babel-node app.js",
  "start": "NODE_ENV=production PORT=80 node app.js"
  // 정식 출시를 위해 PORT를 80으로 설정했으니 .env의 PORT는 지워주도록 합시다.
},

 

  그런데 윈도우 환경에서 npm start를 입력해서 해당 스크립트를 실행해보면 다음과 같은 오류를 만날 수 있다. 'NODE_ENV'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다. 생각해보면 이건 Webpack 설정에서도 만난 오류이다. WEBPACK_ENV'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다.

  이러한 오류가 나는 이유는 동적으로 process.env를 변경하는 것은 리눅스나 맥 환경에서만 작동하기 때문입니다. 윈도우 환경에서 해당 코드가 작동하기 위해서는 cross-env를 설치해야 한다.

 

(하기 싫다면 직접 .env 를 수정하는 방법도 있다.)

 

npm i cross-env
"scripts": {
  "dev": "nodemon --exec babel-node app.js",
  "start": "cross-env NODE_ENV=production PORT=80 node app.js"
},

 

만약 웹팩을 돌린 후라면 해당 파일을 대상으로 node를 돌리면 된다. 

 

  다음은 pm2이다. 지금까지는 node를 통해 app.js를 돌렸다. node는 싱글 스레드라는 단점을 가지고 있다. 요즘 대부분의 CPU가 멀티 코어를 가지고 있으며 하이퍼 스레드 기술을 통해 더 많은 수의 스레드를 사용할 수 있음을 고려하면 이 단점을 꽤나 치명적이라고 할 수 있다. pm2를 사용하면 멀티 프로세싱이 가능해져서 노드 프로세스 갯수를 들릴 수 있습니다. 

  다만 멀티 스레딩은 아니므로 서버의 메모리 자원을 공유하는 것이 불가능합니다. 예를 들어, 유저 정보를 세션 메모리에 저장하는 경우, 로그인을 한 후 새로고침을 했다고 가정해봅시다. 새로고침을 통해 들어간 프로세스에는 로그인 정보가 공유되지 않으므로 로그인을 했음에도 로그인이 안되어 있을 수 도 있습니다. 

  이러한 문제를 해결하기 위해 세션에 store를 지정해줍니다. mongoDB, MySQL, Memchaced나 Redis 같은 서비스를 이용합니다. (Redis는 여기저기 자주 사용하니 언젠가 배우기를 권합니다) 무엇을 이용하는 지에 따라 이점이 다릅니다. 그러나 최대한 프로세스 간에 공유하는 것이 없도록 설계하는 것이 좋습니다.

 

Comparing In Memory Databases: Redis vs. MongoDB

Important Health Checks for your MySQL Master-Slave Servers In a MySQL master-slave high availability (HA) setup, it is important to continuously monitor the he... Read Full Article MongoDB - Bring Your Own SSL Certificates We're happy to announce that you

scalegrid.io

  또한 클라우드 환경에서는 pm2를 사용해야 서버가 유지되고, 서버가 죽었을 때 자동으로 재시작 해주기 때문에 사실상 pm2 사용은 필수라고 할 수 있다.

 

구체적으로 pm2를 활용하는 방법은 다음과 같다

 

npm i pm2

 

pm2 start [실행할 파일]

 

"scripts": {
  "dev": "nodemon --exec babel-node app.js",
  "start": "cross-env NODE_ENV=production PORT=80 pm2 start app.js"
}

 

실행 결과는 다음과 같다 (코드에 오류 있어서 status가 online이 아니라 stopped 되었다)

 

여러개의 CPU 코어를 사용하고 싶다면 뒤에 -i [코어 갯수]를 적어주면 된다.

코어 갯수를 모른다면 0을 입력한다. CPU 코어 갯수를 자동으로 알아보고 그 만큼 프로세스를 생성해준다.

-1을 입력하면 최대 코어 갯수에서 하나를 제외한 만큼 프로세스를 생성합니다.

"start": "cross-env NODE_ENV=production PORT=80 pm2 start app.js -i 6"

여러 개의 CPU 코어를 사용하면 mode가 cluster가 된다. (코드에 에러가 있어서 status가 stopped가 되었다)

 

 

 

pm2의 명령어를 간단하게 알아보자면 (리눅스, 맥 환경에서는 sudo 명령어를 붙여야 합니다. 1024 이하의 포트를 사용하기 때문입니다)

 

 - pm2 restart all : pm2 재시작

 - pm2 list : 현재 프로세스 정보가 표시됩니다. 

 - pm2 kill : pm2를 종료함. 나중에 다시 키고 싶으면 pm2 start app.js로 키던가 package.json의 script를 이용해 npm start로 켜도 됨.

 - pm2 

 - pm2 monit

log, CPU 사용량 등을 조회할 수 있음

 

* pm2가 안 죽어요!

 

우선 npm i -g pm2를 통해 글로벌하게 pm2를 설치한 후 pm2 list를 통해 pm2가 무엇이 돌고 있는지 확인한 후 pm2 kill을 통해 죽여줍니다. pm2를 몇 개 돌렸는지 모르고 있다가 계속 돌아가 리소스를 낭비하는 경우가 많습니다.

 

 

 

- npm audit, npm audit fix

 

해당 명령어를 입력하면 취약점을 검사해서 보고해줍니다. 만약 취약점이 발견되었다면 npm audit fix를 입력해서 고치도록 합시다. 다 해결되는 건 아니고 수동으로 해결해야 하는 부분도 있습니다. 과거에는 retire 패키지를 이용해서 이 과정을 진행했는데 npm 5.10 버전 이상부터 audit 명령어를 지원하면서부터 사용하지 않게 되었습니다.

 

 

 

 

- winston

 

  개발 시에 오류가 나면 console.log를 찍어서 서버 상황을 확인할 수 있지만 배포되어 서버가 돌아가는 중에는 console.log를 사용하기 어렵습니다. 서버 종료시 로그가 사라지고 언제 호출 되었는지도 파악하기 어렵기 때문입니다. 오류가 났는데 서버가 종료되어 찍힌 로그가 사라진다고 생각해보면, 배포시 console.log를 사용하는 건 그다지 좋은 생각이 아님을 알 수 있습니다.

  AWS와 같은 클라우드에서는 로깅 기능을 제공하나 클라우드가 아닌 환경, 클라우드라고 하더라도 신중을 기하기 위해 winston을 사용합니다.

 

npm i winston

 

이후 logger.js 파일을 생성한 후 다음을 복붙한 후 app.js에 연결하여 사용합시다. (export를 제외하고는 문서의 usage를 그대로 가져온 겁니다.)

 

winstonjs/winston

A logger for just about everything. Contribute to winstonjs/winston development by creating an account on GitHub.

github.com

import winston from "winston";

const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  defaultMeta: { service: "user-service" },
  transports: [
    //
    // - Write all logs with level `error` and below to `error.log`
    // - Write all logs with level `info` and below to `combined.log`
    //
    new winston.transports.File({ filename: "error.log", level: "error" }),
    new winston.transports.File({ filename: "combined.log" })
  ]
});

//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== "production") {
  logger.add(
    new winston.transports.Console({
      format: winston.format.simple()
    })
  );
}

export default logger;

./app.js

import logger from "./logger";

// 404 handling
app.use((req, res, next) => {
  const err = new Error("Not Found");
  err.status = 404;
  logger.error(err.message); // winston이 404 에러시 로그를 파일로 남깁니다.
  next(err);
});

 

이 외에도 다양한 방식으로 winston을 활용할 수 있으니 공식 문서를 읽어보며 활용하도록 합시다.

 

- helmet, hpp

 

  습관적으로 활용하는 일종의 보안책입니다. 설치 후 미들웨어 상단 부분에 끼워 넣으면 작동합니다. 배포 환경시 무조건 사용한다고 생각하시면 됩니다. 가급적 미들웨어의 상단부에 넣어놓도록 합시다. 팁이 있다면, 개발 시에 helmet과 hpp 미들웨어를 거치면 반응 속도가 느리므로 배포시에 추가하는 것이 좋습니다.

 

npm i helmet hpp

./app.js

// 배포시에만 helmet, hpp를 거쳐가도록 합니다.

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

 

  helmet의 보안책으로 iframe 사용을 막는 경우가 있습니다. 옵션을 주어서 해제할 수 있으므로 hetmet 공식 문서를 참고합시다. 

 

 

helmet

help secure Express/Connect apps with various HTTP headers

www.npmjs.com

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체