Kubernetes와 headless service를 사용해 캐시 무효화를 해결한 방법
Source: Dev.to
위의 소스 링크 아래에 번역하고 싶은 전체 텍스트를 제공해 주시면, 요청하신 대로 한국어로 번역해 드리겠습니다. 코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.
🛑 문제: 빠른 캐싱 및 확장 가능한 무효화
We decided on in‑memory caching (using @nestjs/cache-manager) to slash DB load and boost response times. In a Kubernetes cluster, where the service runs across multiple, dynamic pods, this creates a major headache: cache inconsistency.
- If an admin updates a package price in MySQL, only the pod that handled the write sees the change immediately.
- All other pods continue serving stale data.
Additionally, pod IPs are temporary; they change during restarts, deployments, and scaling events. Hard‑coding IP lists is impossible, so we needed a reliable way to discover every running pod at the exact moment of a data update—a broadcasting mechanism.
🛠️ 솔루션: K8s 헤드리스 서비스
우리 전략은 하이브리드 접근 방식이었습니다:
- 성능을 위한 로컬 캐싱
- 무효화를 위한 타깃 내부 HTTP 브로드캐스트
Step 1 – 헤드리스 서비스로 Pod 탐색
표준 Kubernetes Service는 단일 Virtual IP를 가진 로드밸런서 역할을 합니다. 헤드리스 서비스(clusterIP: None)는 로드밸런서를 건너뛰고 현재 활성화된 각 Pod의 IP 주소에 대한 개별 DNS A 레코드 목록을 반환합니다.
apiVersion: v1
kind: Service
metadata:
name: booking-service-headless
spec:
clusterIP: None # THE TRICK: makes it headless
selector:
app: booking-service
ports:
- protocol: TCP
port: 80
targetPort: 3000
이제 booking-service-headless.default.svc.cluster.local을 조회하면 현재 정상적인 모든 Pod IP가 배열 형태로 반환됩니다.
Steps 2 & 3 – 브로드캐스트 구현
- Docker 이미지에
kubectl추가(또는@kubernetes/client-node라이브러리 사용)하여 Pod가 Kubernetes API를 직접 조회할 수 있게 합니다. - 최소 권한 원칙 적용: 전용
ServiceAccount를 만들고, 해당 네임스페이스 내endpoints리소스에 대해list와get만 허용하는Role에 바인딩합니다. 이렇게 하면 Pod가 침해당했을 때 피해를 최소화할 수 있습니다. - booking 서비스에 내부
/invalidate-cache엔드포인트 노출. 이 엔드포인트는 캐시 키를 받아 메모리 캐시의del()메서드로 해당 키를 삭제합니다.
관리자 업데이트가 발생하면, 쓰기를 수행한 Pod가 다음 스크립트를 실행합니다:
- 헤드리스 서비스 엔드포인트에서 현재 Pod IP 목록을 가져옵니다.
- 각 Pod의
/invalidate-cache엔드포인트에 HTTPPOST요청을 보냅니다.
# 업데이트 시 Node.js 프로세스가 실행
pod_ips=$(kubectl get endpoints booking-service-headless \
-o jsonpath='{.subsets[0].addresses[*].ip}')
for ip in $pod_ips; do
curl -X POST http://$ip:3000/v2/invalidate \
-H "Content-Type: application/json" \
-d '{"key":"package-123"}' # 특정 키 삭제
done
왜 작동하는가
| ✅ Feature | Description |
|---|---|
| Dynamic | 무효화 시점에 IP 목록이 최신이므로, 스케일 업/다운 및 재시작을 매끄럽게 처리합니다. |
| Targeted | 오래된 패키지 키만 삭제하여 캐시 보존을 최대화합니다. |
| Cost‑Effective | 추가 서비스나 외부 메시지 브로커가 필요 없습니다. |
| Reliable | curl 재시도와 로깅을 추가해 브로드캐스트 실패를 기록합니다. |
대안 – 이미지에
kubectl/curl을 설치하고 싶지 않다면 공식@kubernetes/client-node라이브러리를 사용하세요. 이 라이브러리를 통해 Node.js 코드가 직접 Kubernetes API를 조회할 수 있어 로직을 더 테스트하기 쉽고, 오류 처리가 개선되며, 셸 프로세스를 스폰하는 오버헤드가 사라집니다.
📈 프로덕션 결과 및 시사점
롤아웃 후:
- MySQL 부하가 지속적으로 낮게 유지되었습니다.
- 캐시 적중률이 크게 상승했습니다 (패키지 쿼리에서 ≈ 95 %).
- 응답 지연 시간이 캐시된 요청에 대해 ~200 ms에서 < 30 ms로 감소했습니다.
- 운영 오버헤드는 최소 수준을 유지했습니다—새 서비스 없이, 추가 비용 없이, RBAC 제한 ServiceAccount가 보안 표면을 작게 유지했습니다.
주요 교훈
- Headless Service는 파드 수준 통신을 위한 간단하고 내장된 서비스 디스커버리 메커니즘입니다.
- 인메모리 캐시 + 명시적 무효화를 사용하면 운영 복잡성 없이 분산 캐시와 같은 성능을 얻을 수 있습니다.
- 최소 권한 ServiceAccount는 컨테이너 내부에 내부 도구를 노출하더라도 클러스터를 보호합니다.
대부분 정적 데이터이지만 가끔 업데이트가 필요한 경우, 이 패턴은 저비용의 Kubernetes 네이티브 방식으로 모든 파드의 캐시를 동기화할 수 있게 해줍니다.
결과 요약
- 가동 시간: 95 %+ 이상을 유지했습니다.
- 사고: 사용자가 오래된 패키지 데이터를 보는 사례가 보고되지 않았습니다.
- Headless Service: 트릭은 트래픽이 많은 배포에서도 견고함을 입증했습니다.

시사점
이 솔루션은 가장 우아하고 비용 효율적인 답이 항상 비싼 관리형 서비스는 아니라는 것을 보여줍니다—이미 가지고 있는 도구를 창의적으로 활용할 수 있습니다. 문제에 돈을 쏟아붓는 것이 아니라 인프라를 현명하게 사용하는 것이 중요합니다.
