시나리오
1. Team Home이 운영하는 정적 파일을 제공하는 웹 서버 (localhost:3001)
- pages: /index.html
2. Team Jobs이 운영하는 정적 파일을 제공하는 웹 서버 (localhost:3002)
- pages: /jobs/index.html
3. Team Network이 운영하는 정적 파일을 제공하는 웹 서버 (localhost:3003)
- pages: /network/index.html
4. Nginx Reverse Proxy Server (localhost:3000)
- Nginx를 Docker로 실행
루트 프로젝트 초기 세팅
다음 명령어를 순차적으로 실행해서 프로젝트를 구축한다.
pnpm init
corepack use pnpm@8.10.0
pnpm add turbo -D
pnpm-workspace.yaml을 통해 워크스페이스 설정을 추가한다.
packages:
- "teams/*"
- "apps/*"
teams에는 vite로 세팅한 프로젝트를 구축하고, apps에는 Docker로 실행할 nginx 웹 서버를 구축할 예정이다.
마지막으로 turbo.json을 추가하여, 프로젝트를 한 번에 관리할 수 있게 해준다.
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"dev": {
"cache": false,
"persistent": true
}
}
}
nginx-app 세팅하기
루트에서 apps/nginx-app 디렉토리를 만들고 해당 디렉토리에서 프로젝트 초기화를 진행한다.
pnpm init
proxy-server.conf 설정 파일을 만든다.
upstream team_home {
server host.docker.internal:3001;
}
upstream team_jobs {
server host.docker.internal:3002;
}
upstream team_network {
server host.docker.internal:3003;
}
server {
listen 3000;
location /network/ {
proxy_pass http://team_network;
}
location /jobs/ {
proxy_pass http://team_jobs;
}
location / {
proxy_pass http://team_home;
}
}
upstream을 통해 각 팀의 서버를 정의해준다.
team_home만 해석해보자면, team_home이라는 upstream 그룹을 만들고, 그 안에 host.docker.internal:3001 (Home 서비스)를 등록하는 것이다. (host.docker.internal:3001는 Docker 컨테이너 안에서 로컬 PC를 가리킬 때 사용하는 특별한 주소이다)
nginx는 3000번 포트를 열게(listen) 되고, 요청 경로가 /로 시작하면 team_home(3001) 서버로 트래픽을 프록시 해준다.
upstream 그룹은 실제 서버들의 묶음이며, proxy_pass에서 사용되게 된다.
host.docker.internal은 도커 컨테이너 안에서 로컬(호스트) 컴퓨터를 바라볼 수 있게 해주는 주소다.
실제 도커 컨테이너 안에서 무언가를 실행시키면 컨테이너는 로컬 PC의 localhost를 모른다. 개발 시 로컬 PC에서 돌고 있는 서버에 접속하기 위해 host.docker.internal을 사용하게 된다.
그림으로 보면 다음과 같은 형태가 된다.
3000 (nginx)
├── / → team_home → host.docker.internal:3001
├── /jobs/ → team_jobs → host.docker.internal:3002
└── /network/ → team_network → host.docker.internal:3003
Dockerfile도 만들어준다.
FROM nginx:1.25.3-alpine
COPY proxy-server.conf /etc/nginx/conf.d/proxy-server.conf
EXPOSE 3000
package.json에 build 명령어와 dev 명령어를 추가해준다.
{
"name": "nginx-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "docker build -t nginx-app .",
"dev": "docker run -p 3000:3000 nginx-app",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- build: 현재 디렉토리에 있는 Dockerfile(.)을 읽어서 Dockerfile로 읽은 이미지에 nginx-app라는 이름(tag)를 붙여서 이름을 만든다.
- dev: 만들어진 이미지를 기반으로 컨테이너를 실행한다.
- `-p 3000:3000`: 내 PC 포트:컨테이너 안쪽 포트
- nginx-app: 실행할 이미지 이름
Teams 프로젝트들 세팅하기
루트 디렉토리에 teams 디렉토리를 만들고, 아래의 명령어를 통해 3가지 프로젝트를 세팅한다.
pnpm create vite@latest team-home --template vanilla-ts
pnpm create vite@latest team-jobs --template vanilla-ts
pnpm create vite@latest team-network --template vanilla-ts
pnpm i
각 프로젝트 별 vite.config.ts를 생성해준다.
# team-home vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "/",
server: {
host: true,
port: 3001,
},
});
# team-home vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "/jobs/",
server: {
host: true,
port: 3002,
},
});
# team-home vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "/network/",
server: {
host: true,
port: 3003,
},
});
여기까지 진행이 구현이 되었다면 Docker를 실행하고, 이미지를 빌드한 후 모든 워크스페이스들을 실행하도록 하자.
먼저 apps의 nginx-app의 도커 이미지를 빌드하도록 하자
pnpm --filter nginx-app build
그 다음에 아래의 명령어로 모든 워크스페이스의 프로젝트를 실행하도록 한다.
pnpm exec turbo dev
각각의 앱들이 모두 실행된 모습을 확인할 수 있다.
localhost:3001, localhost:3002, localhost:3003 모두 접속이 잘되지만 localhost:3000으로 접속하면 잘 안되는 것을 확인할 수 있다.
이는 vite.config의 server 옵션에 `allowedHosts: true`를 추가하면 해결이 된다.
import { defineConfig } from "vite";
export default defineConfig({
base: "/",
server: {
host: true,
port: 3001,
allowedHosts: true,
},
});
'
이제 각 teams의 워크스페이스의 main.ts를 각각 수정해주도록 하자.
# Home main.ts
import "./style.css";
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div>
<h1>Home</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/jobs/">Jobs</a></li>
<li><a href="/network/">Network</a></li>
</ul>
</nav>
</div>
`;
# Jobs main.ts
import "./style.css";
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div>
<h1>Jobs</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/jobs/">Jobs</a></li>
<li><a href="/network/">Network</a></li>
</ul>
</nav>
</div>
`;
# Network main.ts
import "./style.css";
document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div>
<h1>Network</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/jobs/">Jobs</a></li>
<li><a href="/network/">Network</a></li>
</ul>
</nav>
</div>
`;
이렇게 변경하게 되면 경로마다 고유의 워크스페이스 프로젝트에 진입할 수 있게 된다.