프로젝트 초기 설정
본 문서에서는 Kubernetes(K8S), Docker, Yarn Workspaces, TypeScript, esbuild, React 및 Express를 활용하여 클라우드 네이티브 웹 애플리케이션을 구성하는 방법을 설명합니다. 최종적으로는 컨테이너화된 형태로 K8S에 배포 가능한 완전한 앱 아키텍처를 구축하게 됩니다.
모노레포 구조 설계
이 프로젝트는 다중 패키지를 포함하는 모노레포 형태로 구성됩니다. 주요 목적은 공통 로직의 재사용성 향상과 서비스 간 통신 예측의 용이함입니다. 다음과 같은 세 가지 하위 패키지로 나뉩니다:
- app: React 기반 프론트엔드
- common: 공유 타입 및 상수 정의
- server: Express 기반 백엔드 서버
Yarn Workspaces 초기화
작업 디렉터리에서 아래 명령어를 실행합니다:
mkdir my-app && cd my-app
yarn init -2
루트 package.json 파일에 다음 내용을 추가합니다:
{
"name": "my-app",
"version": "1.0.0",
"private": true,
"workspaces": ["packages/*"]
}
하위 패키지 폴더를 생성하고 각각에 package.json을 작성합니다:
packages/
├── app/
│ └── package.json
├── common/
│ └── package.json
├── server/
│ └── package.json
예시: packages/common/package.json
{
"name": "@my-app/common",
"version": "0.1.0",
"private": true
}
TypeScript 설정
루트 디렉터리에서 TypeScript를 전역 개발 의존성으로 설치합니다:
yarn add -D -W typescript
루트에 tsconfig.json을 생성하여 컴파일 옵션을 정의합니다:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ESNext", "DOM"],
"moduleResolution": "node",
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@my-app/*": ["packages/*"]
},
"jsx": "react-jsx",
"outDir": "./dist",
"strict": true
},
"exclude": ["node_modules", "dist"]
}
빌드 도구 스크립트 추가
패키지 작업을 단순화하기 위해 루트 package.json에 편의 스크립트를 등록합니다:
"scripts": {
"app": "yarn workspace @my-app/app",
"common": "yarn workspace @my-app/common",
"server": "yarn workspace @my-app/server"
}
이제 yarn app add react처럼 특정 패키지에 의존성을 추가할 수 있습니다.
공통 모듈 작성
packages/common/src/index.ts에 공유 상수를 정의합니다:
export const SITE_TITLE = 'My Cloud App';
해당 패키지의 package.json에 진입점(entry point)을 지정합니다:
"main": "./src/index.ts"
React 프론트엔드 구성
필요한 의존성을 설치합니다:
yarn app add react react-dom
yarn app add -D @types/react @types/react-dom
정적 자산을 위한 public/index.html 파일을 생성합니다:
<!DOCTYPE html>
<html lang="ko">
<head>
<title>${SITE_TITLE}</title>
</head>
<body>
<div id="root"></div>
<script src="/script.js"></script>
</body>
</html>
기본 리액트 컴포넌트를 작성합니다:
// packages/app/src/App.tsx
import { SITE_TITLE } from '@my-app/common';
import React, { useState } from 'react';
export const App = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>{SITE_TITLE}에 오신 것을 환영합니다!</h1>
<p>현재 값: {count}</p>
<button onClick={() => setCount(c => c + 1)}>증가</button>
</div>
);
};
Express 백엔드 서버 개발
서버 패키지에 필요한 의존성을 추가합니다:
yarn server add express cors
yarn server add -D @types/express @types/cors
정적 파일 제공 및 라우팅 처리를 위한 서버 코드를 작성합니다:
// packages/server/src/index.ts
import express from 'express';
import cors from 'cors';
import { SITE_TITLE } from '@my-app/common';
import path from 'path';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.static(path.join(__dirname, '../../app/public')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../../app/public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`${SITE_TITLE} 서버 실행 중: http://localhost:${PORT}`);
});
esbuild를 이용한 번들링
고속 빌드 도구로 esbuild를 사용합니다. 먼저 전역 설치:
yarn add -D -W esbuild ts-node
루트에 scripts/build.ts를 생성하여 빌드 로직을 구현합니다:
import { build } from 'esbuild';
async function compileApp() {
await build({
entryPoints: ['packages/app/src/index.tsx'],
outfile: 'packages/app/public/script.js',
bundle: true,
minify: true,
sourcemap: false,
define: { 'process.env.NODE_ENV': '"production"' },
platform: 'browser',
format: 'esm'
});
}
async function compileServer() {
await build({
entryPoints: ['packages/server/src/index.ts'],
outfile: 'packages/server/dist/index.js',
bundle: true,
external: ['express'],
platform: 'node',
target: 'node16'
});
}
async function runBuild() {
await Promise.all([compileApp(), compileServer()]);
}
runBuild();
빌드 명령어를 루트 스크립트에 추가합니다:
"scripts": {
...
"build": "ts-node scripts/build.ts"
}
Docker 컨테이너화
애플리케이션을 컨테이너로 패키징하기 위해 Dockerfile을 생성합니다:
FROM node:16-alpine
WORKDIR /app
COPY package.json yarn.lock ./
COPY packages/app/package.json ./packages/app/
COPY packages/common/package.json ./packages/common/
COPY packages/server/package.json ./packages/server/
RUN yarn
COPY . .
RUN yarn build
EXPOSE 3000
CMD ["yarn", "serve"]
.dockerignore 파일로 불필요한 파일 복사를 방지합니다:
node_modules
*.log
dist
*.md
이미지 빌드 및 실행 스크립트를 추가합니다:
"scripts": {
...
"docker": "docker build -t my-web-app .",
"start:container": "docker run -p 3000:3000 my-web-app"
}
빌드 후 컨테이너 실행:
yarn docker
docker run -d -p 3000:3000 my-web-app
브라우저에서 http://localhost:3000 접속 시 앱 확인 가능합니다.