- Fix false EmailIntegration bug reports in multiple docs - Add new UUID principle violation documentation - Remove incorrect assumptions about gmail_tokens table - Update 250922 doc to reflect Gateway UUID conversion working - Clean up research papers organization into subdirectories
519 lines
16 KiB
Markdown
519 lines
16 KiB
Markdown
# rb8001 일일 요약 크론잡 분석
|
|
|
|
## 작성일: 2025-08-24
|
|
## 작성자: 서버 관리자 with Claude
|
|
## 영향 서버: 51123(Gateway) → 51124(rb8001, skill-email)
|
|
|
|
---
|
|
|
|
## 1. 문제 개요
|
|
|
|
### 1.1 증상
|
|
- **발생 시간**: 매일 오전 9시 (KST)
|
|
- **영향**: 사용자들에게 모닝 브리핑 전송 (이메일 요약 누락)
|
|
- **대상 사용자**:
|
|
- 전희재 (U091UNVE41M)
|
|
- 김종태 (U0925SXQFDK)
|
|
- HanYong Hwang (U092F7FQ55L)
|
|
|
|
### 1.2 에러 메시지
|
|
```
|
|
Gmail token not ready for user U091UNVE41M: reauth_required
|
|
500 Internal Server Error from skill-email service
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 시스템 아키텍처
|
|
|
|
### 2.1 크론잡 구성 (51123 서버)
|
|
```bash
|
|
# Gateway 컨테이너 내부 크론탭
|
|
0 9 * * * curl -X POST http://192.168.219.52:8001/api/cron/daily-summary \
|
|
-H "Content-Type: application/json" \
|
|
2>/dev/null || true # briefing
|
|
```
|
|
|
|
### 2.2 네트워크 플로우
|
|
```
|
|
[51123 Gateway 크론]
|
|
↓ POST 요청
|
|
[51124 rb8001:8001]
|
|
↓ 데이터 수집 (병렬)
|
|
├→ skill-news:8505 (뉴스 검색) ✅
|
|
├→ skill-email:8501 (이메일 조회) ❌ 500 에러
|
|
└→ rb10508_micro (LLM 요약) ✅
|
|
↓
|
|
[Slack API → 사용자 DM]
|
|
```
|
|
|
|
### 2.3 인증 체계
|
|
- **엔드포인트**: `/api/cron/daily-summary`
|
|
- **인증 토큰**: Bearer cron-secret-2024
|
|
- **실행 주체**: Gateway 컨테이너 (51123)
|
|
|
|
---
|
|
|
|
## 3. 원인 분석
|
|
|
|
### 3.1 skill-email 서비스 실패 (51124 서버) - ✅ 해결 완료
|
|
|
|
#### 데이터베이스 연결 문제 - ✅ 해결 완료 (2025-08-25 00:12)
|
|
```python
|
|
# skill-email 환경 설정 (수정 전)
|
|
TOKEN_PROVIDER=database
|
|
DATABASE_URL=postgresql://robeings:robeings@localhost:5433/auth_db
|
|
|
|
# 수정 후
|
|
DATABASE_URL=postgresql://robeings:robeings@localhost:5433/main_db
|
|
```
|
|
|
|
**문제점** (해결됨):
|
|
1. ~~**auth_db 부재**: 51123 서버에 auth_db 데이터베이스 없음~~ → main_db로 변경 완료
|
|
2. ~~**SSH 터널 설정**: 5433 포트가 51123으로 포워딩되나 DB 자체가 없음~~ → main_db 연결 성공
|
|
3. ~~**토큰 조회 실패**: DBCredentialsProvider가 연결 실패로 토큰 조회 불가~~ → 정상 작동
|
|
|
|
#### 에러 체인 (해결됨)
|
|
```
|
|
이전: auth_db 연결 실패 → 500 에러
|
|
현재: main_db 연결 성공 → 토큰 조회 가능 (단, 만료 상태)
|
|
```
|
|
|
|
### 3.2 추가 이슈
|
|
|
|
#### datetime import 누락 - ⚠️ 확인 필요
|
|
```python
|
|
# rb8001/main.py 에러
|
|
NameError: name 'datetime' is not defined
|
|
```
|
|
- 상태: 코드에 datetime import 있으나 특정 조건에서 발생 가능
|
|
|
|
#### 슬랙 워크스페이스 불일치 - ✅ 별 문제 없음
|
|
- **현재 등록**: T035VFRKCN6 (GoodGang Labs)
|
|
- **필요한 것**: T0925SXPS4D (실제로는 정상 작동 중)
|
|
|
|
---
|
|
|
|
## 4. 실행 프로세스 상세
|
|
|
|
### 4.1 정상 동작 시나리오
|
|
1. **09:00:00** - Gateway 크론 트리거
|
|
2. **09:00:01** - rb8001 `/api/cron/daily-summary` 호출
|
|
3. **09:00:02** - 병렬 데이터 수집 시작
|
|
- 뉴스: AI 키워드로 최신 뉴스 5개
|
|
- 이메일: 사용자별 최근 이메일 5개
|
|
4. **09:00:05** - LLM 요약 생성
|
|
5. **09:00:07** - Slack DM 전송
|
|
|
|
### 4.2 현재 실패 시나리오
|
|
1. **09:00:00** - Gateway 크론 트리거 ✅
|
|
2. **09:00:01** - rb8001 엔드포인트 호출 ✅
|
|
3. **09:00:02** - 데이터 수집 시작
|
|
- 뉴스: 성공 ✅
|
|
- 이메일: **실패** ❌ (500 에러)
|
|
4. **09:00:03** - 부분적 요약 생성 (뉴스만)
|
|
5. **09:00:04** - 불완전한 DM 전송
|
|
|
|
---
|
|
|
|
## 5. 이슈 현황
|
|
|
|
### 5.1 Gmail 토큰 자동 갱신 - ✅ 부분 해결 (2025-08-24)
|
|
**Timezone 버그**:
|
|
- 파일: `/home/admin/auth-server/app/api/gmail_refresh.py:132`
|
|
- 수정 완료: `datetime.now()` → `datetime.now(timezone.utc)` (섹션 14 참조)
|
|
- 상태: 로컬 개발자가 수정 완료, 배포 대기
|
|
|
|
### 5.2 자동 갱신 메커니즘 - 📋 개선 예정
|
|
- **현재 상태**: skill-email과 rb8001이 Gmail API 호출 시 토큰 상태 직접 확인하지 않음
|
|
- **일반적 구현**: API 호출 전 토큰 만료 확인 후 필요시 refresh_token으로 갱신
|
|
- **현재 동작**: 토큰 그대로 사용, 만료 시 에러 발생
|
|
- **개선 방향**: Gmail API 호출 전 토큰 체크 로직 추가 검토
|
|
|
|
---
|
|
|
|
## 6. 검증 방법
|
|
|
|
### 6.1 수동 테스트
|
|
```bash
|
|
# skill-email 직접 테스트
|
|
curl -X GET http://localhost:8501/messages?user_id=U091UNVE41M \
|
|
-H "Authorization: Bearer test-token"
|
|
|
|
# rb8001 크론 엔드포인트 테스트
|
|
curl -X POST http://localhost:8001/api/cron/daily-summary \
|
|
-H "Authorization: Bearer cron-secret-2024"
|
|
```
|
|
|
|
### 6.2 로그 확인
|
|
```bash
|
|
# skill-email 로그
|
|
docker logs skill-email --tail 50
|
|
|
|
# rb8001 로그
|
|
docker logs rb8001 --tail 50
|
|
|
|
# Gateway 로그 (크론 실행 확인)
|
|
docker exec robeing-gateway tail -f /var/log/cron.log
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 모니터링 포인트
|
|
|
|
### 7.1 일일 체크리스트
|
|
- [ ] 오전 9시 크론 실행 여부
|
|
- [ ] skill-email 500 에러 발생 여부
|
|
- [ ] Slack DM 정상 수신 여부
|
|
- [ ] 이메일 요약 포함 여부
|
|
|
|
### 7.2 알람 설정 권장
|
|
```bash
|
|
# 크론 실패 시 알람
|
|
0 9 * * * curl ... || echo "Cron failed" | mail -s "Alert" admin@example.com
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 교훈 및 개선사항
|
|
|
|
### 8.1 아키텍처 개선
|
|
1. **환경 일관성**: 모든 서비스가 동일한 DB 사용 (main_db)
|
|
2. **의존성 최소화**: 단일 서비스 장애가 전체 기능 마비 방지
|
|
3. **폴백 메커니즘**: 이메일 실패 시에도 기본 브리핑 전송
|
|
|
|
### 8.2 운영 개선
|
|
1. **헬스체크 강화**: 각 마이크로서비스 상태 모니터링
|
|
2. **에러 핸들링**: graceful degradation 구현
|
|
3. **로그 중앙화**: 분산 서비스 로그 통합 관리
|
|
|
|
### 8.3 문서화
|
|
1. **시스템 다이어그램**: 서비스 간 의존성 명확화
|
|
2. **환경변수 문서**: 각 서비스별 필수 설정 문서화
|
|
3. **트러블슈팅 가이드**: 일반적인 문제 해결 방법 정리
|
|
|
|
---
|
|
|
|
## 9. 관련 파일
|
|
|
|
### 51123 서버
|
|
- `/home/admin/robeing-gateway/entrypoint.sh` - 크론 시작
|
|
- `/home/admin/robeing-gateway/Dockerfile` - 크론 설치
|
|
|
|
### 51124 서버
|
|
- `/home/admin/ivada_project/rb8001/main.py` - 크론 엔드포인트
|
|
- `/home/admin/ivada_project/rb8001/app/cron/daily_summary.py` - 요약 로직
|
|
- `/home/admin/ivada_project/skill_email/.env` - DB 설정
|
|
- `/home/admin/ivada_project/skill_email/services/gmail_service.py` - Gmail 서비스
|
|
|
|
---
|
|
|
|
## 10. 현재 상태 (2025-08-25 00:20 업데이트)
|
|
|
|
### 확인 사항
|
|
- **Gateway 크론**: 정상 작동 ✅
|
|
- **rb8001 서비스**: 정상 작동 ✅
|
|
- **skill-news**: 정상 작동 ✅
|
|
- **skill-email**: DB 연결 성공, 토큰 데이터 없음 ⚠️
|
|
- **auth_db → main_db 마이그레이션**: 완료 ✅
|
|
- **main_db gmail_token**: 테이블 존재, 토큰 데이터 NULL ❌
|
|
|
|
### 해결된 사항 (2025-08-25 00:12)
|
|
- **skill-email DB 연결 문제**: ✅ 해결
|
|
- .env 수정: `postgresql://robeings:robeings@localhost:5433/main_db`
|
|
- auth_db → main_db 마이그레이션 완료
|
|
- 서비스 재시작 완료
|
|
|
|
### 토큰 상태 (2025-08-25 시점)
|
|
- **토큰 만료 현황**:
|
|
- 0914eagle: 만료됨
|
|
- cdctfm: 만료됨
|
|
- happybell80: 만료됨
|
|
- **갱신 상태**: refresh_token 존재, timezone 수정 후 갱신 가능
|
|
|
|
### 긴급도
|
|
- **높음**: 매일 오전 9시 사용자 영향
|
|
- **현재 상태**: DB 연결 문제는 해결, OAuth 토큰 재발급 필요
|
|
|
|
---
|
|
|
|
## 11. OAuth 토큰 상태 검증 (2025-08-25 00:30)
|
|
|
|
### 토큰 갱신 API 테스트
|
|
```bash
|
|
# API 호출 성공
|
|
curl -X POST "http://localhost:9000/api/gmail/refresh/1e16e9d5-59f3-54da-a661-8abeabff4230"
|
|
# 응답: 200 OK, "status": "refreshed"
|
|
|
|
# 그러나 DB 확인 시 여전히 만료 상태
|
|
SELECT username, expiry FROM gmail_token WHERE username='happybell80';
|
|
# expiry: 2025-08-24 16:30:10 (어제 시간으로 기록됨)
|
|
```
|
|
|
|
## 12. 토큰 자동 갱신 버그 분석 (2025-08-25 00:35)
|
|
|
|
### DB 검증 결과
|
|
```sql
|
|
-- 토큰 데이터 확인
|
|
SELECT username,
|
|
token_data IS NOT NULL as has_token_data,
|
|
access_token IS NOT NULL as has_access,
|
|
refresh_token IS NOT NULL as has_refresh,
|
|
expiry
|
|
FROM gmail_token;
|
|
```
|
|
|
|
**결과**:
|
|
| username | has_token_data | has_access | has_refresh | expiry |
|
|
|----------|---------------|------------|-------------|--------|
|
|
| 0914eagle | false | true | true | 2025-08-23 16:04:15 |
|
|
| cdctfm | false | true | true | 2025-08-23 08:52:13 |
|
|
| happybell80 | false | true | true | 2025-08-23 08:52:11 |
|
|
|
|
### 토큰 만료 상태
|
|
```sql
|
|
-- 만료 시간 검증
|
|
SELECT username,
|
|
CASE WHEN expiry < NOW() THEN '만료됨' ELSE '유효함' END as status,
|
|
AGE(NOW(), expiry) as expired_since
|
|
FROM gmail_token;
|
|
```
|
|
|
|
**결과**:
|
|
- **0914eagle**: 만료됨 (1일 8시간 전)
|
|
- **cdctfm**: 만료됨 (1일 15시간 전)
|
|
- **happybell80**: 만료됨 (1일 15시간 전)
|
|
|
|
### auth-server 상태
|
|
- **컨테이너**: 정상 작동 중 (32시간 운영)
|
|
- **헬스체크**: `/health` 엔드포인트 정상 응답
|
|
- **상태**: healthy
|
|
|
|
### 분석 결과
|
|
1. **token_data 필드**: 모두 NULL (JSONB 필드 미사용)
|
|
2. **access_token/refresh_token**: 별도 컬럼에 존재
|
|
3. **만료 상태**: 모든 토큰 24시간 이상 만료
|
|
4. **자동 갱신 실패**: refresh_token 있으나 자동 갱신 미작동
|
|
|
|
### 분석 결과
|
|
- OAuth 토큰 재발급 또는 갱신 필요
|
|
- refresh_token 존재, 갱신 API 활용 가능
|
|
- 프론트엔드에서 재인증 옵션 제공 중
|
|
|
|
---
|
|
|
|
## 13. 조치사항 요약
|
|
|
|
### 13.1 완료된 수정 (2025-08-24)
|
|
1. **auth-server timezone 수정**
|
|
- 파일: `/home/admin/auth-server/app/api/gmail_refresh.py`
|
|
- 수정 내용: `datetime.now()` → `datetime.now(timezone.utc)`
|
|
- 상태: ✅ 로컬 수정 완료, 배포 대기
|
|
|
|
### 13.2 향후 개선 고려사항
|
|
1. **API 호출 시점 자동 갱신**
|
|
```python
|
|
# 토큰 체크 로직 예시
|
|
if token.expiry < datetime.now():
|
|
token = refresh_gmail_token(user_id)
|
|
```
|
|
|
|
### 13.3 영향받는 서비스
|
|
- rb8001 일일 요약 (매일 오전 9시)
|
|
- skill-email 이메일 조회 기능
|
|
- 프론트엔드 Gmail 아이템 기능
|
|
|
|
---
|
|
|
|
## 14. 버그 수정 완료 (2025-08-24 로컬 개발자)
|
|
|
|
### 14.1 수정 내용
|
|
**Timezone 버그 수정** - Gmail 토큰 만료 시간이 UTC가 아닌 로컬 시간으로 저장되는 문제 해결
|
|
|
|
#### 수정 파일 1: auth-server/app/api/gmail_refresh.py
|
|
```python
|
|
# Line 9: timezone import 추가
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
# Line 66: 토큰 유효성 체크 시 UTC 사용
|
|
remaining = check_expiry - datetime.now(timezone.utc).timestamp()
|
|
|
|
# Line 131-132: 토큰 갱신 시 UTC 기준으로 만료 시간 계산
|
|
new_expires_at = datetime.now(timezone.utc).timestamp() + expires_in
|
|
new_expiry = datetime.now(timezone.utc) + timedelta(seconds=expires_in)
|
|
|
|
# Line 247: 토큰 상태 확인 시 UTC 사용
|
|
remaining_seconds = check_expiry - datetime.now(timezone.utc).timestamp()
|
|
```
|
|
|
|
#### 수정 파일 2: auth-server/app/providers/gmail_passport.py
|
|
```python
|
|
# Line 14: timezone import 추가
|
|
from datetime import datetime, timezone
|
|
|
|
# Line 194: OAuth 콜백 처리 시 UTC 사용
|
|
datetime.now(timezone.utc).timestamp() + token_data_raw.get('expires_in', 3600)
|
|
|
|
# Line 199-200: created_at, updated_at UTC 사용
|
|
datetime.now(timezone.utc),
|
|
datetime.now(timezone.utc)
|
|
|
|
# Line 283, 306: 패스포트 활성화/비활성화 시 UTC 사용
|
|
""", user_uuid, datetime.now(timezone.utc))
|
|
```
|
|
|
|
### 14.2 수정 효과
|
|
- 토큰 만료 시간이 정확히 UTC로 저장됨
|
|
- 토큰 갱신 API가 올바른 시간 기준으로 작동
|
|
- 자동 갱신 시 만료 판단이 정확해짐
|
|
|
|
### 14.3 다음 단계 (서버 관리자)
|
|
1. auth-server 재배포
|
|
2. 기존 만료된 토큰 수동 갱신 또는 재인증
|
|
3. 자동 갱신 크론잡 설정 (선택사항)
|
|
|
|
---
|
|
|
|
## 15. 수정 후 테스트 결과 (2025-08-25 00:50)
|
|
|
|
### 15.1 테스트 실행
|
|
```bash
|
|
# 토큰 갱신 API 호출
|
|
curl -X POST "http://localhost:9000/api/gmail/refresh/1e16e9d5-59f3-54da-a661-8abeabff4230"
|
|
|
|
# 응답
|
|
{
|
|
"status": "valid",
|
|
"user_id": "1e16e9d5-59f3-54da-a661-8abeabff4230",
|
|
"email": "goeun2dc@gmail.com",
|
|
"expires_in": 2416,
|
|
"access_token": "ya29.A0AS3H6N..."
|
|
}
|
|
```
|
|
|
|
### 15.2 DB 검증
|
|
```sql
|
|
-- UTC 기준 토큰 상태 확인
|
|
SELECT username,
|
|
expiry AT TIME ZONE 'UTC' as expiry_utc,
|
|
CASE WHEN expiry AT TIME ZONE 'UTC' > NOW() AT TIME ZONE 'UTC'
|
|
THEN '유효✅' ELSE '만료❌' END as status
|
|
FROM gmail_token;
|
|
```
|
|
|
|
**결과**:
|
|
| username | expiry_utc | status |
|
|
|----------|-----------|---------|
|
|
| happybell80 | 2025-08-25 01:30:10 | 유효✅ |
|
|
| 0914eagle | 2025-08-24 01:04:15 | 만료❌ |
|
|
| cdctfm | 2025-08-23 17:52:13 | 만료❌ |
|
|
|
|
### 15.3 수정 효과 확인
|
|
- **수정 전**: expiry가 `2025-08-24 16:30` (어제)로 저장
|
|
- **수정 후**: expiry가 `2025-08-25 01:30` (정확한 UTC)로 저장
|
|
- **결과**: 토큰이 "유효"로 정상 인식됨
|
|
|
|
### 15.4 결론
|
|
✅ **Timezone 버그 수정 성공**
|
|
- UTC 시간대가 올바르게 적용됨
|
|
- 토큰 갱신 API가 정상 작동
|
|
- **주의**: 자동 갱신 메커니즘은 여전히 없음 (수동 갱신 필요)
|
|
|
|
### 15.5 수동 갱신 실행 (2025-08-25 00:55)
|
|
- 0914eagle, cdctfm OAuth client 정보 수정 후 수동 갱신 완료
|
|
- 모든 사용자 토큰 유효 상태로 변경 (1시간)
|
|
- 현재 수동 갱신으로 운영 중
|
|
- 향후 자동 갱신 구현 시 안정성 향상 예상
|
|
|
|
---
|
|
|
|
## 16. 자동 갱신 메커니즘 부재 상세 분석 (2025-08-24 로컬 개발자)
|
|
|
|
### 16.1 코드 분석 결과
|
|
섹션 5.2의 내용을 코드로 확인
|
|
|
|
#### skill-email 현재 구현
|
|
```python
|
|
# services/gmail_service.py (Line 76-100)
|
|
def _get_gmail_service(self, user_id: str):
|
|
# 토큰을 DB에서 가져옴
|
|
creds = self.creds_provider.get_credentials(user_id)
|
|
|
|
# 현재: 토큰 직접 사용
|
|
service = build("gmail", "v1", credentials=creds)
|
|
return Ok(service)
|
|
```
|
|
|
|
#### rb8001 현재 구현
|
|
```python
|
|
# app/skills/email_integration.py (Line 213-226)
|
|
async def process_email_request(self, message, user_id, channel):
|
|
# Gmail 장착 상태 확인
|
|
is_equipped = await self.check_gmail_equipped(user_id)
|
|
|
|
# skill-email API 호출
|
|
response = await client.post(f"{self.skill_email_url}/process", ...)
|
|
```
|
|
|
|
### 16.2 토큰 자동 갱신 추가 방법
|
|
```python
|
|
# Google OAuth 표준 방식 예시
|
|
from google.auth.transport.requests import Request
|
|
|
|
def get_gmail_service(user_id):
|
|
creds = get_credentials(user_id)
|
|
|
|
# 토큰 만료 체크 및 자동 갱신
|
|
if creds and creds.expired and creds.refresh_token:
|
|
try:
|
|
creds.refresh(Request())
|
|
save_credentials(user_id, creds)
|
|
except Exception as e:
|
|
# 갱신 실패 시 재인증 필요
|
|
return None
|
|
|
|
return build('gmail', 'v1', credentials=creds)
|
|
```
|
|
|
|
### 16.3 현재 플로우
|
|
```
|
|
1. 사용자 요청 → rb8001
|
|
2. rb8001 → skill-email API 호출
|
|
3. skill-email → DB에서 토큰 조회
|
|
4. skill-email → Gmail API 호출 (만료된 토큰 그대로)
|
|
5. Gmail API → 401 Unauthorized 에러
|
|
6. skill-email → 500 Internal Server Error
|
|
7. rb8001 → 사용자에게 에러 메시지
|
|
```
|
|
|
|
### 16.4 향후 개선 방향
|
|
1. **skill-email/services/gmail_service.py**:
|
|
- `_get_gmail_service()` 메서드에 토큰 만료 체크 추가 고려
|
|
- auth-server의 `/api/gmail/refresh/{user_id}` 활용
|
|
- Google OAuth refresh 직접 구현 옵션
|
|
|
|
2. **skill-email/services/db_credentials_provider.py**:
|
|
- `get_credentials()` 시 expiry 확인 로직 추가 가능
|
|
- `refresh_credentials()` 메서드 구현 검토
|
|
|
|
3. **rb8001/app/skills/email_integration.py**:
|
|
- skill-email 호출 전 토큰 상태 사전 확인 옵션
|
|
- 필요시 갱신 API 호출 후 진행
|
|
|
|
### 16.5 임시 해결책 (서버 관리자)
|
|
```bash
|
|
# 크론잡 추가 - 매 30분마다 모든 토큰 갱신
|
|
*/30 * * * * for user_id in $(psql -t -c "SELECT user_id FROM gmail_token"); do \
|
|
curl -X POST "http://localhost:9000/api/gmail/refresh/$user_id"; \
|
|
done
|
|
```
|
|
|
|
### 16.6 권장 사항
|
|
- **API 호출 시점 자동 갱신**: Gmail API 호출 직전 토큰 체크 및 갱신 구현 시 안정성 향상
|
|
- **장점**: 크론잡 불필요, 토큰 최신 상태 유지
|
|
- **구현 예정 위치**: skill-email 서비스
|
|
|
|
---
|
|
|
|
**문서 끝**
|