Blog on Iterating

NATS Core + JetStream

메시징 시스템이 필요한 상황에서 Kafka는 리소스 요구량이 크기 때문에 라즈베리파이 환경에는 어울리지 않는다.

그래서 경량 메시징 시스템인 NATS를 선택했다.

기본 개념

NATS Core는 subject 기반 메시징 시스템이다.

주요 기능은 Pub/Sub, Queue Group, Request/Reply

  • Pub - 이벤트 subject에 메세지 전달.

  • Sub - 이벤트 subject로 전달된 메세지 수신.

  • Queue Group - 같은 Queue Group에 속한 Subscriber들 중 하나에게만 메세지를 분배하는 load balancing 방식.

  • Request / Reply - 요청자/응답자 모두가 양방향 Pub/Sub을 사용하는 1회성 대화 기능.
    요청자는 'subject'로 메세지를 전달하면서 'reply subject'를 함께 전달한다.
    응답자는 'subject'를 구독하다가 메시지를 받으면 'reply subject'로 응답을 전달한다.

NATS Core는 기본적으로 메시지를 영속 저장하지 않고, 현재 연결된 구독자에게 즉시 전달한다.
따라서 메시지 처리 실패 시 유실 가능성이 있으며, 이를 보완하기 위해 JetStream을 사용한다.

JetStream을 사용하면 NATS Core에 Stream, Consumer 개념이 추가된다.

  • Stream - NATS 메세지에 영속성을 부여하기 위한 저장소.
    하나의 Stream에 여러 subject 또는 subject 패턴의 이벤트가 저장될 수 있다.
    저장된 이벤트에는 Stream Sequence가 부여된다.
    Stream은 pub/sub처럼 자동 생성되지 않아 최초 1회 명시적으로 생성해야 한다.

  • Consumer - Stream을 읽기 위한 서버 측 상태 객체.
    읽은 메세지를 Subscriber에 전달하고, 어디까지 전달했고 어디까지 ACK 되었는지를 관리한다.
    ACK는 단순 수신확인이 아니라 메시지 처리 상태를 알려주는 제어 신호다.

즉, NATS Core는 Publisher -> Subscriber 구조이고,
JetStream을 사용하는 NATS는 Publisher -> Stream -> Consumer -> Subscriber 구조다.

인증

NATS는 네트워크를 통해 메세지를 전달하는 시스템이기 때문에 인증된 사용자만 접근할 수 있어야 한다.

인증 방식에는 Token, Username/Password, NKey 등 여러 방식이 존재며, 여기에서는 운영환경에서 많이 사용하는 JWT + NKey 방식을 다룬다.

NATS는 다음의 요소로 구성된다. 각각은 JWT + NKey으로 표현된다. JWT는 권한/정보를 담고, NKey는 인증을 위한 공개키/개인키 쌍이다.

  • Operator - 시스템이 신뢰하는 최상위 서명 주체.
    Operator는 Account JWT를 서명하여 신뢰를 부여하고 해당 Account를 신뢰 체인에 포함시킨다.

  • Account - 신뢰를 부여받은 논리적 tenant 단위.
    독립적인 namespace 역할을 한다.
    User JWT를 서명하여 신뢰를 부여한다.

  • User - 클라이언트가 시스템 접근에 사용하는 identity.
    신뢰를 부여한 Account에 소속된다.

  • .creds - 클라이언트가 사용하는 User의 인증 파일.
    User JWT + User NKey Seed로 이루어져있다.

권한과 역할을 분리하기 위해 Operator -> Account -> User(.creds) 구조의 계층을 가진다.
클라이언트는 User의 .creds 파일을 이용해 접근하며, 서버는 User JWT -> Account JWT -> Operator JWT 순으로 신뢰 체인을 검증한다.

이 방식은 Multi-tenant 운영이 가능한 가장 유연한 방식이다.

운영요소

영속 메시징 기능인 Stream과 Consumer는 JetStream 런타임에서 생성·수정·삭제되는 리소스이므로 시스템 실행 중에도 관리할 수 있다.

반면 루트 인증 체계인 Operator는 서버 초기화 설정으로 관리되므로, 변경 사항 반영에는 재시작 또는 reload 절차가 필요하다.

Account 관리는 NATS Resolver 타입에 따라 달라진다. JWT를 서버 설정 파일에 정적으로 preload하는 방식이라면 Account 변경 시 서버 설정 변경과 reload가 필요할 수 있다. 하지만 NATS Resolver 타입을 full로 구성하면 Account JWT를 런타임에 resolver로 push할 수 있다. 이것은 Account 목록을 지정된 디렉토리에서 동적으로 관리하는 방법이다.

resolver: {
  type: full
  dir: "./jwt"
  allow_delete: false
  interval: "2m"
  limit: 1000
}

nats-console

이 관리를 명령어로 관리하기 어려워 nats-console 이라는 관리사이트를 만들었다.