본문 바로가기
개발 일기장/개발 일지

쿠버네티스에서 nats cluster 띄우기

by 룰루루 2026. 2. 13.

✅ 배경

테스트 서버는 쿠버네티스 위에서 돌아가고 있었다. 이전에 측정한 nats broker latency의 결과를 통해서, 이제는 테스트 환경에서부터 nats broker를 mosquitto 대신 도입해보기로 했다. 그러기 위해서는 nats broker(server)를 띄우고, 기존에 mosquitto broker와 연결되어 있던 중개 서버(cms)의 mosquitto 관련 환경 변수 값을 nats로 바꿔주어야 했다. 
 
듣기만 하면 간단해보이는데, 이 쿠버네티스의 구조를 파악하면서 작업하느라 이것도 체감상 3-4일은 걸린 이슈다. 시행착오와 함께 어떤 일들이 있었는지를 기억이 휘발되기 전에 기록해보자.
 
쿠버네티스는 서버 오케스트레이션 도구라고 알고 있다.... 그런데 찾아보니 그것만은 아니라고 한다. DevOps 도구이나, 기본적으로 containerized 될 수 있는 어플리케이션이라면 그게 뭐든 managing, scheduling, scaling 등을 편리하게 할 수 있도록 도와주는 도구라고 이해했다. 회사에서는 이 쿠버네티스를 관리하는 툴로 argocd를 같이 쓰고 있다.
 
argocd는 gitops 도구이다. gitops와 devops의 차이 중 하나는 source of truth의 유무이다. gitops는 git(여기서는 helm도 포함한다) repo를 유일한 truth source로 보고, 이 truth source와 비교해서 desired state를 맞춰 나간다. 반면 devops는 source of truth가 없고, 배포 event가 발생하면 그것을 잘 CI & CD하는 역할을 한다. 상황마다 장단점이 다른 것 같다. gitops는 아무래도 source of truth가 있으니까 무엇이 desired state, 즉 무엇이 '현재 되어 있어야 하는 상태'인지를 명확히 알 수 있다는 장점이 있겠다. 단점은 찾아봤더니 가파른 러닝커브나 쿠버네티스 환경 등에서만 최적화되어 있다는 점이 있겠다. 또 git이 꼭 필요하다보니 의존성 이슈도 있고, git repository의 성능이 bottleneck이 될 수 있다는 단점도 있다고 한다. 
 
kubernetes도 쓰다보니 왜 gitops와 같이 쓰이는지 조금은 알 것 같다. 둘 다 declarative하다. 예전에 제미나이한테 물어봤을 때였나, DevOps는 procedural하고 GitOps는 declarative하다는 비교를 해 줬는데 그게 좀 맞았다. 쿠버네티스도 결국엔 '그래서 지금 이 리소스가 어떤 상태여야 하는가'를 템플릿으로 표현해주면 그 상태를 맞춰주는 것은 쿠버네티스 내부에서 하는 일이다보니, declarative한 문법을 사용한다. 
 
어쨌든, 이 쿠버네티스는 클러스터라는 독립적인 단위 내에서 움직인다고 이해했다. 하나의 클러스터는 1개 이상의 control plane과 1개 이상의 worker node 조합으로 구성된다. control plane 안에는 하위 구성요소들(api controller, etcd 등)이 있고, worker node에도 service, deployment, statefulset 등 하위 구성요소들이 있다. control plane이 개별 node들의 상태를 관리하기 위해서 명령을 내리는 control center라고 이해했다. 
 
그리고 방금 검색해서 이해해 보니 cluster는 일종의 물리적인 파티션이고(클러스터가 다르면 실제로 다른 공간에 저장됨), namespace는 논리적인 파티션이었다. container의 namespace와 같은 뜻으로 보인다. 같은 container 안에 있어도 namespace가 다르면 서로 접근할 수 없는 것처럼 말이다.
 
아무튼 현재 서비스의 구조는 node는 두 개였고(그런데 거의 하나의 노드에 집중되어 있다), 하나의 node 안에는 여러 service들이 있다. service는 pod의 상위 추상 클래스 또는 wrapper 개념인데, pod의 ephemeral한 속성을 보완하는 엔티티로 이해했다. pod도 ip를 할당받지만, 하나의 pod가 죽고 새 pod가 뜨게 되면 해당 ip는 사라진다. 이렇게 계속 변하는 pod를 static한 service로 wrapping 해서, pod끼리 서로 통신을 가능하게 해 준다. 
 

✅ 문제

직접 리소스별로 .yaml 파일을 작성하다가, 멘토 분의 조언으로 helm chart를 활용하니 나는 환경변수 값만 알맞게 넣어주면 되었다. 그런데 여기서부터가 문제였다.
 
당시 여러 설정들을 고려해야 했다. MQTT 및 JetStream, WebSocket, Clustering 활성화, TLS 및 Authorization 비활성화 등등을 고려해야 했다. 다행히 nats k8s template에서는 꽤 다양한 여러 종류의 값을 제공하는 가이드 문서가 있어서, 이대로 쓰면 되려나 싶었다. 
 
그런데 설정이 제대로 되지 않은 것 같았다. argocd application 자체는 잘 뜨는데 막상 container 로그를 보니 'error connecting to the host...', 'no such host', 'duplicate connection' 등의 에러가 발생했다. 
 
이를 위해 몇 가지 설정을 추가하였다. 우선은 'duplicate connection'으로 cluster 안에서 묶여야 할 서버들이 강제로 연결을 끊어버리는 것을 막기 위해서, cluster 속성의 routes 경로에 서버 한 대만을 넣기로 하였다. 
 
여러 시행착오가 있었는데, 헤멨던 이유는 두 가지였다. 우선은 values 파일을 수동으로 작성할 때, config 하위에 merge를 추가해야 했다. 그래야 쿠버네티스가 merge 하위에 내가 쓴 속성들을 기본값이랑 말 그대로 '병합'해서 server의 config 파일을 만든다. 안 그러면 기껏 적어놔도 override 되고 다시 덮어쓰기를 당해서 값이 반영되지 않았다. 
 
두 번째 복병은 namespace였다. 여전히 'no such host' 에러가 뜨면서 하나의 cluster로 묶여야 할 서버들이 서로를 찾지 못하고 있었다. 답답한 마음에 로그 몇 줄을 그대로 제미나이한테 줘 봤는데, 녀석이 내가 작업하는 타깃 서비스의 namespace가 default가 아님을 그 로그를 통해 알아차렸던 모양이다. 원래는 기본 namespace인 default에서 작업하는 것을 가정하고 계속 추천해 준 코드가 있었는데, 해당 코드의 'default' 부분을 현재 네임스페이스로 바꿔보라는 거였다.
 
그렇게 몇 시간 내의 삽질과 제미나이와의 심도 깊은 대화를 통해 아래와 같이 파일을 작성했더니, routes 경로로 딱 하나의 서버만을 제공할 수 있었다. 

config:
  merge:
    cluster:
      listen: "0.0.0.0:6222"
      routes: 
      - "nats://nats-test-0.nats-test-headless.current-namespace.svc.cluster.local:6222"
      authorization: {}

 
이제 기존 잘 돌아가고 있던 test application으로 가서, 기존 중개 서버가 mosquitto broker로 보내던 트래픽을 nats broker로 보낼 수 있도록 최소한의 변경을 하면 되었다. 
 
중개 서버는 코드상 특정 환경변수 값을 mosquitto broker의 host 값으로 사용하고 있었고, 이는 당연히 코드 내부에 static하게 정의되지 않고 외부(쿠버네티스)에서 넣어주는 값이었다. 
 
중개 서버의 deployment의 config를 찾아보니, 'mosquitto'라는 service의 external IP(여기서는 같은 namespace 내의 service끼리 서로를 식별할 수 있는 IP, 즉 내부 IP를 의미한다고 이해했다) 값을 이 환경변수에 넣어주는 게 아닌가. 그래서 그냥 이 'mosquitto' 문자열을 nats broker의 service 이름인 'nats-test'로 바꿔 주고, 중개 서버를 다시 시작해 주었다.
 
그랬더니 아래와 같은 로그가 나왔다. 중개 서버에서 나온 로그는 이렇게 PUBLISH한 주체로써 'CMS/OUT'이 붙어서 나오므로, 중개 서버에서 nats broker로 정상적으로 publish를 하고 있음을 알 수 있다. 

[2] 2026/02/13 12:43:27.780258 [TRC] 172.30.1.123:51660 - mid:38 - "CMS" - <<- [PUBLISH SERVER/CMS/out dup=false QoS=0 retain=false size=29]
[2] 2026/02/13 12:43:27.780283 [TRC] 172.30.1.123:51660 - mid:38 - "CMS" - <<- MSG_PAYLOAD: ["\n\f\b︼\xcc\x06\x10\xa0Ø\xeb\x02\x12\b\b\xa0\x06\x12\x038\xb5\r\x1a\x03CMS"]