Multi Container를 배포해보자(개발 환경 부분)

cdwde·2022년 10월 13일
0

Docker

목록 보기
8/8


이번에는 React, Node, DB까지 도커를 이용해 어플리케이션을 만들어볼 예정이다.
설계하는 여러 방법이 있는데 무엇을 선택할 것인가?🤔

✅ Multi container를 위한 전체적인 설계

1️⃣ Nginx Proxy를 이용한 설계

nginx의 기능을 크게 보면 두 가지가 있다. 정적파일 제공, 라우팅시켜주는 역할이 있는데 첫번째 설계에서는 두 가지 모두 이용한다.

만일 /api로 시작하는 엔드포인트를 보낸다면 이건 다 백엔드 서버로 가는 요청들인 것이다.

장점

  • Ruequest를 보낼 때 URL부분 host이름이 바뀌어도 변경해주지 않아도 된다.
  • 포트가 바뀌어도 변경을 안해줘도 된다.

단점

ngix 설정, 전체 설계가 복잡하다.

2️⃣ Nginx는 정적파일을 제공만 해주는 설계

클라이언트가 포트를 보고 요청을 보내는 형식이다.

장점

설계가 다소 간단해 구현하는게 쉽다

단점

host name이나 포트 변경 있을 때 Request URL도 변경 필요하다

여기에서는 1️⃣번을 선택해서 진행해보겠다.

✅ 들어가기 전, 순서 정리

  1. 먼저 NodeJs, React 전체 소스 코드를 작성한다.
  2. Dockerfile을 작성한다.
    개발환경을 위한 Dockerfile.dev
    운영환경을 위한 Dockerfile
  3. docker-compose 작성
  4. Github에 push (feature -> main)
  5. Travis CI (테스트 소스 실행 -> 테스트 성공 -> 각각의 Dockerfile을 이용해 Image 생성 -> 이미지 생성 후 Docker Hub로 전달
  6. Docker Hub (Travis CI에서 빌드된 이미지 보관 -> AWS EB에서 가져가려고 할 때 전달)
  7. AWS EB (Travis CI에서 빌드된 이미지 통해 배포)

✅ NodeJS 구성하기

  • package.json
{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "4.18.2",
    "mysql": "2.18.1",
    "nodemon": "2.0.20"
  },
  "author": "",
  "license": "ISC",
  "type": "module"
}
  • db.js
import mysql from "mysql";

const pool = mysql.createPool({
  connectionLimit: 10,
  host: "mysql",	#docker-compose.yml 서비스 이름인 mysql
  user: "root",
  password: "admin",
  database: "myapp",
});

export default pool;
  • server.js
import express from "express";
import db from "./db.js";

const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

db.query(
  `CREATE table lists (id INTEGER AUTO_INCREMENT, value TEXT, PRIMARY_KEY (id))`,
  (err, results, fileds) => {
    console.log(`results: ${results}`);
  }
);

app.get("/api/todos", (req, res, next) => {
  db.query("SELECT * FROM lists", (err, results, fileds) => {
    if (err) return res.status(500).send(err);
    return res.json(results);
  });
});

app.post("/api/todos", (req, res, next) => {
  db.query(
    `INSERT INTO lists (value) VALUES ${req.body.value}`,
    (err, results, fileds) => {
      if (err) return res.status(500).send(err);
      else return res.json({ success: true, value: req.body.value });
    }
  );
});

app.listen(5000, () => console.log(`Server is running`));

✅ ReactJS 구성하기

npx create-react-app frontend
  • App.js
import React, { useState, useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";

function App() {
  const [lists, setLists] = useState([]);
  const [value, setValue] = useState("");

  useEffect(() => {
    axios.get("/api/todos").then((response) => {
      console.log(response);
      setLists(response.data);
    });
  }, []);

  const changeHandler = (event) => {
    setValue(event.currentTarget.value);
  };

  const submitHandler = (event) => {
    event.preventDefault();
    axios.post("/api/todos", { value: value }).then((response) => {
      if (response.data.success) {
        console.log(response);
        setLists([...lists, response.data]);
        setValue("");
      } else {
        alert("값을 넣는데 실패했습니다.");
      }
    });
  };

  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}
            ></input>
            <button type="submit">확인</button>
          </form>
        </div>
      </header>
    </div>
  );
}

export default App;

✅ 리액트 앱을 위한 도커 파일 만들기

Dockerfile은 개발환경과 운영환경에서 다르게 구성하는게 좋기 때문에 개발환경을 위한 Dockerfile을 만들고 그 후 운영환경을 위한 Dockerfile을 만들 예정이다.

이 부분은 이전에 했던 것(Single Container를 배포해보자)과 거의 유사하다!!
하지만 복습 차원으로 여기서도 설명을 끄적거리겠다.(사실 까먹음)

frontend > Dockerfile.dev

FROM node:alpine

WORKDIR /app

COPY package.json ./

RUN npm install

COPY ./ ./

CMD ["npm", "run", "start"]
  • FROM: 베이스 이미지를 도커 허브에서 가져옴
  • WORKDIR: 해당 어플의 소스 코드들이 들어감
  • COPY: 소스 코드가 바뀔 때마다 종속성까지 다시 복사를 해주는 수고를 하지 않기 위해 먼저 종속성 목록을 담고 있는 package.json을 복사한다
  • RUN: package.json에 명시된 종속성을 다운받음
  • COPY: 모든 소스 코드들을 WORKDIR로 복사해줌
  • CMD: 이 컨테이너가 실행될 때 같이 실행할 명령어 지정

frontend > Dockerfile

FROM node:apline as builder
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY ./ ./
RUN npm run build

FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html

첫 번째 부분은 Nginx가 제공을 해줄 Build 파일을 생성하는 단계(Builder Stage)이다.

두 번째 부분은 nginx를 가동하고, 첫 번째에서 생성된 Build 파일들을 제공해준다. 그리고 default.conf에서 해준 설정을 nginx 컨테이너 안에 있는 설정이 되게 복사 해준다.
(/etc/nginx/conf.d/default.conf가 컨테이너 안에 있는 nginx 설정 경로)

nginx 관련 설정

정적파일을 제공해주기 위한 nginx를 위해서 frontend 안에 nginx 폴더를 생성하고, default.conf 파일을 생성한다.

server {
  listen 3000;
  
  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
  }
}
  • listen: nginx가 listen하고 있는 포트
  • root: HTML 파일이 위치할 루트 설정(해당 부분에 build 폴더 들어가면 됨)
  • index index.html index.htm: 사이트의 index 페이지로 할 파일명 설정
  • try_files: React Router를 사용해서 페이지간 이동을 할 때 이 부분 필요

React는 Single Page Application으로 index.html 하나의 정적 파일만 가지고 있다. 만약 {URL}/home 이렇게 접속을 하려고 할 때도 index.html 파일에 접근해 라우팅을 시켜야하는데 nginx에서는 자동으로 이걸 알 수가 없다. 그래서 /home으로 접속하려고 할 때 /home에 매칭되는 것이 없을 때 대안책으로 index.html을 제공해 /home으로 라우팅을 시킬 수 있게 임의로 설정한다.

✅ Node 앱을 위한 도커 파일 만들기

  • Dockerfile.dev
FROM node:alpine

WORKDIR /app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm", "run", "dev"]
  • Dockerfile
FROM node:alpine

WORKDIR /app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm", "run", "start"]

✅ MYSQL을 위한 도커 파일 만들기

개발 환경에서는 도커 이미지를 이용해 설치를 해준다.

  • mysql > Dockerfile
FROM mysql:5.7

Mysql을 시작할 때 Database와 Table이 필요한데, 그것들을 만들 장소를 만들어준다.

  • mysql > sqls > initialize.sql
DROP DATABASE IF EXISTS myapp;

CREATE DATABASE myapp;
USE myapp;

CREATE table lists (
  id INTEGER AUTO_INCREMENT,
  value TEXT,
  PRIMARY_KEY (id)
);

현재 상태에서 어떠한 글을 데이터베이스에 넣어줄 때 한글이 깨지면서 저장할 때 오류가 발생한다. 따라서 한글도 저장할 수 있도록 설정한다.
(여기서는 이모티콘까지 작성할 수 있도록 utf8mb4로 변경한다.)

  • mysql > my.conf
[mysqld]
character-set-handshake=FALSE
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[mysql]
default-character-set=utf8mb4

[client]
default-character-set=utf8mb4

위에 해준 설정을 실제 mysql 설정을 해주는 my.conf파일로 덮어씌어준다.

  • mysql > Dockerfile
FROM mysql:5.7

ADD ./my.conf. /etc/mysql/conf.d/my.cnf

✅ nginx를 위한 도커 파일 만들기

현재 nginx가 쓰이는 곳은 두 군데이며 서로 다른 이유로 쓰이고 있다.
하나는 Proxy를 이유로, 하나는 Static 파일을 제공해주는 역할을 하고 있다.
어떤 식으로 nginx가 프록시 기능을 해주는가?

출처
따라하며 배우는 도커와 CI환경
MySQL 언어 설정(charset) 변경하기!

0개의 댓글