15. AWS와 GCP로 배포하기
포스트
취소

15. AWS와 GCP로 배포하기

기존 블로그에 작성했던 포스트를 이전한 글입니다.

해당 포스트는 NODEJS를 학습하며 정리한 내용에 대한 포스트입니다.




🌈 AWS와 GCP로 배포하기

💻 서비스 운영을 위한 패키지

🍳 실 서비스 배포 준비하기

서비스 개발 시에는 localhost로 결과를 바로 볼 수 있었음

  • 혼자만 볼 수 있기에 다른 사람에게 공개하는 과정이 필요

  • 9장 NodeBird 앱을 배포해볼 것임

  • 배포를 위한 사전 작업 방법에 대해 알아봄

  • 서버 실행 관리, 에러 내역 관리, 보안 위협 대처

  • AWS와 GCP에 배포


🍳 morgan

개발용으로 설정된 익스프레스 미들웨어를 배포용으로 전환

  • process.env.NODE_ENV는 배포 환경인지 개발 환경인지를 판단할 수 있는 환경 변수

  • 배포 환경일 때는 combined 사용(더 많은 사용자 정보를 로그로 남김)

  • NODE_ENV는 뒤에 나오는 cross-env에서 설정해줌

1
2
3
4
5
if (process.env.NODE_ENV !== 'production') {
  app.use(morgan('combined'));
} else {
  app.use(morgan('dev'))
}


🍳 express-session

설정들을 배포용과 개발용으로 분기 처리

  • production일 때는 proxy를 true, secure를 true로

  • 단, https를 적용할 경우에만 secure를 true로 하고, 노드 앞에 다른 서버를 두었을 때 proxy를 true로 함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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));


🍳 sequelize

시퀄라이즈 설정도 하드코딩 대신 process.env로 변경

  • JSON 파일은 변수를 사용할 수 없으므로 JS 파일을 설정 파일로 써야 함
  • config.json을 지우고 config.js 사용

🔻 config/config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require('dotenv').config();

// app.enable('trust proxy') 를 추가해주면 좋음

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

🔻 .env

1
2
3
COOKIE_SECRET=nodebirdsecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
SEQUELIZE_PASSWORD=데이터베이스 비밀번호


🍳 cross-env

동적으로 process.env 변경 가능

  • 운영체제 상관 없이 일괄 적용 가능(맥, 윈도, 리눅스)

  • package.json을 다음과 같이 수정(배포용과 개발용 스크립트 구분)

  • 문제점: 윈도에서는 NODE_ENV를 위와 같이 설정할 수 없음

  • 이 때 cross-env가 필요

1
npm i cross-env

🔻 package.json

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "익스프레스로 만드는 SNS 서비스",
  "main": "server.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 node server",
    "dev": "nodemon server",
    "test": "jest"
  }
}
...


🍳 sanitize-html

XSS(Cross Site Scripting) 공격 방어

1
npm i sanitize-html

허용하지 않은 html 입력을 막음

아래처럼 빈 문자열로 치환됨

1
2
3
4
const sanitizeHtml = require('sanitize-html')

const html = "/<script>location.href = 'https://github.com'</script>"
console.log(sanitizeHtml(html)) // ''


🍳 csrf

CSRF(Cross Site Request Forgery) 공격 방어

1
npm i csurf

csrfToken을 생성해서 프런트로 보내주고(쿠키로)

Form 등록 시 csrfToken을 같이 받아 일치하는지 비교

1
2
3
4
5
6
7
8
9
10
const csrf = require('csrf')
const csrfProtection = csrf({cookie:true})

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

app.port('/form', csrfProtection, (req, res) => {
  res.send('ok')
})


🍳 pm2

원활한 서버 운영을 위한 패키지

  • 서버가 에러로 인해 꺼졌을 때 서버를 다시 켜 줌

  • 멀티 프로세싱 지원(노드 프로세스 수를 1개 이상으로 늘릴 수 있음)

  • 요청을 프로세스들에 고르게 분배

  • 단점: 프로세스간 서버의 메모리 같은 자원 공유 불가

  • 극복: memcached나 redis같은 메모리 DB 사용(공유 메모리를 별도 DB에 저장)

1
npm i pm2

package.json 스크립트 수정

🔻 pakage.json

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "익스프레스로 만드는 SNS 서비스",
  "main": "server.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js",
    "dev": "nodemon server",
    "test": "jest"
  },
...
}
1
npm start

리눅스나 맥에서 pm2를 실행할 때 1024번 이하의 포트를 사용하려면 권리자의 권한이 필요하다.

따라서 sudo 명령어를 앞에 붙여 실행해야 한다.

image


🍳 프로세스 목록 확인하기

pm2 list로 프로세스 목록 확인 가능

프로세스가 백그라운드로 돌아가기 때문에 콘솔에 다른 명령어 입력 가능

image


🍳 pm2로 멀티 프로세싱하기

pm2 start [파일명] –i [프로세스 수] 명령어로 멀티 프로세싱 가능

프로세스 수에 원하는 프로세스의 수 입력

0이면 CPU 코어 개수만큼 생성, -1이면 CPU 코어 개수보다 1개 적게 생성

-1은 하나의 프로세스를 노드 외의 작업 수행을 위해 풀어주는 것

🔻package.json

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "익스프레스로 만드는 SNS 서비스",
  "main": "server.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production PORT=80 pm2 start server.js -i 0",
    "dev": "nodemon server",
    "test": "jest"
  },
...
}


🍳 서버 종료 후 멀티 프로세싱 하기

pm2 kill로 프로세스 전체 종료 가능

1
npx pm2 kill && npm start

재시작하면 프로세스가 CPU 코어 개수만큼 실행됨

image

1
npx pm2 reload all

현재 모든 서버들이 재시작


🍳 프로세스 모니터링하기

pm2 monit으로 프로세스 모니터링

1
npx pm2 monit

프로세스별로 로그를 실시간으로 볼 수 있음

image

1
npx pm2 logs --err

로그로 확인


🍳 winston

console.log와 console.error를 대체하기 위한 모듈

위 두 메서드는 휘발성

로그를 파일에 기록하는 것이 좋음

윈스턴 설치 후 logger.js 작성

1
npm i winston

🔻 logger.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;


🍳 winston 메서드

  • level은 로그의 심각도(error, warn, info, verbose, debug, silly 순, 중요도 순)

  • info를 고른 경우 info보다 심각한 단계 로그도 같이 기록됨

  • format은 로그의 형식(json, label, timestamp, printf, combine, simple 등 지원)

  • 기본적으로는 JSON으로 기록하지만 로그 시간을 표시하려면 timestamp를 쓰는 게 좋음

  • transports는 로그 저장 방식

  • new transports.File은 파일로 저장한다는 뜻, new transports.Console은 콘솔에 출력한다는 뜻

  • 인자로 filename(파일명), level(심각도) 제공


🍳 winston 적용하기

🔻app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...

dotenv.config();
const pageRouter = require('./routes/page');
const authRouter = require('./routes/auth');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const { sequelize } = require('./models');
const passportConfig = require('./passport');
const logger = require('./logger');

const app = express();

...

app.use('/', pageRouter);
app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);

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

...


🍳 winston 로그 확인하기

npm run dev로 개발용 서버 실행

http://localhost:8001/abcd 에 접속

각각의 로그가 파일에 기록됨

image

파일에 로그가 저장되어 관리 가능

winston-daily-rotate-file이라는 패키지로 날짜별로 관리 가능


🍳 helmet, hpp로 보안 관리하기

모든 취약점을 방어해주진 않지만 실무에서 필수인 패키지

1
npm i helmet hpp

배포 환경일 때만 사용하면 됨

🔻 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...

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

...

if (process.env.NODE_ENV === 'production') {
  app.enable('trust proxy');
  app.use(morgan('combined'));
  app.use(
    helmet({
      contentSecurityPolicy: false,
      crossOriginEmbedderPolicy: false,
      crossOriginResourcePolicy: false,
    }),
  );
  app.use(hpp());
} else {
  app.use(morgan('dev'));
}

...


🍳 connect-redis

멀티 프로세스간 메모리 공유를 위해 redis 사용

connect-redis가 익스프레스와 레디스를 연결해줌

1
npm i redis connect-redis

redislabs 접속 회원가입 후 데이터베이스 생성


🍳 레디스 호스팅 생성 완료

Endpoint와 Redis password를 복사해 .env에 붙여 넣기

endpoint에서 host와 port 분리

🔻.env

1
2
3
4
5
6
COOKIE_SECRET=nodebirdsecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
SEQUELIZE_PASSWORD=nodejsbook
REDIS_HOST=redis-18954.c92.us-east-1-3.ec2.cloud.redislabs.com
REDIS_PORT=18954
REDIS_PASSWORD=JwTwGgKM4P0OFGStgQDgy2AcXvZjX4dc


🍳 connect-redis 연결하기

app.js express-session 미들웨어 부분에 store 속성 추가

RedisStore 생성자의 인스턴스를 store 속성에 등록

이제 서버를 껐다 켜도 로그인이 유지됨

🔻 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...

const hpp = require('hpp');
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,
  legacyMode: true
});

redisClient.connect().catch(console.error);
const pageRouter = require('./routes/page')

...

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

...

image


🍳 nvm, n

노드 버전을 업데이트하기 위한 패키지

윈도에서는 nvm-installer, 리눅스나 맥에서는 n 패키지


🍳 nvm-installer

링크참고

nvm-setup.zip 내려받고 압축 해제 후 실행

nvm list로 노드 버전 확인, nvm instal로 버전 설치(nvm install latest로 최신 버전 설치)

image

image


🍳 nvm-installer로 노드 버전 바꾸기

nvm use [버전명]으로 사용하고픈 버전 입력

image


🍳 맥, 리눅스에서 n으로 노드 업데이트

n을 전역 설치

1
sudo npm i –g n

n 명령어로 현재 노드 버전 확인

n 버전으로 새 버전 설치

image




💻 깃과 깃허브 사용하기

🍳 Git으로 소스코드 관리하기

소스 코드가 수정될 때마다 직접 업로드하는 게 성가심

협업할 때도 서로 코드가 달라서 충돌이 발생하는 경우가 발생함

Git이라는 분산형 버전 관리 시스템을 많이 사용

GitHub는 Git으로부터 업로드한 소스 코드를 서버에 저장할 수 있는 원격 저장소

여러 사람이 코드를 공동 관리할 수 있음


🍳 Git 설치하기 생략


🍳 git 명령어 사용하기

콘솔에서 git –version 입력, 제대로 나오면 설치 성공

image

.gitignore에 git으로 소스 관리하지 않을 파일과 디렉터리 등록

🔻 .gitignore

1
2
3
4
5
node_modules
uploads
*.log
coverage
.env

image


🍳 .github 사용방법 생략

🍳 프로젝트에 git 설정하기

nodebird 프로젝트로 이동 후 git init 명령어 입력

image


🍳 모든 파일 git에 추가하기

.gitignore에 적힌 것을 제외한 모든 파일이 git 관리 대상이 됨

점(.)은 모든 파일을 의미함

image


🍳 소스 코드 커밋하기

사용자 정보를 설정한 후 소스 코드 커밋(상태 저장)

image


🍳 GitHub 주소 등록하기

아이디와 비밀번호 부분을 자신의 것으로 대체

image


🍳 GitHub에 코드 올리기

git push origin master 입력

소스 코드가 깃허브에 올라간 것을 확인할 수 있음

image




💻 AWS 시작하기

🍳 AWS 이용하기

aws.amazon.com/ko에 접속해서 회원가입

카드 정보 등의 결제 정보도 입력해야 한다.

소액 결제가 이루어질 수 있는데 본인 확인 이후에 환불을 해준다고 한다.


🍳 AWS 설치과정 생략


🍳 Lightsail 선택

배포서버는 서울로 설정

Lightsail 검색하면 편함

간단하게 노드 서비스를 배포할 수 있는 Lightsail 선택


🍳 Lightsail 인스턴스 생성

image

image


🍳 인스턴스 계획 선택

3 개월은 무료이므로 인스턴스 선택, 나중에 제거해야 과금되지 않음


🍳 인스턴스 생성 확인하기

생성된 인스턴스 클릭

image


🍳 인스턴스 삭제 방법

image




💻 AWS에 배포하기

🍳 SSH 연결하기

SSH를 사용하여 연결 버튼 클릭

image


🍳 LightSail 콘솔 열기

콘솔 명령어로 명령 실행

실무에서는 RDS를 사용한다.

1
2
3
4
sudo apt-get update
sudo apt-get intall -y gnupg
sudo wget https //dev.mysql.com/get/mysql-apt-config_0.8.23-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.23-1_all.deb # dkpg -i 이후 탭누르면 자동완성 
1
2
sudo apt-get update
sudo apt-get install -y mysql-server
1
2
3
4
5
sudo mysql -uroot -p
(비밀번호 입력)
(MySQL 프롬프트 접속)
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '비밀번호';
exit;


🍳 GitHub에서 소스 내려받기

git clone 명령어로 소스 내려받기

image

공개 레포지토리는 비밀번호가 필요하지 않다.

비밀번호가 필요한 경우는 토큰을 발급받아야 한다.


🍳 ightSail 서버 실행하기

기존의 아파치 서버 종료

image

노드 프로젝트 실행

npm ci 로 설치해야 좋음

1
2
3
4
cd 경로 이동
npm ci
npx sequelize db:create --env production
sudo npm i -g pm2

.env 생성

1
vim .env

a 를 누르면 입력 모드

입력하고 esc -> :wq

1
sudo NODE_ENV=production PORT=80 pm2 start server.js -i 0
1
sudo pm2 logs --err

서버가 실행되지 않으면 위의 명령어를 통해 어떤 에러인지 확인 후 해결

1
sudo pm2 reload all

해결 후 위 명령어로 서버 재시작


🍳 LightSail 서버 접속하기

퍼블릭 IP 확인 후 브라우저에 입력해 접속(예제에서는 13.209.84.110)

image




💻 GCP 시작하기

🍳 GCP 콘솔 접속하기

https://console.cloud.google.com에 접속(구글 로그인 필요)

서비스 약관 동의

image


🍳 새 프로젝트 생성하기

프로젝트 이름을 node-deploy로 이름 설정, 만들기 버튼 클릭

image


🍳 GCP 계정 등록하기

정보를 입력하고 계속 버튼으로 진행

image


🍳 무료 평가판 시작하기

300$ 어치의 무료 크레딧 제공

image


🍳 결제 사용 설정하기

VM 인스턴스 화면에서 클릭

image


🍳 VM 인스턴스 만들기

VM 인스턴스 화면에서 기다리다 보면 VM 인스턴스가 활성화 됨

만들기 버튼 클릭

image

머신 유형을 초소형으로 변경

  • 영역은 us-east1, us-central-1, us-west1만 무료

  • 운영체제는 Ubuntu 18.04LTS, e2-micro

  • 방화벽은 http와 https 트래픽 둘 다 허용

  • 만들기 버튼 클릭

image


🍳 VM 인스턴스 확인하기

인스턴스 생성 후 조금 기다리면 인스턴스가 준비됨

image




💻 GCP에 배포하기

🍳 SSH 실행하기

SSH 버튼을 눌러 SSH 실행

image


🍳 배포를 위한 프로그램 설치하기

SSH에 명령어 입력

1
sudo su

git clone 명령어로 소스 내려받기

image

1
2
3
4
5
cd 경로 이동
npm ci
npx sequelize db:create --env production
sudo npm i -g pm2
sudo NODE_ENV=production PORT=80 pm2 start server.js -i 0

image


🍳 서버 실행하기

image

image


🍳 외부 IP로 접속하기

외부 IP 확인 후 브라우저에 입력해 접속

image


🍳 추가로 알아둘 점

https로 접속하고 싶다면 인증서를 발급받아야 함

image




💻 함께 보면 좋은 자료

cross-env

pm2

sanitize-html

csurf

helmet

hpp

redis

connect-redis

rate-limit-redis

n

nvm

깃 설명

GCP

AWS




📚 레퍼런스

조현영. Node.js 교과서 = Node.js Textbook / 조현영 지음 (2022). Print.

[리뉴얼] Node.js 교과서 - 기본부터 프로젝트 실습까지

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.