마이크로 프론트엔드 - Nginx의 Reverse Proxy를 활용하여 여러 페이지 통합하기

시나리오

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>
`;

 

이렇게 변경하게 되면 경로마다 고유의 워크스페이스 프로젝트에 진입할 수 있게 된다.