We Added a Waypoint and Got 503
한국어 원문으로 표시합니다.
목표
k3s 위의 진짜 앰비언트 메시에서 ztunnel 이 강제하는 L4 와 waypoint 가 강제하는 L7 을 나눠 겪고, waypoint 를 붙일 때 함께 고쳐야 하는 것을 요청과 로그로 확인한다.
왜 중요한가
앰비언트는 사이드카를 없애 가볍지만, 정책과 라우팅이 두 층(ztunnel·waypoint)으로 나뉜다. 어느 층이 무엇을 보는지 모르면 HTTP 규칙이 조용히 빠지고, waypoint 를 붙이는 순간 멀쩡하던 트래픽이 끊기며, 파드 IP 로 오는 요청이 L7 정책을 우회한다. 셋 다 매니페스트만으로는 보이지 않아서 진짜 메시에서 겪어 봐야 남는다.
단계
kubectl apply -f /opt/fixtures/istlab/ambient-app.yaml로 재료를 올리고 파드가 준비될 때까지 기다린 뒤, 네임스페이스shop에istio.io/dataplane-mode=ambient라벨을 붙이세요(파드를 다시 만들지 않습니다). 그다음/root/istlab-ambient/01-enroll.txt에web_containers=(web-v1 파드의 컨테이너 이름을 쉼표로)와protocol=(istioctl ztunnel-config workloads에서 shop 파드들의 PROTOCOL 칸) 두 줄을 적으세요.- client 에서
http://web/을 한 번 부르고, 그 연결을 기록한 ztunnel 접근 로그 줄(메시지가connection complete이고src.identity가 client,dst.identity가 web 인 줄) 하나를 그대로/root/istlab-ambient/02-ztunnel.log에 저장하세요. /root/istlab-ambient/web-l4.yaml에AuthorizationPolicyweb-l4(네임스페이스shop, 셀렉터app: web,ALLOW, 주체cluster.local/ns/shop/sa/client)를 적어 적용하세요. 적용 뒤 client 와 outside 의 stranger 에서 각각http://web.shop/을 부르고/root/istlab-ambient/03-l4.txt에client=(상태 코드)와stranger_exit=(stranger 쪽 curl 의 종료 코드) 두 줄을 적으세요./root/istlab-ambient/l7-wrong.yaml에AuthorizationPolicyweb-get-only-l4(셀렉터app: web,ALLOW, 주체 client, 메서드 GET)를 적어 적용하고, 그 정책의status.conditions에서ZtunnelAccepted조건의reason을/root/istlab-ambient/04-l7.txt에status_reason=으로, client 의POST요청 상태 코드를post_code=로 적으세요. 기록한 뒤 이 정책을 지웁니다.istioctl waypoint apply -n shop --enroll-namespace --wait로 shop 에 waypoint 를 붙이세요. 그 직후 client 의http://web/상태 코드를/root/istlab-ambient/05-waypoint.txt에code_after_waypoint=로 적고, 원인을 고치세요 —/root/istlab-ambient/web-l4.yaml의 허용 주체를 waypoint 의 신원cluster.local/ns/shop/sa/waypoint하나로 바꿔 다시 적용합니다. 고친 뒤 client 의http://web/은 200, client 가 web-v1 파드 IP 로 직접 부르면 연결이 끊겨야 합니다./root/istlab-ambient/web-get-only.yaml에AuthorizationPolicyweb-get-only를 적어 적용하세요 —targetRefs로 서비스web(group"", kindService)을 가리키고,ALLOW, 주체cluster.local/ns/shop/sa/client, 메서드GET. 적용 뒤 client 의 GET 은 200, POST 는 403 이어야 합니다./root/istlab-ambient/httproute.yaml에HTTPRouteweb을 적어 적용하세요 — 부모는 서비스web(group"", kindService, 포트 80), 규칙 둘: 헤더x-canary: yes면web-v2, 그 밖에는web-v1. 적용 뒤 client 의x-canary: yes요청은 v2, 헤더 없는 요청은 늘 v1 이어야 합니다./root/istlab-ambient/08-report.md에 여섯 줄 —protocol=(1단계),l7_without_waypoint=(4단계에서 GET 규칙이 강제됐으면enforced, 빠졌으면ignored),broke_with_waypoint=(5단계 code_after_waypoint),l4_allowed_principal=(지금 web-l4 가 허용하는 주체),post_via_waypoint=(지금 client 의 POST 상태 코드),direct_pod_ip=(지금 client 가 파드 IP 로 직접 부르면allowed또는rejected) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
참고
- 이 VM 은 준비에 2~4분 걸립니다. ambient 프로파일(istiod·istio-cni·ztunnel)이
values.global.platform=k3s로 설치되어 있습니다. - 앰비언트 파드에는 사이드카가 없어
kubectl -n shop exec client -- curl …처럼 컨테이너를 지정하지 않아도 됩니다. - ztunnel 이 아는 워크로드·서비스는
istioctl ztunnel-config workloads·istioctl ztunnel-config services로, ztunnel 로그는kubectl -n istio-system logs ds/ztunnel로, waypoint 로그는kubectl -n shop logs deploy/waypoint로 봅니다. - 정책을 바꾼 직후에는 ztunnel·waypoint 로 퍼지는 데 몇 초가 걸립니다.
- 흔한 실수 — HTTP 조건(메서드·경로)을 셀렉터 정책에 쓰는 것. waypoint 정책은
targetRefs로 붙입니다.
라벨 하나로 메시에 넣는다 — 재시작 없이
kubectl apply -f /opt/fixtures/istlab/ambient-app.yaml 로 재료를 올리고 파드가 준비될 때까지 기다린 뒤, 네임스페이스 shop 에 istio.io/dataplane-mode=ambient 라벨을 붙이세요(파드를 다시 만들지 않습니다). 그다음 /root/istlab-ambient/01-enroll.txt 에 web_containers=(web-v1 파드의 컨테이너 이름을 쉼표로)와 protocol=(istioctl ztunnel-config workloads 에서 shop 파드들의 PROTOCOL 칸) 두 줄을 적으세요.
앰비언트에서는 istio-cni 노드 에이전트가 파드의 네트워크 네임스페이스 안에 가로채기 규칙을 심고, 노드마다 하나 있는 ztunnel 이 그 트래픽을 받습니다. 사이드카를 주입하지 않으니 파드를 다시 만들 필요가 없습니다. 편입된 파드는 애너테이션 ambient.istio.io/redirection: enabled 가 붙고, ztunnel 이 아는 워크로드 목록에서 프로토콜이 HBONE 으로 바뀝니다. 메시 밖 stranger 는 TCP 로 남습니다.
ztunnel 이 본 두 신원
client 에서 http://web/ 을 한 번 부르고, 그 연결을 기록한 ztunnel 접근 로그 줄(메시지가 connection complete 이고 src.identity 가 client, dst.identity 가 web 인 줄) 하나를 그대로 /root/istlab-ambient/02-ztunnel.log 에 저장하세요.
앰비언트의 mTLS 는 ztunnel 끼리 HBONE(HTTP CONNECT 위의 mTLS 터널, 포트 15008)으로 맺습니다. ztunnel 은 L4 프록시라 요청 경로나 메서드는 모르지만 양쪽 워크로드의 SPIFFE 신원은 압니다. 로그는 kubectl -n istio-system logs ds/ztunnel 로 보고, 한 연결이 출발 쪽과 도착 쪽에서 한 줄씩 남을 수 있습니다. dst.hbone_addr 가 붙은 줄이 HBONE 으로 들어간 연결입니다.
ztunnel 이 강제하는 L4 정책
/root/istlab-ambient/web-l4.yaml 에 AuthorizationPolicy web-l4(네임스페이스 shop, 셀렉터 app: web, ALLOW, 주체 cluster.local/ns/shop/sa/client)를 적어 적용하세요. 적용 뒤 client 와 outside 의 stranger 에서 각각 http://web.shop/ 을 부르고 /root/istlab-ambient/03-l4.txt 에 client=(상태 코드)와 stranger_exit=(stranger 쪽 curl 의 종료 코드) 두 줄을 적으세요.
셀렉터로 붙인 정책은 그 파드의 ztunnel 이 강제합니다. ztunnel 은 연결을 받는 순간 출발지 신원을 보고 허용 여부를 정하므로, 거절은 HTTP 403 이 아니라 연결 끊김(curl 종료 코드 56)으로 나타납니다. 메시 밖의 stranger 는 신원이 없어 어떤 주체 규칙에도 맞지 않습니다. 종료 코드는 …; echo $? 로 봅니다.
waypoint 없이 쓴 HTTP 규칙은 어떻게 되나
/root/istlab-ambient/l7-wrong.yaml 에 AuthorizationPolicy web-get-only-l4(셀렉터 app: web, ALLOW, 주체 client, 메서드 GET)를 적어 적용하고, 그 정책의 status.conditions 에서 ZtunnelAccepted 조건의 reason 을 /root/istlab-ambient/04-l7.txt 에 status_reason= 으로, client 의 POST 요청 상태 코드를 post_code= 로 적으세요. 기록한 뒤 이 정책을 지웁니다.
ztunnel 은 HTTP 를 해석하지 않습니다. 메서드·경로 같은 조건이 든 규칙을 셀렉터 정책으로 주면 istiod 는 그 규칙을 빼고 ztunnel 에 내려보내며, 정책의 상태에 그 사실을 적어 둡니다. ALLOW 정책끼리는 '하나라도 맞으면 허용' 이라, 이미 있는 web-l4 가 client 를 허용하고 있으면 POST 도 통과합니다 — GET 만 허용한다고 적었는데 POST 가 되는 것입니다. 상태는 kubectl -n shop get authorizationpolicy web-get-only-l4 -o yaml 로 봅니다.
waypoint 를 붙이자 멀쩡하던 요청이 503
istioctl waypoint apply -n shop --enroll-namespace --wait 로 shop 에 waypoint 를 붙이세요. 그 직후 client 의 http://web/ 상태 코드를 /root/istlab-ambient/05-waypoint.txt 에 code_after_waypoint= 로 적고, 원인을 고치세요 — /root/istlab-ambient/web-l4.yaml 의 허용 주체를 waypoint 의 신원 cluster.local/ns/shop/sa/waypoint 하나로 바꿔 다시 적용합니다. 고친 뒤 client 의 http://web/ 은 200, client 가 web-v1 파드 IP 로 직접 부르면 연결이 끊겨야 합니다.
waypoint 가 생기면 서비스로 가는 요청은 client 의 ztunnel → waypoint → web 의 ztunnel 순으로 갑니다. 그래서 web 의 ztunnel 이 보는 출발지는 client 가 아니라 waypoint 입니다. client 만 허용하던 L4 정책이 그 연결을 끊고, waypoint 는 503 을 돌려줍니다(waypoint 로그의 tunnel_response:401). waypoint 신원만 허용하면 서비스로 오는 길은 열리고, waypoint 를 거치지 않는 길(파드 IP 로 직접)은 닫힙니다 — 서비스용 waypoint 는 파드 IP 로 온 요청을 거치지 않기 때문에, 이렇게 닫아야 7단계의 L7 정책을 우회하지 못합니다.
HTTP 규칙은 waypoint 에 건다
/root/istlab-ambient/web-get-only.yaml 에 AuthorizationPolicy web-get-only 를 적어 적용하세요 — targetRefs 로 서비스 web(group "", kind Service)을 가리키고, ALLOW, 주체 cluster.local/ns/shop/sa/client, 메서드 GET. 적용 뒤 client 의 GET 은 200, POST 는 403 이어야 합니다.
waypoint 는 Envoy 라 HTTP 를 해석합니다. 정책을 waypoint 에 붙이는 방법은 셀렉터가 아니라 targetRefs 입니다 — 서비스를 가리키면 그 서비스로 가는 요청을 처리하는 waypoint 가 강제합니다. waypoint 에서 보는 출발지는 client 자신이므로 주체는 client 로 적습니다. 거절은 이번에는 연결 끊김이 아니라 HTTP 403 (RBAC: access denied)입니다.
waypoint 가 라우팅도 맡는다 — HTTPRoute
/root/istlab-ambient/httproute.yaml 에 HTTPRoute web 을 적어 적용하세요 — 부모는 서비스 web(group "", kind Service, 포트 80), 규칙 둘: 헤더 x-canary: yes 면 web-v2, 그 밖에는 web-v1. 적용 뒤 client 의 x-canary: yes 요청은 v2, 헤더 없는 요청은 늘 v1 이어야 합니다.
앰비언트에서는 메시 안 라우팅도 Gateway API 로 적습니다. 부모가 Gateway 가 아니라 서비스라는 점이 인그레스와 다릅니다(GAMMA). 이 라우트를 실제로 처리하는 것은 web 의 waypoint 라서, waypoint 가 없으면 라우트가 적용되지 않습니다. 붙었는지는 HTTPRoute 상태의 ResolvedWaypoints 조건으로 봅니다.
L4 와 L7 을 누가 맡는지 정리한다
/root/istlab-ambient/08-report.md 에 여섯 줄 — protocol=(1단계), l7_without_waypoint=(4단계에서 GET 규칙이 강제됐으면 enforced, 빠졌으면 ignored), broke_with_waypoint=(5단계 code_after_waypoint), l4_allowed_principal=(지금 web-l4 가 허용하는 주체), post_via_waypoint=(지금 client 의 POST 상태 코드), direct_pod_ip=(지금 client 가 파드 IP 로 직접 부르면 allowed 또는 rejected) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
앞 단계 기록과 지금의 상태를 함께 보세요. 설명 줄에는 'ztunnel 과 waypoint 가 각각 무엇을 보고 무엇을 강제하는가' 와 'waypoint 를 붙일 때 L4 정책을 왜 함께 고쳐야 하는가' 를 적어 두세요.