# o2o-infrakit aio2o의 분산된 인프라 서비스 및 설정을 단일 프로젝트로 통합하여 구축한 범용 인프라 솔루션입니다. ## 주요 기능 - **FastAPI 애플리케이션 서버**: Gunicorn + Uvicorn ASGI 워커 기반의 비동기 Python 웹 서버 - **Nginx 리버스 프록시**: SSL/TLS 지원, 정적 파일 서빙, WebSocket 지원 - **캐싱**: Redis 기반 캐싱 지원 - **SSL 인증서 자동화**: Let's Encrypt + Certbot 자동 갱신 - **로그 관리**: 일별 로그 로테이션 및 30일 보관 ## 기술 스택 | 구분 | 기술 | |------|------| | 언어 | Python 3.13 | | 프레임워크 | FastAPI | | ASGI 서버 | Gunicorn + Uvicorn | | 웹 서버 | Nginx 1.26 | | 캐시 | Redis | | 컨테이너 | Docker, Docker Compose | ## 디렉토리 구조 ``` o2o-infrakit/ ├── compose/ # Docker Compose 설정 │ ├── docker-compose.yml # 메인 오케스트레이션 파일 │ ├── .env # 환경 변수 │ ├── redis/ # Redis 설정 및 데이터 │ └── ssl/ # SSL 인증서 및 Let's Encrypt │ ├── config/ # 서비스별 설정 │ ├── app-server/ # 애플리케이션 서버 설정 │ │ └── gunicorn_uvicorn.conf.py │ └── web-server/ # 웹 서버 설정 │ ├── nginx_conf/ # Nginx 메인 설정 │ ├── conf.d/ # 도메인별 Nginx 설정 │ └── proxy_params/ # 프록시 헤더 설정 │ ├── docker/ # Docker 이미지 │ ├── gunicorn/ # Python 앱 서버 Dockerfile │ └── nginx/ # Nginx Dockerfile │ ├── script/ # 유틸리티 스크립트 │ ├── letsencrypt.sh # SSL 인증서 설정 │ ├── crontab_gunicorn_set.sh # Cron 작업 설정 │ └── logrotate/ # 로그 로테이션 설정 │ ├── log/ # 로그 디렉토리 (볼륨) │ ├── nginx/ # Nginx 액세스/에러 로그 │ └── uvicorn/ # Uvicorn/FastAPI 로그 │ └── www/ # 애플리케이션 코드 볼륨 ``` ## 시스템 요구사항 - **CPU**: 4코어 이상 - **RAM**: 4GB 이상 - **스토리지**: SSD 권장 - **OS**: Linux (Ubuntu 24.04 권장) - **Docker**: 20.10 이상 - **Docker Compose**: 2.0 이상 ## 빠른 시작 ### 1. 환경 변수 설정 ```bash cd compose cp .env.example .env # .env 파일을 프로젝트에 맞게 수정 ``` 주요 환경 변수: - `PROJECT_DIR`: 애플리케이션 프로젝트 디렉토리명 ### 2. Redis 볼륨 권한 설정 ```bash cd compose chmod +x set_mysql_permission.sh ./set_mysql_permission.sh ``` ### 3. SSL 인증서 설정 (선택) Let's Encrypt를 통해 무료 SSL 인증서를 발급받습니다. 이 스크립트는 **Nginx 컨테이너 내부**에서 실행해야 합니다. ```bash docker exec -it nginx-uvicorn-webserver bash cd /script chmod +x letsencrypt.sh ./letsencrypt.sh ``` 스크립트 실행 시 아래 3가지 정보를 순서대로 입력해야 합니다: | 입력 항목 | 설명 | 예시 | |-----------|------|------| | **webroot_folder** | Nginx 웹 루트 디렉토리명 (`/www/` 하위 경로) | `o2o-castad-frontend` | | **domain** | 인증서를 발급할 도메인 (공백으로 구분하여 여러 개 입력 가능, 메인 도메인을 먼저 입력) | `example.com www.example.com api.example.com` | | **e-mail** | 인증서 만료 알림을 받을 이메일 주소 | `admin@example.com` | 스크립트 동작 순서: 1. Certbot 및 필수 패키지 설치 2. OpenSSL DH 파라미터 생성 (4096bit, 최초 1회) 3. Certbot을 통한 SSL 인증서 발급 (webroot 방식) 4. 자동 갱신 cron 등록 (매주 월요일 오전 5시) ### 4. Nginx 설정 파일 생성 서비스 도메인에 맞는 Nginx conf 파일을 자동 생성하는 스크립트입니다. 템플릿 기반으로 동작하며, HTTP 또는 HTTPS 용도에 따라 스크립트를 선택합니다. ```bash cd config/web-server ``` #### HTTP 설정 파일 생성 ```bash chmod +x nginx_conf.sh ./nginx_conf.sh ``` `sample_nginx.conf` 템플릿을 기반으로 HTTP conf 파일을 생성합니다. #### HTTPS 설정 파일 생성 ```bash chmod +x nginx_https_conf.sh ./nginx_https_conf.sh ``` `sample_nginx_https.conf` 템플릿을 기반으로 HTTPS conf 파일을 생성합니다. HTTP → HTTPS 리다이렉트, SSL/TLS 설정, 보안 헤더, WebSocket 지원 등이 포함됩니다. #### 입력 항목 두 스크립트 모두 동일한 항목을 입력받습니다: | 입력 항목 | 필수 | 설명 | 예시 | |-----------|------|------|------| | **webroot** | O | 웹 루트 경로 (`/www/` 하위). 하위 경로가 있으면 `\/`로 구분 | `o2o-castad-frontend` | | **portnumber** | O | Nginx가 수신할 포트 번호 | `80` | | **domain** | O | 서비스 도메인 | `example.com` | | **appname** | O | 프록시 대상 컨테이너(서비스)명 | `uvicorn-app` | | **serviceport** | - | 프록시 대상 포트 (빈 값 입력 시 포트 없이 설정) | `8000` | | **filename** | O | 생성될 conf 파일의 접두사 | `example` | #### 생성 파일 경로 | 스크립트 | 생성 경로 | |----------|-----------| | `nginx_conf.sh` | `config/web-server/conf.d/{filename}_gunicorn_ng.conf` | | `nginx_https_conf.sh` | `config/web-server/conf.d/{filename}_gunicorn_https_ng.conf` | 생성된 conf 파일은 Nginx 컨테이너의 `/etc/nginx/conf.d/` 디렉토리에 볼륨 마운트되어 자동으로 로드됩니다. ### 5. 컨테이너 실행 ```bash cd compose docker compose up -d ``` ## 서비스 구성 ### Docker Compose 서비스 | 서비스 | 설명 | 포트 | |--------|------|------| | nginx | 리버스 프록시, SSL 종료 | 80, 443 | | uvicorn-app | FastAPI 애플리케이션 | 8000 (내부) | | redis | 캐시 | 6379 (내부) | ### Gunicorn 설정 (config/app-server/gunicorn_uvicorn.conf.py) - **워커 수**: 4 (4GB RAM 기준 최적화) - **워커 타입**: Uvicorn (비동기 I/O) - **타임아웃**: 300초 (장시간 작업 지원) - **최대 요청**: 1000 (메모리 누수 방지) - **바인드**: 0.0.0.0:8000 ### Nginx 메인 설정 (config/web-server/nginx_conf/nginx.conf) Nginx 전역 동작을 제어하는 메인 설정 파일입니다. 컨테이너 내 `/etc/nginx/nginx.conf`에 마운트되며, 모든 서버 블록에 공통 적용됩니다. #### 워커 설정 | 항목 | 기본값 | 설명 | |------|--------|------| | `worker_processes` | `auto` | 워커 프로세스 수. `auto`는 CPU 코어 수에 맞게 자동 설정 | | `worker_rlimit_nofile` | `8192` | 워커당 열 수 있는 최대 파일 디스크립터 수 | | `worker_connections` | `1024` | 워커당 동시 연결 수. 최대 동시 연결 = `worker_processes × worker_connections` | | `multi_accept` | `off` | `off` 시 워커 간 균등한 연결 분배로 안정적 성능 확보 | | `use` | `epoll` | 리눅스 환경에서 권장하는 이벤트 처리 방식 | #### HTTP 설정 | 항목 | 기본값 | 설명 | |------|--------|------| | `keepalive_timeout` | `30s` | 클라이언트 연결 유지 시간 | | `keepalive_requests` | `1000` | 하나의 keep-alive 연결에서 처리할 최대 요청 수 | | `client_max_body_size` | `50M` | 최대 업로드 크기. 대용량 파일 업로드 시 증가 필요 | | `client_body_buffer_size` | `128k` | 요청 본문 버퍼 크기 (64KB~256KB 권장) | | `client_body_timeout` | `15s` | 클라이언트 본문 수신 타임아웃 | | `client_header_timeout` | `15s` | 클라이언트 헤더 수신 타임아웃 | | `send_timeout` | `15s` | 클라이언트로 응답 전송 타임아웃 | #### 프록시 설정 | 항목 | 기본값 | 설명 | |------|--------|------| | `proxy_connect_timeout` | `300s` | 백엔드 연결 타임아웃 | | `proxy_send_timeout` | `300s` | 백엔드로 요청 전송 타임아웃 | | `proxy_read_timeout` | `300s` | 백엔드 응답 수신 타임아웃 | | `proxy_request_buffering` | `off` | 대용량 파일 스트리밍 업로드를 위해 비활성화 | #### 압축 설정 | 항목 | 기본값 | 설명 | |------|--------|------| | `gzip` | `off` | 실시간 압축 비활성화 (CPU 부하 감소) | | `gzip_static` | `on` | 미리 압축된 `.gz` 파일 제공 방식 사용 | | `gzip_vary` | `on` | `Vary: Accept-Encoding` 헤더 추가 (프록시 캐시 호환성) | #### 기타 - **`server_tokens off`**: Nginx 버전 정보 노출 차단 - **`resolver 127.0.0.11`**: Docker 내부 DNS를 사용하여 컨테이너 이름 해석 - **`map $http_upgrade`**: WebSocket과 일반 HTTP 요청을 동적으로 구분하여 Connection 헤더 설정 ### 프록시 헤더 설정 (config/web-server/proxy_params/proxy_params) 모든 서버 블록에서 공통으로 사용하는 프록시 헤더 설정 파일입니다. `nginx.conf`의 `http` 블록에서 `include`되어 전역 적용됩니다. | 헤더/설정 | 값 | 설명 | |-----------|-----|------| | `Host` | `$http_host` | 클라이언트가 요청한 원본 호스트 정보를 백엔드에 전달 | | `X-Real-IP` | `$remote_addr` | 클라이언트 실제 IP 주소 전달 | | `X-Forwarded-For` | `$proxy_add_x_forwarded_for` | 프록시 체인을 거친 클라이언트 IP 목록 | | `X-Forwarded-Proto` | `$scheme` | 원본 요청 프로토콜 (http/https) 전달 | | `Upgrade` | `$http_upgrade` | WebSocket 업그레이드 요청 헤더 전달 | | `Connection` | `$connection_upgrade` | WebSocket 연결 시 `upgrade`, 일반 요청 시 빈 값 | | `proxy_cache_bypass` | `$http_upgrade` | WebSocket 요청은 캐시를 우회 | | `proxy_buffering` | `off` | 응답 버퍼링 비활성화 (실시간 스트리밍/SSE 지원) | | `proxy_redirect` | `off` | 백엔드 리다이렉트 URL 재작성 비활성화 | | `proxy_buffers` | `32 4k` | 프록시 응답 버퍼 개수 및 크기 | | `proxy_headers_hash_max_size` | `512` | 프록시 헤더 해시 테이블 크기 | | `proxy_headers_hash_bucket_size` | `64` | 프록시 헤더 해시 버킷 크기 | ### 도메인별 서버 설정 (conf.d/) `nginx_conf.sh` 또는 `nginx_https_conf.sh`로 생성되는 도메인별 서버 블록 설정입니다. #### HTTP conf 설정 가이드 (sample_nginx.conf 기반) `nginx_conf.sh`로 생성되는 HTTP 전용 서버 블록의 주요 설정입니다. | 설정 | 템플릿 변수 | 설명 | 설정 가이드 | |------|-------------|------|-------------| | `listen` | `portnumber` | 수신 포트 | 일반적으로 `80`. 내부 서비스는 다른 포트 사용 가능 | | `server_name` | `domain` | 서비스 도메인 | `example.com www.example.com` 형식으로 입력 | | `access_log` | `filename` | 액세스 로그 경로 | `/log/nginx/{filename}.com.gunicorn_access.log` 형식으로 자동 생성 | | `error_log` | `filename` | 에러 로그 경로 | `/log/nginx/{filename}.com.gunicorn_error.log` 형식으로 자동 생성 | | `location /media` | `webroot` | 미디어 파일 경로 | `/www/{webroot}/media` — 사용자 업로드 파일 서빙 경로 | | `location /static` | `webroot` | 정적 파일 경로 | `/www/{webroot}/static` — CSS, JS, 이미지 등 정적 리소스 경로 | | `proxy_pass` | `appname`, `serviceport` | 백엔드 프록시 | `http://{appname}:{serviceport}` — Docker Compose 서비스명과 포트 | | `location /.well-known` | `webroot` | ACME 챌린지 | Let's Encrypt 인증서 발급/갱신을 위한 경로. 수정 불필요 | **보안 설정** (자동 포함, 수정 불필요): - 닷 파일 차단 (`.env`, `.git` 등) - 민감한 파일 확장자 차단 (`.log`, `.sql`, `.key` 등) - 설정 파일 접근 차단 (`composer.json`, `package.json` 등) #### HTTPS conf 설정 가이드 (sample_nginx_https.conf 기반) `nginx_https_conf.sh`로 생성되는 HTTPS 서버 블록의 주요 설정입니다. HTTP conf의 모든 설정을 포함하며, 추가로 아래 설정이 적용됩니다. **HTTP → HTTPS 리다이렉트 (포트 80)** | 설정 | 설명 | 설정 가이드 | |------|------|-------------| | `server_name` | 리다이렉트 대상 도메인 | HTTPS 서버 블록과 동일한 도메인 설정 | | 호스트 검증 정규식 | 허용되지 않은 도메인 차단 | 도메인 확장자가 `.com`, `.kr`, `.net`, `.org` 외라면 정규식에 추가 필요 | | `return 301` | HTTPS로 영구 리다이렉트 | 수정 불필요 | **SSL/TLS 인증서 (포트 443)** | 설정 | 설명 | 설정 가이드 | |------|------|-------------| | `ssl_certificate` | SSL 인증서 경로 | `/etc/letsencrypt/live/{domain}/fullchain.pem` — `letsencrypt.sh` 실행 시 자동 생성 | | `ssl_certificate_key` | SSL 개인키 경로 | `/etc/letsencrypt/live/{domain}/privkey.pem` — 자동 생성 | | `ssl_dhparam` | DH 파라미터 경로 | `/etc/ssl/certs/{domain}/dhparam.pem` — `letsencrypt.sh` 실행 시 자동 생성 | | `ssl_protocols` | 허용 TLS 버전 | `TLSv1.2 TLSv1.3` — 최신 프로토콜만 사용. TLSv1.0/1.1은 보안 취약 | | `ssl_session_cache` | SSL 세션 캐시 | `shared:SSL:50m` — 트래픽이 많을수록 크기 증가 권장 | | `ssl_session_timeout` | SSL 세션 타임아웃 | `10m` — 세션 재사용으로 핸드셰이크 오버헤드 감소 | | `ssl_session_tickets` | 세션 티켓 | `off` — 보안 강화를 위해 비활성화 | | `ssl_stapling` | OCSP 스테이플링 | `on` — SSL 핸드셰이크 성능 향상 | | `ssl_trusted_certificate` | OCSP 신뢰 인증서 | `/etc/letsencrypt/live/{domain}/chain.pem` — 자동 생성 | **보안 헤더** | 헤더 | 기본값 | 설명 | 설정 가이드 | |------|--------|------|-------------| | `Strict-Transport-Security` | `max-age=63072000` | HSTS — 브라우저가 항상 HTTPS 사용 강제 | 최초 적용 시 `max-age`를 짧게 설정하여 테스트 후 증가 권장 | | `X-Frame-Options` | `DENY` | 클릭재킹 방지. iframe 삽입 차단 | 외부 iframe 허용이 필요하면 `SAMEORIGIN`으로 변경 | | `X-Content-Type-Options` | `nosniff` | MIME 스니핑 방지 | 수정 불필요 | | `X-XSS-Protection` | `1; mode=block` | 레거시 XSS 보호 | 수정 불필요 | | `Referrer-Policy` | `strict-origin-when-cross-origin` | 리퍼러 정보 제어 | 수정 불필요 | | `Content-Security-Policy` | `default-src 'self'` | XSS 방지를 위한 CSP | **반드시 서비스에 맞게 수정 필요**. `unsafe-inline`은 프로덕션에서 nonce/hash 방식으로 대체 권장 | **프록시 및 성능 설정** | 설정 | 기본값 | 설명 | 설정 가이드 | |------|--------|------|-------------| | `client_max_body_size` | `100M` | 최대 업로드 크기 | 대용량 파일 업로드 서비스는 증가 필요 | | `proxy_connect_timeout` | `60s` | 백엔드 연결 타임아웃 | 장시간 처리 작업이 있으면 증가 | | `proxy_send_timeout` | `60s` | 백엔드 요청 전송 타임아웃 | 대용량 업로드 시 증가 | | `proxy_read_timeout` | `60s` | 백엔드 응답 수신 타임아웃 | AI 추론 등 오래 걸리는 API가 있으면 증가 | | `proxy_buffering` | `on` | 응답 버퍼링 | WebSocket/SSE 사용 시 `off`로 변경 | | `proxy_request_buffering` | `on` | 요청 버퍼링 | WebSocket/SSE 사용 시 `off`로 변경 | | `open_file_cache` | `max=1000 inactive=20s` | 파일 캐시 | 정적 파일이 많으면 `max` 값 증가 | | `limit_req` | `zone=general burst=20` | 요청 속도 제한 (DDoS 방지) | `burst` 값은 순간 트래픽 허용량. `nginx.conf`의 `limit_req_zone` 정의 필요 | | `limit_conn` | `addr 10` | IP당 동시 연결 제한 | `nginx.conf`의 `limit_conn_zone` 정의 필요 | | `limit_except` | `GET POST HEAD OPTIONS` | 허용 HTTP 메서드 | PUT, DELETE 등이 필요하면 허용 목록에 추가 | **정적 리소스 캐싱** | 설정 | 기본값 | 설명 | |------|--------|------| | `/media` 경로 | `expires 30d` | 미디어 파일 30일 캐시 | | `/static` 경로 | `expires 30d` | 정적 파일 30일 캐시 | | 이미지/폰트/CSS/JS | `expires 1y` | 정적 리소스 1년 캐시. `Cache-Control: public, immutable` 적용 | ## 운영 기능 ### 로그 로테이션 - **주기**: 일별 - **보관 기간**: 30일 - **최대 파일 크기**: 100MB - **설정 위치**: `script/logrotate/` ### SSL 인증서 자동 갱신 - **주기**: 매주 월요일 오전 5시 - **방식**: Certbot + Nginx 리로드 - **설정**: Nginx Dockerfile 내 crontab ### 자동 재시작 - **주기**: 매주 월요일 오전 6시 (선택적) - **설정 스크립트**: `script/crontab_gunicorn_set.sh` ## 주요 스크립트 | 스크립트 | 설명 | |----------|------| | `script/letsencrypt.sh` | Let's Encrypt SSL 인증서 발급 | | `compose/set_mysql_permission.sh` | Redis 볼륨 권한 설정 | | `script/crontab_gunicorn_set.sh` | 컨테이너 자동 재시작 cron 설정 |