Search
▪️

서비스 배포하기

Package 배포 환경으로 전환하기

일반적으로 배포 환경을 설정한다는 것은, 완전히 배포로 설정하는 것이 아니라 개발 환경과 배포 환경 나눠서 쓸 수 있도록 만드는 것이다. (process.env.NODE_ENV는 기본적으로 'development'로 되어 있다. 'production', 'test'로도 사용할 수 있다.)
1.
morgan에 대한 설정
combined (NODE_ENV === 'production')
dev (NODE_ENV === 'development')
2.
session에 대한 설정
$sessionOption.proxy = true, $sessionOption.cookie.secure = true (NODE_ENV === 'production', proxy Server를 쓸 때만!)
$sessionOption.proxy = false, $sessionOption.cookie.secure = false (NODE_ENV === 'development', proxy Server를 쓰지 않으므로)
3.
sequelize의 config.json
해당 File은 json File이기 때문에 dotenv로 이용하지 않으면, 그대로 털릴 위험이 있다. 따라서 config.json을 config.js로 바꾼 뒤, JavaScript를 이용할 수 있도록 만들고 require('dotnev').config()를 통해서 dotenv를 활성화 한다. 이렇게 하여 명시되어 있던 데이터 Field는 process.env.$variableName으로 바꿔준다. 이렇게 바꾼 json 데이터는 외부에서도 사용할 수 있어야 하므로, module.exports를 해준다.
sequelize를 이용하여 Model을 정의할 때, 추가 Option을 줄 수 있다. 나중에 Cloud에 배포를 하게 되면, 설정이 영어로 되어 있을 수도 있기 떄문에 데이터베이스에서 한글로 적어 놓은 것들이 깨질 수 있다. 따라서 Model을 정의할 때, Option Field에 charset: 'utf8', collate: 'utf8_general_ci'를 할당한다.

cross-env, pm2

Package 내부의 설정을 Production으로 이용할 수도 있게끔 바꿨다면, package.json의 설정도 필요하다. 기존에 NODE_ENV는 기본 값으로 development로 사용 중이지만, 이를 production으로 바꿔줘야 한다.
따라서 기존 start Script에 대한 명령어를 nodemon에서 node로 바꾸고, nodemon을 이용하는 Script는 dev Script로 운영한다. start Script는 node 명령어 이전에 NODE_ENV=production PORT=$portNumber를 할당한다. (.env에 적어 놓은 Port Number는 덮어 쓰지만, 불안하다면 .env의 Port Number는 지우도록 한다.)
** start Script에 NODE_ENV에 대한 값을 할당하여 쓰는 것은 Linux, MacOS에서만 지원 가능하므로, Windows OS에서 사용하기 위해선 cross-env라는 Package가 추가로 필요하다. 만일 Windows OS를 사용한다면 cross-env를 Global Package로 설치하여 사용한다. 설치 후에, start Script의 앞에 cross-env를 명시하고 이전에 수행해야 했던 대로 하면 된다.
아래 명령어를 통해서 보안 취약점을 검사를 해볼 수도 있다.
npm audit
만일 위 명령어로 검사를 했을 때 심각한 취약점이 발견 되었다면, 아래 명령어를 통해서 해결이 가능하다. (수동으로 해결해야 하는 부분도 있으니 조심해야 한다.)
npm audit fix
배포 시에는 Server가 죽어도 다시 실행할 수 있어야 하기 때문에, 그리고 Server 재시작을 수동이 아니라 죽자마자 실행되어야 하기 때문에 pm2라는 Package를 사용한다. 아래 명령어를 통해서 설치할 수 있다.
npm install -g pm2
pm2를 사용하는 경우 start Script의 명령어를 node $scriptName이 아니라 pm2 start $scriptName으로 변경하도록 한다. (pm2를 사용하면 Node.js로 작성한 Server Code를 Background에서 실행 해준다.)
pm2를 사용했을 때, Server 재시작의 장점 외에도, Node의 특성상 Single Thread로 동작하여 Core를 한 개만 사용하는 부분을 Multi Core를 활용하여 동작할 수 있도록 만들어 준다. 해당 설정은 start Script의 pm2 start $scriptName 뒤에 -i Option으로 Core 개수를 명시하도록 한다. (만일 Core 수를 모른다면 0을 할당하여 자동으로 Core 수만큼 할당하여 작동하게 만든다.) -1로 사용하게 되면, Core 수보다 한 개 적게 할당하여 구동된다. 즉, 한 개의 Core를 남기는 표기이다. (단일 Core 이용 시 pm2는 fork Mode로 작동한다. 하지만 Multi Core 이용 시에 pm2는 cluster Mode로 작동한다.)
1.
pm2 list : pm2로 동작하고 있는 프로세스들의 목록을 보여준다.
2.
pm2 restart all : pm2로 동작하고 있는 프로세스들을 재시작한다.
3.
pm2 monit : 동작하고 있는 프로세스에 대한 실시간 Monitoring을 할 수 있게 해준다. 생성되는 Log들을 확인할 수 있다.
4.
pm2 kill : 현재 동작하고 있는 프로세스를 Process Kill 할 수 있다.

winston, helmet, hpp

이제까지 Logging을 console.log()를 통해서 Terminal에 남겼는데, winston이라는 것을 이용할 수도 있다. (사실 Cloud에 배포를 하게 되었다면, console.log()만 하더라도 기록이 모두 남는다. GCP의 경우, Stack Driver에, AWS는 Cloud Watch와 같은 곳에 남게 된다.) 하지만 Cloud 환경에서 Server를 운영하게 되는 경우 console.log()로 모든 Logging이 불가능하다. 따라서 winston Package를 사용한다.
Cloud 환경 외에 Server를 운영할 시 winston Package를 이용하는 이유는 간단하다. console.log()로 찍은 Log가 Server의 재시작으로 사라지는 경우가 많기 떄문이다.
아래 명령어를 통해서 설치한다.
npm install winston@next
설치 후에 새로운 Script를 만들어 아래의 Code를 두고, app.js에서 Script를 Import하여 사용한다.
const { createLogger, format, transports } = require('winston'); const logger = createLogger({ level: 'info', format: format.json() transport: [ 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;
JavaScript
복사
위 Code를 보다 싶이 logger를 두어 FIle로 생성하여 관리하는 것을 확인할 수 있다. console.log() 대신에 logger.info()를 이용하고, console.error() 대신에 logger.error()를 사용하도록 한다.
만일 날짜별로 Logging 하여 File로 이용하고 싶다면, npm install winston@next과 함께 npm install winston-daily-rotate-file을 하여 플로그인도 받아온다.
helmet과 hpp는 보안 관련 Package이다.
helmet의 경우, 프로젝트의 어지간한 보안 취약점들을 많이 보완해준다. Package를 단순히 app.js에서 Import하여 사용하면 된다.
hpp Package는 hpp 공격을 막아주는 역할을 한다. helmet과 사용 방법은 동일하다.
두 Package 모두 app.use() 할 때, Production Level에서 사용하도록 하면 된다.
** 단, Helmet은 보안 정도가 강력하여 i-frame을 못 쓰게 될 수도 있어서, helmet의 Option으로 별도로 설정 해준다.

connect-redis

메모리 Caching을 해놓은 경우, Server를 재시작하는 경우 Caching이 모두 사라지는 것을 확인할 수 있다. Caching 정보를 꼭 이용해야 하는데 사라진 경우, 데이터베이스에 접근하여 별도의 작업을 처리해줘야 하는 것을 볼 수 있다. 따라서 메모리 Caching을 하지 않고, redis와 같은 데이터베이스를 쓰거나 memcached를 주로 사용한다. (예를 들어, 사용자의 Session 정보를 메모리에 두지 않고, 데이터베이스에 둔다.)
아래 명령어를 통해서 redis를 설치할 수 있다.
npm install redis npm install connect-redis
Node.js Application과 redis의 연결을 위해서 redislabs.com에서 redis Hosting Service를 받는다.
redislabs.com에서는 최초 1개의 데이터베이스를 무료로 제공한다. (30MB Cloud Service) 여기서 Endpoint가 redis 데이터베이스의 주소가 된다.
Session을 예로 들면 store Option을 줄 수 있는데, store Option을 connect-mongodb를 통해서 Mongo DB에 저장하도록 해도 되고, connect-redis를 통해서 redis에 저장해도 무방하다. connect-redis Package를 이용하는 것은 Import 단계부터 Code를 참고하자. 이와 같이 설정하면 Session은 redis에 저장되게 된다. (redis Version 3.0.0 이후부터 redis Package의 Client에 대한 설정도 필요하다.)
** redis를 Session을 위해서 쓸 수도 있지만, API Server의 사용량 제한 같은 경우에도 사용이 가능하다. 해당 경우에도 사용량 제한 정보를 메모리에 두기 때문에 Server 재시작 시에 사라질 수 있다. 사용량 제한을 redis로 사용할 때는 express-rate-limit Package가 아닌, rate-limit-redis Package를 사용하면 쉽게 구현할 수 있다.
** redis를 Hosting으로 사용하지 않고 Local에도 둘 수 있지만, 일반적으로 데이터베이스는 Web Server를 두는 곳과 분리를 하는 것이 더 좋기 때문에 Hosting을 이용하는 것이 좋다.

nvm, n

npm의 Version이 바뀌는 경우 npm install -g npm을 하게 되면 npm Version이 Update 된다.
그렇다면 Node.js의 Version이 바뀌는 경우는 어떻게 해야 하는가? → 원래대로라면! Node.js를 삭제하고 다시 설치를 해야 했다. 하지만 이런 경우... 문제가 될 수 있는 부분이,, 두 개의 프로젝트를 사용하고, 두 프로젝트의 Node.js Version이 서로 다른 경우이다. 따라서 각 프로젝트마다 사용하는 Node.js Version을 맞춰서 작업할 수 있도록 하는 것이 nvm이다. (Windows OS에서는 nvm을 사용하고, Linux나 MacOS에서는 n을 사용한다.)
따라서 Windows OS의 경우 별도로 설치가 필요하며, Linux나 MacOS의 경우 아래 명령어를 사용하여 Node Version Manager를 설치한다.
npm install -g n
n 명령어를 통해서 별도의 Version을 가진 Node.js를 설치할 수도 있고, 해당 Node.js들이 설치된 경로도 볼 수 있으며, 설치된 Node.js Version도 확인할 수 있을 뿐 아니라, 설치한 Version의 Node.js로 바꿔서 사용할 수 있다.

GCP, AWS에 배포하기 (feat. GitHub)

GCP, AWS를 사용하면 IP Address를 통해서 Service에 Public하게 접근할 수 있다. 이런 배포는 GitHub를 통해서 편리하게 할 수 있다. (Heroku도 마찬가지로 GitHub로 편하게 배포할 수 있다. 단, GCP와 AWS는 가상의 Machine을 만드는 것이지만 Heroku는 그렇지 않다는 차이가 있다.)
GitHub를 통해서 배포를 하게 될 때 반드시 .gitignore File을 생성하여, GitHub에 Upload하지 않을 자료들을 배제하도록 한다. (node_modules, uploads, *.log, .env 등)
GCP를 이용할 시, GCP에서 Application을 생성한 후, Compute Engine Tab에서 VM Instance를 생성한다. Instance를 생성할 때 지역 설정 후, Machine 유형을 초소형, Booting Disk는 Ubuntu를 이용하도록 한다. 방화벽에서 HTTPS 관련은 모두 체크하는 것이 좋다.
위 과정을 완료하면 GCP를 통해 실물 없는 Server를 만들게 된 것이다. (말 그대로 Virtual한 Machine이 생성되어, 해당 Machine을 Server 로 사용하는 것이다.) 생성된 Machine의 내부 IP, 외부 IP를 부여 받는데, 접속을 위해서 사용하는 IP는 외부 IP이다. (옆에 있는 SSH 버튼을 통해 Server에 접속할 수 있다.)
Server에 접속을 하였다면, GitHub에 올려둔 Code를 Clone하여 받아 온다. Clone한 Code를 구동하기 위해서 Node.js MySQL을 설치하도록 하고, 두 가지 모두 설치가 되었다면 npm install을 통해 모든 Dependencies들을 설치하도록 한다. (Package의 Dependencies 이외에 Global 설치를 했던 것들 역시 npm install이 아닌 npm install -g로 별도로 설치한다. 예를 들면, pm2, seuqelize-cli 같은...)
여기서 Node.js와 MySQL 설치가 문제일 수 있는데, 다음과 같이 설치하면 된다.
1.
Node.js
sudo apt-get update sudo apt-get install -y build-essential sudo apt-get install curl curl -sL https://deb.nodesource.com/setup_$mainVersion.x | sudo -E bash — sudo apt-get install -y nodejs node -v npm -v sudo npm install -g npm
2. MySQL
sudo apt-get update sudo apt-get install -y mysql-server mysql_secure_installation mysql -h localhost -u root -p
Dependencies와 Node.js 및 MySQL에 대한 설치가 끝났으면, sequelize를 이용하여 데이터베이스 생성 및 초기 작업을 해줘야 한다. 아래와 같이 설정한다. (단, .env File은 GitHub에 올리지 않기 때문에 Code를 Clone하더라도 생성되지 않는다. 따라서 별도로 만들어줘야 한다.) 경우에 따라서 Seeder가 존재하고, Migrate File이 존재한다면 다음 두 명령어를 수행한다.
sequelize db:create —env production sequelize db:migrate sequelize db:seed:all
AWS의 경우, 여러 Service들 중 Computing 항목에 있는 Service를 이용한다. 배포를 위해서는 EC2를 주로 이용하지만, Lightsail을 이용해도 된다. (EC2에 대한 설정이 다른 Platform에 비해서 상대적으로 많이 복잡한 편이다. 반면 Lightsail은 굉장히 간편하다.)
Lightsail도 GCP에서 VM Instance를 생성했던 것처럼, Instance를 생성한다. Region은 서울, OS는 Linux, 앱 + OS 항목은 Node.js를 선택하여 생성한다.
Instance가 생성되면, GCP와 마찬가지로 SSH를 통하여 해당 Machine에 Access 할 수 있다. 위에서 설정한 것과 마찬가지로 똑같이 수행하여 Server Code를 구동하면 된다. (단, GCP와 달리 가장 먼저 해줘야 하는 사항이 있다. 아래의 명령어를 수행해야 한다. Lightsail의 경우 기본적으로 Apache가 실행 중이기 때문에 이를 종료해줘야 Node.js를 구동할 수 있다. Apache도 Server이고, Node.js로 만든 Application도 Server이므로 같이 돌리기 위해선 복잡한 설정이 필요하므로 Apache를 종료하는 것이 좋다.)
cd /opt/bitnami sudo ./ctlscript.sh stop apache
** Server 구축 마다 항상 위와 같은 많은 명령어들을 치는 것들을 방지 하기 위해서 Docker를 이용한다. 그렇게 되면 Server 구축할 때, Docker만 불러오면 필요한 것들을 모두 한 번에 설치가 가능하다.
** 실제로 Service를 배포한 경우에는, HTTPS로 Server를 운영해야 할 뿐 아니라, IP Address로 운영하지 않아야 하므로 Domain을 얻어서 등록하는 절차도 필요하다.