[Docker 부터 Kubernetes 까지] [#8] React Front 및 Redis 작업하기

이번 글에서는 React Front 와 Redis 작업을 하여 Docker Compose 에 대한 작업을 마무리 해보도록 하겠습니다.

빠른 개발과 편의성을 위해 create-react-app 을 사용하도록 하겠습니다. create-react-app 에대한 정보는 아래 링크를 참조해주시면 되겠습니다.

Create React App

프로젝트 루트 디렉터리에 터미널을 실행해주시고 npm init react-app reactfront 를 실행해주세요. 커맨드 실행이 완료되면 저희 폴더 구조에 맞게 reactfront 라고 되어있는 폴더를 ReactFront 로 대문자화 해주시면 되겠습니다.

커맨드가 완료되면 여러 폴더 및 파일들이 생성될텐데 각 폴더 및 파일에대한 설명은 이 강의의 범위를 벗어나기 때문에 생략하도록 하겠습니다. React에대해 궁금하신점이 있으시면 아래 링크를 참조해주세요.

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만 안뜨시면 돼요).

CRA

드디어 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 에 뜹니다.

react result

각 박스를 클릭하면 해당되는 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 에 올리는 강의로 다시 찾아뵙겠습니다. 감사합니다.

©Code Factory All Rights Reserved