Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

배포환경 재구성 및 안정성 개선 #385

Merged
merged 14 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
*.log.*

# OS
.DS_Store
Expand Down Expand Up @@ -53,5 +54,10 @@ lerna-debug.log*
!.vscode/extensions.json
db.sqlite

# Secret Keys
*.crt
*.key
*.key
*.pem

# Production
data/*
8 changes: 8 additions & 0 deletions apps/backend/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ export class AppController {
getHello(): string {
return this.appService.getHello();
}

@Get('health')
healthCheck() {
return {
status: 'ok',
timestamp: new Date().toISOString()
};
}
}
3 changes: 2 additions & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import { RedLockModule } from './red-lock/red-lock.module';
database: configService.get('DB_NAME'),
entities: [Node, Page, Edge, User, Workspace, Role],
logging: process.env.NODE_ENV === 'development',
synchronize: process.env.NODE_ENV === 'development',
// synchronize: process.env.NODE_ENV === 'development',
synchronize: true,
}),
}),
NodeModule,
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function bootstrap() {
app.enableCors({
origin:
process.env.NODE_ENV === 'production'
? ['https://octodocs.com', 'https://www.octodocs.com']
? ['https://octodocs.site', 'https://www.octodocs.site']
: process.env.origin,
credentials: true,
});
Expand Down
14 changes: 14 additions & 0 deletions apps/websocket/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,36 @@
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.4.8",
"@nestjs/platform-ws": "^10.4.7",
"@nestjs/schedule": "^4.1.1",
"@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.4.8",
"@theinternetfolks/snowflake": "^1.3.0",
"@types/multer": "^1.4.12",
"@types/redlock": "^4.0.7",
"axios": "^1.7.8",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.7",
"ioredis": "^5.4.1",
"lib0": "^0.2.98",
"path": "^0.12.7",
"pg": "^8.13.1",
"prosemirror-view": "^1.37.0",
"redlock": "^5.0.0-beta.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"socket.io": "^4.8.1",
"typeorm": "^0.3.20",
"uuid": "^11.0.3",
"ws": "^8.14.2",
"y-prosemirror": "^1.2.12",
"y-protocols": "^1.0.6",
"y-socket.io": "^1.1.3",
"y-websocket": "^1.5.0",
"yjs": "^13.6.8"
},
"devDependencies": {
Expand Down
15 changes: 15 additions & 0 deletions apps/websocket/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get('health')
healthCheck() {
return {
status: 'ok',
timestamp: new Date().toISOString()
};
}
}
4 changes: 4 additions & 0 deletions apps/websocket/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as path from 'path';
import { YjsModule } from './yjs/yjs.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
imports: [
Expand All @@ -11,5 +13,7 @@ import { YjsModule } from './yjs/yjs.module';
}),
YjsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
4 changes: 4 additions & 0 deletions apps/websocket/src/app.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {}
2 changes: 1 addition & 1 deletion apps/websocket/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async function bootstrap() {
app.enableCors({
origin:
process.env.NODE_ENV === 'production'
? ['https://octodocs.com', 'https://www.octodocs.com']
? ['https://octodocs.site', 'https://www.octodocs.site']
: process.env.origin,
credentials: true,
});
Expand Down
4 changes: 2 additions & 2 deletions compose.init.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ services:
--email [email protected]
--agree-tos
--no-eff-email
-d octodocs.com
-d www.octodocs.com
-d octodocs.site
-d www.octodocs.site

networks:
frontend:
Expand Down
51 changes: 48 additions & 3 deletions compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ services:
networks:
- frontend
depends_on:
- backend
backend:
condition: service_healthy
websocket:
condition: service_healthy

backend:
build:
Expand All @@ -28,12 +31,46 @@ services:
- .env.prod:/app/.env
expose:
- "3000"
- "1234"
networks:
- frontend
- backend
depends_on:
- redis
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: always

websocket:
build:
context: .
dockerfile: ./services/websocket/Dockerfile.prod
image: websocket:latest
env_file:
- .env.prod
volumes:
- .env.prod:/app/apps/websocket/.env
expose:
- "4242"
networks:
- frontend
- backend
depends_on:
backend:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4242/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: always

redis:
image: redis:latest
Expand All @@ -42,6 +79,13 @@ services:
REDIS_PORT: ${REDIS_PORT}
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
retries: 3
start_period: 10s
timeout: 5s
restart: always

certbot-renewer:
image: certbot/certbot:latest
Expand All @@ -50,6 +94,7 @@ services:
- ./data/certbot/www:/var/www/certbot
- ./data/certbot/log:/var/log/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 12h & wait $${!}; done;'"
restart: always

networks:
frontend:
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"dev": "turbo run dev --parallel",
"build": "turbo run build",
"start": "node apps/backend/dist/main.js",
"start:backend": "node apps/backend/dist/main.js",
"start:websocket": "node apps/websocket/dist/main.js",
"lint": "turbo run lint",
"test": "turbo run test",
"docker:dev": "docker compose -f compose.local.yml up",
Expand Down
22 changes: 15 additions & 7 deletions services/backend/Dockerfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ FROM node:20-alpine as builder

WORKDIR /app

# yarn 설정 추가
RUN yarn config set network-timeout 300000 && \
yarn config set network-concurrency 1

# 의존성 파일 복사
COPY package.json yarn.lock ./
COPY turbo.json ./
COPY apps/backend/package.json ./apps/backend/
COPY apps/frontend/package.json ./apps/frontend/

# 의존성 설치
RUN yarn install --frozen-lockfile
# 의존성 설치 (재시도 옵션 추가)
RUN yarn install --frozen-lockfile --network-timeout 300000 || \
yarn install --frozen-lockfile --network-timeout 300000 || \
yarn install --frozen-lockfile --network-timeout 300000

# 소스 코드 복사
COPY . .
Expand All @@ -23,16 +29,18 @@ FROM node:20-alpine

WORKDIR /app

# wget 설치
RUN apk add --no-cache wget

# 프로덕션에 필요한 파일만 복사
COPY --from=builder /app/package.json /app/yarn.lock ./
COPY --from=builder /app/apps/backend/package.json ./apps/backend/
COPY --from=builder /app/apps/backend/dist ./apps/backend/dist

# 프로덕션 의존성만 설치
RUN yarn install --frozen-lockfile --production
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/apps/backend/node_modules ./apps/backend/node_modules

ENV NODE_ENV=production

EXPOSE 3000 1234
EXPOSE 3000

CMD ["yarn", "start"]
CMD ["yarn", "start:backend"]
77 changes: 77 additions & 0 deletions services/nginx/conf.d/prod_nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
server {
listen 80;
server_name octodocs.site www.octodocs.site;

# Certbot 인증용 경로 (최상단에 위치)
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
try_files $uri =404;
break;
}

# 나머지 모든 HTTP 트래픽은 HTTPS로 리다이렉트
location / {
return 301 https://$server_name$request_uri;
}
}

server {
listen 443 ssl;
server_name octodocs.site www.octodocs.site;

# Let's Encrypt 인증서 경로
ssl_certificate /etc/letsencrypt/live/octodocs.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/octodocs.site/privkey.pem;

# SSL 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# 인증서가 없을 때 fallback
ssl_trusted_certificate /etc/letsencrypt/live/octodocs.site/chain.pem;
ssl_stapling on;
ssl_stapling_verify on;

# 에러 페이지 설정
error_page 497 https://$server_name$request_uri;

# gzip 압축 설정
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# 프론트엔드 정적 파일
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
expires 30d;
}

# API 프록시
location /api {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}

# Socket.IO 프록시 (일반 웹소켓)
location /socket.io {
proxy_pass http://websocket:4242;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}

# Y-Socket.IO 프록시 (YJS 웹소켓)
location /flow-room {
proxy_pass http://websocket:4242;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
}
14 changes: 14 additions & 0 deletions services/nginx/conf.d/prod_nginx_init.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
server {
listen 80;
server_name octodocs.site www.octodocs.site;

# Certbot 인증용 경로
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}

# 나머지 요청에 대한 처리 (필요에 따라 수정)
location / {
return 200 '인증서 발급을 위한 임시 nginx 서버입니다.';
}
}
Loading
Loading