[목표]
프로젝트 흐름/진행도 구성(파이프 라인)(진행중)
AWS 오토스케일링 적용하기(CORS 해결하기)
배포완료 후 발표 자료 구상하기
배포 안정화 진행
과부하 발생기 확인을 위한 비주얼 도구 도입
오토스케일링 관련
URL 들어가 있는 부분 확인
pm2 파일 확인해보기(현재 https://code-planner.com:5000 부분만 서브 EC2랑 IDE에만 패치함)
오토스케일링 CORS 문제를 해결하기 위해 아마존 Q를 쓰려했지만 잘 작동되지 않아서 못썼다.
식사를 했다.

Nginx, pm2, AWS 설정을 모두 확인해보며 CORS 허용이 왜 안되는지에 대해 살펴보고 있다.
일단 프론트엔드의 Nginx CORS 헤더가 백엔드의 헤더하고 겹쳐서 두번 추가되어, nginx.conf 파일을 다음과 같이 수정했다.
server {
listen 80;
server_name code-planner.com;
# 프론트엔드 정적 파일 서빙
location / {
# rewrite ^/(.*)$ /maintenance break; # 서버 점검시 주석 해제, 점검완료시 주석
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# API 요청을 백엔드로 프록시 + CORS 헤더 및 OPTIONS 처리
location /api/ {
# rewrite ^/(.*)$ /maintenance break; # 서버 점검시 주석 해제, 점검완료시 주석
proxy_pass http://localhost:5000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
백엔드의 pm2 환경 설정도 바꾸었다.
// ⚠️⚠️⚠️배포환경 관련 설정 주의 필요!!!⚠️⚠️⚠️
module.exports = {
apps: [
{
name: 'Codeplanner_Backend',
script: 'dist/main.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development',
PORT: 5000,
CORS_ORIGIN: 'http://localhost:3000',
},
env_production: {
NODE_ENV: 'production',
PORT: 5000,
CORS_ORIGIN: 'https://code-planner.com,https://www.code-planner.com,http://-.--.---.---:3000',
// 추가 환경변수들
DATABASE_URL: process.env.DATABASE_URL,
JWT_SECRET: process.env.JWT_SECRET,
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
},
},
],
};
위의 조치로 다음과 같은 에러만 나오게 됐다. CORS 중복문제는 어느정도 해결한것 같다. 근데 이게 중복됐다는 의미이다. 더 수정해보겠다.
Access to fetch at 'https://code-planner.com/api/auth/login' from origin '[http://-.--.---.---:3000](http://-.--.---.---:3000/)' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values '[http://-.--.---.---:3000](http://-.--.---.---:3000/), *', but only one is allowed. Have the server send the header with a valid value.Understand this error
requests.js:1
POST <https://code-planner.com/api/auth/login> net::ERR_FAILED
깨달은게 있는데, nginx.conf 에서 밑에 부분에서 서버 네임부분을 예전에 바꿧더니 됐던 기억이 있다.
server {
listen 80;
server_name code-planner.com;
server_name -.--.---.---;
# 프론트엔드 정적 파일 서빙
location / {
# rewrite ^/(.*)$ /maintenance break; # 서버 점검시 주석 해제, 점검완료시 주석
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
.
.
.
위와 같이 작성을 한 후에 실행해보니 CORS 오류가 -.--.---.--- 로 뜬다. 그래서 CORS 설정을 기존 -.--.---.---:3000에서 -.--.---.--- 로 바꿔서 다시 세팅해보겠다.
근데도 해결되지 않는다. 그래서 nginx.conf 의 CORS 관련 헤더도 주석 처리를 했다.
# OPTIONS preflight 요청 처리
if ($request_method = 'OPTIONS') {
# add_header 'Access-Control-Allow-Origin' $http_origin;
# add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
# add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
혹시 몰라서 EC2 자체의 nginx 설정도 확인을 해보았다. 다음과 같은 문제가 있었따.
nginx 설정에 두 개의 server 블록이 있다.
code-planner.com, -.--.---.---
→ 여기에만 CORS 헤더가 추가되어 있음
default_server ()
→ 여기는 CORS 헤더가 없음
그래서 다음과 같은 명령어로 sites-enabled/default symlink를 삭제 ****후 다시 로드했다.
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
이렇게 하니까 CORS 문제가 차단으로 나왔다.
/auth/login:1 Access to fetch at 'https://code-planner.com/api/auth/login' from origin '[http://-.--.---.---](http://-.--.---.---/)' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
프론트 엔드 재빌드 후 CORS 헤더 수정후 다시함
무효화 설정하다가 본서버도 다운 됐다. 급히 작동했던 예전 파일을 덮어 씌우고 있다.
팀원분들의 요약 페이지 개선, 최근 활동 기록, 코드 품질 검사에 대한 기능을 같이 본 서버에 패치했다.
구글 Gemini CLI도 써봤지만 별로다.
식사를 하고 왔다.

하다하다 안되서 아마존 큐로 해보겠다.
아마존 큐가 제안한 코드는 다음과 같다. 해당 코드들을 반영해서 EC2에 올려보겠다.
## 문제 상황 인식
AWS 오토스케일링으로 구성된 EC2 환경에서 다음과 같은 CORS 오류가 발생했다.
```
Access to fetch at 'https://code-planner.com/api/auth/login' from origin 'http://-.--.---.---' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values 'http://-.--.---.---, *', but only one is allowed.
```
이 오류는 `Access-Control-Allow-Origin` 헤더에 여러 값이 설정되어 있어서 발생하는 전형적인 CORS 중복 설정 문제였다.
## 문제 원인 분석
### 1. 프로젝트 구조 파악
먼저 로컬 프로젝트 구조를 확인했다.
```bash
# 프로젝트 루트 디렉토리 확인
ls -la /Users/kty/Documents/JungleProject/codeplanner
```
- `Codeplanner_Backend`: NestJS 백엔드 애플리케이션
- `Codeplanner_Frontend`: Next.js 프론트엔드 애플리케이션
### 2. 백엔드 CORS 설정 확인
백엔드의 main.ts 파일에서 CORS 설정을 확인했다.
```tsx
// src/main.ts
const corsOrigin = process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'];
app.enableCors({
origin: corsOrigin,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
```
### 3. 환경 변수 설정 확인
`.env.production` 파일에서 CORS_ORIGIN 설정을 확인했다.
```bash
# 기존 설정
CORS_ORIGIN=https://code-planner.com,https://code-planner.com:5000,https://code-planner.com:3000,http://-.--.---.---
```
### 4. PM2 설정 확인
`ecosystem.config.js` 파일에서도 CORS 설정이 중복되어 있었다.
```jsx
env_production: {
NODE_ENV: 'production',
PORT: 5000,
CORS_ORIGIN: 'https://code-planner.com,https://www.code-planner.com,http://-.--.---.---',
}
```
### 5. Nginx 설정 확인
가장 중요한 부분인 `nginx.conf` 파일에서 CORS 헤더가 중복 설정되어 있었다.
```
# 문제가 된 기존 설정
add_header 'Access-Control-Allow-Origin' '*' always;
```
## 해결 과정
### 1단계: 백엔드 환경 변수 정리
불필요한 포트 설정을 제거하고 필요한 origin만 남겼다.
```bash
# .env.production 수정
CORS_ORIGIN=https://code-planner.com,http://-.--.---.---
```
### 2단계: PM2 설정 정리
ecosystem.config.js에서도 동일하게 정리했다.
```jsx
env_production: {
NODE_ENV: 'production',
PORT: 5000,
CORS_ORIGIN: 'https://code-planner.com,http://-.--.---.---',
}
```
### 3단계: Nginx CORS 설정 개선
가장 핵심적인 부분으로, Nginx에서 특정 origin만 허용하도록 변경했다.
```
# 개선된 nginx.conf 설정
location /api/ {
proxy_pass http://localhost:5000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# CORS 헤더 추가 - 특정 origin만 허용
set $cors_origin "";
if ($http_origin ~* "^https://code-planner\.com$|^http://3\.39\.195\.132$") {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# OPTIONS preflight 요청 처리
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
}
```
### 4단계: 백엔드 CORS 비활성화
Nginx에서 CORS를 처리하므로 백엔드에서는 CORS 설정을 비활성화했다.
```tsx
// src/main.ts 수정
// Nginx에서 CORS를 처리하므로 백엔드에서는 비활성화
// const corsOrigin = process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'];
// app.enableCors({
// origin: corsOrigin,
// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
// credentials: true,
// });
```
## AWS 콘솔에서 확인할 사항
### 1. Application Load Balancer 설정 확인
AWS 콘솔 > EC2 > Load Balancers에서 다음 사항들을 확인한다:
- **Target Groups**: 모든 EC2 인스턴스가 healthy 상태인지 확인
- **Health Check**: 경로가 올바르게 설정되어 있는지 확인 (보통 `/` 또는 `/health`)
- **Listeners**: 80번 포트와 443번 포트 설정 확인
### 2. Auto Scaling Group 확인
AWS 콘솔 > EC2 > Auto Scaling Groups에서:
- **Desired Capacity**: 원하는 인스턴스 수
- **Min/Max Size**: 최소/최대 인스턴스 수
- **Health Check Type**: ELB 또는 EC2 선택
- **Health Check Grace Period**: 인스턴스 시작 후 헬스체크 대기 시간
### 3. Security Groups 확인
AWS 콘솔 > EC2 > Security Groups에서:
- **Inbound Rules**: 80, 443, 3000, 5000 포트가 적절히 열려있는지 확인
- **Outbound Rules**: 모든 트래픽 허용 또는 필요한 포트만 허용
## 배포 및 적용
### 1. 코드 배포
수정된 파일들을 EC2 인스턴스에 배포한다.
```bash
# EC2에서 실행
git pull origin main
npm run build # 프론트엔드
npm run build # 백엔드
```
### 2. 서비스 재시작
```bash
# PM2 애플리케이션 재시작
pm2 restart Codeplanner_Backend
pm2 restart Codeplanner_Frontend
# Nginx 설정 재로드
sudo systemctl reload nginx
# 또는 완전 재시작
sudo systemctl restart nginx
```
### 3. 상태 확인
```bash
# PM2 프로세스 상태 확인
pm2 status
# Nginx 상태 확인
sudo systemctl status nginx
# 포트 사용 확인
sudo netstat -tlnp | grep :80
sudo netstat -tlnp | grep :3000
sudo netstat -tlnp | grep :5000
```
## 오토스케일링 동작 원리 이해
### 로드밸런서 트래픽 분산
사용자가 `code-planner.com`에 접속하면:
1. Route 53 (DNS)가 로드밸런서 주소로 라우팅
2. Application Load Balancer가 여러 EC2 인스턴스 중 선택
3. 라운드 로빈 방식으로 트래픽 분산
4. 각 요청마다 다른 서버로 연결될 수 있음
### 세션 관리 고려사항
- **JWT 토큰 사용**: 상태가 없으므로 어느 서버든 처리 가능
- **파일 저장**: S3 같은 공유 스토리지 사용 권장
- **데이터베이스**: RDS 같은 공유 데이터베이스 사용
## 결과 및 검증
### 1. CORS 오류 해결 확인
브라우저 개발자 도구에서 네트워크 탭을 확인하여 CORS 오류가 더 이상 발생하지 않는지 확인한다.
### 2. 헬스체크 정상 동작 확인
AWS 콘솔에서 Target Groups의 모든 인스턴스가 healthy 상태인지 확인한다.
### 3. 로드밸런싱 동작 확인
여러 번 새로고침하여 다른 서버로 요청이 분산되는지 확인할 수 있다.
```jsx
// 브라우저 콘솔에서 실행
fetch('/api/server-info')
.then(res => res.json())
.then(data => console.log('현재 서버:', data));
```
## 핵심 포인트
1. **CORS는 한 곳에서만 처리**: Nginx 또는 백엔드 중 하나에서만 설정
2. **특정 origin 허용**: 대신 구체적인 도메인 지정
3. **환경별 설정 분리**: 개발/운영 환경에 맞는 설정 사용
4. **오토스케일링 이해**: 로드밸런서가 트래픽을 분산 처리함
이러한 과정을 통해 AWS 오토스케일링 환경에서 발생한 CORS 오류를 성공적으로 해결할 수 있었다.
nginx.conf
server {
listen 80;
server_name code-planner.com;
server_name -.--.---.---;
# 프론트엔드 정적 파일 서빙
location / {
# rewrite ^/(.*)$ /maintenance break; # 서버 점검시 주석 해제, 점검완료시 주석
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# API 요청을 백엔드로 프록시 + CORS 헤더 및 OPTIONS 처리
location /api/ {
# rewrite ^/(.*)$ /maintenance break; # 서버 점검시 주석 해제, 점검완료시 주석
proxy_pass http://localhost:5000/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# CORS 헤더 추가 - 특정 origin만 허용
set $cors_origin "";
if ($http_origin ~* "^https://code-planner\.com$|^http://3\.39\.195\.132$") {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# OPTIONS preflight 요청 처리
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
}
}
main.ts
// ⚠️⚠️⚠️배포환경 관련 설정 주의 필요!!!⚠️⚠️⚠️
import * as crypto from 'crypto';
// crypto 모듈을 전역으로 설정
if (!(global as any).crypto) {
(global as any).crypto = crypto.webcrypto;
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 모든 라우트에 /api 프리픽스 적용
// app.setGlobalPrefix('api');
// Nginx에서 CORS를 처리하므로 백엔드에서는 비활성화
// const corsOrigin = process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'];
// app.enableCors({
// origin: corsOrigin,
// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
// credentials: true,
// });
const port = process.env.PORT || 5000; // 백엔드 포트를 5000으로 설정
await app.listen(port);
console.log(`🚀 Listening on ${port}`);
}
bootstrap();
.env.production
# ⚠️⚠️⚠️배포환경 관련 설정 주의 필요!!!⚠️⚠️⚠️
# 배포 환경시
PORT=5000
CORS_ORIGIN=https://code-planner.com,http://-.--.---.---
DB_HOST=codeplannerdb.cjyeyk2qmmx3.ap-northeast-2.rds.amazonaws.com
DB_PORT=5432
DB_USERNAME=codeplanner
DB_PASSWORD=codeplanner1234
DB_DATABASE=codeplanner
DB_SSL=true
DB_SSL_CA_PATH=./global-bundle.pem
BASE_URL=https://code-planner.com
GITHUB_CLIENT_ID=Ov23lijeWMuKS4KS6a78
GITHUB_CLIENT_SECRET=1e4bfeef43c62d4e374a34e0ac5290907588779d
WEBHOOK_BASE_URL=https://code-planner.com/api
GEMINI_API_KEY=AIzaSyDaZ8g-l5cxKRQ-Nfwl9w8Zqe_Ug8wD2Jk
ecosystem.config.js
// ⚠️⚠️⚠️배포환경 관련 설정 주의 필요!!!⚠️⚠️⚠️
module.exports = {
apps: [
{
name: 'Codeplanner_Backend',
script: 'dist/main.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development',
PORT: 5000,
CORS_ORIGIN: 'http://localhost:3000',
},
env_production: {
NODE_ENV: 'production',
PORT: 5000,
CORS_ORIGIN: 'https://code-planner.com,http://-.--.---.---',
// 추가 환경변수들
DATABASE_URL: process.env.DATABASE_URL,
JWT_SECRET: process.env.JWT_SECRET,
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
},
},
],
};
위의 내용을 main에 올려버려서 PR을 통해 dev와 동기화했다.
근데 궁금한게 오토스케일링을 하면 유저가 우리 도메인에 접근하면 과부하가 덜 걸린 EC2로 분산 처리 해주는거야? 내가 직접해보니까 어떨땐 메인 서버로 가고 어떨땐 오토스케일링 서버로 가던데
→ 네, 맞습니다! 오토스케일링과 로드밸런서가 함께 동작해서 트래픽을 분산 처리합니다.
오토스케일링 + 로드밸런서 동작 방식
• 요청3 → 서버A
• 요청4 → 서버B현재 상황 분석
당신이 경험한 것이 정상적인 동작입니다:
• 때로는 "메인 서버"로 연결
• 때로는 "오토스케일링으로 생성된 서버"로 연결
확인 방법
현재 어느 서버에 연결되었는지 확인하려면:
주의사항
세션 관리
• 사용자가 로그인하면 다른 서버로 요청이 갈 수 있음
• JWT 토큰 사용 시: 문제없음 (stateless)
• 세션 스토어 사용 시: Redis 등 공유 저장소 필요
파일 업로드/다운로드
• 각 서버에 파일이 따로 저장되면 문제 발생
• S3 같은 공유 스토리지 사용 권장
이런 분산 처리가 바로 오토스케일링의 핵심 기능입니다! 트래픽이 증가하면 서버를 자동으로 추가하고, 로드밸런서가 트래픽을 분산해서 안정적인 서비스를 제공합니다.
터미널에서 aws configure 후 다음 코드 실행
export AWS_ACCESS_KEY_ID="-------------------"
export AWS_SECRET_ACCESS_KEY="-----------”
export AWS_DEFAULT_REGION="----------”
근데 이렇게 아마존 큐가 시키는 대로 했다가 로컬과 본서버에도 CORS 오류가 나와서 롤백했다…
일단은 발표준비를 먼저하겠다.
나중에 맥북에서도 아마존 Q로 작업할 수 있도록 PostgreSQL을 세팅해두었다. npm run dev 도된다.
시나리오 작성, 기술적 챌린지, 구현적 관점으로 설명하겠다. CI/CD와 과부하 및 동적/정적 검사도 이야기하기
우리의 발표를 위해 이슈 생애 주기를 기준으로 설명해보도록 하겠다. 하나의 이슈를 해결하기까지를 발표하면 될 것 같다.
시연용 구글 계정을 새로 만들었다. 이제 해당 계정을 기반으로 더미데이터를 작성해야겠다.
1차 발표 스크립트를 보면서, 이미 발표했던 내용은 제외하고 작성중이다. 간간히 팀원 버그 픽스 배포까지 도와주면서 작성진행중이다.
CORS 설정 허용을 CloudFront 설정을 할 수 있다. 내일 와서 해보자. 헤더 허용은 해놨다.
명석이형 로직을 기반으로 발표 스크립트 작성중