본문 바로가기
Development(Web, Server, Cloud)/Cloud : 정리

[개발회고] Docker Swarm(Docker Stack) + React + Express(Node Server) + Nginx + Reverse Proxy

by tonyhan18 2022. 3. 31.
728x90

프로젝트 간신히 성공한 기념으로 정리해보자

 

참고가 된 자료들

Load Balance and Scale Node.js Containers with Nginx and Docker Swarm | by Aagam Vadecha | Level Up Coding (gitconnected.com)

 

Load Balance and Scale Node.js Containers with Nginx and Docker Swarm

In my previous blog we saw how to deploy a Node.js app in a docker container on different host ports and load balance it with Nginx. It was a good approach and can be used to understand the basics…

levelup.gitconnected.com

Docker 사용기(feat. React, Nginx, nodejs) (tistory.com)

 

Docker 사용기(feat. React, Nginx, nodejs)

도커를 이용해서 배포를 해볼 것인데, 2가지를 배포해볼 것이다. 첫번째는 React와 Nginx를 묶어서 이미지화 하고 컨테이너화 해서 클라이언트를 배포할 것이고, 두번째는 node 서버를 배포해볼 것

tobegood.tistory.com

7월 25일 모각코 종료 — 현수 세상 (tistory.com)

 

7월 25일 모각코 종료

1. Dockerfile에 대한 공부 2. 깃허브 브랜치에 대한 이해 Dockerfile 설명 설명드리겠습니다. FROM node:10.19.0 맨 처음에는 FROM으로 시작합니다. 우리는 node version 10.19.0 으로 된 이미지를 만들고 싶다는..

junghyeonsu.tistory.com

docker-compose 를 활용한 react 및 nodejs express 배포 (tistory.com)

 

docker-compose 를 활용한 react 및 nodejs express 배포

옛날 spring, jsp 시절에는maven(or gradle) 빌드로 나온 war 결과를 tomcat 에 배포하면 쉽게 배포가 되었다. 하지만 react 와 nodejs express 를 개발하고 배포하려고 보면, 어떻게 배포해야할지 난감할 때가 있

songjang.tistory.com

Docker - Node +Mysql + React - 개발및 Dockerfile 생성하기 (tistory.com)

 

Docker - Node +Mysql + React - 개발및 Dockerfile 생성하기

github.com/loy124/docker-react-fullstack loy124/docker-react-fullstack Contribute to loy124/docker-react-fullstack development by creating an account on GitHub. github.com 풀스택 어플리케이션 선행..

loy124.tistory.com

 

---

 

실습용 코드

tonyhan18/react-express-nginx-dockerstack (github.com)

 

GitHub - tonyhan18/react-express-nginx-dockerstack

Contribute to tonyhan18/react-express-nginx-dockerstack development by creating an account on GitHub.

github.com

 

 

개발 회고록

위쪽 네개의 블로그의 내용을 짬뽕해서 docker swarm으로 Docker Swarm(Docker Stack) + React + Express(Node Server) + Nginx + Reverse Proxy를 만들 수 있었다.

 

이 프로젝트를 위해 4일이 갈려나갔다.

상당히 어렵다.

 

최종적으로 위의 화면이 나오면 성공이다.

 

 

환경구성하기

 

개발 환경 : VMWARE(VirtualBox도 가능)

OS : CentOS7

FE : React

BE : Express

배포 : docker, docker stack, docker compose

DB : mysql(추가될 예정)

 

manager에서

worker1(kvm1) : 192.168.1.101, 192.168.2.101 (네트워크 됨)

worker2(kvm2) : 192.163.1.102, 192.168.2.101 (네트워크 됨)

manager(storage) : 211.183.3.199, 192.168.2.199 (네트워크 됨)

vi /etc/hosts 에 대상 컴퓨터들 등록하기

 

manager, kvm1, kvm2에서

```

echo "1" > /proc/sys/net/ipv4/ip_forward

```

 

manager에서

```

docker swarm init

```

해서 나오는 토큰을 모든 worker들 kvm1, kvm2 에 넣어서 Enter을 눌러주자

 

 

폴더구조

 

프로그램 설치

환경구성

 

 

 

 

Reverse-Proxy 알기

Docker - Node +Mysql + React - 개발및 Dockerfile 생성하기 (tistory.com)

 

Docker - Node +Mysql + React - 개발및 Dockerfile 생성하기

github.com/loy124/docker-react-fullstack loy124/docker-react-fullstack Contribute to loy124/docker-react-fullstack development by creating an account on GitHub. github.com 풀스택 어플리케이션 선행..

loy124.tistory.com

docker-compose 를 활용한 react 및 nodejs express 배포 (tistory.com)

 

docker-compose 를 활용한 react 및 nodejs express 배포

옛날 spring, jsp 시절에는maven(or gradle) 빌드로 나온 war 결과를 tomcat 에 배포하면 쉽게 배포가 되었다. 하지만 react 와 nodejs express 를 개발하고 배포하려고 보면, 어떻게 배포해야할지 난감할 때가 있

songjang.tistory.com

 

reverse proxy라는 것은 docker로 서버를 띠었을때 REACT와 EXPRESS간의 통신이 가능하도록 해주는 중간 매개자이다.

만약 PHP 를 쓴다면 SSR(Server Side Rendering)을 해주고, 서버에서 프론트페이지를 던지기 때문에 구지 이걸 할필요가 없을거 같지만...

REACT같이 CSR(Client Side Rendering)을 한다면 반드시 REVERSE PROXY가 필수이다.

안하면 아예 연결이 안된다.

 

원리는 이러하다.

1. 사용자는 NGINX로 접속한다.

2. NGINX가 가지고 있던 REACT 페이지를 사용자에게 던져준다.

3. REACT 페이지가 Express와 소통을 위해 NginX로 요청을 던진다.

4. NginX는 그걸 받고 Express에게 던진다.

5. Express는 요청에 응답을 NginX에게 던진다.

6. NginX는 받은 응답을 React에게 던진다.

7. 응답을 받은 React가 데이터를 잘 요리해서 보여준다.

 

---

 

[ 1. 리액트/서버 구현 ]

1. REACT(FE) 만들기

```

mkdir docker-fullstack

cd docker-fullstack

npx create-react-app client

cd client

```

```

npm install -y react-router-dom axios http-proxy-middleware

npm start

```

react-router-dom : 다른 페이지로 이동하기 위한 패키지

axios : 백과 통신하기 위한 패키지

http-proxy-middleware : nginx를 이용하기 위한 프록시

 

잘 동작한다.

 

 

2. EXPRESS 만들기

```

cd ..   #(docker-fullstack 폴더로 오기)

npx express-generator server

```

```

cd server

npm install

npm install -y nodemon

```

설치된 패키지의 bin/www.js의 port 부분을 5000이라고 바꾸어주자

이건 우리가 지정해놓는거기 때문에 마음대로 바꾸어도 된다.

 

nodemon은 서버 코드 수정시 자동으로 반영해주는 런타임 패키지이다.

 

Express 서버가 정상 작동한다.

 

 

 

3. React - Server 통신하기

Client/setupProxy.js

const {createProxyMiddleware} = require('http-proxy-middleware');

module.exports = function(app) {
	app.use(
		createProxyMiddleware('/api',{
			target: 'http://localhost:5000',
			changeOrigin: true
		})
	)
}

src/setupProxy.js

 

테스트 환경에서는 localhost 5000번으로 해놓았다. 이건 바로 서버로 연결된다.

 

위와같이하면 react에서 수행하는 axios 통신들은 모두 이 proxy의 주소를 타고 이동하게 된다.

target은 서버 위치이고

changeOrigin은 보내는 패킷의 헤더를 바꾸게 하겠다는 의미이다.

 

 

 

import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
import axios from 'axios';

function App() {
  const [os, setOS] = useState('');
  const [list, setList] = useState([]);
  const [item, setItem] = useState({});

  useEffect(()=>{
    (async () => {
      // let data = await axios.get('/api/list');
      const { data : list } = await axios.get('/api/list');
      setList(list);
      const {data : item} = await axios.get('/api/item');
      setItem(item);
      const {data : os} = await axios.get('api/os');
      setOS(os);
    })();
  },[])
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>

        <div>Data From</div>
        <div>{os}</div>
        <div>Get List</div>
        {list.map((item, idx)=>(
          <div key={idx}>{`${item.id}/${item.name}`}</div>
        ))}

        <div>Get Item</div>
        <div>{`${item.id}/${item.name}`}</div>
      </header>
    </div>
  );
}

export default App;

서버와 통신하기 위한 axios도 만들어주었다.

client/src/App.js

 

서버쪽에는 apiRouter라는 것을 만들어주었다.

 

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var apiRouter = require('./routes/api');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/api', apiRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

server/app.js

 

var express = require('express');
var router = express.Router();
const os = require('os');

/* GET home page. */
router.get('/os', function(req,res,next) {
  res.send(`${os.hostname()}`);
});

router.get('/list', function(req, res, next) {
  res.json([
      { id: 'test1', name: '테스트1' },
      { id: 'test2', name: '테스트2' },
      { id: 'test3', name: '테스트3' },
      { id: 'test4', name: '테스트4' },
  ]);
});

router.get('/item', (req, res) => {
  res.json({
      id: 'test1',
      name: '테스트1'
  });
});


module.exports = router;

server/routes/api.js

 

 

실행은 vscode의 콘솔 화면을 잘라서 두개에서 실행시키면 된다.

 

두개다 실행 명령어는 npm start이다.

 

 

정상적으로 데이터를 받아온다.

 

 

---

 

[ 2. 엔진엑스 - 리액트 구현 ]

이번에는 엔진엑스가 리액트와 통신해서 서버에서 데이터를 받아오게 할 계획이다.

client/conf/nginx.conf 를 만들어주자

 

upstream backend {
	least_conn;
	server 211.183.3.199:5000;
}

upstream frontend {
	least_conn;
	server 211.183.3.199:3000;
}

server {
	listen 80;
	location / {
		root /usr/share/nginx/html;
		index index.html index.htm
		try_files $uri $uri/ /index.html;
	}

	location /api {
		proxy_pass http://backend;
	}
	error_page 500 502 503 504 /50x.html;

	location = /50x.html {
		root /usr/share/nginx/html;
	}

	location /sockjs-node {
        proxy_pass http://frontend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}

간단하게 구성했다.

 

이걸 가지고 Dockerfile을 구현해보자.

##### REACT to BUILD FILE
FROM node:16-alpine as builder

# # make environment
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json .
RUN npm install --silent
RUN npm install react-scripts@5.0.0 -g --silent

# # COPY files
COPY . ./
RUN npm run build

##### nginx ENV install
FROM nginx:latest
RUN rm -rf /etc/nginx/conf.d/default.d
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /usr/src/app/build /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

 

.dockerignore에 node_modules는 함께 이미지에 복붙하지 말라고 넣어주자.

 

```

docker build -t tonyhan18/react:1.0 .

docker container run -it -p 80:80 tonyhan18/react:1.0

```

서버와 통신하는지 확인하기 위해서 서버쪽도 켜주고 확인해보자

아래와 같이 데이터를 받아온다면 성공이다.

(서버를 킬려면 서버 폴더(package.json 이 있는 곳)으로 가서 npm start 하면 된다.)

 

위와같이 된다.

 

+++++++++++++++

 

 

빌드 과정을 최적화 해줄 수 있다.

```

npm run build

```

##### nginx ENV install
FROM nginx:latest
RUN rm -rf /etc/nginx/conf.d/default.d
COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./build /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

```

docker build -f Dockerfile.dev -t tonyhan18/react:1.0 .

docker container run -it -p 80:80 tonyhan18/react:1.0

````

미리 build된 파일을 복붙해는 방법이다. 이러면 정말 빠르게 되지만... 다른 개발자가 이걸 못할 수 있으니 일반 Dockerfile도 남기어놓자

 

 

[ 2. 익스프레스 이미지화 ]

FROM node
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD npm start
EXPOSE 5000

앞에서 그 개고생을 했다면 얘는 걱정없다.

 

주의할점은 base image 파일은 가급적 그냥 node 를 써주자 REACT를 쓰기에는 alpine과 같은 작은 버전은 여러가지 에러를 불러왔다.

 

```

docker build -t tonyhan18/express:1.0 .

docker container run -it -p 5000:5000 tonyhan18/express:1.0

```

둘간의 통신이 된다면 성공이다.

 

```

docker image push tonyhan18/express1.0

```

이게 안되면 안된다. 필수다!

 

 

[ 3. 도커 스택]

1. docker swarm 등록하기

```

docker swarm init

# docker swarm init --advertise-addr=211.183.3.199  # 만약 네트워크 포트가 여러개라서 에러가 난 경우

# docker swarm join-token worker # 만약 토큰을 까먹었다면

```

하면 join token이 뜬다.

이걸 복사해서 다른 가상머신에 넣고 Enter을 처주자

 

그리고 반드시 다른 가상머신들은 manager과 연결되어 있고 인터넷과도 연결되어 있어야 한다.

 

dev 폴더 아래에 docker-compose.yml 을 만들어주자

 

version: '3'

services:
  nodeapp:
   image: tonyhan18/express:1.0
   ports:
     - 5000:5000
   deploy:
    replicas: 6
    restart_policy:
      max_attempts: 3
      condition: on-failure
    update_config:
      parallelism: 3
      delay: 10s
    placement:
      constraints: [node.role == worker]
   networks: 
    - balance 
  
  proxy:
    image: tonyhan18/react:1.0
    ports:
      - 80:80
    depends_on:
      - nodeapp
    deploy:
      placement:
        constraints: [node.role == manager]
    networks: 
      - balance

networks:
  balance:
    driver: overlay

 

```

docker stack deploy -c docker-compose.yml swarm

```

 

```

docker service ls

```

6개의 서버와

1개의 nginx + react가 살아났다.

 

서로 다른 컨테이너에서 정상적으로 데이터를 받아온다.

 

LB가 성공했다!

 

 

 

[ 4. 도커 스택 정리하기]

```

docker stack rm swarm

```

 

 

응용 코드

tonyhan18/TRIPLY_keduit (github.com)

 

GitHub - tonyhan18/TRIPLY_keduit

Contribute to tonyhan18/TRIPLY_keduit development by creating an account on GitHub.

github.com

 

728x90

'Development(Web, Server, Cloud) > Cloud : 정리' 카테고리의 다른 글

CICD : Jenkins, gitlab, githosting, github action  (0) 2022.05.19
kubernetes(k8s) 정리 part1  (0) 2022.04.14
7. MySQL  (0) 2022.03.23
6. Docker 도커  (0) 2022.03.03
5. 클라우드 가상화(KVM, Libvirt)  (0) 2022.02.24