github.com/loy124/docker-react-fullstack
풀스택 어플리케이션
선행 조건 : node.js, mysql, react에 대해 조금은 알고 있어야 수월하게 진행이 가능합니다.
브라우저 -> nginx -> react
브라우저 -> nginx -> node -> mysql 의 형태의 다중 컨테이너의 프로그램을 제작및 배포하는 것이 목표이다.
도커및 배포에 초점이 맞춰져 있습니다.
위의 방식은 크게 2가지 방식으로 설계가 가능하다고 한다.
1. nginx의 proxy를 활용한 설계
client -> nginx -> front(nginx) -> html css js
client -> nginx -> server -> mysql
- Request를 보낼 떄 URL 부분을 host이름이 바뀌어도 변경시켜주지 않아도 된다
- 포트가 바뀌어도 변경을 안해줘도 된다
- 설정 및 설계가 복잡하다.
리버스 프록시
클라이언트로부터 요청을 받아서 웹서버로 요청을 전송한다 -> 웹서버는 응답을 클라이언트가 아닌 Reverse Proxy로 반환 -> Reverse Proxy는 클라이언트로 응답 반환
따라서 외부 클라이언트가 내부 서비스에 접근할때 해당 리버스 프록시를 거쳐간다고 생각하면 좋을듯 싶다.
내부 서버를 숨길 수 있어 보안상의 이점이 있으며 서버 부하에 따라 요청을 분배할 수 있다.(사용자 증가에 따른 Web Server나 WAS를 유연하게 늘릴 수 있다)
2. nginx는 정적 파일만 제공해주는 설계
client -> nginx -> front(nginx) -> html css js
client -> nginx -> server -> mysql
- 설계가 간단하며 구현이 쉽다
- host name이나 포트 변경이 있을때 Request URL도 변경시켜 주어야 한다.
여기서는 1번방식을 활용해서 진행한다.
front -> server -> mysql -> nginx
순서는 전체 소스 코드 작성 -> Dockerfile 작성 -> Docker-compose 작성 -> github push -> trabis CI -> Docker hub -> AWS elasticBeanStalk
소스코드 작성 - Node.js
backend 폴더에서 npm init을 통해 package.json을 만들어 준다.
package.json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
"author": "",
"license": "ISC"
}
server.js를 생성해준다.
const express = require("express");
const app = express();
app.use(express.json());
app.listen(5000, () => {
console.log("this server listening on 5000");
});
server.js에서 사용하기 위한 db.js를 생성해 준다.
const mysql = require("mysql");
const pool = mysql.createPool({
connectionLimit: 10,
host: 'mysql',
user: 'root',
password: 'root',
database: 'myapp',
});
exports.pool = pool;
db.js를 활용해서 server.js에 내용을 추가해준다.
값들을 가져오는 get방식의 /api/values와 값을 입력하기위한 posts방식의 /api/value 를 정의하였다.
const express = require("express");
const db = require("./db");
const app = express();
app.use(express.json());
// 테이블 생성하기 예시
// mysql은 mysql폴더에서 데이터베이스및 table을 생성해줄 예정이다
//db.pool.query(`CREATE TABLE lists (
// id INTEGER AUTO_INCREMENT,
// value TEXT,
// PRIMARY KEY (id)
//)`, (err, results, fields) => {
// console.log('results', results);
//})
app.get("/api/values", (req, res, next) => {
db.pool.query("SELECT *FROM lists;", (err, results, fields) => {
if (err) return res.status(500).send(err);
else return res.json(results);
});
});
app.post("/api/value", (req, res, next) => {
db.pool.query(
`INSERT INTO lists (value) VALUES("${req.body.value}");`,
(err, results, fields) => {
if (err) return res.status(500).send(err);
else return res.json({ success: true, value: req.body.value });
}
);
});
app.listen(5000, () => {
console.log("this server listening on 5000");
});
여기까지 작성하면 backend 파트가 1차적으로 마무리 된다.
소스코드 작성 - React.js
해당 frontend 폴더에서 프로젝트를 진행한다.
npx create-react-app .
react 패키지를 설치해준다.
frontend -> src의 App.js를 수정해준다.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import logo from './logo.svg';
import './App.css';
function App() {
// db로부터 화면에 보여주는 리스트
const [lists, setLists] = useState([]);
// input 박스로 입력한 값
const [value, setValue] = useState("");
useEffect(() => {
// DB에 있는 값을 가져온다.
axios.get(`/api/values`).then(response => {
console.log('response', response.data);
setLists(response.data);
});
},[]);
const ChangeHandler = (e) => {
setValue(e.currentTarget.value);
}
const submitHandler =(e) => {
e.preventDefault();
axios.post('/api/value', {value: value}).then(response => {
if(response.data.success){
console.log('response.data', response.data);
setLists([...lists, response.data]);
setValue("");
}else{
alert("값을 DB에 넣는데 실패했습니다.")
}
})
}
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div className="container">
{lists && lists.map((list, index) => (
<li key={index}>{list.value}</li>
))}
<form className="example" onSubmit={submitHandler}>
<input
type="text"
placeholder="입력해주세요"
onChange={ChangeHandler}
value={value}
/>
<button type="submit">확인</button>
</form>
</div>
</header>
</div>
);
}
export default App;
App.test.js 또한 수정해준다.
주석 처리를 하는 이유는 만든 부분과 테스트 할 부분이 일치하지 않기 때문에 주석처리로 넘겨준다.
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
// render(<App />);
// const linkElement = screen.getByText(/learn react/i);
// expect(linkElement).toBeInTheDocument();
});
app.css또한 수정해준다.
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
form.example{
display: flex;
}
form.example input{
padding: 10px;
font-size: 17px;
border: 1px solid grey;
width: 74%;
background: #f1f1f1;
}
form.example button{
width: 20%;
padding: 10px;
background: #2196F3;
color:white;
font-size: 17px;
border:1px solid grey;
}
form.example button:hover{
background: #0b7dda;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
여기까지 진행하면 일단 frontend 파트는 마무리 된다.
Dockerfile 생성하기 - Node
먼저 Dockerfile.dev를 만들어 준다
FROM node:alpine
COPY ./package.json ./
RUN npm install
COPY ./ ./
#nodemon 사용
CMD ["npm", "run", "dev"]
그후
dockerfile을 생성해준다.
FROM node:alpine
COPY ./package.json ./
RUN npm install
COPY ./ ./
#nodemon 사용
CMD ["npm", "run", "start"]
여기까지 설정하면 node에 대한 설정은 간단하게 마무리된다.
Dockerfile 생성하기 - MySQL
DB구성은 또한 개발 환경과 운영환경을 사용할 것이다
개발환경 - Docker 환경
운영환경 - AWS RDS
먼저 개발 환경의 mysql 폴더를 생성하고 sqls 폴더를 생성한다
sqls 폴더안에는 Mysql을 실행 할 때 Database와 Table이 필요한데 그것들을 만들 장소를 만들어준다
initialize.sql
DROP DATABASE IF EXISTS myapp;
CREATE DATABASE myapp;
USE myapp;
CREATE TABLE lists(
id INTEGER AUTO_INCREMENT,
value TEXT,
PRIMARY KEY (id)
)
또한 인코딩문제(한글깨짐)을 해결하기 위한 설정인 my.cnf를 설정해 준다.(Docker에서 덮어 씌울 예정)
my.cnf
[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
이제 Dockerfile을 생성해준다 .
FROM mysql:5.7
#mysql내 my.cnf 설정을 my.cnf 파일로 덧씌운다.
ADD .my.cnf /etc/mysql/conf.d/my.cnf
Dockerfile은 개발환경에서만 사용해주기 때문에 이번에는 Dockerfile.dev만 생성해주도록 하였다
Dockerfile 생성하기 - React
먼저 Dockerfile.dev를 생성해준다.
Dockerfile.dev
FROM node:alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "run", "start"]
Dockerfile.dev를 통해 정상적으로 실행되면
Dockerfile을 build하는 방식이다
react로 build된 app은 nginx내에서 돌아가게 되는데 이에 따른 react 설정을
nginx에서 지정해줘야한다.
nginx폴더와 default.conf파일을 생성한다.
default.conf
server{
listen 3000;
location / {
#HTML 파일이 위치할 루트 설정
root /usr/share/nginx/html;
#사이트의 index페이지로 할 파일명 설정
index index.html index.htm
#react router을 사용해서 페이지간 이동을 할 때 필요한 부분
#react는 SPA기 때문에 하느이 index.html만 가지고 있기 때문에
#nginx에서는 자동으로 라우팅을 할 수 없기 때문에
#/home 등 특정 라우터에 접속 하려고 할때 매칭이 되는것이 없을 경우
#index.html을 제공해서 /home으로 라우팅을 시킬 수 있게 임의로 설정해주는것
#1.$uri가 정확하게 일치하는 것이 있는지 파악한다.
#2.그다음 $uri/가 정확히 일치하는지 파악한다.
#3.없으면 root/index.html을 실행.
try_files $uri $uri/ /index.html;
}
}
default.conf를 다 만들었다면 이제 Dockerfile을 생성해준다.
Dockerfile
FROM node:alpine as builder
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./ ./
RUN npm run build
FROM nginx
EXPOSE 3000
#default.conf에서 해준 설정을 nginx컨테이너 안에 있는 설정이 되게 복사를해준다.
#현재 frontend/nginx에 있는 default.conf를 복사해준다.
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
#builder로 부터 /app/에 build파일이 생기면 /usr/share/nginx/html 파일에 복사하는 설정
COPY --from=builder /app/build /usr/share/nginx/html
Dockerfile 생성하기 - Nginx
react내부 build파일을 실행하는 nginx가 아닌 reverse proxy방식의 nginx를 위한 방식이다
/api로 요청이 오게 되면 express 단으로 보내주고
그외 / 로 오게되면 react단으로 나누어 준다.
이러한 기능을 위한 nginx설정을 해준다.
nginx 폴더를 만든 후 default.conf 파일을 만들어 준다.
default.conf
#3000번 포트에서 frontend가 돌아가는것을 명시
upstream frontend {
server frontend:3000;
}
#5000번 포트에서 backend가 돌아가는것을 명시
upstream backend {
server backend:5000;
}
server {
#nginx 포트를 80번으로 열어준다.
listen 80;
# /로 시작하는 부분이 우선순위가 가장 낮다
# /로 들어오는 요청은 http://frontend로 보내준다
# frontend라는 이름은 docker-compose에서 정의해줄 예정
location / {
proxy_pass http://frontend
}
# /api로 들어오는 요청은 http://backend로 보내준다
# backend라는 이름은 docker-compose에서 정의해줄 예정
location /api {
proxy_pass http://backend;
}
#아래 부분을 만들어 줘야 개발환경 내에서 에러가 발생하지 않는다.
location /sockjs-node {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
이제 Dockerfile을 만들어 준다.
개발환경과 운영환경이 같으므로 Dockerfile 하나만 만들어준다.
FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf
여기까지 진행하면 전반적인 docker 설정이 마무리된다
docker-compose 파일 만들기
docker file들을 생성했으니 이제 docker-compose 파일을 생성해서 하나로 묶어준다.
docker-compose.yml
version: "3"
services:
frontend:
build:
dockerfile: Dockerfile.dev
context: ./frontend
#코드 수정후 다시이미지 build없이 코드가 반영 될 수 있게 volume을 사용한다.
volumes:
- /app/node_modules
- ./frontend:/app
stdin_open: true
nginx:
#재시작 정책
#no: 어떠한 상황에서도 재시작을 하지 않습니다
#always: 항상 재시작
#on-failure: 에러코드와 함께 컨테이너가 멈추었을 때만 재시작
#unless-stopped 개발자가 임의로 멈추려고 할때 빼고는 항상 재시작
restart: always
build:
dockerfile: Dockerfile
context: ./nginx
ports:
- "3000:80"
backend:
build:
dockerfile: Dockerfile.dev
context: ./backend
#container_name: app_backend
volumes:
- /app/node_modules
- ./backend:/app
mysql:
build: ./mysql
restart: unless-stopped
container_name: app_mysql
ports:
- "3307:3307"
volumes:
- ./mysql/mysql_data:/var/lib/mysql
- ./mysql/sqls/:/docker-entrypoint-initdb.d/
environment:
MYSQL_USER: root
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: myapp
MYSQL_TCP_PORT: 3307
#mysql의 root 비밀번호와 db를 설정해준다.
msyql 파트는 볼륨을 활용해서 진행해 주었다
컨테이너를 삭제 할 떄 컨테이너 안의 db 값까지 지워지게 된다
이에 따라 volume을 활용해준다
여기 까지 완료 하였다면
정상적으로 실행이 되는가 테스트를 진행한다
만약 정상적으로 실행이 되지 않는다면 docker ps -a 를 통해 컨테이너들을 지워주고
docker images에 있는 해당 프로젝트와 관련해서 만들어진 images를 모두 지워주고 다시 실행한다.
(에러 해결하는데 꽤 시간을 소요 한 것 같다)
여기까지 docker파일 및 docker-compose를 활용해서 1차적으로 마무리하였고 이제 배포 부분이 남았다.
배포는 해당 파트에서 이어진다.
위 글은
해당 강의를 듣고 정리하는 내용입니다.
'Docker, CI' 카테고리의 다른 글
Docker - Node +Mysql + React - AWS 배포하기(Travis CI) (2) | 2020.12.30 |
---|---|
docker , travis, aws 를 활용한 react 자동 배포하기 (2) | 2020.12.29 |
Docker 를 활용한 react-nginx 실행하기 (0) | 2020.12.29 |
docker - docker compose (0) | 2020.12.26 |
docker - node.js 어플만들기 (0) | 2020.12.26 |