Search
▪️

Deploying our App

Deploying Different Kinds of Apps

Server Side Rendered Views Application이나 REST API 혹은 GraphQL과 같은 API Application들 모두 일반 Node.js Server를 구동하게 되고, Express와 같은 Framework를 이용하며, 동일한 Hosting Requirements를 갖는다.
즉, 두 Application 모두 Server를 구동하는데 있어서는 크게 다를 것이 없기 때문에 Deploying하는데에도 크게 차별을 둘 필요가 없다. 그저 Server를 구동하기만 하면 되는 것이다.

Deployment Preparation

Production을 위한 Code Preparation은 Building중인 Application에 따라서 달라진다. 몇 가지 특성이 있는데 다음과 같다.
1.
Use Environment Variables ⇒ Avoid hard-coded values in your code (API Key, Port Number, Password, URL 등)
2.
Use Production API Keys ⇒ Don't use that testing Stripe API
3.
Reduce Error Output Details ⇒ Don't send sensitive info to your users
4.
Set Secure Response Headers ⇒ Implement best practices
5.
Add Asset Compression ⇒ Reduce Response Size
6.
Configure Logging ⇒ Stay up to date about what's happening
7.
Use SSL/TLS ⇒ Encrypt data in transit
** 5, 6, 7 번은 주로 Hosting Provider에 의해서 처리 된다.

Using Environment Variables

Environment Variables라고 함은 Node.js에서 지원하는, 외부의 특정 Configuration을 담당하는 변수들을 의미한다.
이렇게 Environment Variable로 빼둘 수 있는 값들은 Node.js Server가 구동되는 순간 값이 할당 되기 때문에 아무런 문제가 되지 않을 뿐더러, Development와 Production 사이에 바뀔 수 있는 부분에 대해서 유동적으로 대처할 수 있다.
Environment Variables를 이용할 때는 (String에서) `${}`와 같이 이용하며, 해당 Object내에는 process.env와 같이 process Object의 모든 Environment Variables를 처리하는 env로 값에 접근한다. process Object는 Node.js에서 지원하는 Global하게 값을 이용할 수 있도록 돕는 Object이다. (process Object는 Node Core Runtime의 일부이다.) 자세한 것들은 Code를 참고하도록 하자.
Environment Variables 사용은 위와 같이 하는데, Environment Variables를 사용할 수 있도록 값을 어떻게 설정하는가? → 우리는 Node.js Application을 Nodemon을 통해서 구동하고 있다. Nodemon은 Configuration File을 받는다. 프로젝트의 Root 폴더 내에 nodemon.json이라는 파일을 생성한 후, JSON 형태로 데이터를 작성한다. 여기서 "env" Key의 Value로 Object를 받고, 이 Object내에 env 값들을 설정하면 된다.
이렇게 nodemon.json을 통해서 Environment Variables를 설정한 후, 설정된 값들을 통해서 Application을 구동하게 된다. 하지만 실제로 Production Level에서 Server를 구동할 때는 Nodemon을 사용하는 경우가 드물다. (대체로 pm2를 사용한다.) Production Level에서는 Development와 달리 매 변경 때마다 Server를 자동으로 Restart할 필요가 없기 때문이다. 따라서 package.json에서 start 명령어에 대한 것도 nodemon start에서 node start로 수정이 필요하다. (혹은 pm2에 대한 것으로 말이다.)
start 명령어에 대한 것을 nodemon에서 node로 바뀜에 따라, 정의 했던 Environment Variables를 nodemon.json에서 package.json로 다시 작성할 필요가 있다. (즉, Development Level에서 Nodemon을 사용했다면, package.json에서 start 명령어는 nodemon으로 적용하고 Environment Variables도 nodemon.json에 정의한다. 반대로 Production Level이라면, package.json에서 start 명령어는 node로 적용하고 Environment Variables도 package.json에 정의한다.) ⇒ Production Level에서 package.json에 Environment Variables를 정의할 때, start 명령어를 정의하는 곳에 Environment Variables를 함께 정의한다. 이런 과정들이, 만일 Hosting Provider를 이용하면 Environment Variables들을 Dashboard에 정의하여 편하게 이용할 수 있다.
process.env.NODE_ENV라는 Environment Variable은 Hosting Provider를 이용하지 않으면 수동으로 작성해야 하는 Environment Variable이지만, Hosting Provider를 이용하면 자동으로 정의된다. (production이 Default 값이다.) 이 Environment Variable이 기본적으로 자동으로 설정되지 않음에도 중요한 이유는, 이 Environment Variable이 설정되어 있다면 Express.js는 Environment Mode라고 인식하여 자동으로 Deployment에 맞춰서 Error에 대한 Detail을 줄이고, 몇 가지 사항들을 Optimize한다. 따라서 Hosting Provider를 이용하지 않는다면 수동으로 process.env.NODE_ENV를 정의해줘야 한다.

Setting Secure Response Headers with Helmet

API Key를 Developement 혹은 Testing에서 Production Key로 바꿨다면, Secure Header 설정을 해줘야 한다. 이에 대한 설정을 돕는 Third Party Package가 존재한다. Helmet이라는 Package이다. Node Express Application의 Secure하게 이용할 수 있도록 돕는다.
Helmet의 동작 방식은 특정 Header를 Response에 추가하여 사용자가 Response를 받게 한다. Helmet Official Document를 통해 어떤 공격 패턴이 있고 어떤 보안 이슈가 있는지 확인할 수 있다.
아래 명령어를 통해 설치할 수 있다.
npm install —save helmet
Package를 설치한 후, 해당 Object를 상수에 할당 후에 Middleware에 Instance를 추가하면 된다. 이 구문은 Express Application을 Initialize 한 후에 선언하는 것이 좋다. 혹은 Header에 관한 내용이니 Middleware의 가장 처음에 설정하는 것이 좋다.
app.use(helmet())

Compressing Assets

Response를 Secure하게 이용할 수 있게 되었으니, Optimize된 Asset을 이용할 수 있게 바꿔줘야 한다. 이 역시도 Third Party Package를 통해서 쉽게 처리할 수 있다. Node.js Compression 중에서 Express.js가 지원하는 Compression Middleware Package가 있다. 아래 명령어를 통해서 설치할 수 있다.
npm install —save compression
Compression 역시 Middleware에 추가하여 사용한다. Helmet을 사용한 Middleware 바로 아래에 사용하는 것이 좋고, 사용 방법 역시 Helmet처럼 함수를 호출하듯이 이용하면 된다.
사용 전후로 Network에 대한 Inspect Factors를 확인해보면, Asset을 이용하는 Size가 상당히 많이 줄어드는 것을 볼 수 있다. Frontend Asset이 늘어날수록 큰 장점으로 작용한다. 즉, 용량이 큰 File이 쓰이거나 File이 많을 때 뿐만 아니라 Frontend에서 CSS, JavaScript Code가 많을 때도 유효하게 작용한다.
** 참고로 Image File들은 Compress되지 않는다. Compress 작업을 하는 것이 더 오래 걸리기 때문이다. 또한 Hosting Provider를 이용하게 되면 대부분 Compression을 지원한다.

Setting Up Request Logging

Secure Header, Compression까지 모두 끝났다면 Request에 대한 Logging을 처리해야 한다.
실제로 Production을 하다보면, 예기치 못한 오류들도 많이 나오고 이에 대해서 Server를 Run하는 동안 어떤 오류가 났고 어디서 생긴 오류인지 유추하고 수정하기 위해선 Logging을 하는 것이 굉장히 중요하다.
이런 Logging 작업 역시도 쉽게 관리해주는 Third Party Package가 존재한다. 아래 명령어를 통해서 설치할 수 있다.
npm install —save morgan
사용법 역시 Helmet, Compression과 동일하다. Request에 대한 Logging을 남기는 것이므로 Helmet, Compression Middleware 다음에 이용하는 것이 좋다. Middleware에 함수를 호출하듯이 이용 가능하고, 인자로는 어떻게 Logging을 남길 것인지를 받는다. (어떤 데이터들이 Logging되고, 어떻게 Fomatting 되는지는 Official Document를 참고한다.) 일반적으로 'combined'인자를 받는다.
이와 같이 설정하면 매 Request 동작에 대해서 Console에 Logging을 수행한다. HTTP Method와 어떤 Browser와 Operating System에서 동작된 것인지도 확인 가능하다. 이런 Logging들을 Deployment 시에 Console에서 보는 것이 아니라 File로 확인을 하는 것이 일반적이다. 이를 위해서 추가적으로 해줘야 하는 것들이 있다. Node.js의 File System Library를 통해서 Writable Stream을 생성한다. (Server는 멈추지 않고 계속 동작하면서 Logging을 해야하므로 Stream으로 만들어서 기록을 해야 한다.) 이 때, Flag에 대한 설정도 필요한데, 새로운 데이터들은 Append 되어야 하므로 'a'를 flag Key에 할당하여 Writable Stream을 생성한다. 생성한 Writable Stream은 morgan() Method에 할당할 수 있다. morgan의 두 번째 인자로 Object를 넘기고 stream Key 값의 Value로 Writable Stream을 할당한다.
morgan을 이용하면 console.log()에 대한 것들도 Logging이 가능하다.
Hosting Provider를 쓰면 Request Logging Package 역시 두지 않아도 자동으로 처리해준다. Express.js의 Deployment Optimization을 위한 NODE_ENV, Compression, Logging 등을 자동으로 처리해준다고 하니 Hosting Provider가 얼마나 유용한지 새삼 느낄 수 있다.

Setting Up a SSL Server

Deployment를 위해 Hosting Provider 혹은 Server로 Application을 옮기기 전, 마지막이지만 크게 중요한 것은 아니지만 SSL / TLS Server를 Setting 할 수 있다.
(TLS는 SSL의 조금 더 최신 Version으로 보면 된다. 대부분의 사람들은 SSL이라는 용어에 더 익숙하다.) SSL과 TLS는 모두 Client로부터 전송되는 모든 데이터들을 Secure하게 Server가 받을 수 있도록 돕는다.
Client와 Server간 주고 받는 데이터들을 실제로 어마어마하게 많은데 이런 것들을 공격자가 Eavesdropping을 하는 것도 가능한 일이다. Production 중인 Service에서는 이렇게 공격자가 데이터들을 엿보는 작업에 대해서도 방어 해줘야 한다. (특히 Cash와 같은 정보들이 들어간다면 더더욱 방어를 해줘야 한다.) 따라서 데이터에 대한 도청 자체를 막는 것도 방법이지만, 데이터를 Encrypt하여 공격자가 데이터를 취득하더라도 무용지물로 만드는 것이 좋은 방법이다. SSL과 TLS Encryption이 이를 가능하게 해준다. 이렇게 Encrypt된 데이터는 Server에 도달하면 Decrypt되어 Server에서 처리 된다.
SSL과 TLS가 이용하는 Encrypt 방식은 무엇일까? → 비대칭 키를 이용한다. 즉, Private Key와 Public Key를 이용한다. Public Key (노출 가능)를 통해서 Encrypting을 진행하고, Private Key (노출 불가능)를 통해서 Server에서 Decryting을 진행한다.
여기서 중요한 것이, Server와 Client 간의 데이터를 주고 받는 과정에서 양방향 모두에게 Encrypting & Decrypting이 필요한 것이다. 하지만 Server는 Public Key & Private Key를 모두 갖고 있지만, Client는 암호화를 위한 Public Key를 갖고 있지 않다. SSL과 TLS를 통해서 Server가 갖고 있는 Public Key를 Client에게 먼저 보내주고 데이터를 받아 Decrypting 할 수 있도록 해야 한다.
그렇다면 실제로 Server에서 SSL과 TLS를 이용하는 Production들이 많을 테고 이에 따라 Client는 여러 Public Key들을 갖고 있을 수 있다는 것인데, 요청을 보내려는 Server의 Public Key를 어떻게 인지하는가? → 모든 Public Key들은 SSL Certificate를 통해서 Server Identity와 Bind 되어 있다. (Server Identity라고 함은 Server의 Domain, Admin의 Email 등이 되겠다.) 즉, Client는 SSL Certificate를 통해서 요청을 보내려는 Server에 대한 Public Key를 알 수 있다.
SSL, TLS를 이용함에 있어서 Key 값 생성, Certificate 생성, Key 값과 Certificate의 Binding, Certificate의 전송 모두 Server가 해줘야 하는 것들이다.
** Server에서 자체적으로 Key를 생성했다고 해도, 이를 SSL Certificate에 묶어서 Certificate를 Client에게 전달한다고 했을 때, Browser는 Default로 이에 대해서 신뢰하지 않는다. (따라서 'SSL을 사용하지만 신뢰할 수 없는 사이트'라고 뜨는 것이다.) → 이에 따라 SSL Certificate를 사용하더라도 이미 알려진 상태의 Certificate 권한으로 제공된 SSL Certificate를 사용하여야 정말로 Secure하고 Trusted Protection을 받을 수 있다. 즉, Certificate는 임의로 Server에서 생성하는 것이 아니라 Trusted Authority로 생성해줘야 한다.
Server이 Identity를 담은 Certificate와 Server를 Connection을 맺기 위해선, (Testing이 아니라 Production이라면) Authority를 통해서 Certificate를 생성해야 한다.
하지만 Testing이므로 자체적으로 Certificate를 먼저 생성하도록 한다. (Production에서는 사용하지 말 것) 아래 명령어를 통해서 Public Key와 Private Key를 생성하고 이를 새롭게 생성한 Certificate에 Packaging 하여 Certificate를 취득할 수 있다.
openssl req -nodes -new -x509 -keyout server.key -out server.cert
이렇게 Certificate를 생성할 때 추가적으로 정보를 몇 가지 입력해야 하는데, Common Name만큼은 localhost로 작성하여야 SSL이 정상적으로 작동한다. (Domain 설정이기 때문이다.) 실제로 Authority에 의해서 Certificate를 생성하게 되면 Certificate에 Bindind되는 Domain을 넘기면서 해당 Domain으로 Server의 Identity가 정해진다.
위 과정으로 프로젝트에는 server.cert, server.key가 생성이 되는데, server.cert만 Client에게 전송이 되고 server.key는 항상 Server에 유지하게 된다.
생성된 두 File들을 이용하기 위해선 새로운 Node Module을 Import해야 한다. 일반적인 Import 없이 HTTP Method를 이용하게 되면 HTTP Protocol로 통신하게 되지만, 데이터를 주고 받는 동안 Encryption & Decryption이 일어나는 SSL / TLS를 이용하기 위해선 Secure된 Protocol인 HTTPS를 이용해야 한다. 따라서 아래와 같이 Import한다.
const https = require('https)
server.key File을 File System Library를 통해서 Key Value를 읽어 와야 한다. 읽을 때는 Blocking Function인 readFileSync() Method를 통해서 읽어 온다. (Private Key 없이는 Server가 구동되는 것을 막고자 Blocking Code를 쓰는 것이다.) server.cert 역시 server.key를 읽어 올 때와 마찬가지로 File을 읽어 온다.
또한 HTTPS Protocol로 Server를 구동해야 하므로 단순히 app.listen() Method를 사용하지 않는다. https.createServer() Method를 이용하며, 이는 두 개의 인자를 받는다. 첫 째는 Server를 Configure하며, 여기에 Private Key와 Certificate를 이용한다. 두 번째 인자는 Express Application과 같은 Request Handler를 두게 된다. 이렇게 먼저 사용된 https.createServer()의 Chaining으로 listen() Method를 이용한다. 아래와 같이 말이다. (첫 번째 인자는 JavaScript Object 형태이며, 다시 두 가지를 설정해야 한다. key와 cert라는 Key에 대한 Value를 설정해야 한다.)
https.createServer({key: $privateKey, cert: $cert}, app).listen(process.env.PORT || 3000)
이와 같이 Server를 구동하면, HTTPS Protocol을 통해서 Server를 구동하게 되고, SSL Encryption을 이용하게 된다. 이런 SSL / TLS 역시도 Hosting Provider에 의해서 자동으로 처리하게 할 수 있다. Hosting Provider 구조상 내가 만든 Server 앞에 Hosting Provider도 자체적으로 Server를 갖고 있기 때문에, 자체적으로 갖는 Server에서 SSL과 TLS를 이용할 수 있으므로 자동으로 처리할 수 있는 것이다. 그렇다면 Hosting Provider를 이용하는 경우, 내 Server와 Client 사이에 Hosting Provider의 In-Between Server가 존재하는 것이고, 이 때 내 Server와 Hosting Provider의 In-Between Server는 HTTPS가 아닌 HTTP로 통신하게 된다. (실제 내 Server는 Hosting Provider에 의해서 둘러 싸여 있기 떄문에 Public에 노출되는 상태가 아니므로 Public에서는 이용할 수 없는 Block 상태이기 때문이다.) 즉, Hosting Provider를 이용하면 이렇게 SSL을 Manual하게 설정할 필요가 없다.

Using a Hosting Provider

Hosting Provider로 많이 쓰는 것은 Heroku라는 Hosting Provider이다.
Hosting Provider를 쓰지 않을 수도 있는데, 이는 별로 추천되는 방식은 아니다. 무엇을 해야하는지 모른다면 보안에 취약할 수도 있고 확장성이 떨어질 수도 있다. 따라서 AWS나 Heroku같은 Hosting Provider를 사용하는 것이다.
Hosting Provider의 Managed Space / Virtual Server에 우리가 작성한 코드를 Deploy하고, (일반적으로 Managed Space의 실제 저장 장치는 굉장히 크지만 우리가 사용하는 것은 그 일부분일 뿐이다.) 이렇게 Deploy한 Code는 동일한 컴퓨터에서 구동하는 Application과는 완전히 차별성을 갖는다.
일단 Managed Space에 Application을 Deploy했다면 이를 사용자가 액세스 할 수 있도록 해야 한다. 일반적으로는 Managed Space에 직접적으로 사용자가 접근하게 하지 않는다. 즉, In-Between Server로 Managed Servers를 두어 많은 작업들(Helmet, Compression, Logging, Load Balancing, SSL 등)을 대신 처리한다.
Virtual Server와 Managed Servers는 일반적으로 Private Network로 구동된다. 그렇다면 둘 다 Private Network에서 구동되는데 사용자가 어떻게 Server에 접근하게 되는가? → Managed Server의 Private Network에 접근할 수 있도록 Public Server Gateway를 이용한다. 이를 통해 사용자는 Private Network를 이용하는 Virtual Server까지 Request를 보낼 수 있는 것이다. 반대로 Virtual Server에서 반환하는 Response도 사용자에게 도달할 수 있는 것이다.
Git은 Heroku의 것은 아니지만, Heroku에서도 사용할 수 있는 Version Control Tool이다.
Save & Manage Your Source Code가 가장 큰 특징이고, Git을 통해서 Commits, Branches, Remote Repositories를 두는 것이 가능하다.
1.
Commits
Snapshot of your code
Easily switch between commits
Create commit after bug fixes, new features
2.
Branches
Different versions of your code (여러 스냅샷의 히스토리 갈래를 여럿 둘 수 있다는 것)
e.g. master(production), development, new features
Separate development of new features and bug fixing
3.
Remote Repositories
Store code + commits + branches in the cloud
Protect against loss of local data
Deploy code automatically (Heroku does)
** Code를 Remote Repository에 Push하면 Heroku가 자동적으로 Production에 적용하고 Server가 이에 맞춰서 자동으로 Spawn Up 되는 것이다.

A Deployment Example with Heroku

Heroku는 다른 Hosting Provider가 Drag & Drop으로 Code를 작업하는 것과 달리, CLI 환경에서 작업을 하게 된다. 즉, 특정 명령어에 따라서 서버를 Run하고 Deploy하게 된다.
우선 Heroku CLI를 받아서 heroku 명령어를 쓸 수 있도록 해준다.
heroku login을 통해서 Heroku CLI에 로그인을 하도록 한다.
Git Repository를 Remote Git Repository 형태로 바꿈으로써 Heroku에 등록하고 Deploy 할 수 있도록 한다. Git이 Initialize 되었다면 아래 명령어만으로 충분하다.
$ heroku git:remote -a $herokuApplicationName
위와 같이 Heroku를 Remote로 설정했다면, package.json에 engines라는 Entry를 두고 (scripts Entry 위에 두도록 한다.), 이 값에 대해서 정의할 필요가 있다. 값은 JavaScript Object 형태를 갖는다. Object 내에는 node라는 Key를 갖고 Value는 Node.js의 Version이 되겠다. (이렇게 engines Entry를 추가함으로써 Node.js Version을 명시 했다면, app.js에서 정의했던 Hosting Provider가 제공하는 기능들에 대해서 수정할 필요가 있다. Heroku의 경우 다른 Hosting Provider와 다르게 Compression을 지원하지 않는다.)
또한 Heroku를 이용하기 위해선 Root 폴더에 확장자명 없이 Procfile이라는 File이 필요하다. Procfile에는 Server를 구동시킬 수 있는 node app.js와 같은 명령어가 들어가도록 한다. 이와 같이 설정이 끝난 뒤 아래 명령어를 통해서 Deploy를 하게 되면, Heroku는 package.json에 명시된 대로 필요한 Dependency를 자동으로 Install하게 된다. 따라서 .gitignore에는 node_modules를 해주는 것이 좋다.
git push heroku master
Deployment 이후에 가장 중요한 것은 환경 변수 설정이다. 비록 package.json에는 start 명령어에 대해서 Environment Variables를 모두 정의 했지만, 이는 Deployment된 Application에서 읽을 수 있는 명령어가 아니다. Deploy된 Application이 읽을 수 있는 명령어는 오로지 Procfile에 들어가 있는 명령어 뿐이며, 작성된 명령어는 Server를 구동하는 node app.js 밖에 없다. 심지어 Profile에는 명령어만 취급하기 때문에 이곳에 Environment Variables를 정의할 수도 없는 노릇이다. (Heroku를 이용했을 때 오로지 자동으로 생성되는 Environment Variable는 NODE_EVN=production 한 가지이다.) 따라서 나머지 Environment Variables들은 Heroku의 Dashboard에 별도로 Environment Variables를 설정할 수 있는 Config Vars에서 설정하도록 한다.
사용자는 In-Between Server인 Managed Server로 접근하기 위해서 Public Server Gateway를 이용한다고 했다. 즉, Application에 접근하기 위한 Public IP를 데이터베이스와 같이 IP Address Pattern을 허용해줘야 하는 Module들에 IP Address를 White List에 등록해줘야 Deploy된 Application을 성공적으로 쓸 수 있다. (하지만 Heroku의 경우 Static IP를 주지 않기 때문에 White List에 등록하기 위해선 Allow Anywhere로 해둬야 한다...혹은 Static IP를 Manual하게 할당하여 특정 IP만 허용하는 것이 Best이다.)
** Heroku Git은 Github에 add, commit, push하는 것과 크게 다르지 않은데, Github에서는 Remote Repository의 Origin에 add, commit, push를 했다면, Heroku Git은 위 Remote 설정을 Origin이 아닌 Heroku로 했기 때문에 add, commit, push에 대한 작업이 Heroku로 가도록 명령어를 날려야 한다.
** heroku logs를 통해서 Deploy 중인 Application의 모든 로그를 확인할 수 있다.
** Heroku 초기 세팅에서 언어에 맞는 Buildpacks를 필수로 설정해줘야 한다.
** Heroku에서의 Dyno라는 것은 실제 Code가 올라간 Virtual Server를 의미한다.
** Virtual Server에서의 File Storage는 Not Persistent하기 때문에, Server가 Restart되면 File 들이 증발할 수도 있다. 따라서 File들은 Hosting Provider의 Virtual Server에 Upload할 것이 아니라, AWS S3와 같은 곳에 Hosting Provider와 별도의 공간에 저장하는 것이 바람직하다. AWS SDK를 통해서 File을 쉽게 Interacting할 수 있고, Multer S3와 S3 Proxy를 통해서 쉽게 설정할 수 있다.
** REST API나 GraphQL과 같은 API 역시 Heroku를 통해서 Deploy할 수 있다. 방법은 크게 다르지 않다. REST API나 GraphQL에서 Request를 보내는 URL에 대해서 수정이 필요하다. (SSR이 아니기 때문이다.)