카테고리 없음

[Trouble Shooting]밋업 알림 중복 장애 조치 및 경량화 스크립트 전환

seongw00 2026. 7. 31. 09:59

1. 배경 및 장애 현황

  • 개요: 밋업 RSS 피드를 모니터링하여 Mattermost 채널로 신규 이벤트를 전송하는 자동화 알림 봇 운영 및 고도화
  • 장애 내용:
    1. 기존 자동화 플랫폼(n8n) 환경에서 동일한 밋업 공지가 스케줄 주기마다 반복 전송되는 중복 발송 장애 지속 발생
    2. 시스템 개편 및 보안 정책 설정 변경 과정에서 세션/암호화 데이터가 꼬여 n8n 대시보드 웹 UI 접근 불가능(로그인 무한 튕김) 장애 중첩
  • 목적: 무겁고 세션 에러가 잦은 웹 GUI 기반 툴(n8n) 운영을 중단하고, 서버 운영 리소스를 최소화하는 가벼운 Native Python 스크립트 + Linux Cron 서비스 구조로 전면 마이그레이션하여 가용성 확보

2. 문제 원인 분석

2.1. n8n 알림 봇 중복 발송의 원인

기존 도커 볼륨 마운트 구조 변경 및 컨테이너 재배포 과정에서 이전에 발송 완료된 링크들을 대조해야 하는 내부 DB(SQLite) 세션 이력이 디스크에 정상 커밋(Commit)되지 않고 휘발됨. 이로 인해 스케줄러가 깨어날 때마다 RSS 피드의 모든 과거 글을 "처음 보는 신규 이벤트"로 오인하여 매 주기마다 알림 폭탄을 생성함.

2.2. n8n 무한 로그인 루프 및 UI 먹통 원인

브라우저 보안 및 사설 IP 대역 바인딩을 재구성하는 과정에서 기존에 파일 시스템에 남아있던 구버전 쿠키 세션 메타데이터와 신규 환경 변수(N8N_SECURE_COOKIE=false)가 충돌함. n8n 백엔드 엔진 자체에서 웹 토큰 매칭을 거부하면서 브라우저 캐시 삭제 및 시크릿 모드 진입으로도 우회가 불가능한 무한 튕김 현상이 발생함.

3. 해결 조치 및 기술적 대안 (Python 경량화 전환)

웹 GUI 엔진의 쿠키 고장 및 불필요한 시스템 리소스 점유(상시 메모리 수백 MB 이상 소모)를 근본적으로 방지하기 위해, 우분투 호스트 운영체제(OS)에서 단독 가동하는 경량형 파일 기반 파이썬 스크립트 체계를 설계 및 도입함.

3.1. 작업 디렉토리 인프라 및 부품 구성

서버 내부의 일관성 및 다이어트 운영을 위해 전용 독립 폴더를 구축하고, 무거운 파이썬 패키지 매니저(pip3) 설치 대신 우분투 공식 패키지 매니저(apt)를 통해 필수 외부 통신 라이브러리 패키지만 설치함.

# 1. 알림 봇 전용 독립 작업 디렉토리 생성 및 폴더 이동
mkdir -p ~/awskrug_bot
cd ~/awskrug_bot

# 2. 인프라 경량화 유지를 위해 OS 패키지 매니저로 파이썬 requests 라이브러리만 설치
sudo apt update && sudo apt install -y python3-requests

3.2. 영구 비휘발성 데이터 필터 스크립트 구현 (awskrug_bot.py)

이력 저장 방식을 텍스트 파일 입출력의 "a" (Append) 모드로 처리하여, 컨테이너 유실이나 데몬 재시작 등 서버 환경 변화와 무관하게 발송 백로그가 보존되도록 구축함.

import os
import re
import xml.etree.ElementTree as ET
import requests

# 1. 경로 및 Mattermost 웹훅 엔드포인트 바인딩
RSS_URL = "https://www.meetup.com/awskrug/events/rss/"
WEBHOOK_URL = "https://mattermost.example.com/웹훅주소"
HISTORY_FILE = os.path.join(os.path.dirname(__file__), "sent_links.txt")

# 2. 로컬 디스크의 텍스트 파일로부터 과거 발송 완료 주소 리스트 로드
if os.path.exists(HISTORY_FILE):
    with open(HISTORY_FILE, "r") as f:
        sent_links = set(f.read().splitlines())
else:
    sent_links = set()

# 3. RSS 데이터 원격 파싱
try:
    response = requests.get(RSS_URL, timeout=10)
    root = ET.fromstring(response.content)
except Exception as e:
    print(f"RSS 수집 실패: {e}")
    exit()

new_links = []
items = list(root.findall(".//item"))

for item in items:
    title = item.find("title").text
    link = item.find("link").text
    description = item.find("description").text or ""

    # [중복 검증] 과거 전송 이력이 적힌 파일에 주소가 있다면 코드 레벨에서 철저히 Skip (중복 전송 원천 차단)
    if link in sent_links:
        continue

    # 4. 정규표현식을 이용한 데이터 정제 파이프라인 (본문 서식 중구난방 버그 해결)
    clean_text = re.sub(r"<[^>]*>", "", description)     # 불필요한 HTML 태그 제거
    clean_text = re.sub(r"[\*\-\_\>#`]", "", clean_text)  # 시각적 노이즈를 일으키는 마크다운 특수문자 제거
    
    # 의미 없는 공백 줄을 압축하고 상위 12줄만 컷오프
    lines = [line.strip() for line in clean_text.split("\n") if line.strip()]
    formatted_body = "\n".join([f"> {l}" for l in lines[:12]]) # 정갈한 카드형 인용구 UI 레이아웃 고정 매핑

    # 5. Mattermost 마크다운 페이로드 템플릿 빌드
    payload = {
        "text": f"### 🚀 AWSKRUG New MeetUP!!\n---\n**제목:** [{title}]({link})\n\n**[주요 내용]**\n{formatted_body} ...\n\n---\n*상세 내용은 위 제목 링크를 통해 확인하세요.*\n*본 알림은 낭만인프라 자동화 봇에 의해 생성되었습니다.*"
    }

    # 6. Webhook 전송 실행
    try:
        res = requests.post(WEBHOOK_URL, json=payload, timeout=5)
        if res.status_code == 200:
            new_links.append(link)
    except Exception as e:
        print(f"전송 장애 발생: {e}")

# 새로 보낸 링크가 존재할 경우 파일 끝에 실시간 누적 기록 (Write가 아닌 Append 모드로 파일 재생성 차단)
if new_links:
    with open(HISTORY_FILE, "a") as f:
        for link in new_links:
            f.write(f"{link}\n")
    print(f"{len(new_links)}개의 신규 모임 전송 완료.")
else:
    print("새로운 밋업이 없습니다.")

3.3. 크론탭(Crontab) 스케줄러 정밀 탑재

1시간 주기로 돌며 불필요한 연산을 유발하던 기존 폴링 주기를 제거하고, 스터디 알림 목적에 부합하도록 낮 12시, 밤 12시 정각 및 퇴근 전 집중 시간대인 오후 6시 정각(하루 총 3회)에만 정밀 격리 실행하도록 시스템 배치 스케줄러(cron)를 세팅함.

  • 실제 적용된 Crontab 구문 (crontab -e) 실행 유저 계정명인 swtae0214 홈 디렉토리 경로와 파이썬 절대 경로를 명확히 매칭시켜 환경 변수 드롭 리스크를 방지함.
0 0,12,18 * * * /usr/bin/python3 /home/swtae0214/awskrug_bot/awskrug_bot.py

4. 최종 조치 결과 및 기대 효과

  1. 인프라 오버헤드 최적화: 메모리와 프로세스를 대량 상시 점검하던 무거운 Node.js 컨테이너 스택(n8n)을 영구 종료 처리함으로써 시스템 가용 자원을 확보하고, 지정된 스케줄 주기에만 찰나 실행 후 소멸하는 경량 인프라 구축.
  2. 알림 반복 장애(중복 발송) 완전 종결: n8n 백엔드의 인스턴스 인식 버그나 도커 Stateless 휘발성 구조와 상관없이, 호스트 실제 리눅스 파일 시스템에 물리 텍스트 형태(sent_links.txt)로 블랙리스트를 직접 저장하므로 중복 발송 가능성이 기술적으로 0%로 수렴함.
  3. 가시성 및 관리 편의성 극대화: 웹 GUI 장애나 복잡한 설정 파일 권한 꼬임으로 인한 관리자 차단 리스크를 완벽히 격리함. 향후 발송 완료 백로그 확인이 필요할 시 터미널에서 cat sent_links.txt 입력 한 줄만으로 투명한 동기화 모니터링 가능.