- FPDI/FPDF → FPDI/TCPDF (PDF 서명 합성, MNG PdfSignatureService) - DOCX→PDF 변환 추가 (LibreOffice headless, MNG DocxToPdfConverter) - GD 확장, 나눔 폰트, Lucide 아이콘 등 실제 사용 기술 반영 - 4개 문서 일괄 업데이트 (technical-design, implementation-guide, operations-guide, changelog) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
25 KiB
25 KiB
SAM E-Sign 운영/배포 가이드
프로젝트명: SAM E-Sign (전자계약 서명 솔루션) 작성일: 2026-02-12 버전: v1.0 대상: 시스템 운영자, DevOps 엔지니어
목차
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)
필수 설정
# 애플리케이션
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 관련 설정
# 메일 (서명 요청/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)
# 애플리케이션
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: 마이그레이션 실행
# 로컬 (Docker)
docker exec sam-api-1 php artisan migrate
# 서버
cd /home/webservice/api
php artisan migrate
마이그레이션 결과 확인:
# 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: 스토리지 디렉토리 생성
# 로컬 (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: 라우트 캐시 갱신
# 로컬
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로 수동 등록합니다.
# 상위 메뉴 생성
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' => <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' => <PARENT_ID>,
'name' => '새 계약 생성',
'url' => '/esign/create',
'icon' => 'ti ti-file-plus',
'sort_order' => 2,
'is_active' => true,
]);
\""
Step 5: 라우트 확인
# 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)
# 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
서버 (운영)
# 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 스토리지 모니터링
# 전체 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 권한 설정
# 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 설정
# API .env (현재 Gmail SMTP)
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=shine1324@gmail.com
MAIL_PASSWORD=<Gmail 앱 비밀번호>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=shine1324@gmail.com
MAIL_FROM_NAME="SAM E-Sign"
5.3 Gmail 앱 비밀번호 발급
- Google 계정 → 보안 → 2단계 인증 활성화
- 앱 비밀번호 생성 (항목: 메일, 기기: 기타 → "SAM API")
- 생성된 16자리 비밀번호를
.env의MAIL_PASSWORD에 설정
5.4 메일 발송 테스트
# 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 메일 발송 실패 시
# 메일 로그 확인
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 크론탭 설정
# 서버 크론탭 (이미 설정된 경우 확인만)
crontab -l
# Laravel 스케줄러 크론 (1분마다 실행)
* * * * * cd /home/webservice/api && php artisan schedule:run >> /dev/null 2>&1
7. 모니터링
7.1 헬스체크
# 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 로그 모니터링
# 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 계약 현황 모니터링
# 상태별 계약 통계
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 백업
# 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 파일 백업
# E-Sign 스토리지 백업
rsync -av /home/webservice/api/storage/app/esign/ \
/backup/esign/$(date +%Y%m%d)/
8.4 복구 절차
# 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 정기 보안 점검
# 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 인증서 관리
# 인증서 만료일 확인
openssl x509 -enddate -noout -in /etc/nginx/ssl/sam.kr.crt
# 인증서 갱신 후 Nginx 재시작
docker exec sam-nginx-1 nginx -s reload
10. 장애 대응
10.1 장애 유형별 대응
API 서버 응답 없음
# 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 연결 실패
# 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
메일 발송 실패
# 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
서명 링크 접속 불가
# 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 업로드 실패
# 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 현재 설치된 패키지 (구현 완료)
# 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 큐 워커 설정 (운영 권장)
메일 발송을 비동기로 처리하려면 큐 워커를 상시 실행해야 합니다.
# 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로 전환할 경우:
# 패키지 설치
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 예시:
# .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 예시:
# .env 설정
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=<SendGrid API Key>
MAIL_FROM_ADDRESS=esign@sam.kr
부록: 명령어 모음
일상 운영
# 서비스 상태 확인
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
데이터 조회
# 계약 건수
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');"
트러블슈팅
# 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