# 8. 장애 대응 가이드

[목차로 돌아가기](./README.md)

---

## 운영서버 장애

### Nginx 502 Bad Gateway

**증상:** 브라우저에서 502 에러. 정적 파일은 정상, 동적 요청만 실패.

**진단:**

```bash
sudo tail -50 /var/log/nginx/api.sam.it.kr.error.log
# "connect() failed" 또는 "no live upstreams" 메시지 확인

# Laravel 사이트인 경우
sudo systemctl status php8.4-fpm
ls -la /run/php/php8.4-fpm-*.sock

# Next.js 사이트인 경우
pm2 status
```

**조치:**

```bash
# PHP-FPM이 죽은 경우
sudo systemctl restart php8.4-fpm

# PM2가 죽은 경우
cd /home/webservice && pm2 start ecosystem.config.js
pm2 save

# Nginx 자체 문제
sudo nginx -t && sudo systemctl restart nginx
```

**예방:** PHP-FPM과 PM2는 자동 재시작 설정됨. 반복 발생 시 메모리 부족을 의심.

---

### Nginx 504 Gateway Timeout

**증상:** 요청이 오래 걸린 후 504 에러. 무거운 API 호출에서 발생.

**진단:**

```bash
sudo tail -50 /var/log/nginx/api.sam.it.kr.error.log
# "upstream timed out" 메시지 확인
sudo tail -50 /var/log/mysql/slow.log
```

**조치:**

```bash
# 장시간 실행 중인 MySQL 쿼리 kill
sudo mysql -e "SHOW PROCESSLIST;" | grep -v Sleep
sudo mysql -e "KILL 프로세스_ID;"

# Nginx timeout 일시적 증가 (필요시)
# /etc/nginx/sites-available/api.sam.it.kr 에서 fastcgi_read_timeout 값 조정
sudo nginx -t && sudo systemctl reload nginx
```

**예방:** 무거운 작업은 Queue로 처리. 현재 fastcgi_read_timeout은 60초.

---

### MySQL 연결 거부 / Too Many Connections

**증상:** "Connection refused" 또는 "Too many connections" 에러.

**진단:**

```bash
sudo systemctl status mysql
sudo mysql -e "SHOW STATUS LIKE 'Threads_connected';"
sudo mysql -e "SHOW VARIABLES LIKE 'max_connections';"
sudo mysql -e "SHOW PROCESSLIST;"
```

**조치:**

```bash
# MySQL이 정지된 경우
sudo systemctl start mysql

# Sleep 연결 정리 (300초 이상 유휴)
sudo mysql -e "SELECT id FROM information_schema.processlist WHERE command='Sleep' AND time > 300;" | while read id; do
  [ "$id" != "id" ] && sudo mysql -e "KILL $id;"
done

# 임시로 max_connections 증가 (재시작 없이)
sudo mysql -e "SET GLOBAL max_connections = 150;"
```

**예방:** max_connections(100)은 현재 규모에 적합. 부족 시 sam-tuning.cnf 조정.

---

### [개발] MySQL 8.4 인증 플러그인 오류

**증상:** `SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client`

**원인:** MySQL 8.4에서 `mysql_native_password` 플러그인이 기본 비활성화됨. 레거시 PHP(5130 등)의 mysqlnd가 `caching_sha2_password`를 지원하지 못함.

**조치:**

```bash
# 1. mysqld.cnf에 플러그인 활성화 추가
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
# [mysqld] 섹션에 추가:
# mysql_native_password=ON

# 2. MySQL 재시작
sudo systemctl restart mysql

# 3. 레거시 PHP용 계정 인증 방식 변경
mysql -u debian-sys-maint -p'비밀번호' -e "
ALTER USER '계정'@'localhost' IDENTIFIED WITH mysql_native_password BY '비밀번호';
FLUSH PRIVILEGES;"
```

**실제 사례 (2026-02-25):**

1. 5130 레거시 사이트 로그인 시 2054 에러 발생
2. `/etc/mysql/mysql.conf.d/mysqld.cnf`에 `mysql_native_password=ON` 추가 후 MySQL 재시작
3. `codebridge`, `pro`, `chandj` 계정을 `mysql_native_password`로 변경하여 해결

**참고:** debian-sys-maint 비밀번호는 `/etc/mysql/debian.cnf`에서 확인 가능.

---

### .env 권한 오류로 전체 500 에러 (API + MNG)

**증상:** api.sam.it.kr, mng.sam.it.kr 모든 요청에서 500 에러. PHP-FPM, Redis, MySQL 모두 정상.

**원인:** `.env` 파일 권한이 `600`(`-rw-------`)으로 변경되어 PHP-FPM(`www-data`)이 읽지 못함. 모든 환경변수가 null이 되면서 `DB_CONNECTION`, `CACHE_STORE` 등이 기본값(SQLite)로 fallback.

**주요 발생 원인:**
- `vi`로 `.env` 편집 시 파일이 재생성되면서 umask에 따라 `600`으로 변경됨
- 배포 스크립트에서 권한 미설정

**진단:**

```bash
# 1. .env 권한 확인 (640이어야 정상)
ls -la /home/webservice/api/shared/.env
ls -la /home/webservice/mng/shared/.env

# 2. www-data로 읽기 테스트
sudo -u www-data cat /home/webservice/api/shared/.env | head -1
# "Permission denied" → 권한 문제 확인

# 3. artisan으로 config 확인 (CLI는 sudo로 정상 작동)
sudo php /home/webservice/api/current/artisan config:show cache
# default가 database/file이면 .env 미반영 상태
```

**조치:**

```bash
sudo chmod 640 /home/webservice/api/shared/.env
sudo chmod 640 /home/webservice/mng/shared/.env
```

**예방:**
- 서버 계정 `~/.vimrc`에 `set backupcopy=yes` 추가 (vi 편집 시 권한 유지)
- 배포 스크립트에 `chmod 640` 포함
- 자세한 내용: 09-security.md "[운영] .env 파일 보안" 참조

**실제 사례 (2026-03-03):**

1. `.env`에서 `GEMINI_MODEL` 값을 `vi`로 변경
2. `vi`가 파일을 재생성하면서 권한 `600`으로 변경
3. PHP-FPM(`www-data`)이 `.env` 읽기 실패 → 모든 env 값 null
4. `CACHE_STORE=null` → 기본값 `database` → SQLite 연결 시도 → 500 에러
5. `chmod 640`으로 권한 복원하여 즉시 해결

**진단 포인트:**
- CLI(`artisan tinker`)에서는 정상인데 웹만 500 → **파일 권한 문제 의심**
- Laravel 로그에 기록이 없으면 **로그 파일 쓰기 권한도 확인**

---

### Redis 메모리 부족

**증상:** "OOM command not allowed" 메시지.

**진단:**

```bash
redis-cli info memory | grep used_memory_human
redis-cli config get maxmemory
redis-cli dbsize
redis-cli --bigkeys
```

**조치:**

```bash
cd /home/webservice/api/current && php artisan cache:clear
redis-cli keys "laravel_cache:*" | xargs redis-cli del
redis-cli flushall                 # 전체 초기화 (세션도 삭제 - 주의)
redis-cli config set maxmemory 768mb   # 임시 증가
```

**예방:** allkeys-lru 정책 설정됨. 512MB 부족 시 redis.conf에서 maxmemory 조정.

---

### PM2 프로세스 크래시 / 재시작 반복

**증상:** sam.it.kr 접속 불가 또는 간헐적 502. PM2 status에서 restart 횟수 급증.

**진단:**

```bash
pm2 status
pm2 logs sam-front --err --lines 100
pm2 describe sam-front | grep memory
```

**조치:**

```bash
pm2 reload sam-front

# 문제 지속 시 완전 재시작
pm2 stop sam-front
cd /home/webservice && pm2 start ecosystem.config.js --only sam-front
pm2 save

# 로그 파일이 너무 큰 경우
pm2 flush
```

**예방:** max_memory_restart=300M 설정됨. 반복 크래시 시 코드 문제 조사.

---

### Queue Worker 정지 / 미처리

**증상:** 이메일, 알림 등 비동기 작업 미처리.

**진단:**

```bash
sudo supervisorctl status
sudo tail -50 /home/webservice/api/shared/storage/logs/queue-worker.log
cd /home/webservice/api/current && php artisan queue:monitor redis:default
```

**조치:**

```bash
sudo supervisorctl restart sam-queue-worker:*

cd /home/webservice/api/current
php artisan queue:failed           # 실패한 작업 확인
php artisan queue:retry all        # 실패한 작업 재시도
php artisan queue:flush            # 실패한 작업 전체 삭제
```

**예방:** max-time=3600 설정 (1시간마다 자동 재시작). Supervisor가 프로세스 자동 복구.

---

### SSL 인증서 만료

**증상:** 브라우저에서 "연결이 비공개가 아닙니다" 경고.

**진단:**

```bash
sudo certbot certificates
sudo systemctl status certbot.timer
echo | openssl s_client -servername api.sam.it.kr -connect 211.117.60.189:443 2>/dev/null | openssl x509 -noout -dates
```

**조치:**

```bash
sudo certbot renew
sudo certbot certonly --nginx -d api.sam.it.kr    # 특정 도메인만
sudo systemctl reload nginx
```

**예방:** certbot.timer 정상 작동 시 만료 30일 전 자동 갱신.

---

### PHP-FPM Pool 소진 (max_children)

**증상:** 응답 지연 후 502. PHP-FPM 로그에 "server reached max_children" 경고.

**진단:**

```bash
sudo grep "max_children" /var/log/php8.4-fpm.log
ps aux | grep "php-fpm" | grep -v grep | wc -l
```

**조치:**

```bash
sudo systemctl restart php8.4-fpm

# max_children 조정 (예: api pool 10 -> 15)
sudo vi /etc/php/8.4/fpm/pool.d/api.conf
sudo systemctl reload php8.4-fpm
```

**예방:** 프로세스당 약 80MB. API pool: 10 x 80MB = 800MB. 메모리 여유 시만 증가.

---

### Laravel Storage 권한 문제

**증상:** "Permission denied". 로그 파일 작성 불가. 파일 업로드 실패.

**진단:**

```bash
ls -la /home/webservice/api/shared/storage/
ls -la /home/webservice/api/shared/storage/logs/
```

**조치:**

```bash
sudo chown -R www-data:webservice /home/webservice/api/shared/storage
sudo chmod -R 775 /home/webservice/api/shared/storage
sudo chown -R www-data:webservice /home/webservice/api/current/bootstrap/cache
sudo chmod -R 775 /home/webservice/api/current/bootstrap/cache
```

**예방:** 배포 스크립트에 권한 설정 포함. shared/storage 심링크 확인.

---

### MNG 500 에러 (storage/logs 권한 + SOAP)

**증상:** mng.codebridge-x.com 특정 페이지에서 500 에러. Laravel 로그에 기록 없음.

**배경:** 2026-02-26 이후 MNG `storage/logs`는 shared로 심링크됨. 이전에는 릴리즈 디렉토리에 직접 생성되어 배포마다 로그가 유실되었음.

**진단 순서:**

```bash
# 1. 로그 심링크 확인
ls -la /home/webservice/mng/current/storage/logs
# → shared/storage/logs 심링크인지 확인

# 2. 로그 파일 소유자 확인
ls -la /home/webservice/mng/shared/storage/logs/laravel.log

# 3. nginx 접근 로그에서 500 확인
sudo tail -20 /var/log/nginx/mng.codebridge-x.com.access.log | grep " 500 "
```

**조치:**

```bash
# 로그 심링크가 아닌 경우 (이전 배포 방식)
rm -rf /home/webservice/mng/current/storage/logs
ln -sfn /home/webservice/mng/shared/storage/logs /home/webservice/mng/current/storage/logs

# shared 로그 권한 수정
sudo chown www-data:webservice /home/webservice/mng/shared/storage/logs/
sudo chown www-data:webservice /home/webservice/mng/shared/storage/logs/laravel.log

# 로그 확인
cat /home/webservice/mng/shared/storage/logs/laravel.log
```

**실제 사례 (2026-02-25):**

1. 최초 증상: `Table 'sam.cache' doesn't exist` → `CACHE_STORE=database`였으나 cache 테이블 미존재
2. 해결: `.env`에서 `CACHE_STORE=redis`로 변경 + `php artisan config:cache`
3. 여전히 500 → 로그 파일 권한 문제로 에러 미기록 → 권한 수정 후 실제 에러 확인
4. 실제 원인: `Class "SoapClient" not found` → `php8.4-soap` 미설치
5. 최종 해결: `sudo apt install php8.4-soap && sudo systemctl restart php8.4-fpm`

**교훈:**
- MNG 로그는 `shared/storage/logs/`에 있음 (2026-02-26~)
- 500 에러인데 로그가 비어있으면 **심링크 여부 → 파일 권한** 순서로 확인
- PHP 확장 누락은 artisan tinker로 확인 가능: `php artisan tinker --execute="new SoapClient('test');"`

---

### MNG 배포 후 storage/logs 권한 500 에러

**증상:** MNG 배포 직후 finance/barobill 관련 페이지에서 500 에러. Laravel 로그에 에러 기록 없음.

**원인:** 배포 시 `php artisan migrate --force`가 배포 사용자(hskwon)로 실행되면서 `storage/logs/laravel-YYYY-MM-DD.log` 파일이 hskwon 소유로 생성됨. 이후 웹 요청에서 www-data가 로그 파일에 쓰기 시도 → `Permission denied` → 500 에러.

**진단:**

```bash
# 로그 파일 소유자 확인 (www-data:webservice여야 정상)
ls -la /home/webservice/mng/current/storage/logs/

# storage/logs가 심링크인지 디렉토리인지 확인
stat -c '%F' /home/webservice/mng/current/storage/logs

# tinker로 에러 재현
cd /home/webservice/mng/current && sudo -u www-data php artisan tinker --execute="Log::info('test');"
```

**긴급 조치:**

```bash
sudo chown www-data:webservice /home/webservice/mng/current/storage/logs/laravel-*.log
```

**근본 해결 (2026-03-19 적용):**

Jenkinsfile에서 `storage/logs`를 디렉토리(`mkdir`)가 아닌 shared 심링크로 생성하도록 수정:

```bash
# 변경 전 (문제 발생)
mkdir -p storage/logs && sudo chown -R www-data:webservice storage/logs

# 변경 후 (심링크 — shared는 www-data 소유이므로 권한 문제 없음)
rm -rf storage/logs && ln -sfn /home/webservice/mng/shared/storage/logs storage/logs
```

**실제 사례 (2026-03-19):**

1. `fix: [finance] 더존 3자리→KIS 5자리 계정코드 데이터 마이그레이션` 커밋 배포
2. `migrate --force` 실행 시 로그 출력 → `laravel-2026-03-19.log`가 hskwon:hskwon으로 생성
3. 이후 웹 요청에서 www-data가 Log::info() 호출 → Permission denied → 500
4. Nginx 에러 로그에만 `recv() failed (104: Connection reset by peer)` 기록
5. `chown www-data:webservice`로 긴급 조치 후 즉시 해소
6. Jenkinsfile 수정으로 재발 방지

---

### MNG 전자계약(E-Sign) PDF 서명 합성 오류

**증상:** 전자계약 완료 후 다운로드한 PDF에 서명/도장/텍스트가 적용되지 않음. DB에서 `signed_file_path`가 null.

**진단:**

```bash
# 1. 완료됐지만 signed_file_path 없는 계약 확인
cd /home/webservice/mng/current && php artisan tinker --execute="
\$contracts = App\Models\ESign\EsignContract::withoutGlobalScopes()
    ->where('status', 'completed')->whereNull('signed_file_path')
    ->get(['id','tenant_id','status','completed_at']);
echo \$contracts->toJson(JSON_PRETTY_PRINT);
"

# 2. 서명 이미지 파일 존재 확인
sudo ls -la /home/webservice/mng/shared/storage/app/private/esign/*/signatures/

# 3. signed 디렉토리 존재 및 권한 확인
ls -la /home/webservice/mng/shared/storage/app/private/esign/*/signed/

# 4. 로그 확인
grep -i "서명\|esign\|pdf" /home/webservice/mng/shared/storage/logs/laravel.log | tail -20

# 5. 한글 폰트 확인
ls -la /usr/share/fonts/truetype/nanum/NanumGothic.ttf
```

**조치 (수동 PDF 재합성):**

```bash
cd /home/webservice/mng/current && sudo -u www-data php artisan tinker --execute="
try {
    \$contract = App\Models\ESign\EsignContract::withoutGlobalScopes()->find(<계약ID>);
    \$pdfService = new App\Services\ESign\PdfSignatureService;
    \$result = \$pdfService->mergeSignatures(\$contract);
    echo 'SUCCESS: ' . \$result;
} catch (\Throwable \$e) {
    echo 'ERROR: ' . \$e->getMessage();
}
"
```

**주의:** 반드시 `sudo -u www-data`로 실행해야 서명 이미지 파일 접근 가능.

**주요 원인 및 해결:**

| 원인 | 진단 방법 | 해결 |
|------|----------|------|
| `signed/` 디렉토리 미존재 | `ls esign/*/signed/` | `sudo -u www-data mkdir -p esign/{tenant_id}/signed` |
| `signatures/` 권한 부족 | `stat esign/*/signatures/` | `sudo chmod 2775 esign/*/signatures/` |
| 로그 유실로 에러 추적 불가 | `ls -la current/storage/logs` | `storage/logs` → shared 심링크 확인 |
| 한글 폰트 미설치 | `ls /usr/share/fonts/truetype/nanum/` | `sudo apt install fonts-nanum` |
| FPDI/TCPDF 미설치 | `composer show setasign/fpdi` | `composer install` |
| TCPDF 폰트 정의 파일 오류 | 아래 "TCPDF 폰트 정의 파일 오류" 참고 | `registerKoreanFont()` 코드 수정 |

**esign 디렉토리 권한 기준:**

```bash
# 모든 esign 하위 디렉토리: www-data:webservice 2775
sudo chown -R www-data:webservice /home/webservice/mng/shared/storage/app/private/esign/
sudo chmod -R 2775 /home/webservice/mng/shared/storage/app/private/esign/
```

**실제 사례 (2026-02-26):**

1. 계약 #17이 `completed`인데 `signed_file_path`가 null
2. 원인: `signatures/` 디렉토리 권한 `2700` (www-data만 접근 가능), `signed/` 디렉토리 미존재
3. 추가 원인: `storage/logs`가 릴리즈 디렉토리에 있어 이전 배포 로그 유실
4. 조치: 권한 `2775`로 수정 + `sudo -u www-data`로 수동 재합성 + storage/logs 심링크 적용
5. 결과: 409KB signed PDF 생성 (원본 265KB + 서명 이미지 144KB)

---

### TCPDF 폰트 정의 파일 오류 (font definition file)

**증상:** 전자계약 서명 페이지에서 `TCPDF ERROR: Could not include font definition file: pretendard` (또는 `nanumgothic`) 오류.

**근본 원인:**

운영 환경에서 `vendor/tecnickcom/tcpdf/fonts/` 디렉토리가 배포 사용자(`hskwon`) 소유이므로 PHP-FPM(`www-data`)이 쓰기 불가.
`TCPDF_FONTS::addTTFfont()`는 폰트 캐시 파일(.php, .z, .ctg.z)을 **생성만** 하고,
`$pdf->SetFont('폰트명')`은 `K_PATH_FONTS`(vendor 경로)에서 **찾기만** 해서 경로 불일치 발생.

개발서버는 `vendor/` 권한이 `2775 pro:develop`이라 PHP가 직접 쓸 수 있어 문제없음.

**진단:**

```bash
# 폰트 캐시 존재 확인 (storage에 있으나 vendor에 없는 상태)
ls -la /home/webservice/mng/shared/storage/app/private/fonts/
ls /home/webservice/mng/current/vendor/tecnickcom/tcpdf/fonts/pretendard* 2>/dev/null

# vendor fonts 소유자 확인
stat -c "%U:%G %a" /home/webservice/mng/current/vendor/tecnickcom/tcpdf/fonts/

# 에러 로그 확인
grep -i "font definition\|Could not include" /home/webservice/mng/shared/storage/logs/laravel.log
```

**영구 해결 (코드 수정 - 2026-02-26 적용):**

`PdfSignatureService.php`에서 `registerKoreanFont(Fpdi $pdf)` 메서드로 분리하여:
1. 폰트 캐시를 `storage/app/private/fonts/`에 생성 (vendor 의존 제거)
2. `$pdf->AddFont('pretendard', '', $fontDefFile)` — PDF 인스턴스에 **전체 경로로 등록**
3. 이후 `SetFont('pretendard')`가 이미 등록된 폰트를 사용하므로 K_PATH_FONTS 미참조

**긴급 임시 조치 (코드 수정 전):**

```bash
# vendor 폰트 디렉토리 권한 변경 (배포 시마다 초기화됨)
sudo chown -R www-data:webservice /home/webservice/mng/current/vendor/tecnickcom/tcpdf/fonts/
sudo chmod -R 775 /home/webservice/mng/current/vendor/tecnickcom/tcpdf/fonts/

# 기존 캐시 삭제 (코드 수정 후 새 경로로 재생성)
sudo rm -f /home/webservice/mng/shared/storage/app/private/fonts/pretendard.*
sudo rm -f /home/webservice/mng/shared/storage/app/private/fonts/nanumgothic.*
```

**개발 vs 운영 환경 차이:**

| 항목 | 개발 서버 | 운영 서버 |
|------|----------|----------|
| vendor/ 소유자 | `pro:develop` (2775) | `hskwon:hskwon` (배포 사용자) |
| www-data vendor 쓰기 | ✅ 가능 | ❌ 불가 |
| 폰트 캐시 위치 | vendor 내부 (기본) | storage/app/private/fonts/ |
| `addTTFfont()` 결과 | vendor에 캐시 생성 → SetFont 성공 | storage에 캐시 생성 → SetFont 실패 (경로 불일치) |

---

## 공통 장애

### 디스크 공간 부족

**증상:** 서비스 오류. 로그 기록 실패. MySQL 쓰기 실패.

**진단:**

```bash
df -h
sudo du -sh /var/log/*
```

**[운영] 정리:**

```bash
cd /home/webservice/api/releases && ls -1dt */ | tail -n +4 | xargs rm -rf
cd /home/webservice/react/releases && ls -1dt */ | tail -n +4 | xargs rm -rf
sudo find /var/log -name "*.gz" -mtime +30 -delete
sudo truncate -s 0 /home/webservice/api/shared/storage/logs/laravel.log
pm2 flush
sudo mysql -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);"
sudo apt clean
```

**[CI/CD] 정리:**

```bash
sudo rm -rf /var/lib/jenkins/workspace/*
sudo find /var/lib/jenkins/jobs/*/builds -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
sudo journalctl --vacuum-size=500M
find /home/hskwon/backups -name "*.sql.gz" -mtime +14 -delete
sudo apt clean && sudo apt autoremove -y
```

---

### 메모리 부족 (OOM)

**증상:** 프로세스 갑자기 종료. dmesg에 "Out of memory" 메시지.

**진단:**

```bash
free -h
sudo dmesg | grep -i "out of memory"
sudo dmesg | grep -i "killed process"
ps aux --sort=-%mem | head -15
```

**[운영] 조치:**

```bash
cd /home/webservice/api/current && php artisan cache:clear
redis-cli flushall
```

**[운영] 메모리 배분:** MySQL 2GB, Redis 512MB, PHP-FPM ~1.5GB, PM2 ~0.75GB, OS ~3GB

**[CI/CD] 조치:**

```bash
# Jenkins JVM 메모리 축소 (긴급)
# override.conf: -Xmx2048m -> -Xmx1536m
sudo systemctl daemon-reload
sudo systemctl restart jenkins
```

---

### 서버 접속 불가 (SSH 타임아웃)

**진단 (로컬에서):**

```bash
ping 서버_IP
nc -zv 서버_IP 22
nc -zv 서버_IP 80
```

**조치:**

- ping 응답 없음: IDC 업체에 서버 상태 확인 요청
- ping 응답, SSH 불가: fail2ban IP 차단 의심. IDC 콘솔 또는 다른 IP에서 접속하여 `sudo fail2ban-client set sshd unbanip 본인_IP`
- 웹은 되나 SSH만 불가: `sudo systemctl restart sshd` (IDC 콘솔)

**예방:** 관리자 IP를 fail2ban whitelist에 추가.

---

### fail2ban 정상 IP 차단

**진단:**

```bash
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd banned | grep 차단의심_IP
```

**조치:**

```bash
sudo fail2ban-client set sshd unbanip 차단된_IP주소
sudo systemctl restart fail2ban       # 전체 차단 초기화
```

**예방:**

```bash
# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 관리자_IP_1 관리자_IP_2
```

---

## CI/CD 서버 장애

### Jenkins 시작 실패

**진단:**

```bash
sudo journalctl -u jenkins --since "10 minutes ago" --no-pager
ps aux | grep java
df -h
free -h
```

**(a) Java Heap 메모리 부족** (로그: `java.lang.OutOfMemoryError: Java heap space`)

```bash
cat /etc/systemd/system/jenkins.service.d/override.conf
# -Xmx 값 조정
sudo systemctl daemon-reload
sudo systemctl restart jenkins
```

**(b) 디스크 공간 부족** (로그: `No space left on device`)

```bash
sudo rm -rf /var/lib/jenkins/workspace/*
sudo find /var/lib/jenkins/jobs/*/builds -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
sudo journalctl --vacuum-size=500M
sudo systemctl restart jenkins
```

**(c) 플러그인 충돌** (업데이트 후 시작 실패, ClassNotFoundException)

```bash
ls -lt /var/lib/jenkins/plugins/*.jpi | head -10
sudo rm /var/lib/jenkins/plugins/문제플러그인.jpi
sudo systemctl restart jenkins
```

---

### Jenkins 빌드 실패

**(a) npm/composer 오류:**

```bash
sudo -u jenkins npm cache clean --force
sudo rm -rf /var/lib/jenkins/workspace/<JOB>/node_modules
```

**(b) SSH 키 문제:** (`Permission denied`, `Host key verification failed`)

```bash
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519 hskwon@211.117.60.189 "echo OK"
sudo -u jenkins ssh-keyscan -H 211.117.60.189 >> /var/lib/jenkins/.ssh/known_hosts
sudo -u jenkins ssh-keyscan -H 114.203.209.83 >> /var/lib/jenkins/.ssh/known_hosts
```

**(c) rsync 실패:** (`connection unexpectedly closed`)

```bash
ssh sam-prod "df -h"
ssh sam-prod "ls -la /home/webservice/react/"
```

---

### Gitea 접속 불가

**진단:**

```bash
sudo systemctl status gitea
curl -I http://localhost:3000
sudo ss -tlnp | grep 3000
```

**(a) 포트 충돌:**

```bash
sudo fuser 3000/tcp
sudo systemctl restart gitea
```

**(b) DB 연결 실패:**

```bash
sudo systemctl status mysql
mysql -u gitea -p gitea -e "SELECT 1;"
sudo systemctl restart mysql && sudo systemctl restart gitea
```

**(c) 설정 파일 오류:**

```bash
sudo chown git:git /etc/gitea/app.ini
sudo systemctl restart gitea
```

---

### Gitea push/pull 느림

```bash
sudo tail -50 /var/lib/gitea/log/gitea.log
sudo du -sh /var/lib/gitea/data/repositories/SamProject/*

# Git GC (저장소 최적화)
sudo -u git git -C /var/lib/gitea/data/repositories/SamProject/sam-react-prod.git gc --aggressive
sudo systemctl restart gitea
```

---

### Prometheus 스크래핑 실패

**증상:** Grafana에서 데이터 없음.

```bash
sudo systemctl status prometheus
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -A5 "health"
promtool check config /etc/prometheus/prometheus.yml

# 대상 서버 연결 확인
curl -s --connect-timeout 5 http://211.117.60.189:9100/metrics | head -5
ssh sam-prod "sudo ufw status | grep 9100"
```

---

### Grafana 대시보드 로딩 실패

```bash
sudo systemctl status grafana-server
curl -I http://localhost:3100
sudo systemctl restart grafana-server
```

---

## 긴급 연락처

| 역할 | 연락처 | 비고 |
|------|--------|------|
| 서버 관리 | hskwon | SSH 접속 가능 |
| IDC 업체 | (확인 후 기입 필요) | 서버 물리적 장애, 네트워크 |
