Istio Ambient mode란?
Istio는 쿠버네티스 환경에서 서비스 간 통신을 제어하고, 보안과 관측 기능을 제공하는 Service Mesh 플랫폼이다.
Sidecar mode는 각 Pod에 Envoy 프록시를 주입하지만,
Ambient mode는 애플리케이션 Pod에 사이드카를 주입하지 않고, L3/L4와 L7 처리를 서로 다른 컴포넌트로 분리한다.
- ztunnel (L3/L4): 노드 단위로 동작하며 mTLS, 인증, L4 인가, L4 텔레메트리와 HBONE 터널을 처리한다.
- waypoint (L7): HTTP 라우팅, L7 인가, retry, timeout, tracing이 필요할 때 선택적으로 배치하는 Envoy 프록시다.
waypoint와 Istio Gateway는 모두 Envoy 기반이지만 처리하는 트래픽의 범위가 다르다.
waypoint는 mesh 내부에서 목적지 워크로드의 L7 정책을 처리하고,
Istio Gateway는 클러스터 외부에서 들어오는 트래픽의 진입점 역할을 한다.
이번 데모에서는 외부 트래픽의 라우팅과 인가 흐름을 전환하기 때문에 waypoint를 별도로 배치하지 않고 Istio Gateway를 사용한다.
Istio 전용 Gateway API vs Kubernetes Gateway API
여기서 말하는 Istio Gateway는 실제로는 두 가지 서로 다른 API로 구성할 수 있어서 구분이 필요하다.
| 구분 | Istio 전용 API | Kubernetes Gateway API |
|---|---|---|
| 리소스 | networking.istio.io Gateway + VirtualService | gateway.networking.k8s.io Gateway + HTTPRoute(TCPRoute 등) |
| 표준 여부 | Istio 전용 CRD | Kubernetes SIG 표준(여러 구현체가 공통 지원) |
| 프록시 프로비저닝 | 별도로 ingress gateway Deployment/Service를 설치, 관리해야 함 | GatewayClass 컨트롤러(istiod)가 Gateway 리소스를 감지해 Deployment/Service를 자동 생성 |
| 라우팅 규칙 정의 | VirtualService에서 정의 | HTTPRoute에서 정의, ReferenceGrant로 네임스페이스 간 위임 가능 |
| 이식성 | Istio에서만 동작 | Envoy Gateway, Cilium 등 다른 구현체로도 이식 가능 |
Istio는 향후 방향을 Kubernetes Gateway API 쪽으로 잡고 있지만, 기존 networking.istio.io Gateway API가 deprecated된 것은 아니고 현재도 계속 지원한다.
이번 데모는 gatewayClassName: istio로 Kubernetes Gateway API를 사용하며, Gateway 리소스를 선언하면 istiod가 이를 감지해서 Envoy 기반 Deployment/Service(demo-gw-istio)를 자동으로 생성한다.
아키텍처 / 구성 요소 역할
Ambient mode는 설정을 관리하는 Control Plane과 실제 트래픽을 처리하는 Data Plane으로 나뉜다.
istiod는 설정을 배포하고, ztunnel과 waypoint, Istio Gateway는 각 레이어에서 트래픽을 처리한다.

주요 컴포넌트 정리
| 컴포넌트 | 역할 |
|---|---|
| istiod | 설정과 정책을 관리하고, ztunnel과 Envoy 프록시에 xDS 설정을 배포. ztunnel이 사용할 워크로드 정보와 mTLS 인증서도 제공 |
| ztunnel | 노드 단위로 배치되는 L3/L4 프록시. mTLS, 인증, L4 인가, L4 텔레메트리를 처리하며 HTTP 헤더는 해석하지 않음 |
| waypoint | 애플리케이션 Pod 외부에 배치되는 Envoy 기반 L7 프록시. 목적지 워크로드의 HTTP 라우팅, L7 인가, 관측 기능을 처리 |
| istio-cni | Ambient mesh에 포함된 Pod의 network namespace에 리다이렉션 규칙을 구성하고 node-local ztunnel로 트래픽을 전달 |
| pilot-agent | waypoint와 Istio Gateway 안에서 Envoy bootstrap 설정과 lifecycle을 관리 |
| Istio Gateway | 클러스터 외부의 HTTP/TCP 요청을 받는 Envoy 기반 진입점. Gateway API의 Gateway와 HTTPRoute 설정을 반영 |
| HBONE | HTTP/2, HTTP CONNECT, mTLS를 조합해 여러 TCP stream을 하나의 암호화된 연결로 전달하는 터널 |
트래픽 흐름

그림의 경로를 트래픽 종류별로 정리하면 아래와 같다.
| 트래픽 | 경로 | 적용되는 정책 |
|---|---|---|
| 외부 요청 (기본) | Client → Istio Gateway → destination ztunnel → Pod | Gateway의 HTTPRoute(L7 라우팅), destination ztunnel의 L4 AuthorizationPolicy |
| 외부 요청 (waypoint 사용) | Client → Istio Gateway → waypoint → destination ztunnel → Pod | 위 정책 + waypoint의 L7 정책 |
| 내부 통신 (waypoint 없음) | Pod → source ztunnel → destination ztunnel → Pod | HBONE(mTLS), L4 정책 |
| 내부 통신 (waypoint 있음) | Pod → source ztunnel → waypoint → destination ztunnel → Pod | HBONE(mTLS), L4 정책 + waypoint의 L7 정책 |
Istio Gateway에서 시작한 트래픽은 기본적으로 waypoint를 거치지 않는데, 거치게 하려면 Service나 Namespace에 istio.io/ingress-use-waypoint: true 라벨을 붙이면 된다. istiod의 ENABLE_INGRESS_WAYPOINT_ROUTING 플래그도 필요하지만, Istio 1.30.1 소스 기준으로 ambient가 켜져 있으면(PILOT_ENABLE_AMBIENT=true) 기본값이 true라서 따로 켤 필요는 없다.
Quick Start - 로컬에서 Istio Ambient 설치하기
이번 데모는 kind(Kubernetes IN Docker) 로 구성했다.
kind는 노드 자체가 Docker 컨테이너라서 별도 VM(hypervisor 권한 승인 등)이 필요 없고,
Docker Desktop의 host.docker.internal을 그대로 활용할 수 있어 호스트의 docker-compose 컨테이너와 연동하기 더 단순하다.
Docker Desktop이 실행 중이어야 하며, Gateway와 HTTPRoute를 적용하려면 Kubernetes Gateway API CRD도 필요하다. 예제의 전환 스크립트는 CRD가 없을 때 설치까지 처리한다.
brew install kind kubectl istioctl
kind create cluster --name istio-demo
istioctl install --context kind-istio-demo --set profile=ambient -y
kubectl --context kind-istio-demo get pods -n istio-system
NAME READY STATUS RESTARTS AGE
istio-cni-node-xxxxx 1/1 Running 0 1m
istiod-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
ztunnel-xxxxx 1/1 Running 0 1m
내부 프로세스를 들여다보면 ztunnel은 ztunnel proxy ztunnel 명령으로 실행되는 단일 프로세스로 동작하고,
istiod 안에는 실제 설정 배포를 담당하는 pilot-discovery 프로세스가 있다.
Envoy 설정 구조 (참고)
Istio Gateway 내부는 결국 Envoy이기 때문에, 트래픽 흐름을 이해하려면 Envoy의 기본 구성 단위를 알아두면 도움이 된다.
- Listener: 어떤 포트/프로토콜에서 트래픽을 수신할지 정의
- Route: 수신한 요청을 어떤 Cluster로 보낼지 결정하는 라우팅 규칙
- Cluster: 라우팅 대상이 되는 논리적 서비스 그룹(로드밸런싱 대상)
- Endpoint: Cluster에 포함된 실제 인스턴스(Pod/IP/Port)
Demo 1 - 단순 라우팅을 Istio Gateway로 전환
가장 기본적인 시나리오부터 본다. 기존에는 애플리케이션(리버스 프록시 역할)이 백엔드 서비스를 직접 호출하고 응답을 가공해서 내려주는 구조였다고 가정하자.

| 역할 | AS-IS | TO-BE |
|---|---|---|
| L7 LB 진입점 | nginx(ALB 역할) | nginx(ALB 역할) |
| 역방향 프록시 | 애플리케이션(Spring Cloud Gateway) | 제거 |
| k8s 클러스터 진입 | X | port-forward |
| 진입점 / L7 Route | X | Istio Gateway + HTTPRoute |
| 백엔드 서비스 | A 서버 | A 서버 (Service/Endpoints로 등록) |
핵심은 애플리케이션 코드 변경 없이 라우팅 경로를 전환할 수 있는가다. AS-IS의 Gateway 애플리케이션은 A 서버 응답에 "Gateway Result -> " 접두어를 붙여서 내려준다. 이 접두어가 사라지고 백엔드 응답이 그대로 오면 Istio Gateway가 직접 라우팅했다는 시각적 증거가 된다.
HTTPRoute로 라우팅 규칙 정의
apiVersion: gateway.networking.k8s.io/v1 # k8s 표준 Gateway API (Istio 전용 API 아님)
kind: Gateway
metadata:
name: demo-gw
namespace: istio-demo
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route-api
namespace: istio-demo
spec:
parentRefs:
- name: demo-gw
hostnames:
- "external-a.local"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: external-a
port: 9000
gatewayClassName: istio로 선언하면 istiod가 이 리소스를 감지해서
demo-gw-istio 라는 Deployment + Service를 자동으로 만들어준다.
HTTPRoute 의 hostnames에 external-a.local을 지정했으므로, nginx가 Istio Gateway로 요청을 전달할 때 Host 헤더도 external-a.local로 설정해야 한다. Host 헤더가 일치하지 않으면 route-api가 매칭되지 않는다.
클러스터 외부(Docker)에 떠 있는 백엔드는 selector 없는 Service + Endpoints 조합으로 등록했다.
HTTPRoute 의 backendRef 를 일반 Kubernetes Service 로 맞추려고 고른 방법인데, Istio 는 group: networking.istio.io, kind: Hostname 으로 ServiceEntry 의 호스트를 backendRef 에 직접 지정하는 것도 지원한다.
AS-IS에서 TO-BE로 전환하는 과정
switch-simple-to-istio.sh는 다음 순서로 전환을 처리한다.
- Gateway API CRD가 설치되어 있는지 확인하고, 없으면 설치한다.
- 클러스터 외부(Docker)의 A 서버 IP를 조회해서 Service + Endpoints에 등록한다.
- Gateway, HTTPRoute를 적용하고
demo-gw-istioDeployment가 뜰 때까지 대기한다. kubectl port-forward로 Istio Gateway를 호스트의 18080 포트에 연결한다.- nginx 설정을 port-forward 경로로 교체해서 재시작한다.
전환 전후로 같은 요청을 보내보면 차이가 바로 드러난다. AS-IS에서는 Gateway 애플리케이션이 붙인 "Gateway Result -> " 접두어가 보이고, TO-BE에서는 그 접두어 없이 A 서버 응답이 그대로 온다.
curl http://localhost:8080/api
# Gateway Result -> A (external server) <- AS-IS
curl http://localhost:8080/api
# A (external server) <- TO-BE, prefix가 사라짐
Demo 2 - 인가(authz) 로직을 ext_authz로 전환
두 번째 시나리오는 좀 더 현실적이다. 기존 애플리케이션이 세션 검증 서비스를 호출해서 인가 여부를 판단하고, 통과하면 인가 헤더를 주입해서 백엔드를 호출하는 구조라고 가정한다.

핵심은 검증 로직을 애플리케이션 코드에서 인프라 레이어(Istio)로 옮기는 것이다.
Istio는 AuthorizationPolicy의 action: CUSTOM을 통해 rules에 매칭되는 /api 요청마다 외부 gRPC 서버(ext_authz)를 호출해서
허용/차단을 결정할 수 있다.
AuthorizationPolicy + ext_authz
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istio-demo-authz
spec:
selector:
matchLabels:
gateway.networking.k8s.io/gateway-name: demo-gw
action: CUSTOM
provider:
name: demo-authz-grpc
rules:
- to:
- operation:
paths: ["/api"]
provider.name은 istiod ConfigMap의 extensionProviders에 등록한 이름과 일치해야 한다.
switch-authz-to-istio.sh는 다음 순서로 전환을 처리한다.
- authz-server 이미지를 빌드하고 kind 클러스터에 로드한다.
- istiod ConfigMap에
demo-authz-grpcprovider를 등록한 뒤 istiod를 재시작한다. istiod 는 mesh config 를 감시해서 재시작 없이도 반영하지만, ConfigMap 이 파일로 마운트되어 있어 kubelet 이 바뀐 내용을 파일에 동기화할 때까지 시간이 걸리기 때문에 데모에서는 재시작으로 바로 반영한다. - authz-server Deployment, Service + Endpoints, Gateway, HTTPRoute, AuthorizationPolicy를 순서대로 적용한다.
kubectl port-forward로 Istio Gateway를 호스트의 18080 포트에 연결한다.- nginx 설정을 port-forward 경로로 교체해서 재시작한다.
extensionProviders:
- name: demo-authz-grpc
envoyExtAuthzGrpc:
service: authz-server.istio-demo-authz.svc.cluster.local
port: "9001"
ext_authz gRPC 서버는 Envoy의 Authorization v3 인터페이스(AuthorizationGrpc.AuthorizationImplBase)를 구현하면 된다.
Spring Boot + Spring gRPC + io.envoyproxy.controlplane:api를 쓰면 별도 proto 컴파일 없이 바로 구현할 수 있다.
@Service
public class ExtAuthzGrpcService extends AuthorizationGrpc.AuthorizationImplBase {
@Override
public void check(CheckRequest request, StreamObserver<CheckResponse> responseObserver) {
String demoUser = request.getAttributes()
.getRequest().getHttp()
.getHeadersOrDefault("x-demo-user", "");
if (isAllowed(demoUser)) {
responseObserver.onNext(allow(demoUser)); // OkHttpResponse + 헤더 주입
} else {
responseObserver.onNext(deny()); // DeniedHttpResponse(403)
}
responseObserver.onCompleted();
}
// ...
}
허용 시에는 OkHttpResponse에 헤더를 추가해서 Envoy가 업스트림 요청에 그대로 주입하게 하고, 차단 시에는 DeniedHttpResponse로 업스트림 호출 없이 즉시 403을 반환한다. AS-IS에서 애플리케이션이 직접 헤더를 주입하던 역할을 Envoy가 대신 수행하는 셈이다.
AS-IS와 TO-BE 응답 비교
x-demo-user 헤더 없이 요청하면 AS-IS와 TO-BE 모두 403으로 차단되고, 헤더를 채워서 요청하면 둘 다 동일하게 B 서버까지 도달한다.
# AS-IS
curl -v http://localhost:8080/api # 403 (session-svc 차단)
curl -H 'x-demo-user: alice' http://localhost:8080/api # 허용
# TO-BE (switch-authz-to-istio.sh 전환 후)
curl -v http://localhost:8080/api # 403 (ext_authz 차단)
curl -H 'x-demo-user: alice' http://localhost:8080/api # 허용, 응답 헤더 동일
AS-IS와 TO-BE 모두 x-authz-user: alice, x-authz-result: allow 헤더가 동일하게 찍히는 걸 확인할 수 있다. ext_authz로 옮긴 인가 로직이 기존 애플리케이션과 같은 헤더 계약을 그대로 유지한다는 뜻이다.
x-request-id 값은 달라지는데, 누가 어떤 형식으로 ID 를 만드느냐가 다르기 때문이다.
- AS-IS:
SessionAuthzGatewayFilter가 들어온X-Request-Id가 있으면 그대로 쓰고, 없으면"req-" + UUID형식으로 직접 만든다. - TO-BE: 요청을 받은 Istio Gateway(Envoy)의 HTTP Connection Manager 가 기본 옵션(
generate_request_id)으로 UUID 를 만든다.
이 순서가 중요한데, Envoy 는 요청을 받자마자 ID 를 먼저 만들고 그 다음에 ext_authz 필터를 호출한다.
Envoy 를 따로 띄워서(ext_authz HTTP 서비스의 allowed_headers 에 x-request-id 를 추가) 인가 서버와 업스트림이 받은 헤더를 찍어보면 아래와 같다.
| 클라이언트 요청 | ext_authz 서버가 받은 x-request-id | 업스트림이 받은 x-request-id |
|---|---|---|
| 헤더 없음 | 45485e6d-2846-4ab5-b21f-f16a7a39c1fb | 45485e6d-2846-4ab5-b21f-f16a7a39c1fb |
x-request-id: req-from-client | req-from-client | req-from-client |
ext_authz 서버가 받는 시점에 이미 ID 가 들어 있고, 데모의 ExtAuthzGrpcService 는 이 헤더를 건드리지 않으니 그대로 업스트림까지 간다.
즉 ext_authz 서버가 헤더를 보존하지 않아서 생긴 차이가 아니라, AS-IS 는 애플리케이션이, TO-BE 는 Envoy 가 ID 를 만든다는 차이다.
실무에서 인가 로직을 인프라로 옮길 때는 이런 식으로 "애플리케이션이 암묵적으로 하던 일(헤더 생성 형식, 보존 등)"이 바뀌지 않는지 한 번씩 점검할 필요가 있다.
로그나 트레이싱에서 req- 접두어로 ID 를 구분하고 있었다면, 전환 후에는 그 규칙이 깨진다.
정리
예제 코드
이 글에서 다룬 코드와 실행 스크립트는 let-me-code 저장소의 istio-ambient-demo 에서 확인할 수 있다.
- gateway - AS-IS 비교군(Spring Cloud Gateway, simple/authz 프로필)
- authz-server - ext_authz gRPC 서버(Spring Boot + Spring gRPC)
- k8s - Namespace, Gateway, HTTPRoute, AuthorizationPolicy 매니페스트
- run - 데모 실행, 전환, 정리 스크립트
📚 Reference
- Istio Ambient mode 공식 문서
- Istio Ambient data plane
- Istio ztunnel traffic redirection
- Istio Waypoint proxy
- Istio HBONE
- Kubernetes Gateway API
- Istio AuthorizationPolicy
- Envoy ext_authz filter
- kind (Kubernetes IN Docker)