# SAM E-Sign 운영/배포 가이드 > **프로젝트명**: SAM E-Sign (전자계약 서명 솔루션) > **작성일**: 2026-02-12 > **버전**: v1.0 > **대상**: 시스템 운영자, DevOps 엔지니어 --- ## 목차 1. [시스템 구성](#1-시스템-구성) 2. [환경 변수](#2-환경-변수) 3. [배포 절차](#3-배포-절차) 4. [스토리지 관리](#4-스토리지-관리) 5. [메일 발송 설정](#5-메일-발송-설정) 6. [스케줄러 설정](#6-스케줄러-설정) 7. [모니터링](#7-모니터링) 8. [백업 및 복구](#8-백업-및-복구) 9. [보안 운영](#9-보안-운영) 10. [장애 대응](#10-장애-대응) 11. [확장 가이드](#11-확장-가이드) --- ## 1. 시스템 구성 ### 1.1 인프라 아키텍처 ``` 인터넷 │ ▼ ┌───────────────┐ │ Nginx (443) │ sam-nginx-1 │ SSL 종단점 │ api.sam.kr / mng.sam.kr └──────┬────────┘ │ ┌──────────┴──────────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ API (FPM) │ │ MNG (FPM) │ │ sam-api-1 │ │ sam-mng-1 │ │ :9000 │ │ :9000 │ └──────┬───────┘ └──────┬───────┘ │ │ └────────┬───────────┘ ▼ ┌──────────────┐ ┌──────────────┐ │ MySQL 8.0 │ │ phpMyAdmin │ │ sam-mysql-1 │ │ :8080 │ │ :3306 │ └──────────────┘ └──────────────┘ ``` ### 1.2 도메인 매핑 | 도메인 | 대상 컨테이너 | 용도 | 프로토콜 | |--------|-------------|------|---------| | `api.sam.kr` | sam-api-1:9000 | E-Sign API 백엔드 | HTTPS | | `mng.sam.kr` | sam-mng-1:9000 | E-Sign 관리 화면 + 공개 서명 | HTTPS | ### 1.3 E-Sign 관련 컨테이너 | 컨테이너 | 역할 | E-Sign 관련 기능 | |----------|------|-----------------| | **sam-api-1** | API 서버 | 계약 CRUD, OTP, 서명 처리, 메일 발송, PDF 처리 | | **sam-mng-1** | 관리 화면 | 대시보드, 계약 생성, 서명 위치 지정, 공개 서명 UI | | **sam-mysql-1** | 데이터베이스 | esign_* 4개 테이블 | | **sam-nginx-1** | 웹서버 | SSL, 리버스 프록시, 보안 헤더 | ### 1.4 기술 스택 | 항목 | 버전 | 비고 | |------|------|------| | PHP | 8.3 | | | Laravel | 11 (API), 11 (MNG) | | | MySQL | 8.0 | Multi-tenant | | Nginx | 최신 | | | Docker Compose | v2 | | | React | 18 (CDN + Babel) | 브라우저 트랜스파일링 | | FPDI/TCPDF | 2.6 / 6.10 | PDF 서명 합성 (MNG) | | LibreOffice | headless (writer-nogui) | DOCX→PDF 변환 (MNG) | | GD 확장 | PHP 내장 | 서명 이미지 처리 (MNG) | | 나눔 폰트 | fonts-nanum | DOCX 한글 렌더링 (MNG) | | PDF.js | CDN | 브라우저 PDF 표시 | | signature_pad.js | 4.x | 터치/마우스 서명 캡처 | | Lucide | CDN | 아이콘 | --- ## 2. 환경 변수 ### 2.1 API 프로젝트 (`/home/aweso/sam/api/.env`) #### 필수 설정 ```bash # 애플리케이션 APP_URL=https://api.sam.kr/ APP_ENV=production # local → production APP_DEBUG=false # true → false (운영) APP_TIMEZONE=Asia/Seoul APP_LOCALE=ko # 데이터베이스 DB_CONNECTION=mysql DB_HOST=sam-mysql-1 # Docker 네트워크 DB_PORT=3306 DB_DATABASE=samdb DB_USERNAME=samuser DB_PASSWORD=sampass # 큐 (메일 비동기 발송) QUEUE_CONNECTION=database ``` #### E-Sign 관련 설정 ```bash # 메일 (서명 요청/OTP 발송용) MAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=shine1324@gmail.com MAIL_PASSWORD=<앱 비밀번호> MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=shine1324@gmail.com MAIL_FROM_NAME="SAM E-Sign" # 파일 저장 FILESYSTEM_DISK=local FILE_MAX_SIZE=20480 # 20MB (KB 단위) # 인증 토큰 SANCTUM_ACCESS_TOKEN_EXPIRATION=120 # 2시간 SANCTUM_REFRESH_TOKEN_EXPIRATION=10080 # 7일 ``` #### 환경별 차이 | 설정 | 로컬 (Docker) | 서버 (운영) | |------|-------------|-------------| | APP_ENV | local | production | | APP_DEBUG | true | **false** | | DB_HOST | sam-mysql-1 | 127.0.0.1 | | MAIL_MAILER | log (테스트) | smtp (실제 발송) | | QUEUE_CONNECTION | sync | database | ### 2.2 MNG 프로젝트 (`/home/aweso/sam/mng/.env`) ```bash # 애플리케이션 APP_URL=https://mng.sam.kr APP_ENV=production APP_DEBUG=false # 데이터베이스 (API와 동일 DB 공유) DB_HOST=sam-mysql-1 DB_DATABASE=samdb DB_USERNAME=samuser DB_PASSWORD=sampass ``` > MNG는 화면 렌더링만 담당하므로 E-Sign 관련 별도 환경변수는 없습니다. > API 호출은 프론트엔드(React)에서 `api.sam.kr`로 직접 요청합니다. --- ## 3. 배포 절차 ### 3.1 최초 배포 (신규 설치) #### Step 1: 마이그레이션 실행 ```bash # 로컬 (Docker) docker exec sam-api-1 php artisan migrate # 서버 cd /home/webservice/api php artisan migrate ``` 마이그레이션 결과 확인: ```bash # 4개 테이블 생성 확인 docker exec sam-api-1 php artisan migrate:status | grep esign # 기대 결과: # Ran 2026_02_12_100000_create_esign_contracts_table # Ran 2026_02_12_110000_create_esign_signers_table # Ran 2026_02_12_120000_create_esign_sign_fields_table # Ran 2026_02_12_130000_create_esign_audit_logs_table ``` #### Step 2: 스토리지 디렉토리 생성 ```bash # 로컬 (Docker) docker exec sam-api-1 mkdir -p storage/app/esign # 서버 cd /home/webservice/api mkdir -p storage/app/esign chmod 775 storage/app/esign chown -R www-data:www-data storage/app/esign ``` #### Step 3: 라우트 캐시 갱신 ```bash # 로컬 docker exec sam-api-1 php artisan route:cache docker exec sam-mng-1 php artisan route:cache # 서버 cd /home/webservice/api && php artisan route:cache cd /home/webservice/mng && php artisan route:cache ``` #### Step 4: 메뉴 등록 메뉴 시더 실행 금지. tinker로 수동 등록합니다. ```bash # 상위 메뉴 생성 ssh sam-server "cd /home/webservice/mng && php artisan tinker --execute=\" \\\$parent = App\\\\Models\\\\Commons\\\\Menu::create([ 'tenant_id' => 1, 'parent_id' => null, 'name' => '전자계약 (E-Sign)', 'url' => '/esign', 'icon' => 'ti ti-file-certificate', 'sort_order' => 90, 'is_active' => true, ]); echo 'Parent ID: ' . \\\$parent->id; \"" # 하위 메뉴 생성 (parent_id를 위에서 출력된 값으로 교체) ssh sam-server "cd /home/webservice/mng && php artisan tinker --execute=\" App\\\\Models\\\\Commons\\\\Menu::create([ 'tenant_id' => 1, 'parent_id' => , 'name' => '계약 대시보드', 'url' => '/esign', 'icon' => 'ti ti-dashboard', 'sort_order' => 1, 'is_active' => true, ]); App\\\\Models\\\\Commons\\\\Menu::create([ 'tenant_id' => 1, 'parent_id' => , 'name' => '새 계약 생성', 'url' => '/esign/create', 'icon' => 'ti ti-file-plus', 'sort_order' => 2, 'is_active' => true, ]); \"" ``` #### Step 5: 라우트 확인 ```bash # API 라우트 확인 (16개) docker exec sam-api-1 php artisan route:list --path=esign # MNG 라우트 확인 (8개) docker exec sam-mng-1 php artisan route:list --path=esign ``` ### 3.2 업데이트 배포 코드 변경 사항을 반영할 때: #### 로컬 (Docker) ```bash # 1. 코드 pull cd /home/aweso/sam/api && git pull cd /home/aweso/sam/mng && git pull # 2. 의존성 업데이트 (composer.json 변경 시) docker exec sam-api-1 composer install --no-dev --optimize-autoloader docker exec sam-mng-1 composer install --no-dev --optimize-autoloader # 3. 마이그레이션 (API에서만) docker exec sam-api-1 php artisan migrate # 4. 캐시 갱신 docker exec sam-api-1 php artisan config:cache docker exec sam-api-1 php artisan route:cache docker exec sam-api-1 php artisan view:cache docker exec sam-mng-1 php artisan config:cache docker exec sam-mng-1 php artisan route:cache docker exec sam-mng-1 php artisan view:cache ``` #### 서버 (운영) ```bash # API 프로젝트 cd /home/webservice/api git pull composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan config:cache php artisan route:cache php artisan view:cache # MNG 프로젝트 (마이그레이션 없음) cd /home/webservice/mng git pull composer install --no-dev --optimize-autoloader php artisan config:cache php artisan route:cache php artisan view:cache ``` ### 3.3 배포 체크리스트 ``` □ API: git pull 완료 □ MNG: git pull 완료 □ API: composer install 완료 □ MNG: composer install 완료 □ API: php artisan migrate 완료 (신규 마이그레이션 시) □ API: php artisan config:cache □ MNG: php artisan config:cache □ API: route:list --path=esign 로 16개 라우트 확인 □ MNG: route:list --path=esign 로 8개 라우트 확인 □ 브라우저: mng.sam.kr/esign 접속 확인 □ API: esign 테이블 4개 존재 확인 □ 메일: 테스트 서명 요청 발송 → 이메일 수신 확인 ``` --- ## 4. 스토리지 관리 ### 4.1 파일 저장 구조 ``` storage/app/esign/ └── {tenant_id}/ ├── originals/ # 원본 PDF │ └── {contract_id}/ │ └── {hash}.pdf ├── signatures/ # 서명 이미지 │ └── {signer_id}/ │ └── {timestamp}.png └── signed/ # 서명 완료 PDF (v1.1) └── {contract_id}/ └── {contract_code}_signed.pdf ``` ### 4.2 용량 산정 | 파일 유형 | 평균 크기 | 월 100건 기준 | |----------|----------|--------------| | 원본 PDF | 2MB | 200MB | | 서명 이미지 | 50KB x 2인 | 10MB | | 서명 완료 PDF | 2.5MB (v1.1) | 250MB | | **월 합계** | | **약 460MB** | | **연간 합계** | | **약 5.5GB** | ### 4.3 스토리지 모니터링 ```bash # 전체 E-Sign 스토리지 용량 확인 du -sh /home/webservice/api/storage/app/esign/ # 테넌트별 용량 확인 du -sh /home/webservice/api/storage/app/esign/*/ # 디스크 여유 공간 확인 df -h /home/webservice/ ``` ### 4.4 파일 정리 정책 | 항목 | 보관 기간 | 근거 | |------|----------|------| | 완료된 계약 PDF | 5년 | 전자상거래법 | | 서명 이미지 | 5년 | 법적 증거 | | 취소/만료 계약 PDF | 1년 | 운영 판단 | | 감사 로그 | 영구 | 삭제 불가 | ### 4.5 권한 설정 ```bash # API 스토리지 디렉토리 권한 chown -R www-data:www-data /home/webservice/api/storage/app/esign/ chmod -R 775 /home/webservice/api/storage/app/esign/ # 보안: 웹에서 직접 접근 차단 (Nginx에서 처리) # storage 디렉토리는 public 경로에 포함되지 않음 ``` --- ## 5. 메일 발송 설정 ### 5.1 발송 시점 | 이벤트 | 수신자 | 메일 내용 | |--------|--------|----------| | 서명 요청 발송 | 첫 번째 서명자 | 계약 제목, 서명 링크, 기한 | | OTP 발송 | 해당 서명자 | 6자리 인증코드, 5분 유효 | | 리마인더 | 미서명 서명자 | 서명 독촉, 남은 기한 | | 서명 완료 알림 | 다음 서명자 | "상대방이 서명했습니다", 서명 링크 | | 계약 완료 | 양쪽 서명자 | 완료 안내, 다운로드 링크 | ### 5.2 SMTP 설정 ```bash # API .env (현재 Gmail SMTP) MAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=shine1324@gmail.com MAIL_PASSWORD= MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=shine1324@gmail.com MAIL_FROM_NAME="SAM E-Sign" ``` ### 5.3 Gmail 앱 비밀번호 발급 1. Google 계정 → 보안 → 2단계 인증 활성화 2. 앱 비밀번호 생성 (항목: 메일, 기기: 기타 → "SAM API") 3. 생성된 16자리 비밀번호를 `.env`의 `MAIL_PASSWORD`에 설정 ### 5.4 메일 발송 테스트 ```bash # tinker를 이용한 메일 발송 테스트 docker exec sam-api-1 php artisan tinker --execute=" Mail::raw('E-Sign 메일 테스트', function(\$m) { \$m->to('test@example.com')->subject('SAM E-Sign 테스트'); }); echo 'Mail sent'; " ``` ### 5.5 메일 발송 실패 시 ```bash # 메일 로그 확인 docker exec sam-api-1 tail -50 storage/logs/laravel.log | grep -i mail # 큐 실패 작업 확인 docker exec sam-api-1 php artisan queue:failed # 실패 작업 재시도 docker exec sam-api-1 php artisan queue:retry all ``` ### 5.6 운영 환경 권장 사항 | 항목 | 현재 | 운영 권장 | |------|------|----------| | SMTP 서비스 | Gmail | AWS SES / Mailgun / SendGrid | | 일 발송 한도 | 500건 (Gmail) | 무제한 (전문 서비스) | | 발송 모드 | 동기(sync) | **비동기(database/redis)** | | 큐 워커 | 미실행 | `php artisan queue:work` 상시 실행 | --- ## 6. 스케줄러 설정 ### 6.1 E-Sign 관련 예정 스케줄 작업 > 현재 v1.0에서는 스케줄러 미구현. v1.1에서 추가 예정. | 작업 | 실행 주기 | 설명 | 상태 | |------|----------|------|------| | 계약 만료 처리 | 매일 01:00 | expires_at 초과 계약 → expired 상태 변경 | v1.1 예정 | | 자동 리마인더 | 매일 09:00 | 만료 3일 전 서명자에게 알림 | v1.1 예정 | | 임시 파일 정리 | 매일 03:30 | 7일 이상 된 임시 파일 삭제 | 기존 설정 | ### 6.2 기존 SAM 스케줄러 (참고) API 프로젝트의 `routes/console.php`에 이미 등록된 작업: ``` 02:00 일간 통계 집계 03:10 감사 로그 정리 (13개월 보관) 03:30 임시 파일 정리 (7일 이상) 03:40 휴지통 파일 삭제 (30일 이상) 03:50 공유 링크 정리 05:00 DB 백업 상태 확인 ``` ### 6.3 크론탭 설정 ```bash # 서버 크론탭 (이미 설정된 경우 확인만) crontab -l # Laravel 스케줄러 크론 (1분마다 실행) * * * * * cd /home/webservice/api && php artisan schedule:run >> /dev/null 2>&1 ``` --- ## 7. 모니터링 ### 7.1 헬스체크 ```bash # API 서버 동작 확인 curl -s https://api.sam.kr/api/health | jq . # MNG 서버 동작 확인 curl -s -o /dev/null -w "%{http_code}" https://mng.sam.kr/esign # MySQL 연결 확인 docker exec sam-api-1 php artisan tinker --execute="DB::connection()->getPdo(); echo 'DB OK';" # E-Sign 테이블 확인 docker exec sam-api-1 php artisan tinker --execute=" echo 'contracts: ' . \App\Models\ESign\EsignContract::withoutGlobalScopes()->count(); echo 'signers: ' . \App\Models\ESign\EsignSigner::withoutGlobalScopes()->count(); " ``` ### 7.2 로그 모니터링 ```bash # API 에러 로그 실시간 감시 docker exec sam-api-1 tail -f storage/logs/laravel.log | grep -i "error\|exception" # MNG 에러 로그 실시간 감시 docker exec sam-mng-1 tail -f storage/logs/laravel.log | grep -i "error\|exception" # E-Sign 관련 로그만 필터 docker exec sam-api-1 grep -i "esign\|e-sign" storage/logs/laravel.log | tail -50 ``` ### 7.3 주요 모니터링 지표 | 지표 | 확인 방법 | 임계치 | |------|----------|--------| | API 응답 시간 | Nginx access log | > 3초 경고 | | 에러 발생률 | Laravel log | 시간당 10건 이상 경고 | | 디스크 사용량 | `df -h` | 80% 이상 경고 | | 메일 발송 실패 | `queue:failed` | 1건 이상 즉시 확인 | | DB 연결 수 | `SHOW STATUS LIKE 'Threads_connected'` | 80% 이상 경고 | ### 7.4 계약 현황 모니터링 ```bash # 상태별 계약 통계 docker exec sam-api-1 php artisan tinker --execute=" \$stats = \App\Models\ESign\EsignContract::withoutGlobalScopes() ->selectRaw('status, count(*) as cnt') ->groupBy('status') ->pluck('cnt', 'status'); print_r(\$stats->toArray()); " # 만료 임박 계약 (3일 이내) docker exec sam-api-1 php artisan tinker --execute=" \$expiring = \App\Models\ESign\EsignContract::withoutGlobalScopes() ->whereIn('status', ['pending', 'partially_signed']) ->where('expires_at', '<=', now()->addDays(3)) ->count(); echo '만료 임박: ' . \$expiring . '건'; " ``` --- ## 8. 백업 및 복구 ### 8.1 백업 대상 | 대상 | 방법 | 주기 | |------|------|------| | DB (esign_* 4개 테이블) | mysqldump | 일간 | | 원본 PDF 파일 | rsync / 파일 복사 | 일간 | | 서명 이미지 | rsync / 파일 복사 | 일간 | | .env 설정 파일 | 수동 백업 | 변경 시 | ### 8.2 DB 백업 ```bash # E-Sign 테이블만 백업 docker exec sam-mysql-1 mysqldump -u samuser -psampass samdb \ esign_contracts esign_signers esign_sign_fields esign_audit_logs \ > /home/aweso/sam/db_backup/esign_$(date +%Y%m%d).sql # 전체 DB 백업 (기존 방식 활용) docker exec sam-mysql-1 mysqldump -u samuser -psampass samdb \ > /home/aweso/sam/db_backup/samdb_$(date +%Y%m%d).sql ``` ### 8.3 파일 백업 ```bash # E-Sign 스토리지 백업 rsync -av /home/webservice/api/storage/app/esign/ \ /backup/esign/$(date +%Y%m%d)/ ``` ### 8.4 복구 절차 ```bash # 1. DB 복구 docker exec -i sam-mysql-1 mysql -u samuser -psampass samdb \ < /home/aweso/sam/db_backup/esign_20260212.sql # 2. 파일 복구 rsync -av /backup/esign/20260212/ \ /home/webservice/api/storage/app/esign/ # 3. 권한 복원 chown -R www-data:www-data /home/webservice/api/storage/app/esign/ # 4. 캐시 클리어 cd /home/webservice/api && php artisan cache:clear ``` --- ## 9. 보안 운영 ### 9.1 Nginx 보안 설정 (적용 완료) ``` # SSL/TLS - TLSv1.2, TLSv1.3만 허용 - HSTS: max-age=31536000 (1년) # 보안 헤더 - X-Frame-Options: SAMEORIGIN - X-Content-Type-Options: nosniff - X-XSS-Protection: 1; mode=block # 경로 차단 - ../, .env, .git, .htaccess, .sql 패턴 차단 (403) - sqlmap, nikto, nmap 등 스캐너 User-Agent 차단 (403) ``` ### 9.2 E-Sign 보안 포인트 | 항목 | 보호 방법 | 점검 주기 | |------|----------|----------| | access_token | 128자 랜덤, API 응답에 미포함 ($hidden) | 코드 리뷰 시 | | OTP 코드 | 6자리, 5분 만료, 5회 제한 | 배포 시 | | 파일 접근 | Controller 스트리밍만 허용, 직접 경로 차단 | 배포 시 | | 문서 무결성 | SHA-256 hash_equals() (타이밍 공격 방지) | 코드 리뷰 시 | | 테넌트 격리 | BelongsToTenant 글로벌 스코프 | 배포 시 | | 감사 로그 | SoftDeletes 미적용, 삭제 API 없음 | 분기 점검 | ### 9.3 정기 보안 점검 ```bash # 1. 비정상 접근 시도 확인 docker exec sam-nginx-1 grep "403\|401" /var/log/nginx/access.log | tail -20 # 2. OTP 시도 횟수 초과 확인 docker exec sam-api-1 php artisan tinker --execute=" \$blocked = \App\Models\ESign\EsignSigner::withoutGlobalScopes() ->where('otp_attempts', '>=', 5) ->count(); echo 'OTP 차단 서명자: ' . \$blocked . '명'; " # 3. 만료된 토큰으로 접근 시도 감사 docker exec sam-api-1 php artisan tinker --execute=" \$suspicious = \App\Models\ESign\EsignAuditLog::withoutGlobalScopes() ->where('action', 'like', '%failed%') ->where('created_at', '>=', now()->subDays(7)) ->count(); echo '최근 7일 실패 이벤트: ' . \$suspicious . '건'; " ``` ### 9.4 SSL 인증서 관리 ```bash # 인증서 만료일 확인 openssl x509 -enddate -noout -in /etc/nginx/ssl/sam.kr.crt # 인증서 갱신 후 Nginx 재시작 docker exec sam-nginx-1 nginx -s reload ``` --- ## 10. 장애 대응 ### 10.1 장애 유형별 대응 #### API 서버 응답 없음 ```bash # 1. 컨테이너 상태 확인 docker ps | grep sam-api # 2. PHP-FPM 프로세스 확인 docker exec sam-api-1 ps aux | grep php-fpm # 3. 에러 로그 확인 docker exec sam-api-1 tail -50 storage/logs/laravel.log # 4. 컨테이너 재시작 docker restart sam-api-1 ``` #### DB 연결 실패 ```bash # 1. MySQL 컨테이너 확인 docker ps | grep sam-mysql # 2. MySQL 프로세스 확인 docker exec sam-mysql-1 mysqladmin -u samuser -psampass ping # 3. 연결 수 확인 docker exec sam-mysql-1 mysql -u samuser -psampass -e \ "SHOW STATUS LIKE 'Threads_connected';" # 4. MySQL 재시작 docker restart sam-mysql-1 ``` #### 메일 발송 실패 ```bash # 1. SMTP 연결 테스트 docker exec sam-api-1 php artisan tinker --execute=" try { \$transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport( 'smtp.gmail.com', 587, true ); echo 'SMTP 연결 가능'; } catch (\Exception \$e) { echo 'SMTP 에러: ' . \$e->getMessage(); } " # 2. 큐 실패 작업 확인 docker exec sam-api-1 php artisan queue:failed # 3. 실패 작업 재시도 docker exec sam-api-1 php artisan queue:retry all # 4. 로그에서 메일 에러 확인 docker exec sam-api-1 grep "Swift_Transport\|Mail\|smtp" storage/logs/laravel.log | tail -20 ``` #### 서명 링크 접속 불가 ```bash # 1. 토큰 유효성 확인 docker exec sam-api-1 php artisan tinker --execute=" \$signer = \App\Models\ESign\EsignSigner::withoutGlobalScopes() ->where('access_token', '<토큰값>') ->first(); if (\$signer) { echo 'Status: ' . \$signer->status; echo ', Expires: ' . \$signer->token_expires_at; echo ', Contract: ' . \$signer->contract->status; } else { echo 'Token not found'; } " # 2. Nginx 로그에서 해당 요청 확인 docker exec sam-nginx-1 grep "esign/sign" /var/log/nginx/access.log | tail -10 ``` #### PDF 업로드 실패 ```bash # 1. 스토리지 디렉토리 권한 확인 docker exec sam-api-1 ls -la storage/app/esign/ # 2. 디스크 여유 공간 확인 docker exec sam-api-1 df -h /var/www/api/storage/ # 3. PHP 업로드 설정 확인 docker exec sam-api-1 php -i | grep -i "upload_max_filesize\|post_max_size" # 4. Nginx 업로드 크기 확인 docker exec sam-nginx-1 grep "client_max_body_size" /etc/nginx/nginx.conf ``` ### 10.2 긴급 복구 체크리스트 ``` □ 장애 내용 파악 (에러 메시지, 발생 시점, 영향 범위) □ 에러 로그 확인 (API, MNG, Nginx, MySQL) □ 컨테이너 상태 확인 (docker ps) □ 네트워크 확인 (도메인, DNS, SSL) □ 서비스 재시작 (필요 시) □ DB 연결 확인 □ 캐시 클리어 (config:cache, route:cache) □ 복구 확인 (브라우저 접속 테스트) □ 장애 보고서 작성 (원인, 조치, 재발 방지) ``` --- ## 11. 확장 가이드 ### 11.1 현재 설치된 패키지 (구현 완료) ```bash # FPDI/TCPDF (PDF 서명 합성) - MNG 프로젝트에 설치됨 # setasign/fpdi: ^2.6, tecnickcom/tcpdf: ^6.10 # LibreOffice (DOCX→PDF 변환) - MNG Docker 컨테이너에 설치됨 # libreoffice-writer-nogui, fonts-nanum, fonts-nanum-extra # GD 확장 (서명 이미지 처리) - MNG Docker 컨테이너에 설치됨 ``` ### 11.2 큐 워커 설정 (운영 권장) 메일 발송을 비동기로 처리하려면 큐 워커를 상시 실행해야 합니다. ```bash # Supervisor 설정 (/etc/supervisor/conf.d/esign-worker.conf) [program:esign-worker] process_name=%(program_name)s_%(process_num)02d command=php /home/webservice/api/artisan queue:work database --sleep=3 --tries=3 --max-time=3600 autostart=true autorestart=true stopasleepy=false numprocs=2 user=www-data redirect_stderr=true stdout_logfile=/home/webservice/api/storage/logs/worker.log stopwaitsecs=3600 # Supervisor 적용 supervisorctl reread supervisorctl update supervisorctl start esign-worker:* ``` ### 11.3 S3 스토리지 전환 (선택) 로컬 스토리지에서 AWS S3로 전환할 경우: ```bash # 패키지 설치 composer require league/flysystem-aws-s3-v3 "^3.0" # .env 설정 추가 AWS_ACCESS_KEY_ID=<키> AWS_SECRET_ACCESS_KEY=<시크릿> AWS_DEFAULT_REGION=ap-northeast-2 AWS_BUCKET=sam-esign FILESYSTEM_DISK=s3 ``` ### 11.4 운영 환경 SMTP 전환 (권장) Gmail SMTP는 일 500건 제한이 있으므로, 운영 환경에서는 전문 메일 서비스를 권장합니다. **AWS SES 예시:** ```bash # .env 설정 MAIL_MAILER=ses AWS_ACCESS_KEY_ID=<키> AWS_SECRET_ACCESS_KEY=<시크릿> AWS_DEFAULT_REGION=ap-northeast-2 MAIL_FROM_ADDRESS=esign@sam.kr ``` **SendGrid 예시:** ```bash # .env 설정 MAIL_MAILER=smtp MAIL_HOST=smtp.sendgrid.net MAIL_PORT=587 MAIL_USERNAME=apikey MAIL_PASSWORD= MAIL_FROM_ADDRESS=esign@sam.kr ``` --- ## 부록: 명령어 모음 ### 일상 운영 ```bash # 서비스 상태 확인 docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep sam # 전체 재시작 cd /home/aweso/sam && docker compose restart # 개별 재시작 docker restart sam-api-1 docker restart sam-mng-1 # 캐시 전체 클리어 docker exec sam-api-1 php artisan optimize:clear docker exec sam-mng-1 php artisan optimize:clear ``` ### 데이터 조회 ```bash # 계약 건수 docker exec sam-mysql-1 mysql -u samuser -psampass samdb \ -e "SELECT status, COUNT(*) AS cnt FROM esign_contracts GROUP BY status;" # 최근 감사 로그 docker exec sam-mysql-1 mysql -u samuser -psampass samdb \ -e "SELECT action, ip_address, created_at FROM esign_audit_logs ORDER BY id DESC LIMIT 20;" # 서명 대기 건수 docker exec sam-mysql-1 mysql -u samuser -psampass samdb \ -e "SELECT COUNT(*) AS pending FROM esign_contracts WHERE status IN ('pending','partially_signed');" ``` ### 트러블슈팅 ```bash # Laravel 로그 실시간 docker exec sam-api-1 tail -f storage/logs/laravel.log # Nginx 접근 로그 docker exec sam-nginx-1 tail -f /var/log/nginx/access.log # PHP 설정 확인 docker exec sam-api-1 php -i | grep -E "memory_limit|upload_max|post_max|max_execution" # 라우트 목록 docker exec sam-api-1 php artisan route:list --path=esign --columns=method,uri,name ``` --- *이 문서는 SAM E-Sign v1.0 운영/배포 가이드입니다. 최종 업데이트: 2026-02-12*