이번 글에서는 React Front 와 Redis 작업을 하여 Docker Compose 에 대한 작업을 마무리 해보도록 하겠습니다.
빠른 개발과 편의성을 위해 create-react-app 을 사용하도록 하겠습니다. create-react-app 에대한 정보는 아래 링크를 참조해주시면 되겠습니다.
프로젝트 루트 디렉터리에 터미널을 실행해주시고 npm init react-app reactfront 를 실행해주세요. 커맨드 실행이 완료되면 저희 폴더 구조에 맞게 reactfront 라고 되어있는 폴더를 ReactFront 로 대문자화 해주시면 되겠습니다.
커맨드가 완료되면 여러 폴더 및 파일들이 생성될텐데 각 폴더 및 파일에대한 설명은 이 강의의 범위를 벗어나기 때문에 생략하도록 하겠습니다. React에대해 궁금하신점이 있으시면 아래 링크를 참조해주세요.
가장먼저 ReactFront 폴더에 Dockerfile 을 생성하고 아래 코드를 붙여넣겠습니다.
FROM node:12-alpine
# work directory
WORKDIR /usr/app
# Copy dependencies first for effective caching
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 6000
CMD ["npm", "run", "start"]
지금까지 써왔던 다른 Docker 스크립트와 별 차이 없이 컨테이너를 빌드하고 npm run start 라는 커맨드를 실행시키는 스크립트입니다.
create-react-app 을 실행하며 생성된 package.json 을 잠시 보면 npm run start 커맨드에 react-scripts start 라는 커맨드가 매핑되어있는걸 보실 수 있습니다. 도커 컨테이너에서 ReactFront 컨테이너를 실행할때 해당 커맨드를 사용하게 될것입니다.
다음은 이전 강의에서 작업했던 Nginx 폴더의 dev.conf 파일을 고쳐보도록 하겠습니다.
# Express Server에 대한 정의를 해줍니다.
# Docker Compose를 사용하면 자동으로 Docker Compose에서 사용한
# 이름에 따라 도커 네트워크에서 DNS가 생성됩니다.
# 저희 Express Server는 express_server라는 이름으로
# Docker Compose 파일에 정의해놨기 때문에 해당 이름을
# DNS로 사용하시면 요청이 Docker Compose 내부 내트워크를 타고
# Express Server로 전달이 됩니다.
# 포트가 4000번이 아니고 3000번인 이유는
# 호스트를 통해서 네트워크 연결이 될 경우 4000번이 맞지만
# 다이렉트하게 Express Server의 컨테이너에 꼿히기때문에
# 내부 포트인 3000번을 사용해주셔야 합니다.
upstream express_server {
server express_server:3000;
}
upstream react_front {
server react_front:3000;
}
server {
listen 80;
# 로그파일을 저장하는 부분입니다. 이 부분을
# Docker Compose에서 Volume 쉐어를 해놓으면
# 호스트 기기에서도 도커에서 생성된 로그파일을 볼수있습니다.
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# React Front 컨테이너로 루트 패스를 모두
# 매핑해줍니다.
location / {
proxy_pass http://react_front;
}
# 이건 딱히 신경쓰실 필요는 없는데
# React 에서 파일이 변경 됐을때
# 브라우저로 refresh를 하라고 알려주는 패스를
# 매핑해주는 역할을 합니다.
location /sockjs-node {
proxy_pass http://react_front;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
# /api의 경로로 Nginx를 hit 하게되면
# express Server로 reverse proxy 역할을 하라는 코드입니다.
# 다만 서버 개발의 편의를 위해 /api 부분을 제외하고
# /api 이후에 오는 부분들만 전달하게됩니다.
# rewrite 룰을 정의하지 않을경우
# Express Server에서 모든 api포인트에 /api를 최상위
# 라우트로 지정해줘야해서 귀찮아집니다.
location /api {
proxy_pass http://express_server;
rewrite /api/(.*) /$1 break;
}
}
위 스크립트로 dev.conf 파일을 통째로 덮어쓰기 해주세요. 추가된부분은 location / 부분과 location /socketjs-node 입니다. 변경 내용은 주석을 참조해주세요.
마지막으로 ReactFront 의 docker-compose.yml 파일 및 .env파일 작업을 해줄 차례입니다. 아래 코드를 docker-compose.yml 파일에 추가해주세요.
react_front:
build: ReactFront
ports:
- "${REACT_FRONT_PORT}:3000"
volumes:
- /usr/app/node_modules
- ./ReactFront:/usr/app
env_file:
- .env
아래 코드는 .env 파일에 추가해주세요.
## React Front
REACT_FRONT_PORT=6000
이건 약간의 본론에서 벗어나기는 하지만 Nginx 를 reverse proxy로 사용하게된 이상 ReactFront, ExpressServer, PollingServer, MongoDB 는 모두 더이상 포트를 호스트 디바이스와 매핑해주실 필요가 전혀 없습니다. 어차피 Nginx 의 8080 포트를 타고들어가서 Docker Compose 내부 네트워크로 통신이 이루어지기 때문이죠.
docker-compose up --build 커맨드를 프로젝트 루트에서 실행하면 ReactFront 를 포함한 5개의 컨테이너가 실행됩니다.
이쯤되면 5개의 컨테이너를 빌드하는데 컴퓨터에 따라 시간이 좀 오래걸릴 수 있습니다. 현재는 한개의 쓰레드만 사용해서 컨테이너를 순서대로 빌드를 하지만 docker-compose build --parallel 을 실행하시면 모든 컨테이너를 병렬로 빌드 하실 수 있습니다. 빌드가 완료되면 docker-compose up (빌드 태그를 생략하고)를 실행해서 병렬로 빌드된 컨테이너들을 한번에 실행하실 수 있습니다.
잘 따라오셨다면 브라우저에서http://localhost:8080 을 접속하셨을때 아래와같은 페이지를 볼 수 있으십니다. (create react app의 버전에 따라 랜딩 페이지는 살짝 다를수도 있습니다 — 400만 안뜨시면 돼요).
드디어 Docker Compose 로 프론트엔드를 띄웠다는 만족감을 즐기며 잠시 랜딩페이지를 감상하시다 ReactFront 폴더로 돌아오셔서 npm install axios 커맨드를 실행해주세요. ReactFront 에서 HTTP 요청을 할때 사용하게될 라이브러리입니다.
ReactFront/src 폴더에 있는 App.js 의 코드를 아래 코드로 대체해주세요.
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios';
function App() {
const [posts, updatePosts] = useState([]);
useEffect(() => {
async function fetchData() {
const resp = await axios.get('/api/posts');
updatePosts(resp.data.data)
}
fetchData();
}, []);
return (
<div className="App">
{posts.map((x) => {
return (
<div style={{
border: '1px solid #000000',
margin: 20,
padding: 10,
}}>
<a href={x.url}>
<div>
id: {x._id}
</div>
<div>
title: {x.title}
</div>
<div>
url: {x.url}
</div>
</a>
</div>
)
})}
</div>
)
;
}
export default App;
GET /api/posts 로 요청을 해 응답 값들을 텍스트로 매핑해주는 역할을 합니다. axios 를 설치했기때문에 ReactFront 컨테이너를 다시 빌드해야합니다. docker-compose up --build 를 이용해 다시 빌드 후 실행해주시면 아래와 비슷한 화면이 http://localhost:8080 에 뜹니다.
각 박스를 클릭하면 해당되는 Hacker News로 이동하실 수 있습니다.
마지막으로 Redis 컨테이너를 작업하고 마무리하겠습니다.
Redis 는 조금 독특하게 따로 폴더를 생성하지 않고 Docker Compose 에서 실행하는 방법을 알려드리겠습니다. 아래 코드를 docker-compose.yml 에 추가해주세요.
redis:
image: redis:5
위와같이 image 키에 직접 이미지를 명시해주면 Dockerfile 없이도 컨테이너를 빌드하고 실행할 수 있습니다.
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const mongoose = require('mongoose');
const axios = require('axios');
const Post = require('mongoose/Post');
const redis = require('redis');
const {promisify} = require('util');
const mongoUri = 'mongodb://root:root@mongodb:27017/admin';
const client = redis.createClient({
host:'redis'
});
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
const incrAsync = promisify(client.incr).bind(client);
mongoose.connect(mongoUri, {useNewUrlParser: true});
app.get('/count', async (req, res) => {
const count = await getAsync('count');
console.log(count);
res.json({
count
})
});
app.get('/posts', async (req, res) => {
console.log('Getting Posts Started!!!');
/*
* 만일 count 라는 키가 존재하지 않으면
* count 를 1로 세팅하고
* count가 존재하면 +1을 합니다.
* */
const count = await getAsync('count');
if(count !== 0 && !count){
await setAsync('count', 1);
}else{
await incrAsync('count');
}
let posts = await Post.find();
if (posts.length === 0) {
await axios.get('http://polling_server:3000/');
posts = await Post.find();
}
console.log('Getting Posts Finished');
res.json({
success: true,
version:1,
data:posts
});
});
app.listen(port, () => {
console.log(`server is listening at localhost:${port}`);
});
위 코드로 ExpressServer 의 server.js 코드를 덮어씌워주세요. GET /count 라우트가 추가되고 GET /posts 라우트에 요청을 할때마다 count 변수를 +1 해주는 코드를 추가하였습니다.
이제 아래 코드를 ReactFront/src/App.js 파일에 덮어씌우겠습니다.
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios';
function App() {
const [posts, updatePosts] = useState([]);
const [count, updateCount] = useState(null);
useEffect(() => {
async function fetchData() {
const resp = await axios.get('/api/posts');
const respC = await axios.get('/api/count');
updatePosts(resp.data.data);
updateCount(respC.data.count);
}
fetchData();
}, []);
return (
<div className="App">
<div>
count: {count}
</div>
{posts.map((x) => {
return (
<div style={{
border: '1px solid #000000',
margin: 20,
padding: 10,
}}>
<a href={x.url}>
<div>
id: {x._id}
</div>
<div>
title: {x.title}
</div>
<div>
url: {x.url}
</div>
</a>
</div>
)
})}
</div>
)
;
}
export default App;
Count 를 서버에서 가져오고 HTML 로 display 해주는 코드입니다. 변경후 docker-compose up --build 를 재실행하시고 http://localhost:8080 에 접속하시면 위쪽에 count 가 뜨고 새로고침을 할때마다 count 가 +1 되는걸 보실 수 있습니다.
여기까지 따라오시는데 고생하셨습니다. Docker Compose 강의는 여기서 마치고 조만간 이 컨테이너들을 Kubernetes 에 올리는 강의로 다시 찾아뵙겠습니다. 감사합니다.