Files
sam-docs/projects/e-sign/operations-guide.md
김보곤 aab9dc0799 docs:E-Sign 기술 스택 문서 업데이트 (실제 구현 반영)
- 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>
2026-02-13 13:02:16 +09:00

25 KiB

SAM E-Sign 운영/배포 가이드

프로젝트명: SAM E-Sign (전자계약 서명 솔루션) 작성일: 2026-02-12 버전: v1.0 대상: 시스템 운영자, DevOps 엔지니어


목차

  1. 시스템 구성
  2. 환경 변수
  3. 배포 절차
  4. 스토리지 관리
  5. 메일 발송 설정
  6. 스케줄러 설정
  7. 모니터링
  8. 백업 및 복구
  9. 보안 운영
  10. 장애 대응
  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)

필수 설정

# 애플리케이션
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 앱 비밀번호 발급

  1. Google 계정 → 보안 → 2단계 인증 활성화
  2. 앱 비밀번호 생성 (항목: 메일, 기기: 기타 → "SAM API")
  3. 생성된 16자리 비밀번호를 .envMAIL_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