“쿠버네티스 안에 있는 DB도 접근통제할 수 있나요?”

처음 들으면 간단한 질문처럼 보인다.

터널 하나 만들고, DB 주소 넣고, 정책을 걸면 끝 아닐까?

그런데 이 질문을 제대로 풀어보면 문이 세 개 나온다.

1. 그 DB까지 갈 수 있는가?
2. 늘 올바른 DB를 가리키는가?
3. 도착한 사람이 해도 되는 일만 하는가?

재미있는 건 세 문이 서로 다른 열쇠를 쓴다는 점이다. 네트워크가 열렸다고 권한이 생기는 것도 아니고, Service DNS가 있다고 실행 중인 쿼리가 무중단이 되는 것도 아니다.

오늘은 이 세 문을 한꺼번에 열려다가 자주 넘어지는 지점을 정리해보려 한다.

터널은 한 종류가 아니다

먼저 가장 헷갈리는 장면부터 보자.

개발자의 노트북에서 사설 DB로 접속한다고 해보자. 중간에 접근통제 프록시가 있고, DB는 쿠버네티스 내부망에 있다.

경로는 대략 이렇게 생긴다.

DB Client
→ 사용자 PC의 local tunnel
→ 접근통제 Proxy
→ target network의 reverse tunnel
→ Kubernetes Service
→ DB Pod

여기에는 터널이 두 개다.

첫 번째는 사용자 쪽 터널이다. DB Client의 트래픽을 사용자 PC에서 접근통제 시스템으로 들여보낸다.

두 번째는 대상망 쪽 reverse tunnel이다. 외부에서 직접 들어가기 어려운 사설망 안의 connector가 먼저 바깥의 tunnel server로 연결을 만든다. 이후 접근통제 시스템은 그 길을 거꾸로 이용해 DB에 도달한다.

둘은 이름에 모두 ‘터널’이 들어가지만 방향도, 설치 위치도, 실패 원인도 다르다.

고속도로로 비유하면 첫 번째는 집에서 요금소까지 가는 진입로고, 두 번째는 산을 통과해 목적지 마을로 들어가는 터널이다. 진입로가 멀쩡하다고 산속 터널까지 열린 건 아니다.

그래서 장애를 볼 때도 나눠야 한다.

  • DB Client에서 local listener까지 연결되는가?
  • 접근통제 Proxy가 target route를 선택했는가?
  • 사설망 connector가 reverse tunnel을 유지하는가?
  • connector가 DB hostname을 해석할 수 있는가?
  • connector에서 실제 DB port까지 갈 수 있는가?

“터널이 연결됐다”는 말 하나로는 어느 구간이 정상인지 알 수 없다.

클러스터마다 connector 하나? 숫자보다 지도를 봐야 한다

다음 질문도 자주 나온다.

“쿠버네티스 클러스터마다 reverse tunnel connector를 하나씩 설치해야 하나요?”

정답은 클러스터 개수만 보고 정할 수 없다.

봐야 할 것은 네트워크 지도다.

한 connector가 여러 클러스터의 DB Service에 안전하게 도달할 수 있다면 클러스터마다 하나씩 둘 이유가 없을 수 있다. 반대로 한 클러스터 안에서도 namespace, NetworkPolicy, 보안 구역이 강하게 나뉘어 있다면 connector 하나로는 부족할 수 있다.

설치 단위는 보통 다음 질문으로 정한다.

이 connector가 어디까지 갈 수 있는가?
어디까지 가도 되는가?
한 구역이 장애 나면 어디까지 함께 영향을 받는가?

첫 번째는 routing 문제고, 두 번째는 보안 경계 문제며, 세 번째는 장애 범위 문제다.

클러스터 수는 참고 숫자일 뿐이다. 진짜 기준은 reachability와 isolation이다.

Pod IP는 방 번호, Service DNS는 안내 데스크다

이제 DB 주소를 정해보자.

Pod IP를 직접 등록하면 당장은 잘 붙을 수 있다. 하지만 Pod가 재기동되면 방 번호가 바뀔 수 있다.

Kubernetes Service DNS는 이 문제를 줄여준다.

불안정한 주소
10.42.3.17:5432

안정적인 이름
orders-db.database.svc:5432

Pod가 교체되어도 Service가 새 endpoint를 가리키면 신규 연결은 같은 논리적 이름을 계속 사용할 수 있다.

여기까지만 들으면 Service DNS가 만능처럼 보인다. 하지만 안내 데스크가 새 방을 알려준다고 이미 방 안에서 진행 중이던 회의까지 새 방으로 순간이동하지는 않는다.

Service DNS가 주로 해결하는 것은 ‘다음 연결이 어디로 갈지’다.

해결하지 못하는 것은 이런 것들이다.

  • 이미 수립된 TCP session
  • 실행 중인 장시간 Query
  • transaction 중간 상태
  • Primary가 바뀌는 순간의 write 가능 여부
  • client와 proxy의 재시도 정책

그래서 “Service를 쓰니 무중단”이 아니라 이렇게 말해야 한다.

Service DNS는 신규 연결을 위한 안정적인 논리 endpoint를 제공한다. 기존 session과 실행 중 작업의 연속성은 별도로 검증해야 한다.

문장이 조금 길어졌지만 훨씬 정직하다.

DB replica는 웹 서버 replica와 다르다

웹 애플리케이션 Pod를 세 개로 늘리면 Load Balancer가 요청을 나눠주는 장면에 익숙하다.

그 감각으로 DB replica를 보면 사고가 난다.

DB는 보통 역할이 있다.

Writer Service → 현재 Primary
Reader Service → 읽기 Replica들

replica가 늘었다고 아무 Pod에나 쓰기 요청을 보내도 되는 게 아니다. failover가 일어나면 Writer Service가 새 Primary를 정확히 가리켜야 한다.

Headless Service나 Pod별 DNS를 쓰는 구성이라면 더 세밀한 판단이 필요하다. 개별 instance를 직접 관리해야 할 수도 있고, DB Operator가 역할 전환을 어떻게 반영하는지도 봐야 한다.

reverse tunnel은 여기서 길을 제공할 뿐이다.

어느 DB가 Primary인지 판단하지 않는다. Writer와 Reader의 의미를 만들어주지도 않는다. DB topology를 모른 채 network tag만 잘 붙인다고 write route가 안전해지는 것은 아니다.

길 안내 시스템이 “부산행 고속도로는 열려 있습니다”라고 알려줄 수는 있다. 하지만 어느 건물이 오늘의 본사인지는 회사 조직도가 정해야 한다.

도착할 수 있음과 해도 됨은 완전히 다르다

네트워크 연결이 성공하면 이제 세 번째 문이 나온다.

“누가 무엇을 할 수 있는가?”

reverse tunnel은 transport다. 접근통제 정책 자체가 아니다.

적어도 다음 층은 따로 봐야 한다.

Network path
→ Authentication
→ DB access privilege
→ SQL policy
→ Sensitive-data masking
→ Audit evidence

connector가 DB까지 갈 수 있다는 사실은 잠재적 접근 범위를 뜻한다. 그래서 방화벽과 NetworkPolicy는 connector가 정말 필요한 target만 볼 수 있도록 좁혀야 한다.

그 위에서 개인 사용자 인증, 기간이 있는 권한, 허용·차단 SQL, 민감정보 마스킹을 적용한다.

공용 DB 계정을 쓰더라도 앞단에서는 실제 사용자를 식별해야 한다. 그래야 감사 담당자가 이런 질문에 답할 수 있다.

  • 누가 접속했나?
  • 왜 권한을 받았나?
  • 어느 DB 계정을 사용했나?
  • 어떤 SQL을 실행했나?
  • 차단된 시도는 무엇이었나?
  • 민감정보는 어떻게 보호됐나?

여기서도 현재 실행 중인 Query 화면과 과거 SQL 감사 이력을 섞으면 안 된다. ‘지금 달리는 기차’를 보는 전광판과 ‘어제 누가 어떤 기차를 탔는지’ 보는 승차 기록은 다른 기능이다.

HA는 형용사가 아니라 장면이다

“고가용성입니다.”

듣기 좋은 말이지만 검증하기는 어렵다.

더 좋은 질문은 장애 장면을 구체적으로 만드는 것이다.

장면 1: DB Pod 하나를 재기동한다
장면 2: DB Primary를 전환한다
장면 3: reverse tunnel connector를 재기동한다
장면 4: tunnel server 한 노드를 중단한다
장면 5: 접근통제 Proxy 한 노드를 중단한다

각 장면에서 네 가지를 따로 기록한다.

  1. 신규 연결은 다시 성공하는가?
  2. 기존 session은 유지되는가, 끊기는가?
  3. 실행 중 Query는 어떤 오류를 받는가?
  4. 장애 전후 감사 기록은 빠지거나 중복되지 않는가?

이렇게 하면 “HA 지원”이라는 한 줄이 실제 운영 계약으로 바뀐다.

특히 Redis 같은 공유 저장소가 있다고 기존 network session이 자동으로 다른 node로 순간이동한다고 생각하면 안 된다. membership discovery와 live session migration은 전혀 다른 문제다.

좋은 PoC는 정상 화면보다 실패 장면을 먼저 정한다

정상 접속만 확인하는 PoC는 쉽다.

진짜 운영 가능성을 알려주는 건 실패 장면이다.

추천하는 최소 시나리오는 이렇다.

  • Service DNS로 신규 DB 연결
  • DB Pod 교체 후 신규 연결
  • Primary 전환 후 Writer endpoint 검증
  • Reader replica 증감 후 read route 검증
  • connector 재기동 후 tunnel 재수립
  • tunnel server 한 노드 중단 후 신규 연결
  • 장시간 Query 중 장애 발생
  • 장애 전후 접속·SQL 감사 연속성 확인

그리고 결과는 단순히 성공/실패로만 적지 않는 편이 좋다.

PASS
- 정상 경로에서 기대한 동작

CONDITIONAL PASS
- 신규 연결은 복구되지만 기존 session은 종료
- 재시도 또는 운영 절차 필요

STOP
- 잘못된 DB role로 연결
- 정책 우회
- 감사 누락

이 표가 있으면 기술팀과 보안팀이 같은 문장을 읽게 된다.

마지막으로 기억할 세 문

쿠버네티스 안의 DB를 접근통제하는 일은 터널 하나의 문제가 아니다.

세 개의 문을 차례로 열어야 한다.

도달성: target network까지 갈 수 있는가?
안정성: 올바른 논리 endpoint로 다시 연결되는가?
통제성: 허용된 사람이 허용된 일만 하는가?

그리고 좋은 보안은 기록만 많이 남기는 데서 끝나지 않는다.

누가 어떤 데이터에 왜, 얼마 동안 접근했는지 설명할 수 있어야 한다. 동시에 범위를 벗어난 시도는 거부되고, 민감정보 노출은 마스킹과 예외 승인으로 제한되어야 한다.

터널은 길을 만든다.

Service는 목적지의 이름을 지킨다.

DB topology는 역할을 정한다.

접근통제는 사람과 행동의 경계를 세운다.

이 네 가지를 분리해서 볼 때, “접속됩니다”가 비로소 “안전하게 운영할 수 있습니다”로 바뀐다.