Envoy Gateway 멀티 네임스페이스와 팀 소유권: allowedRoutes, ReferenceGrant, 그리고 팀 간 거버넌스
Gateway API의 가장 큰 가치는 YAML이 더 예뻐진 것만이 아닙니다. 오랫동안 흐릿했던 질문에 마침내 명확한 답을 준다는 점입니다:
누가 ingress 계층을 소유하는가? 누가 route를 바꿀 수 있는가? 네임스페이스끼리 서로를 참조할 수 있는가?
많은 Ingress의 고통은 모든 것이 뒤엉켜 있었다는 데서 왔습니다. 플랫폼 팀은 승인되지 않은 노출 변경을 두려워했고, 앱 팀은 path 하나 바꾸려 해도 티켓을 열어야 한다고 느꼈습니다. 모두가 YAML 속에서 서로를 괴롭히게 된 셈이죠.
Gateway API는 이를 아주 분명하게 나눕니다:
- 플랫폼 ingress는 독립적으로 거버넌스 가능
- route는 앱 팀에 위임 가능
- 네임스페이스 간 동작은 명시적 승인 필요
이 글에서는 이 소유 모델을 위에서 아래까지 설명합니다.
먼저 여기부터: Attachment와 Reference는 다른 문제다
이건 가장 자주 헷갈리는 구분입니다.
Route가 Gateway / Listener에 Attach되는 문제
이것은 다음에 의해 결정됩니다:
- route의
parentRefs - listener의
allowedRoutes
즉 route가 listener에 attach할 수 있는지는 ReferenceGrant가 아니라, 그 listener가 받아줄 의사가 있는지로 결정됩니다.
네임스페이스 간 리소스 참조 문제
이때 주로 등장하는 것이 ReferenceGrant입니다.
예를 들면:
Gateway가 다른 네임스페이스의Secret을 참조하려는 경우HTTPRoute가 다른 네임스페이스의Service를 참조하려는 경우
이건 attach의 문제가 아닙니다. "대상 네임스페이스가 내 리소스를 써도 된다고 정식으로 승인했는가"의 문제입니다.
많은 사람이 이 두 가지를 하나로 뭉뚱그려 생각하다가 스스로 제정신을 의심하게 됩니다. 역할만 분리해 보면 규칙은 생각보다 신비롭지 않습니다.
allowedRoutes: 먼저 누가 Attach할 수 있는지 결정하자
가장 보수적인 listener는 이렇게 생깁니다:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: infra
spec:
gatewayClassName: eg
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Same이 뜻은 infra 네임스페이스에 있는 route만 이 listener에 attach할 수 있다는 것입니다.
특정 팀 네임스페이스만 attach하도록 허용하려면 Selector를 사용합니다:
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"이 패턴은 플랫폼 팀에 특히 잘 맞습니다.
모든 걸 완전히 잠글 필요도 없지만, 그렇다고 from: All로 활짝 열어 아무나 붙게 둘 필요도 없습니다. 특정 label을 가진 네임스페이스만 attach 권한을 얻도록 만들 수 있습니다.
💡
allowedRoutes는 Gateway 단위가 아니라 listener 단위입니다. 서로 다른 listener마다 다른 접근 정책을 가질 수 있습니다.
ReferenceGrant: 대상 네임스페이스가 결정한다
route나 gateway가 다른 네임스페이스의 리소스를 참조하려면, 대상 네임스페이스가 권한을 줘야 합니다.
예를 들어 앱 팀의 HTTPRoute가 공유 네임스페이스에 있는 백엔드로 트래픽을 보내려는 경우:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
namespace: team-a
spec:
parentRefs:
- name: shared-gateway
namespace: infra
sectionName: https
rules:
- backendRefs:
- name: shared-api
namespace: shared-services
port: 8080그러면 shared-services 네임스페이스에는 이런 ReferenceGrant가 필요합니다:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-team-a-route
namespace: shared-services
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-a
to:
- group: ""
kind: Service쉽게 풀면:
team-a의HTTPRoute리소스가shared-services의Service리소스를 참조하도록 승인받았다
중요한 점은 이 승인이 소스가 아니라 대상 네임스페이스에서 만들어진다는 것입니다. 소스 쪽이 "접근하고 싶다"고 선언한다고 되는 게 아니라, 리소스 소유자가 결정합니다. 사실 꽤 원칙적인 설계입니다. 자기 리소스에 누가 접근할지 소유자가 통제해야 하니까요.
흔한 플랫폼 팀 / 앱 팀 분리 모델
가장 일반적인 소유 모델은 이렇습니다:
플랫폼 팀이 소유
GatewayClassGatewayListener- TLS 인증서 관리
allowedRoutes정책
앱 팀이 소유
- 각자 네임스페이스의
HTTPRoute/GRPCRoute - 백엔드 서비스
- 라우팅 규칙 조정
이 분리의 장점은 분명합니다:
- 플랫폼 팀은 ingress 노출과 보안 경계를 지킨다
- 앱 팀은 자신의 트래픽 규칙을 빠르게 바꿀 수 있다
- path 하나 바꾸려고 공유 Gateway를 건드릴 필요가 없다
물론 모든 권한을 한 그룹에 몰아주는 것도 틀린 건 아닙니다. 하지만 팀이 커질수록 그 모델은 보통 "누구나 바꿀 수 있으니, 깨졌을 때 누가 뭘 했는지 아무도 모른다"로 진화합니다.
실전 조언: 멀티 테넌트 재난 피하는 법
정말 유용한 습관들:
- 공유 Gateway는 기본값을
allowedRoutes.namespaces.from: Selector로 시작하기 - 네임스페이스 label 규칙은 안정적으로 유지하기. 다음 달에
team-a를allow-gw로 바꾸지 않기 - 네임스페이스 간 backend/Secret 참조는 모두
ReferenceGrant를 통과시키기 - Route 이름에 팀이나 서비스를 드러내기. 예:
team-a-api-route - 공유 listener에서는 route가 잘못된 입구에 붙지 않도록 항상
sectionName사용하기
붙잡아 둘 만한 원칙 하나:
ingress 계층에서 경계를 강제할 수 있다면, 문서와 자율 통제에만 기대지 마세요.
엔지니어링에서는 말로 한 약속보다 시스템화된 절차가 더 강합니다. 좀 연륜 있는 조언처럼 들릴 수 있지만, 실제로 꽤 유용합니다.
한 줄 요약
Gateway API에서 allowedRoutes는 "누가 들어올 수 있는지"를 제어하고, ReferenceGrant는 "들어온 뒤 무엇에 접근할 수 있는지"를 제어합니다.
이 둘만 명확히 구분해도 멀티 네임스페이스와 멀티 팀 시나리오는 훨씬 다루기 쉬워집니다.
다음 단계
소유권과 권한을 정리했으니, 마지막 퍼즐은 이것입니다: 설정이 반영되지 않을 때, 어떤 status와 어떤 condition, 어떤 명령을 먼저 봐야 할까?
다음 글에서는 디버깅과 status 해석을 다룹니다: 👉 상태와 디버깅 핸드북