tableSentinel ADR

✍️

🛠️

👤

들어가기 앞서서

본 글은 tableSentinel 프로젝트를 진행하면서 소프트웨어 아키텍처의 결정과 그 결정의 맥락과결과를 Architecture Decision Record 구조로 남긴 글입니다.

해당 프로젝트의 중요 구성요소로서 미래의 프로젝트 방향성을 제시하는 글입니다.

ADR 리스트

ADR-001: 프로젝트 최초 설계

  • Status: Accepted
  • Context: XDP filter를 통해 서버에서 고속으로 패킷을 드롭 가능함을 인지함. 여기에 기존 netfilter(nftables, iptables)의 다양한 패킷 제어 기능을 잘 연계하여 UI/UX 경험에 개선을 준다면 고성능과 편의성을 융합한 솔루션 가능성을 확인함.
  • Decision: eBPF/XDP + netfilter를 하이브리드 아키텍쳐 채택.
  • Decision(Process): AI(Gemini) 협업 모델 채택. 1인 개발자가 커널부터 프론트엔드까지의 수직 계층을 단기간에 구축하기 위해, 아키텍쳐를 설계하여 AI의 코드 생성을 하고 직접 코드 리뷰 및 아키텍쳐 설계 검증을 목적으로 활용하기로함.
  • Consequences: 기술적 복잡도에 따른 병목 현상을 AI와의 페어 프로그래밍으로 해결 가능해짐. 단, AI가 제안하는 커널 로직의 안전성을 직접 검증해야 하는 책임과 코드 이해가 따름.

ADR-002: 에이전트 언어 결정

  • Status: Accepted
  • Context: 새로운 트렌드 강자인 Rust, 호환성의 Python 중 에이전트 언어의 선택이 필요함. Rust는 에이전트 호스트에서 가볍게 돌아가고 속도가 좋지만 개발의 난이도가 증가함. Python은 Rust 대비 무겁지만 pip 라이브러리 확장성과 언어 난이도가 낮다는 장점이 있음. 두 언어 중 트레이드 오프를 계산하여 선택이 필요함.
  • Decision: Python 채택. 배포 및 의존성 문제는 Docker compose를 통해 처리하기로 결정.
  • Rationale: PoC 단계에서는 커널 로직과의 인터랙션 검증 속도가 최우선이며, 이미 숙련된 Python을 통해 개발 기간을 단축함. (추후 Rust 포팅 고려)
  • Consequences: Rust 대비 무겁고 속도가 느릴 수 있지만, 빠르게 프로젝트를 PoC하는데 있어 전략적인 이점을 가짐.

ADR-003: 백엔드 프레임워크 결정

  • Status: Accepted
  • Context: Node.js(프론트와의 통신 편의성) vs Spring Boot(엔터프라이즈 확장성 및 생태계)
  • Decision: Spring Boot 채택.
  • Rationale: 국내 개발 생태계의 강점을 기반으로 현 1인 프로젝트 이후 팀 단위 확장 가능성을 고려함.
  • Consequences: 초기 설정 오버헤드는 크지만, Java의 강력한 타입 안정성이 엔터프라이즈 프로젝트에 가까운 본 프로젝트에 적합함.

ADR-004: 프론트엔드 프레임워크 결정

  • Status: Accepted
  • Context: 순수 Bootstrap(빠른 UI) vs React/Vue.js(컴포넌트 기반 고도화).
  • Decision: Vue.js 채택 및 AI 협업 강화.
  • Rationale: React 대비 학습 곡선이 낮고 단일 파일 컴포넌트(SFC) 구조가 직관적임. 대시보드 템플릿을 적극 활용하여 디자인을 빠르게 구성하고, AI 코드 및 아키텍쳐 설계 지분을 늘려 백엔드/에이전트에 역량 집중.
  • Consequences: AI를 통해 test ui를 빠르게 구축하였지만, AI 코드 지분이 높아져 추후 기술부채로 쌓일 수 있음.

ADR-005: XDP 구현 방식의 결정

  • Status: Accepted
  • Context: XDP를 구현하기 위해서는 C언어, Rust, Python 등으로 직접 BPF 프로그램을 작성하여 로드하거나, Redhat에서 제작한 xdp-tools 같은 라이브러리를 래핑(Wrappig)하는 방법이 있음. PoC 단계에서는 빠른 프로토타이핑과 안정성이 모두 중요함.
  • Decision: xpd-filter CLI 래핑하여 인터페이스 제어와 규칙 모니터링을 불러와 처리하는 방식을 채택.
  • Rationale: Python으로 작성된 eBPF 코드를 xdp-loader를 통해 적용시 커널 헤더 문제(특히 ubuntu)발생과 검증기(Verifyer) 통과의 문제가 발생하여 검증된 xdp-filter를 채택함.
  • Consequences: xdp-filter를 통해 안정적인 기능 지원이 가능하지만, xdp-filter가 지원하는 기능으로만 제약됨.

ADR-006: 백엔드와 에이전트 사이의 통신 구현

  • Status: Superseded → ADR-012_D: gRPC 도입 채택
  • Context: 백엔드와 에이전트 사이의 통신은 REST API 범주에 속하는 구성이 필요함. 이를 구현하기 위한 통신 디자인패턴은 다양하지만 구현 난이도는 Polling이 쉬워보이며, gRPC는 다양한 통신 디자인 패턴을 구현 가능해보임. Kafaka와 같은 발행-구독 패턴과 메시지 큐 패턴 등 디자인 패턴도 확인됨.
  • Decision: 구현 난이도와 빠른 PoC를 위해서 Polling 채택
  • Rationale: 다른 통신 디자인 패턴은 구현 난이도나 인프라 설계의 챌린지가 수반되며, 방화벽 처리에 대한 고민을 해야함(일반적으로 Inboud 차단, Outbound 허용이므로). 이러한 이슈에 대응이 쉬운 Polling 방식으로 구현하기로 결정.
  • Consequences: Polling을 동기 방식과 단일 스레드로 PoC 구현하다보니 백엔드 지시에 따른 결과값이 지연 반영될 가능성 있음. 하지만, 빠른 PoC가 가능한 이점을 챙김.

ADR-007: 에이전트 CLI 래핑 API의 구성

  • Status: Accepted
  • Context: 에이전트의 subprocess.run()을 통한 CLI 래핑시 간단하게 CLI 명령어를 “명령어1”, “명령어2″과 같은 명령어 고정 형태로 할지 아니면 코드의 간결함을 위해 “입력값”과 같이 유연성을 주는 방법으로 할지 트레이드 오프 계산 필요.
  • Decision: CLI 래핑은 각 API별 하드코딩 형태로 명령어 제약하여 유연성을 차단하는 방식으로 선택.
  • Rationale: 자유로운 변수형태로 백엔드 또는 프론트엔드에서 처리가 가능해질 경우 명령어 인젝션(Commnad Injection) 리스크도 그만큼 기하급수적으로 높아짐. 또한, subprocess.run()의 shell 변수값의 기본이 False인 만큼 더욱 명령어 인젝션을 방어할 수 있어보임.
  • Consequences: API가 기능별로 하드코딩 되므로 유지보수면이나 코드의 간결함이 줄어들며, CLI 파라미터를 통해 공격하는 방식은 아직 존재함. 하지만, 지금 당장의 root 또는 eBPF Map을 공격할 수 있는 많은 경로는 차단함.

ADR-008: 프로젝트의 Git Repository 디렉터리 구조 결정

  • Status: Accepted
  • Context: MSA(Microservices Architecture)를 위한 프로젝트 설계를 위해서는 Git Repository 디렉터리 구조를 어떻게 가져가야할지 계산이 필요함. 서비스별로 별도의 프로젝트 Repo를 생성하여 관리하여 선재적 분리를 해야할지, Mono Repo 기반으로 진행해야할지 트레이드 오프 계산 필요
  • Decision: Mono Repo 구조로 Git 디렉터리 구조를 가져가는 방향을 채택
  • Rationale: 현재 1인 프로젝트이므로 모든 영역을 MSA를 위해 Repo별로 분리하게 될 경우 컨텍스트 스위칭이 과도하게 발생할 수 있으므로, 단일(mono) Repo를 통해 컨텍스트 스위칭 비용을 줄이기로 함.
  • Consequences: Mono Repo 구조 덕분에 vscode 단일로 빠르게 작업이 가능해졌지만, 추후 프로젝트 복잡도 증가시 분리할 수 있도록 미리 영역별 디렉터리 분리를 고려해서 구성해야함.

ADR-009: 프로젝트 라이센스 결정

  • Status: Accepted
  • Context: 프로젝트를 진행 중 LGPL이나 GPL과 같은 소스를 추후 사용시 전체 프로젝트의 코드 라이센스를 어떻게 해야할지 고민할 단계에 직면함. 프로젝트에 사용된 코드와 도구들을 확인하여 라이센스 선택이 필요해짐
  • Decision: MIT License 채택.
  • Rationale: 프로젝트에 사용된 라이센스 확인 결과 MIT License를 채택해도 문제없음을 확인함. xdp-tools와 nftables(iptables)를 CLI로 래핑하여 사용하므로 GPL의 영향을 받지 않으며, 프론트엔드와 백엔드에 사용된 도구의 경우 Apache 또는 MIT 라이센스인 것으로 확인하여 MIT License를 선택하기로 결정.
  • Consequences: 추후 GPL(또는 LGPL) 소스코드가 삽입될 시 프로젝트 라이센스가 변경될 가능성이 있음을 주의해야하며, MIT 라이센스를 통해 GPL의 단점을 해결함.

ADR-010: 프론트엔드 템플릿 도입 결정

  • Status: Accepted
  • Context: 프론트엔드를 기존 AI가 생성한 test-ui를 활용하고 있었는데 디자인이 밋밋하여, Figma에서 템플릿의 컴포넌트를 가져와서 조립할지 웹에 공개되어있는 디자인 템플릿을 가져올지 결정이 필요해짐.
  • Decision: tailAdmin 채택.
  • Rationale: Figma를 통한 몇몇개의 템플릿을 가져와서 체크해본 결과 1) 디자인이 너무 올드하거나(Android 5~8 시절 템플릿), 2) 구성의 복잡함으로 프로젝트에 바로 반영하기 어려운 경우가 발생하여, 웹에서 템플릿을 서칭하여 MIT License로 배포되는 tailAdmin(tailwind CSS 기반)을 채택하기로 결정.
  • Consequences: 디자인 심미성이나 구성이 좋아서 활용하기 좋은 이점을 가져왔지만, 컴포넌트 분석과 백엔드 및 에이전트 집중도가 떨어질 수 있는 문제 발생.

ADR-011: 에이전트 구분으로 UUIDv4 채택

  • Status: Accepted
  • Context: 에이전트의 백엔드 등록시 구분하기 위한 기준점이 필요함. hostname과 IP 기반의 경우 변경 가능성으로 에이전트 식별 영속성을 보장하지 못함. 다른 대체제 필요
  • Decision: UUIDv4 채택
  • Rationale: UUIDv4는 동일한 식별 데이터를 생성할 확률이 낮아 충돌 가능성 적음.
  • Consequences: UUID를 통해 에이전트가 설치된 호스트 식별 영속성 보장 가능해짐. UUID 생성 로직 추가의 수고만 발생.

ADR-012: 기존 백엔드와 에이전트 통신 Polling 패턴 교체 필요

  • Status: Accepted
  • Context: 백엔드와 에이전트 사이의 통신을 기존 Polling으로 구현하여 처리. Polling이 가진 제약으로 인한 백엔드 명령어 지시시 느린 반영 속도를 확인 및 대기 속도 낮출시 에이전트 리소스 소모 과다 확인. 다른 디자인 패턴을 확인 후 통신 로직 리팩터링 필요.
  • Decision: protobuf 규격과 gRPC 채택
  • Rationale: protobuf의 Polyglot 이점과 gRPC의 스트림 채널 기반(HTTP/2)의 이점을 통해 백엔드와 에이전트 통신의 개선을 가져올 수 있음.
  • Consequences: gRPC 채택으로 백엔드와 에이전트의 속도 개선을 가져오지만, protobuf 문서화 및 gRPC 학습이 추가되어 프로젝트 지연 발생 가져올 수 있음.

ADR-013: 에이전트 파서 로직 변경 필요

  • Status: Accepted
  • Context: 기존 에이전트 파서 로직은 xdp-filter status의 결과값을 정규표현식을 통해 슬라이스 하는 방식으로 수집함.
  • Decision: bpftool을 통한 xdp-filter Map 직접 조회로 선회
  • Rationale: nftables의 -j 옵션처럼 bpftool 또한 json 형식으로 빠르게 추출 가능. 클린 코드를 위해 리팩터링 결정
  • Consequences: bpftool을 통해 xdp-filter Map 직접 조회하여 json 규격으로 빠른 처리 가능해짐. 기존 코드는 제거 필요해짐.

ADR-014: 에이전트, 백엔드, 프론트엔드 일관성을 위한 Docker 도입

  • Status: Accepted
  • Context: 각 분리된 MSA내 서비스들을 안정적으로 배포의 필요성. 특히 에이전트의 경우 xdp-filter를 제어하기 위해서는 일관적인 환경이 필요(추후 커널 드라이버 버전 문제로 인한 컴파일 에러 차단 필요성). 프론트엔드와 백엔드의 경우에도 추후 CI/CD를 고려했을때 일관성 있는 컨테이너 이미지로 빌드해야함.
  • Decision: Dockerfile을 통한 컨테이너 이미지 빌드 채택
  • Rationale: 기존에 오랫동안 사용하던 도구인 Docker이므로 학습없이 빠르게 적용.
  • Consequences: 에이전트 먼저 Dockerfile로 적용하여 배포. 자작 공유기에서도 기초 환경 설정 없이 빠른 에이전트 실행이 가능해짐.

ADR-015: Docker Build시 프론트엔드 빌드 실패 발생

  • Status: Accepted
  • Context: Docker Compose를 통한 빌드 자동화를 위해 .Dockerfile 작성 후 프론트엔드 빌드시 타입 에러가 발생하여 실패하는 것을 확인함. 확인결과 package.json 파일에서 build 옵션을 변경하여 타입 검사를 생략할 수 있음을 확인.
  • Decision: build 옵션을 “build”: “vite build” 로 변경하기로 결정.
  • Rationale: 현재 단계에서 모든 타입 에러를 검수하여 처리하기에는 프로젝트 목표 타임테이블상 어려우므로 빠른 프로젝트 진행을 위해 타입 검사를 생략하기로 결정.
  • Consequences: 타입 검사를 생략하여 빠른 프로젝트 진행 속도를 얻었지만, 반대급부로 프론트엔드 코드 불안정성을 늘리게 됨.

ADR-016: 다른 환경에서의 에이전트 XDP 호환성 체크 필요성

  • Status: Accepted
  • Context: 에이전트의 xdp-filter의 드라이버 로드가 가능한지 확인 여부 필요.
  • Decision: terraform, ansible, cloud-init 도구를 활용하여 AWS EC2에 에이전트에 배포하기로 결정
  • Rationale: IaC 도구 학습과 함께 AWS ENA 드라이버 환경에서 XDP 인터페이스 로드 테스트까지 처리하여 투트랙 전략이 가능함.
  • Consequences: 초기 배포 도구 설정시 시행착오 발생. 설정 및 테스트 완료 후 어디서나 일관적인 에이전트 배포가 가능해짐.

ADR-017: CI/CD 도입 필요성

  • Status: Accepted
  • Context: 코드 테스트시 수동으로 실행하는 불편함을 느껴 CI/CD 도입 필요성을 느낌.
  • Decision: IaC로 CI/CD 관리 도입. Github Actions, ArgoCD, Docker hub 등 활용
  • Rationale: 기존의 proxmox 서버를 활용하여 k8s 클러스터 배포하기 용이. GitOps 방식을 통해 배포 이력 투명하게 관리하고, ArgoCD는 이 소스를 기반으로 k8s 배포 용이.
  • Consequences: 초기 환경 설정 및 인프라 배포에서 인프라 아키텍쳐/네트워크 설계 요소가 더해져서 프로젝트 지연 발생. 하지만 설정 완료 후 git push 이후 코드 자동화가 되어 빠른 테스트가 가능해짐.

ADR-018: UUIDv4 -> UUIDv7 전환 필요성

  • Status: Proposed → DB 추가시 채택 필요
  • Context: 추후 대시보드 접근 보안 제어 및 액션 감사에 따른 DB 연동이 필수적임. 기존 UUIDv4는 무작위성으로 인해 DB 인덱스 성능 저하 우려가 있음.
  • Rationale: UUIDv7은 타임스탬프 기반으로 앞 부분이 계속 커지는 형태로 이를 네이티브로 지원하는 postgresql에서 페이지 중간에 데이터를 꼽는 비용을 줄이고 B-Tree 인덱스 가장 오른쪽 페이지만 채우는 강력한 속도를 보여주므로 UUIDv7 전환을 채택함
  • Decision: UUIDv7으로 전환 채택

ADR-019: MIT 라이센스 → AGPL 라이센스로 변경

  • Status: Accepted → v0.3.0-dev
  • Context: 추후 에이전트의 Rust 전환 시 eBPF와 netfilter 라이브러리 동적링킹을 위한 라이센스 전환 필요.
  • Decision: AGPL 라이센스 변경 채택
  • Rationale: AGPL 라이센스 채택으로 bpf 라이브러리와 netfilter 라이브러리 접근성 자유로워진 반면 MIT 라이센스를 포기하여 타인이 본 프로젝트를 활용하기에 제약사항이 됨.

XDP-filter vs iptables(netfilter)

테스트 환경

  • Attacker
    • mac mini M4 (기본형 + 10Gbps 이더넷)
    • 공격 도구
      • UDP Flooder (AI generate Go LANG)
  • Defense
    • Intel G4560 (2C 4T) + X540-T2 (10Gbps 이더넷)
    • 방화벽 도구
      • iptables(legacy) – netfilter
      • xdp-filter(native) – eBPF/XDP

패킷 처리량

UDP 패킷 초당 평균 14.8만 PPS (rxpck/s)

iptables(netfilter)로 UDP Attack DROP시 CPU 및 SoftIRQ 점유율

XDP로 UDP Attack DROP시 CPU 및 SoftIRQ 점유율

결과비교

측정 항목iptables (Legacy)eBPF/XDP개선율
특정 코어 점유율100%18%82% 감소
SoftIRQ 부하25%3%약 8배 최적화
시스템 가용성서비스 지연 발생 가능정상 운영 가능
  • XDP로 패킷 DROP시 스레드 2번 점유율 100% → 18%로 감소
  • XDP로 패킷 DROP시 SoftIRQ 점유율 25% → 3%로 감소
목차