가시다(gasida) 님이 진행하는 Istio Hands-on Study 1기 과정을 참여하여 정리한 글입니다.
9주차는 Ambient Mesh 주제로 학습을 하였습니다.
Ambient Mesh를 EKS v1.32와 Istio v1.26.1 환경에서 실습한 내용을 정리하였습니다.
(ingress-gateway 대신 Gateway API로 ALB 이용)
Ambient 모드에서 Istio는 노드마다 배포되는 L4(레이어 4) 프록시와, 선택적으로 네임스페이스마다 배포되는 L7(레이어 7) 프록시를 사용하여 기능을 구현합니다.
이러한 계층적 접근 방식은 Istio를 점진적으로 도입할 수 있도록 해주며, 메쉬가 없는 상태에서 시작해 보안이 적용된 L4 오버레이를 거쳐, 필요에 따라 네임스페이스 단위로 L7 처리 및 정책 적용까지 원활하게 전환할 수 있게 합니다.
또한, 서로 다른 Istio 데이터 플레인 모드에서 실행 중인 워크로드 간에도 원활하게 상호 운용이 가능하므로, 사용자는 시간이 지나며 요구 사항이 변화함에 따라 다양한 기능을 조합해 사용할 수 있습니다.
워크로드 파드에 더 이상 사이드카로 실행되는 프록시가 필요하지 않기 때문에, ambient 모드는 비공식적으로 “사이드카 없는 메쉬(sidecar-less mesh)”라고 불리기도 합니다.

Ambient 모드는 Istio의 기능을 두 개의 명확한 계층으로 분리합니다.
기본 계층에서는 ztunnel 보안 오버레이가 트래픽의 라우팅과 제로 트러스트 보안을 처리합니다.
그 위의 계층에서는 필요할 경우 L7 웨이포인트 프록시(waypoint proxy)를 활성화하여 Istio의 전체 기능을 사용할 수 있습니다. 웨이포인트 프록시는 ztunnel만 사용하는 경우보다 무겁지만, 여전히 인프라의 ambient 구성 요소로 실행되며, 애플리케이션 파드에는 어떠한 수정도 필요하지 않습니다.
사이드카 모드를 사용하는 파드 및 워크로드는 ambient 모드를 사용하는 파드와 동일한 메쉬 내에서 공존할 수 있습니다.
“Ambient 메쉬”라는 용어는 ambient 모드를 지원하도록 설치된 Istio 메쉬를 의미하며, 사이드카 방식 또는 ambient 방식 중 어느 데이터 플레인 방식이든 사용하는 메쉬 파드를 지원할 수 있습니다.
Ambient 모드의 설계 및 Istio 컨트롤 플레인과의 상호 작용에 대한 자세한 내용은 데이터 플레인 및 컨트롤 플레인 아키텍처 문서를 참고하세요.
ztunnel(Zero Trust tunnel) 컴포넌트는 Istio의 Ambient 데이터 플레인 모드를 구동하는, 노드마다 배포되는 특수 목적의 프록시입니다.
ztunnel은 메쉬 내에서 워크로드 간의 보안 연결 및 인증을 담당합니다.
ztunnel 프록시는 Rust로 작성되었으며, mTLS, 인증, L4 권한 부여, 텔레메트리 등 L3 및 L4 기능만을 처리하도록 설계되었습니다.
ztunnel은 워크로드의 HTTP 트래픽을 종료하거나 HTTP 헤더를 파싱하지 않습니다.
ztunnel은 L3 및 L4 트래픽이 워크로드, 다른 ztunnel 프록시, 또는 웨이포인트 프록시로 효율적이고 안전하게 전송되도록 보장합니다.
“보안 오버레이(secure overlay)”라는 용어는 ambient 메쉬에서 ztunnel 프록시를 통해 구현되는 L4 네트워크 기능 집합을 통칭합니다.
이 전송 계층은 HBONE(HTTP-Based Overlay Network Environment)이라 불리는 HTTP CONNECT 기반의 트래픽 터널링 프로토콜로 구현됩니다.
웨이포인트 프록시(Waypoint Proxy)는 Istio의 사이드카 데이터 플레인 모드에서도 사용하는 Envoy 프록시를 기반으로 한 배포 방식입니다.
웨이포인트 프록시는 애플리케이션 파드 외부에서 실행되며, 애플리케이션과 독립적으로 설치, 업그레이드, 확장할 수 있습니다.
일부 Ambient 모드 사용 사례는 L4 보안 오버레이 기능만으로 충분하며, 이 경우 L7 기능이 필요 없기 때문에 웨이포인트 프록시를 배포하지 않아도 됩니다.
반면, 고급 트래픽 관리 및 L7 네트워크 기능이 필요한 경우에는 웨이포인트 프록시의 배포가 필요합니다.
| 애플리케이션 배포 사용 사례 | Ambient 모드 구성 |
|---|---|
| 상호 TLS를 통한 제로 트러스트 네트워킹, 클라이언트 애플리케이션 트래픽의 암호화 및 터널링 전송, L4 권한 부여, L4 텔레메트리 | ztunnel만 사용 (기본값) |
| 위 기능에 더해 고급 Istio 트래픽 관리 기능 (L7 권한 부여, 텔레메트리, VirtualService 기반 라우팅 포함) | ztunnel + 웨이포인트 프록시 사용 |
istioctl이라는 명령줄 도구를 사용하여 설정합니다. 이 도구와 샘플 애플리케이션을 다운로드하세요:$ curl -L https://istio.io/downloadIstio | sh -
$ cd istio-1.26.1
$ export PATH=$PWD/bin:$PATH
istioctl 명령어가 정상 작동하는지 버전을 출력하여 확인하세요.$ istioctl version
Istio is not present in the cluster: no running Istio pods in namespace "istio-system"
client version: 1.26.1
istioctl은 여러 구성 프로파일을 지원하며, 각기 다른 기본 옵션을 포함합니다.ambient 프로파일에 포함되어 있습니다.$ istioctl install --set profile=ambient --skip-confirmation
설치가 완료되면 다음과 같은 출력 결과를 통해 모든 컴포넌트가 성공적으로 설치되었음을 확인할 수 있습니다:
✔ Istio core installed
✔ Istiod installed
✔ CNI installed
✔ Ztunnel installed
✔ Installation complete
$ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
git clone https://github.com/aws-samples/istio-on-eks.git
cd istio-on-eks/terraform-blueprint/ambient
# main.tf 수정 (서울리전, 노드스펙다운)
locals {
name = "istio-sejkim" # basename(path.cwd)
region = "ap-northeast-2" # us-west-2
...
eks_managed_node_groups = {
ng-sejkim = { # initial = {
instance_types = ["t3.medium"] # ["m5.large"]
min_size = 2 # 1
max_size = 5
desired_size = 2
...
# main.tf 수정 (추후 gateway-api 배포 위해 istio-ingress는 주석처리)
# istio-ingress = {
# chart = "gateway"
# chart_version = local.istio_chart_version
# repository = local.istio_chart_url
# name = "istio-ingress"
# namespace = "istio-ingress" # per https://github.com/istio/istio/blob/master/manifests/charts/gateways/istio-ingress/values.yaml#L2
# create_namespace = true
# values = [
# yamlencode(
# {
# labels = {
# istio = "ingressgateway"
# }
# service = {
# annotations = {
# "service.beta.kubernetes.io/aws-load-balancer-type" = "external"
# "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
# "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
# "service.beta.kubernetes.io/aws-load-balancer-attributes" = "load_balancing.cross_zone.enabled=true"
# }
# }
# }
# )
# ]
# }
❯ terraform apply -target='module.vpc' -auto-approve
❯ terraform apply -target='module.eks' -auto-approve
❯ aws eks --region ap-northeast-2 update-kubeconfig --name istio-sejkim
❯ terraform apply -target='module.eks_blueprints_addons' -auto-approve
❯ terraform apply -auto-approve
# eks 1.32
❯ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-10-0-27-246.ap-northeast-2.compute.internal Ready <none> 9m46s v1.32.3-eks-473151a 10.0.27.246 <none> Amazon Linux 2023.7.20250512 6.1.134-152.225.amzn2023.x86_64 containerd://1.7.27
ip-10-0-40-177.ap-northeast-2.compute.internal Ready <none> 9m45s v1.32.3-eks-473151a 10.0.40.177 <none> Amazon Linux 2023.7.20250512 6.1.134-152.225.amzn2023.x86_64 containerd://1.7.27
❯ terraform state list | grep helm
module.eks_blueprints_addons.helm_release.this["istio-base"]
module.eks_blueprints_addons.helm_release.this["istio-cni"]
module.eks_blueprints_addons.helm_release.this["istiod"]
module.eks_blueprints_addons.helm_release.this["ztunnel"]
module.eks_blueprints_addons.module.aws_load_balancer_controller.helm_release.this[0]
# istio 1.26.1, istio-ingress gateway는 설치 안함)
❯ helm -n istio-system list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-base istio-system 1 2025-06-03 12:37:09.922264 +0900 KST deployed base-1.26.1 1.26.1
istio-cni istio-system 1 2025-06-03 12:37:07.079489 +0900 KST deployed cni-1.26.1 1.26.1
istiod istio-system 1 2025-06-03 12:37:02.573573 +0900 KST deployed istiod-1.26.1 1.26.1
ztunnel istio-system 1 2025-06-03 12:37:04.649074 +0900 KST deployed ztunnel-1.26.1 1.26.1
❯ kubectl get pod,svc,ep -n istio-system
NAME READY STATUS RESTARTS AGE
pod/istio-cni-node-b2cgm 1/1 Running 0 7m51s
pod/istio-cni-node-qz65k 1/1 Running 0 7m51s
pod/istiod-7d56d75f5b-pxq6n 1/1 Running 0 7m54s
pod/ztunnel-4vr7d 1/1 Running 0 7m53s
pod/ztunnel-7wstc 1/1 Running 0 7m53s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/istiod ClusterIP 172.20.142.108 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 7m54s
NAME ENDPOINTS AGE
endpoints/istiod 10.0.42.144:15012,10.0.42.144:15010,10.0.42.144:15017 + 1 more... 7m54s
❯ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created
# istio resouce 확인
❯ kubectl api-resources | grep istio
wasmplugins extensions.istio.io/v1alpha1 true WasmPlugin
destinationrules dr networking.istio.io/v1 true DestinationRule
envoyfilters networking.istio.io/v1alpha3 true EnvoyFilter
gateways gw networking.istio.io/v1 true Gateway
proxyconfigs networking.istio.io/v1beta1 true ProxyConfig
serviceentries se networking.istio.io/v1 true ServiceEntry
sidecars networking.istio.io/v1 true Sidecar
virtualservices vs networking.istio.io/v1 true VirtualService
workloadentries we networking.istio.io/v1 true WorkloadEntry
workloadgroups wg networking.istio.io/v1 true WorkloadGroup
authorizationpolicies ap security.istio.io/v1 true AuthorizationPolicy
peerauthentications pa security.istio.io/v1 true PeerAuthentication
requestauthentications ra security.istio.io/v1 true RequestAuthentication
telemetries telemetry telemetry.istio.io/v1 true Telemetry
# gateway class
❯ kubectl get gatewayclasses.gateway.networking.k8s.io
NAME CONTROLLER ACCEPTED AGE
istio istio.io/gateway-controller True 3h11m
istio-remote istio.io/unmanaged-gateway True 3h11m
istio-waypoint istio.io/mesh-controller True 3h11m

❯ kubectl create namespace bookinfo
namespace/bookinfo created
❯ kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfo
❯ kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/bookinfo/platform/kube/bookinfo-versions.yaml -n bookinfo
❯ kubectl get pods -n bookinfo
NAME READY STATUS RESTARTS AGE
productpage-v1-54bb874995-h9tvr 1/1 Running 0 3m33s
details-v1-766844796b-smp85 1/1 Running 0 3m34s
reviews-v1-598b896c9d-t9n2j 1/1 Running 0 3m34s
reviews-v2-556d6457d-ccfc6 1/1 Running 0 3m34s
reviews-v3-564544b4d6-f978n 1/1 Running 0 3m34s
ratings-v1-5dc79b6bcd-w96zf 1/1 Running 0 3m34s
❯ cat <<EOF > gateway-httproute-bookinfo.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: bookinfo
annotations:
external-dns.alpha.kubernetes.io/hostname: "bookinfo.ksj7279.click"
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:ap-northeast-2:1**********3:certificate/415404eb-e2e2-4744-b2e4-1108735b5903"
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: bookinfo
namespace: bookinfo
spec:
parentRefs:
- name: bookinfo-gateway
hostnames:
- "bookinfo.ksj7279.click"
rules:
- matches:
- path:
type: Exact
value: /productpage
- path:
type: PathPrefix
value: /static
- path:
type: Exact
value: /login
- path:
type: Exact
value: /logout
- path:
type: PathPrefix
value: /api/v1/products
backendRefs:
- name: productpage
port: 9080
EOF
❯ kubectl apply -f gateway-httproute-bookinfo.yaml
gateway.gateway.networking.k8s.io/istio-gateway created
httproute.gateway.networking.k8s.io/bookinfo created
❯ kubectl -n bookinfo get gtw,httproute
NAME CLASS ADDRESS PROGRAMMED AGE
gateway.gateway.networking.k8s.io/istio-gateway istio k8s-bookinfo-istiogat-fbc8749077-dda4c1959031124a.elb.ap-northeast-2.amazonaws.com True 6m3s
NAME HOSTNAMES AGE
httproute.gateway.networking.k8s.io/bookinfo ["bookinfo.ksj7279.click"] 6m3s
❯ kubectl -n bookinfo get svc | grep gateway
bookinfo-gateway-istio LoadBalancer 172.20.27.47 k8s-bookinfo-istiogat-fbc8749077-dda4c1959031124a.elb.ap-northeast-2.amazonaws.com 15021:31898/TCP,80:31258/TCP,443:31608/TCP 6m57s
# Gateway LB IP 확인 및 /etc/hosts에 추가
LB_IP=`dig k8s-bookinfo-istiogat-fbc8749077-dda4c1959031124a.elb.ap-northeast-2.amazonaws.com +short`
❯ echo "$LB_IP bookinfo.ksj7279.click" | sudo tee -a /etc/hosts
43.202.125.139 bookinfo.ksj7279.click
# 어플리케이션 접속
❯ curl -I https://bookinfo.ksj7279.click/productpage
HTTP/1.1 200 OK
server: istio-envoy
date: Tue, 03 Jun 2025 07:30:02 GMT
content-type: text/html; charset=utf-8
content-length: 15070
vary: Cookie
x-envoy-upstream-service-time: 165
# 반복 호출
❯ for i in $(seq 1 10000); do curl -sSI -o /dev/null https://bookinfo.ksj7279.click/productpage; done
or
watch -n 1000 'curl -o /dev/null -s -w "%{http_code}\n" https://bookinfo.ksj7279.click/productpage'
Application LoadBalancer 화면

bookinfo 어플리케이션 웹접속 화면

Kiali 접속
❯ kubectl port-forward svc/kiali 20001:20001 -n istio-system &
❯ open http://localhost:20001 ## 아직 Kiali 화면에선 bookinfo 보이지 않음
❯ kubectl label namespace bookinfo istio.io/dataplane-mode=ambient
namespace/bookinfo labeled
🎉 이제 bookinfo 네임스페이스에 있는 모든 파드가 재배포 없이 Ambient Mesh에 성공적으로 추가되었습니다.
브라우저에서 Bookinfo 애플리케이션을 열면 이전과 마찬가지로 제품 페이지가 나타납니다.
하지만 이번에는 Bookinfo 애플리케이션의 파드 간 통신이 mTLS를 사용해 암호화된다는 점이 다릅니다.
또한, Istio가 파드 간 모든 트래픽에 대해 TCP 텔레메트리를 수집하고 있습니다.

이제 모든 파드 간에 mTLS 암호화가 적용되었습니다 — 애플리케이션을 재시작하거나 재배포하지도 않았는데 말이죠!
❯ for ADDON in kiali jaeger prometheus grafana
do
ADDON_URL="https://raw.githubusercontent.com/istio/istio/release-1.22/samples/addons/$ADDON.yaml"
kubectl apply -f $ADDON_URL
done
❯ kubectl get pods,svc -n istio-system
NAME READY STATUS RESTARTS AGE
pod/grafana-bdbb6d879-2fm82 1/1 Running 0 119s
pod/istio-cni-node-b2cgm 1/1 Running 0 30m
pod/istio-cni-node-qz65k 1/1 Running 0 30m
pod/istiod-7d56d75f5b-pxq6n 1/1 Running 0 30m
pod/jaeger-7b5f69d965-tm7m5 1/1 Running 0 2m3s
pod/kiali-9968cd59d-hxwmx 1/1 Running 0 2m4s
pod/prometheus-644fc9cd87-sxwcp 2/2 Running 0 2m1s
pod/ztunnel-4vr7d 1/1 Running 0 30m
pod/ztunnel-7wstc 1/1 Running 0 30m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/grafana ClusterIP 172.20.139.227 <none> 3000/TCP 119s
service/istiod ClusterIP 172.20.142.108 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 30m
service/jaeger-collector ClusterIP 172.20.196.135 <none> 14268/TCP,14250/TCP,9411/TCP,4317/TCP,4318/TCP 2m2s
service/kiali ClusterIP 172.20.209.215 <none> 20001/TCP,9090/TCP 2m4s
service/prometheus ClusterIP 172.20.140.100 <none> 9090/TCP 2m1s
service/tracing ClusterIP 172.20.132.226 <none> 80/TCP,16685/TCP 2m3s
service/zipkin ClusterIP 172.20.39.168 <none> 9411/TCP 2m3s
# Visualize Istio Mesh console using Kiali
kubectl port-forward svc/kiali 20001:20001 -n istio-system
open http://localhost:20001
# Get to the Prometheus UI
kubectl port-forward svc/prometheus 9090:9090 -n istio-system
# Visualize metrics in using Grafana
kubectl port-forward svc/grafana 3000:3000 -n istio-system
# Visualize application traces via Jaeger
kubectl port-forward svc/tracing 16686:80 -n istio-system

애플리케이션을 Ambient Mesh에 추가한 후에는 L4(레이어 4) 인가 정책을 사용하여 애플리케이션 접근을 보호할 수 있습니다.
이 기능을 통해 메시에 있는 모든 워크로드에 자동으로 발급되는 클라이언트 워크로드 ID를 기반으로, 서비스에 대한 들어오고 나가는 트래픽에 대한 접근 제어를 설정할 수 있습니다.
❯ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: productpage-ztunnel
namespace: bookinfo
spec:
selector:
matchLabels:
app: productpage
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/bookinfo/sa/bookinfo-gateway-istio
EOF
authorizationpolicy.security.istio.io/productpage-ztunnel created
이제 클러스터 내 다른 클라이언트에서 Bookinfo 애플리케이션에 접근해 보겠습니다:
❯ kubectl apply -f samples/curl/curl.yaml
serviceaccount/curl created
service/curl created
deployment.apps/curl created
❯ kubectl exec deploy/curl -- curl -sv "http://productpage.bookinfo:9080/productpage"
* Host productpage.bookinfo:9080 was resolved.
* IPv6: (none)
* IPv4: 172.20.220.161
* Trying 172.20.220.161:9080...
* Connected to productpage.bookinfo (172.20.220.161) port 9080
* using HTTP/1.x
> GET /productpage HTTP/1.1
> Host: productpage.bookinfo:9080
> User-Agent: curl/8.13.0
> Accept: */*
>
* Request completely sent off
* Recv failure: Connection reset by peer
* closing connection #0
command terminated with exit code 56
❯ istioctl waypoint apply --enroll-namespace -n bookinfo --wait
✅ waypoint bookinfo/waypoint applied
✅ waypoint bookinfo/waypoint is ready!
✅ namespace bookinfo labeled with "istio.io/use-waypoint: waypoint"
❯ kubectl get ns --show-labels
NAME STATUS AGE LABELS
bookinfo Active 3h57m istio.io/dataplane-mode=ambient,istio.io/use-waypoint=waypoint,kubernetes.io/metadata.name=bookinfo
default Active 7h15m kubernetes.io/metadata.name=default
istio-system Active 7h6m kubernetes.io/metadata.name=istio-system
kube-node-lease Active 7h15m kubernetes.io/metadata.name=kube-node-lease
kube-public Active 7h15m kubernetes.io/metadata.name=kube-public
kube-system Active 7h15m kubernetes.io/metadata.name=kube-system
❯ kubectl get gtw waypoint -n bookinfo
NAME CLASS ADDRESS PROGRAMMED AGE
waypoint istio-waypoint 172.20.236.17 True 110s
예시 정책은 다음과 같습니다:
❯ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: productpage-waypoint
namespace: bookinfo
spec:
targetRefs:
- kind: Service
group: ""
name: productpage
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/default/sa/curl
to:
- operation:
methods: ["GET"]
EOF
authorizationpolicy.security.istio.io/productpage-waypoint created
targetRefs 필드는 Waypoint 프록시에 대한 인가 정책에서 대상 서비스를 지정할 때 사용됩니다.
rules 섹션은 이전과 유사하지만, 이번에는 to 섹션이 추가되어 허용할 연산(operation)을 명시하고 있습니다.
기억하시죠?
이전에 설정한 L4 인가 정책은 ztunnel이 게이트웨이에서 오는 연결만 허용하도록 설정되어 있었습니다.
이제 Waypoint 프록시도 productpage 서비스에 연결할 수 있도록 L4 정책을 업데이트해야 합니다.
❯ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: productpage-ztunnel
namespace: bookinfo
spec:
selector:
matchLabels:
app: productpage
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/bookinfo/sa/bookinfo-gateway-istio
- cluster.local/ns/bookinfo/sa/waypoint
EOF
authorizationpolicy.security.istio.io/productpage-ztunnel configured
❯ kubectl -n bookinfo get sa
NAME SECRETS AGE
bookinfo-details 0 4h5m
bookinfo-gateway-istio 0 70m
bookinfo-productpage 0 4h5m
bookinfo-ratings 0 4h5m
bookinfo-reviews 0 4h5m
default 0 4h6m
waypoint 0 9m29s
# This fails with an RBAC error because you're not using a GET operation
❯ kubectl exec deploy/curl -- curl -sv "http://productpage.bookinfo:9080/productpage" -X DELETE
* Host productpage.bookinfo:9080 was resolved.
* IPv6: (none)
* IPv4: 172.20.220.161
* Trying 172.20.220.161:9080...
* Connected to productpage.bookinfo (172.20.220.161) port 9080
* using HTTP/1.x
> DELETE /productpage HTTP/1.1
> Host: productpage.bookinfo:9080
> User-Agent: curl/8.13.0
> Accept: */*
>
* abort upload
* Empty reply from server
* shutting down connection #0
command terminated with exit code 52
# This fails with an RBAC error because the identity of the reviews-v1 service is not allowed
❯ kubectl -n bookinfo exec deploy/reviews-v1 -- curl -sv http://productpage.bookinfo:9080/productpage
* Trying 172.20.220.161:9080...
* Connected to productpage.bookinfo (172.20.220.161) port 9080 (#0)
> GET /productpage HTTP/1.1
> Host: productpage.bookinfo:9080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< content-length: 19
< content-type: text/plain
< date: Tue, 03 Jun 2025 10:57:57 GMT
< server: istio-envoy
< x-envoy-decorator-operation: productpage.bookinfo.svc.cluster.local:9080/*
<
{ [19 bytes data]
RBAC: access denied* Connection #0 to host productpage.bookinfo left intact
# This works as you're explicitly allowing GET requests from the curl pod
❯ kubectl exec deploy/curl -- curl -s http://productpage.bookinfo:9080/productpage | grep -o "<title>.*</title>"* Request completely sent off
* Recv failure: Connection reset by peer
* closing connection #0
command terminated with exit code 56
❯ kubectl label namespace default istio.io/dataplane-mode=ambient
namespace/default labeled
❯ kubectl exec deploy/curl -- curl -s http://productpage.bookinfo:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
이제 Waypoint 프록시가 설치되었으므로, 서비스 간 트래픽 분할(Traffic Splitting)을 설정하는 방법을 배워보겠습니다.
Waypoint 프록시는 레이어 7 트래픽을 처리할 수 있으므로, 특정 조건(예: 비율, 사용자, 쿠키 등)에 따라 트래픽을 여러 백엔드 서비스로 분산할 수 있습니다.
❯ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
namespace: bookinfo
spec:
parentRefs:
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- backendRefs:
- name: reviews-v1
port: 9080
weight: 90
- name: reviews-v2
port: 9080
weight: 10
EOF
httproute.gateway.networking.k8s.io/reviews created
❯ kubectl -n bookinfo get httproutes.gateway.networking.k8s.io
NAME HOSTNAMES AGE
bookinfo ["bookinfo.ksj7279.click"] 157m
reviews 49s

❯ kubectl exec deploy/curl -- sh -c "for i in \$(seq 1 10); do curl -s http://productpage.bookinfo:9080/productpage | grep reviews-v.-; done"
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v2-556d6457d-jxzf2
reviews-v2-556d6457d-jxzf2
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c
reviews-v1-598b896c9d-4vr6c

❯ kubectl label namespace bookinfo istio.io/use-waypoint-
namespace/bookinfo unlabeled
❯ istioctl waypoint delete --all -n bookinfo
waypoint bookinfo/waypoint deleted
❯ kubectl label namespace default istio.io/dataplane-mode-
namespace/default unlabeled
❯ kubectl delete httproute reviews -n bookinfo
❯ kubectl delete authorizationpolicy productpage-viewer -n bookinfo
❯ kubectl delete -f samples/curl/curl.yaml
❯ kubectl delete -f samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfo
❯ kubectl delete -f samples/bookinfo/platform/kube/bookinfo-versions.yaml -n bookinfo
❯ kubectl delete -f samples/bookinfo/gateway-api/bookinfo-gateway.yaml -n bookinfo
❯ helm -n istio-system list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-base istio-system 1 2025-06-03 12:37:09.922264 +0900 KSTdeployed base-1.26.1 1.26.1
istio-cni istio-system 1 2025-06-03 12:37:07.079489 +0900 KSTdeployed cni-1.26.1 1.26.1
istiod istio-system 1 2025-06-03 12:37:02.573573 +0900 KSTdeployed istiod-1.26.1 1.26.1
ztunnel istio-system 1 2025-06-03 12:37:04.649074 +0900 KSTdeployed ztunnel-1.26.1 1.26.1
❯ helm -n istio-system uninstall ztunnel
release "ztunnel" uninstalled
❯ helm -n istio-system uninstall istio-cni
release "istio-cni" uninstalled
❯ helm -n istio-system uninstall istiod
release "istiod" uninstalled
❯ helm -n istio-system uninstall istio-base
These resources were kept due to the resource policy:
[CustomResourceDefinition] wasmplugins.extensions.istio.io
[CustomResourceDefinition] authorizationpolicies.security.istio.io
[CustomResourceDefinition] peerauthentications.security.istio.io
[CustomResourceDefinition] requestauthentications.security.istio.io
[CustomResourceDefinition] telemetries.telemetry.istio.io
[CustomResourceDefinition] destinationrules.networking.istio.io
[CustomResourceDefinition] envoyfilters.networking.istio.io
[CustomResourceDefinition] gateways.networking.istio.io
[CustomResourceDefinition] proxyconfigs.networking.istio.io
[CustomResourceDefinition] serviceentries.networking.istio.io
[CustomResourceDefinition] sidecars.networking.istio.io
[CustomResourceDefinition] virtualservices.networking.istio.io
[CustomResourceDefinition] workloadentries.networking.istio.io
release "istio-base" uninstalled
❯ istioctl uninstall -y --purge
All Istio resources will be pruned from the cluster
✔ Uninstall complete
❯ kubectl delete namespace istio-system
namespace "istio-system" deleted
❯ kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
customresourcedefinition.apiextensions.k8s.io "gatewayclasses.gateway.networking.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "gateways.gateway.networking.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "grpcroutes.gateway.networking.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "httproutes.gateway.networking.k8s.io" deleted
customresourcedefinition.apiextensions.k8s.io "referencegrants.gateway.networking.k8s.io" deleted
❯ terraform destroy -target='module.eks_blueprints_addons' -auto-approve
❯ terraform destroy -target='module.eks' -auto-approve
❯ terraform destroy -target='module.vpc' -auto-approve
❯ terraform destroy -auto-approve
⛔️ 다음 조건이 모두 해당된다면:
🆘 다음 설정을 반드시 적용해야 합니다:
POD_SECURITY_GROUP_ENFORCING_MODE를standard로 명시적으로 설정해야 합니다.
그렇지 않으면 파드의 health probe가 실패합니다.
⁉️ 이유:
Istio는 kubelet health probe를 식별하기 위해 link-local SNAT 주소(예: 169.254.0.0/16)를 사용합니다.
그러나 VPC CNI는 Pod Security Group strict 모드에서 link-local 패킷을 잘못 라우팅합니다.
이 제한 사항에 대해서는 VPC CNI 컴포넌트에 오픈된 이슈(open issue)가 있습니다.
🧑🏻💻 현재 권장 사항 (VPC CNI 팀 기준):
✅ 확인 방법:
1. Pod ENI 트렁킹 사용 여부 확인:
$ kubectl set env daemonset aws-node -n kube-system --list | grep ENABLE_POD_ENI
$ kubectl get securitygrouppolicies.vpcresources.k8s.aws
POD_SECURITY_GROUP_ENFORCING_MODE를 standard로 설정하고 관련 파드를 재시작:$ kubectl set env daemonset aws-node -n kube-system POD_SECURITY_GROUP_ENFORCING_MODE=standard
이 설정 후, 영향을 받는 파드들을 재시작하여 변경 사항을 적용해야 합니다.
⛔️ 사이드카 모드와 달리, 앰비언트 모드(Ambient mode)에서는 실행 중인 애플리케이션 파드를 강제 재시작이나 재스케줄 없이 업그레이드된 ztunnel 프록시로 전환할 수 있습니다.
하지만 ztunnel을 업그레이드하면 해당 노드에서 유지되던 모든 장기 TCP 연결이 리셋됩니다. 또한 Istio는 현재 ztunnel에 대한 카나리 업그레이드(canary upgrade)를 지원하지 않으며, 리비전(revision)을 사용하더라도 이를 수행할 수 없습니다.
운영 환경에서 ztunnel 업그레이드 시 애플리케이션 트래픽의 영향 범위를 줄이기 위해,
노드 cordon(스케줄링 금지) 또는 블루/그린(blue/green) 노드 풀 전략을 사용하는 것이 권장됩니다.
자세한 내용은 사용 중인 Kubernetes 제공자의 문서를 참고하세요.
모든 Istio 업그레이드는 컨트롤 플레인(Control Plane), 데이터 플레인(Data Plane), Istio CRD(Custom Resource Definition)의 업그레이드를 포함합니다.
앰비언트(Ambient) 데이터 플레인은 ztunnel과 게이트웨이(waypoint 포함)라는 두 개의 컴포넌트로 분리되어 있으므로, 업그레이드는 이들 각각에 대해 별도의 절차가 필요합니다.
컨트롤 플레인과 CRD 업그레이드는 여기서 간단히 다루지만, 이는 사이드카 모드에서 해당 구성 요소들을 업그레이드하는 절차와 사실상 동일합니다.
사이드카 모드와 마찬가지로, 게이트웨이(waypoint 포함)는 리비전 태그(revision tag)를 통해 업그레이드에 대한 세밀한 제어 및 롤백이 가능한 방식으로 운영할 수 있습니다.
하지만 사이드카 모드와 달리, ztunnel은 DaemonSet(노드당 1개 프록시) 형태로 동작하므로, ztunnel 업그레이드는 최소한 하나의 전체 노드에 영향을 주게 됩니다.
이러한 방식은 대부분의 경우 수용 가능하지만, 장기 TCP 연결을 사용하는 애플리케이션은 중단될 수 있습니다. 이러한 경우, 해당 노드에서 ztunnel을 업그레이드하기 전에
노드를 cordon(스케줄링 금지) 하고 drain(파드 종료 후 재배치) 하는 것을 권장합니다.
이 문서에서는 단순함을 위해 ztunnel의 제자리(in-place) 업그레이드 방법을 설명하며,
이 과정에서는 짧은 다운타임이 발생할 수 있습니다.
$ istioctl x precheck
✔ No issues found when checking the cluster. Istio is safe to install or upgrade!
To get started, check out <https://istio.io/latest/docs/setup/getting-started/>
이후, Helm 저장소를 업데이트합니다:
$ helm repo update istio
Error: rendered manifests contain a resource that already exists. Unable to continue with update: CustomResourceDefinition "wasmplugins.extensions.istio.io" in namespace "" exists and cannot be imported into the current release: invalid ownership metadata
for crd in $(kubectl get crds -l chart=istio -o name && kubectl get crds -l app.kubernetes.io/part-of=istio -o name)
do
kubectl label "$crd" "app.kubernetes.io/managed-by=Helm"
kubectl annotate "$crd" "meta.helm.sh/release-name=istio-base" # Helm 릴리스 이름이 다르면 수정
kubectl annotate "$crd" "meta.helm.sh/release-namespace=istio-system" # Istio 네임스페이스가 다르면 수정
done
📌 주의사항:
release-name=istio-base 및 release-namespace=istio-system은 Helm 릴리스 구성에 맞게 변경해야 합니다.
$ helm upgrade istio-base istio/base -n istio-system
Istiod 컨트롤 플레인은 메쉬 내 트래픽을 라우팅하는 프록시를 관리하고 구성합니다.
다음 명령은 현재 컨트롤 플레인과 함께 새로운 Istiod 인스턴스를 설치하지만,
기존 게이트웨이 프록시나 웨이포인트를 가져오지 않으며, 새로운 게이트웨이나 웨이포인트를 생성하지도 않습니다.
만약 이전 설치나 업그레이드에서 Istiod 설정을 사용자 정의했다면, 기존의 values.yaml 파일을 재사용하여 컨트롤 플레인 구성을 일관되게 유지할 수 있습니다.
In-place upgrade
$ helm upgrade istiod istio/istiod -n istio-system --wait
Revisioned upgrade
$ helm install istiod-"$REVISION" istio/istiod -n istio-system --set revision="$REVISION" --set profile=ambient --wait
Istio CNI 노드 에이전트는 Ambient Mesh에 추가된 파드(Pod)를 감지하고, 해당 파드 내에 프록시 포트를 설정해야 한다는 정보를 ztunnel에 전달하며, 파드의 네트워크 네임스페이스 안에서 트래픽 리디렉션을 구성하는 역할을 합니다.
이 구성요소는 데이터 플레인이나 컨트롤 플레인에는 포함되지 않습니다.
Istio CNI 버전 1.x는 컨트롤 플레인 버전 1.x 및 1.x+1과 호환됩니다.
즉, CNI와 컨트롤 플레인의 버전 차이가 한 마이너 버전 이내인 경우, 컨트롤 플레인을 먼저 업그레이드한 후 CNI를 업그레이드해야 합니다.
🛑 Istio는 현재 istio-cni의 카나리 업그레이드를, revision 기능을 사용하더라도 지원하지 않습니다.
따라서 업그레이드로 인한 서비스 중단이 민감한 환경이거나, CNI 업그레이드 시 블라스트 반경(영향 범위)에 대한 더 엄격한 제어가 필요한 경우, 다음과 같은 전략을 권장합니다:
🛑 Istio CNI 노드 에이전트는 system-node-critical DaemonSet입니다.
즉, 해당 노드에서 Istio의 ambient 트래픽 보안 및 운영 보장을 유지하려면 각 노드에서 반드시 실행되어야 합니다.
기본적으로, Istio CNI 노드 에이전트 DaemonSet은 안전한 인플레이스(in-place) 업그레이드를 지원합니다. 에이전트가 업그레이드되거나 재시작되는 동안에는, 에이전트가 다시 활성화되기 전까지 해당 노드에서는 새로운 파드를 시작할 수 없습니다. 이는 보안되지 않은 트래픽 누수를 방지하기 위한 조치입니다.
다만, 업그레이드 이전에 ambient mesh에 정상적으로 추가된 기존 파드들은, 업그레이드 중에도 Istio의 트래픽 보안 요구 사항에 따라 계속 동작합니다.
$ helm upgrade istio-cni istio/cni -n istio-system
ztunnel DaemonSet는 노드 프록시(Node Proxy) 구성 요소입니다.
버전 1.x의 ztunnel은 1.x 및 1.x+1 버전의 Istio 제어 플레인(Control Plane)과 호환됩니다.
즉, 버전 차이가 한 메이저 버전 이내인 경우, 제어 플레인을 먼저 업그레이드한 후 ztunnel을 업그레이드해야 합니다.
이전에 ztunnel 설치 시 사용자 정의 설정을 적용한 경우, 이전 업그레이드나 설치에 사용했던 values.yaml 파일을 재사용하여 데이터 플레인 구성을 일관되게 유지할 수 있습니다.
🛑 ztunnel을 인플레이스(in-place) 방식으로 업그레이드하면, 리비전(revision)을 사용하더라도 해당 노드에서 실행 중인 모든 Ambient Mesh 트래픽에 일시적인 중단이 발생합니다.
실제 중단 시간은 매우 짧으며, 주로 장시간 지속되는 연결(long-running connections)에 영향을 줍니다.
프로덕션 환경에서 업그레이드 시 영향 범위(blast radius)를 최소화하려면 노드 cordon(스케줄 비활성화) 및 블루/그린(blue/green) 노드 풀 전략을 사용하는 것이 좋습니다.
자세한 내용은 사용하는 Kubernetes 클러스터 제공업체의 문서를 참고하세요.
In-place upgrade
$ helm upgrade ztunnel istio/ztunnel -n istio-system --wait
Revisioned upgrade
$ helm upgrade ztunnel istio/ztunnel -n istio-system --set revision="$REVISION" --wait
In-place upgrade
$ helm upgrade istio-ingress istio/gateway -n istio-ingress
Revisioned upgrade
$ kubectl get mutatingwebhookconfigurations -l 'istio.io/tag' -L istio\.io/tag,istio\.io/rev
$ helm template istiod istio/istiod -s templates/revision-tags.yaml --set revisionTags="{$MYTAG}" --set revision="$REVISION" -n istio-system | kubectl apply -f -
이 명령은 수동 게이트웨이 배포 모드를 사용하는 객체와 사이드카(ambient 모드에서는 사용되지 않음)를 제외한, 해당 태그를 참조하는 모든 객체를 업그레이드합니다.
업그레이드된 데이터 플레인을 사용하는 애플리케이션의 상태를 면밀히 모니터링한 후 다음 태그를 업그레이드하는 것이 좋습니다. 문제가 감지되면, 태그를 롤백하여 이전 리비전 이름을 가리키도록 다시 설정할 수 있습니다:
$ helm template istiod istio/istiod -s templates/revision-tags.yaml --set revisionTags="{$MYTAG}" --set revision="$OLD_REVISION" -n istio-system | kubectl apply -f -
In-place upgrade
$ helm upgrade istio-ingress istio/gateway -n istio-ingress
$ helm delete istiod-"$OLD_REVISION" -n istio-system
모든 Istio 데이터 플레인 모드와 마찬가지로, Ambient 모드도 Istio 컨트롤 플레인을 사용합니다. Ambient 모드에서는 컨트롤 플레인이 각 Kubernetes 노드에 있는 zTunnel 프록시와 통신합니다.
아래 그림은 컨트롤 플레인 관련 구성 요소들과 zTunnel 프록시와 Istiod 컨트롤 플레인 간의 흐름을 개략적으로 보여줍니다.
zTunnel 프록시는 xDS API를 사용하여 Istio 컨트롤 플레인(Istiod)과 통신합니다. 이를 통해 현대의 분산 시스템에 필요한 빠르고 동적인 구성 업데이트가 가능합니다. 또한 zTunnel 프록시는 자신의 Kubernetes 노드에 스케줄된 모든 Pod의 서비스 계정에 대한 mTLS 인증서를 xDS를 통해 획득합니다.
하나의 zTunnel 프록시는 같은 노드에 있는 여러 Pod들을 대신하여 L4 데이터 플레인 기능을 수행할 수 있기 때문에, 관련 구성 정보와 인증서를 효율적으로 획득할 수 있어야 합니다. 이러한 멀티 테넌시 아키텍처는 각 애플리케이션 Pod마다 별도의 프록시를 두는 사이드카 모델과는 확연히 다른 방식입니다.
또한 주목할 점은, Ambient 모드에서는 zTunnel 프록시 구성을 위해 사용되는 xDS 리소스가 단순화되어 있다는 것입니다. 이로 인해 다음과 같은 이점이 있습니다:
Ambient 모드에서 워크로드는 다음의 세 가지 범주 중 하나에 속할 수 있습니다:
Mesh 외부 (Out of Mesh)
Mesh 기능이 전혀 활성화되지 않은 일반적인 Pod입니다.
Istio나 Ambient 데이터 플레인이 적용되지 않습니다.
Mesh 내부 (In Mesh)
Ambient 데이터 플레인에 포함된 Pod로, 트래픽이 zTunnel에 의해 L4 (Layer 4) 수준에서 인터셉트됩니다.
이 모드에서는 L4 수준의 정책(L4 RBAC 등)을 Pod 트래픽에 적용할 수 있습니다.
이 모드는 Pod에 istio.io/dataplane-mode=ambient 라벨을 설정하여 활성화할 수 있습니다.
자세한 내용은 관련 라벨 문서를 참고하세요.
Mesh 내부 + Waypoint 활성화 (In Mesh, Waypoint enabled)
Ambient Mesh에 포함되며, Waypoint 프록시가 함께 배포된 Pod입니다.
이 모드에서는 L7 수준의 정책(HTTP 라우팅, L7 RBAC 등)을 Pod 트래픽에 적용할 수 있습니다.
이 모드는 Pod에 istio.io/use-waypoint 라벨을 설정하여 활성화할 수 있습니다.
자세한 내용은 관련 라벨 문서를 참고하세요.
Ambient Mesh에 포함된 Pod가 아웃바운드 요청을 보낼 때, 해당 요청은 투명하게 노드 로컬의 zTunnel로 리디렉션되며, zTunnel이 해당 요청을 어디로, 어떻게 전달할지를 결정합니다.
일반적으로 트래픽 라우팅 동작은 기본 Kubernetes 라우팅 방식과 동일하게 동작합니다.
즉, Service로의 요청은 해당 Service의 엔드포인트로 전송되고, Pod IP로 직접 요청을 보낼 경우에는 해당 IP로 직접 전달됩니다.
하지만 목적지의 기능(역할)에 따라 동작 방식은 달라질 수 있습니다.
또한 다음 사항에 유의해야 합니다:
참고로, Deployment에 속한 일부 Pod에만 특정 라벨을 설정하는 것도 가능하므로, 일부 Pod만 Waypoint를 사용하는 고급 구성도 기술적으로는 가능합니다.
하지만 사용자에게는 이러한 고급 사용 방식은 일반적으로 권장되지 않습니다.
Ambient Mesh에 포함된 Pod가 인바운드 요청을 받을 때, 해당 요청은 투명하게 노드 로컬의 zTunnel로 리디렉션됩니다. zTunnel이 요청을 받으면, Authorization Policy(인가 정책)를 적용하며, 이 검사를 통과한 요청만을 다음 단계로 전달합니다.
Pod는 HBONE 트래픽 또는 평문(plaintext) 트래픽을 받을 수 있습니다. 기본적으로 zTunnel은 두 종류의 트래픽을 모두 허용합니다.
Mesh 외부에서 온 요청은 인가 정책 평가 시 피어 ID가 없기 때문에, 사용자는 모든 평문 트래픽을 차단하기 위해 특정 ID(모든 ID 또는 특정 ID)를 요구하는 정책을 설정할 수 있습니다.
대상 Pod가 Waypoint가 활성화된 경우, 출발지가 Ambient Mesh 내부라면 출발지의 zTunnel이 요청이 Waypoint를 반드시 거쳐 정책이 적용되도록 보장합니다.
하지만 Mesh 외부의 워크로드는 Waypoint 프록시에 대한 정보를 알지 못하기 때문에, 목적지가 Waypoint를 사용하더라도 Waypoint 프록시를 거치지 않고 직접 목적지로 요청을 보냅니다.
현재로서는 사이드카와 게이트웨이에서 나가는 트래픽도 Waypoint 프록시를 거치지 않습니다.
이 부분은 향후 릴리스에서 Waypoint 프록시 인지를 지원할 예정입니다.
🔐 Identity
Ambient Mesh 내 워크로드 간의 모든 인바운드 및 아웃바운드 L4 TCP 트래픽은 데이터 플레인에 의해 mTLS (HBONE, zTunnel, x509 인증서 사용)로 보호됩니다.
mTLS가 강제하는 바에 따라, 출발지와 목적지는 고유한 x509 아이덴티티(신원)를 가져야 하며, 이 아이덴티티를 이용해 연결을 위한 암호화 채널이 설정됩니다.
이를 위해 zTunnel은 프록시하는 워크로드들을 대신하여 여러 개의 서로 다른 워크로드 인증서(서비스 계정별로 각 노드 로컬 Pod마다 하나씩)를 관리해야 합니다.
단, zTunnel 자체의 아이덴티티는 워크로드 간 mTLS 연결에 사용되지 않습니다.
인증서를 가져올 때, zTunnel은 자신의 아이덴티티로 CA에 인증하지만, 다른 워크로드의 아이덴티티를 요청합니다.
이때 중요한 점은 CA가 zTunnel이 해당 아이덴티티를 요청할 권한이 있는지 반드시 검증해야 한다는 것입니다.
노드에 존재하지 않는 아이덴티티 요청은 거부됩니다.
이는 노드가 침해되더라도 전체 메시가 침해되는 것을 방지하기 위한 매우 중요한 보안 조치입니다.
이 CA 권한 검증은 Istio CA가 Kubernetes 서비스 계정 JWT 토큰을 사용해 수행하며, 이 토큰에는 Pod 정보가 인코딩되어 있습니다.
이 같은 권한 검증은 zTunnel과 통합되는 다른 CA에도 필수 요구 사항입니다.
zTunnel은 노드 내 모든 아이덴티티에 대해 인증서를 요청합니다.
이는 컨트롤 플레인에서 받은 구성 정보를 바탕으로 판단합니다.
새로운 아이덴티티가 노드에서 발견되면 최적화를 위해 우선순위가 낮은 대기열에 등록되어 인증서 요청이 진행됩니다.
그러나 특정 아이덴티티가 필요한 요청이 들어오면, 아직 인증서를 받지 않았더라도 즉시 인증서를 요청합니다.
또한 zTunnel은 인증서의 만료가 다가올 때 인증서 갱신(로테이션) 작업도 처리합니다.
🎛️ Telemetry
zTunnel은 Istio 표준 TCP 메트릭 전체 세트를 내보냅니다.
다음 그림은 Layer 4 Ambient 데이터플레인을 보여줍니다.
그림은 Kubernetes 클러스터의 노드 W1과 W2에서 실행 중인 여러 워크로드가 Ambient Mesh에 추가된 모습을 보여줍니다. 각 노드에는 zTunnel 프록시가 한 개씩 실행되고 있습니다. 이 시나리오에서 애플리케이션 클라이언트 Pod인 C1, C2, C3는 Pod S1이 제공하는 서비스를 호출해야 합니다. L7 트래픽 라우팅이나 L7 트래픽 관리 같은 고급 L7 기능이 필요 없기 때문에, mTLS와 L4 정책 적용만을 위한 L4 데이터 플레인으로 충분하며, Waypoint 프록시는 필요하지 않습니다.
그림에서는 노드 W1에서 실행 중인 Pod C1과 C2가 노드 W2에서 실행 중인 Pod S1과 통신하는 모습을 보여줍니다.
C1과 C2의 TCP 트래픽은 zTunnel이 생성한 HBONE 연결을 통해 안전하게 터널링됩니다. 암호화 및 터널링되는 트래픽의 상호 인증을 위해 상호 TLS(mTLS)가 사용됩니다. 각 연결 양쪽 워크로드를 식별하기 위해 SPIFFE 아이덴티티가 활용됩니다. 터널링 프로토콜 및 트래픽 리디렉션 메커니즘에 관한 자세한 내용은 HBONE 및 zTunnel 트래픽 리디렉션 가이드를 참고하시기 바랍니다.
ℹ️ 참고: 그림에서는 HBONE 터널이 두 zTunnel 프록시 간에 형성된 것으로 보이지만, 실제 터널은 출발지와 목적지 Pod 간에 형성됩니다. 트래픽은 출발지 Pod의 네트워크 네임스페이스 내에서 HBONE으로 캡슐화되고 암호화되며, 최종적으로 목적지 워커 노드의 목적지 Pod 네트워크 네임스페이스에서 캡슐화가 해제되고 복호화됩니다.
ztunnel 프록시는 여전히 HBONE 전송에 필요한 제어 평면과 데이터 평면을 논리적으로 처리하지만, 이는 출발지 및 목적지 Pod의 네트워크 네임스페이스 내부에서 수행됩니다.
📌 참고로, 그림에서 워커 노드 W2에 있는 Pod C3에서 목적지 Pod S1으로 향하는 로컬 트래픽도 로컬 zTunnel 프록시 인스턴스를 거치게 됩니다.
따라서 노드 경계를 넘는 트래픽이든 아니든 상관없이, L4 권한 부여(L4 Authorization) 및 L4 텔레메트리(L4 Telemetry) 같은 L4 트래픽 관리 기능이 동일하게 적용됩니다.
Istio Waypoint는 오직 HBONE 트래픽만을 수신합니다. 요청을 받으면, Waypoint는 해당 트래픽이 자신을 사용하는 Pod 또는 Service를 위한 것인지 확인합니다.
트래픽을 수락한 후에는, Waypoint가 L7 정책들(예: AuthorizationPolicy, RequestAuthentication, WasmPlugin, Telemetry 등)을 적용한 뒤에 트래픽을 전달합니다.
Pod에 대한 직접 요청인 경우, 정책이 적용된 후 요청은 단순히 바로 전달됩니다.
Service에 대한 요청인 경우, Waypoint는 라우팅과 로드 밸런싱도 수행합니다. 기본적으로 Service는 자신의 엔드포인트들 사이에서 L7 로드 밸런싱을 수행하며, 요청을 자신에게 라우팅합니다. 이 동작은 해당 Service에 대한 라우트(Routes)를 설정하여 변경할 수 있습니다.
예를 들어, 아래 정책은 echo 서비스에 대한 요청이 echo-v1으로 전달되도록 보장합니다:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo
spec:
parentRefs:
- group: ""
kind: Service
name: echo
rules:
- backendRefs:
- name: echo-v1
port: 80
다음 그림은 L7 정책 적용을 위해 Waypoint가 구성되어 있는 경우, zTunnel과 Waypoint 간의 데이터 경로를 보여줍니다.
이 경우 zTunnel은 HBONE 터널링을 사용해 트래픽을 Waypoint 프록시로 전송하며, L7 처리가 이루어진 후 Waypoint는 두 번째 HBONE 터널을 통해 트래픽을 선택된 서비스 목적지 Pod가 위치한 노드의 zTunnel로 전달합니다.
일반적으로 Waypoint 프록시는 출발지 Pod나 목적지 Pod와 동일한 노드에 존재할 수도 있고, 그렇지 않을 수도 있습니다.

HBONE(HTTP-Based Overlay Network Environment)은 Istio 컴포넌트 간에 사용되는 보안 터널링 프로토콜입니다. HBONE은 Istio에서 사용하는 전용 용어로, 다양한 애플리케이션 연결에 대한 TCP 스트림을 단일 mTLS 암호화 네트워크 연결(즉, 암호화된 터널) 위에서 투명하게 멀티플렉싱하는 메커니즘입니다.
현재 Istio에서 구현된 HBONE 프로토콜은 다음의 세 가지 오픈 표준으로 구성되어 있습니다:
HTTP CONNECT는 터널 연결을 수립하는 데 사용되며, mTLS는 그 연결을 보호하고 암호화합니다. HTTP/2는 단일 암호화된 터널 위에서 여러 애플리케이션 연결 스트림을 멀티플렉싱하고, 추가적인 스트림 수준 메타데이터를 전달하는 데 사용됩니다.
mTLS 사양에 따라, 각 기본 터널 연결은 고유한 출발지(identity)와 목적지(identity)를 가져야 하며, 이들 아이덴티티를 사용해 해당 연결의 암호화가 설정되어야 합니다.
이는 곧, HBONE 프로토콜을 통해 동일한 목적지 아이덴티티로 향하는 애플리케이션 연결들이 하나의 공유된, 암호화되고 보안이 유지된 HTTP/2 연결 위에서 멀티플렉싱된다는 의미입니다. 즉, 각 고유한 출발지와 목적지 조합은 전용의 보안 터널 연결을 가져야 하며, 해당 전용 연결이 애플리케이션 수준의 여러 개별 연결을 동시에 처리할 수 있다 하더라도 기본적으로는 독립된 터널이 생성되어야 합니다.
Istio의 관례에 따라, ztunnel 및 HBONE 프로토콜을 이해하는 다른 프록시들은 TCP 포트 15008에서 리스너(listener)를 노출합니다.
HBONE은 단지 HTTP/2, HTTP CONNECT, 그리고 mTLS의 조합일 뿐이므로, HBONE을 지원하는 프록시 간에 흐르는 HBONE 터널 패킷은 다음 그림과 같은 형태를 가집니다.
HBONE 터널의 중요한 특징 중 하나는, 원래의 애플리케이션 요청을 변경하지 않고도 투명하게 프록시할 수 있다는 점입니다.
즉, 애플리케이션 트래픽 스트림을 변경하지 않고도 연결에 대한 메타데이터를 목적지 프록시에 전달할 수 있으므로, 예를 들어 Istio 전용 헤더를 애플리케이션 트래픽에 덧붙일 필요가 없습니다.
HBONE 및 HTTP 터널링의 추가적인 활용 사례(예: UDP 지원)는 향후 Ambient 모드와 관련 표준의 발전에 따라 연구될 예정입니다.
Ambient 모드의 맥락에서, 트래픽 리다이렉션(traffic redirection)은 데이터 플레인 기능을 의미하며, 이는 Ambient가 활성화된 워크로드로부터 들어오고 나가는 트래픽을 가로채어 zTunnel 노드 프록시를 통해 라우팅하는 역할을 합니다. 이와 관련하여 때때로 트래픽 캡처(traffic capture)라는 용어도 사용됩니다.
zTunnel은 애플리케이션 트래픽을 투명하게 암호화하고 라우팅하는 것을 목표로 하기 때문에, “Mesh 내부(in mesh)”에 있는 Pod로 들어오고 나가는 모든 트래픽을 가로채는 메커니즘이 필요합니다.
이 작업은 보안상 매우 중요한 작업으로, 만약 zTunnel이 우회될 수 있다면, Authorization Policy 또한 우회될 수 있기 때문입니다.
Ambient 모드의 Pod 내부 트래픽 리다이렉션을 가능하게 하는 핵심 설계 원칙은 zTunnel 프록시가 워크로드 Pod의 Linux 네트워크 네임스페이스 내에서 데이터 경로 캡처(data path capture)를 수행할 수 있다는 점입니다.
이 기능은 istio-cni 노드 에이전트와 zTunnel 노드 프록시 간의 협력을 통해 구현됩니다.
이 모델의 주요 이점은, Istio의 Ambient 모드가 Kubernetes의 어떤 CNI 플러그인과도 투명하게 함께 동작할 수 있으며, 동시에 Kubernetes의 네트워킹 기능에 영향을 주지 않는다는 점입니다.
다음 그림은 Ambient가 활성화된 네임스페이스에 새로운 워크로드 Pod가 시작되거나 추가될 때 발생하는 이벤트의 순서를 보여줍니다.
istio-cni 노드 에이전트는 Pod 생성 및 삭제와 같은 CNI 이벤트에 반응하며, Ambient 라벨이 Pod나 네임스페이스에 추가되는 등의 이벤트를 감지하기 위해 Kubernetes API 서버를 지속적으로 감시합니다.
istio-cni 노드 에이전트는 또한 기존 CNI 플러그인 실행 이후에 호출되는 체이닝(Chained) CNI 플러그인을 설치합니다. 이 플러그인의 유일한 목적은, Ambient 모드에 등록된 네임스페이스에서 컨테이너 런타임에 의해 새로운 Pod가 생성될 때 이를 istio-cni 에이전트에 알려주고, 새로운 Pod의 컨텍스트를 전달하는 것입니다.
Pod가 메시에 추가되어야 함을 감지한 경우(새로운 Pod인 경우에는 CNI 플러그인을 통해, 이미 실행 중인 Pod인 경우에는 Kubernetes API 서버를 통해), 다음과 같은 순서로 작업이 진행됩니다:
istio-cni는 해당 Pod의 네트워크 네임스페이스에 진입하여 트래픽 리다이렉션 규칙을 설정합니다. 이 규칙은 Pod 내부 및 외부로 이동하는 패킷을 가로채어, 노드 로컬 ztunnel 프록시 인스턴스(지정된 포트: 15008, 15006, 15001)로 투명하게 리다이렉션합니다.
istio-cni 노드 에이전트는 Unix 도메인 소켓을 통해 ztunnel 프록시에 Pod의 네트워크 네임스페이스 내부에 리스닝 포트(15008, 15006, 15001)를 생성하도록 요청합니다. 이때 Pod의 네트워크 네임스페이스를 나타내는 저수준 Linux 파일 디스크립터도 함께 전달됩니다.
일반적으로는 네트워크 네임스페이스 내에서 실행 중인 프로세스가 소켓을 생성하지만, Linux의 저수준 소켓 API를 이용하면 다른 네트워크 네임스페이스 안에 리스닝 소켓을 생성하는 것도 가능합니다. 단, 대상 네임스페이스를 생성 시점에 알고 있어야 합니다.
노드 로컬 ztunnel은 내부적으로 해당 Pod를 위한 새로운 논리적 프록시 인스턴스 및 리스닝 포트 세트를 생성합니다. 이는 여전히 같은 프로세스 내에서 실행되며, 단지 Pod를 위한 전용 작업(task)이 추가되는 것입니다.
Pod 내부 리다이렉션 규칙이 적용되고 ztunnel이 리스닝 포트를 생성하면, 해당 Pod는 메시에 추가되고 트래픽이 노드 로컬 ztunnel을 통해 흐르기 시작합니다.
메시 내 Pod 간의 트래픽은 기본적으로 mTLS로 암호화됩니다.
이제 Pod 네트워크 네임스페이스로 들어오고 나가는 데이터는 암호화된 상태로 전송됩니다. 메시 내의 모든 Pod는 메시 정책을 강제하고 트래픽을 안전하게 암호화할 수 있는 능력을 가지며, Pod 내부에서 실행 중인 사용자 애플리케이션은 이러한 동작을 전혀 인지하지 못합니다.
다음 다이어그램은 Ambient 메시 모델에서 Pod 간의 암호화된 트래픽이 어떻게 흐르는지를 보여줍니다.
✅ Check the ztunnel proxy logs
$ kubectl logs ds/ztunnel -n istio-system | grep inpod
Found 3 pods, using pod/ztunnel-hl94n
inpod_enabled: true
inpod_uds: /var/run/ztunnel/ztunnel.sock
inpod_port_reuse: true
inpod_mark: 1337
2024-02-21T22:01:49.916037Z INFO ztunnel::inpod::workloadmanager: handling new stream
2024-02-21T22:01:49.919944Z INFO ztunnel::inpod::statemanager: pod WorkloadUid("1e054806-e667-4109-a5af-08b3e6ba0c42") received netns, starting proxy
2024-02-21T22:01:49.925997Z INFO ztunnel::inpod::statemanager: pod received snapshot sent
2024-02-21T22:03:49.074281Z INFO ztunnel::inpod::statemanager: pod delete request, draining proxy
2024-02-21T22:04:58.446444Z INFO ztunnel::inpod::statemanager: pod WorkloadUid("1e054806-e667-4109-a5af-08b3e6ba0c42") received netns, starting proxy
✅ *Confirm the state of sockets
kubectl debug $(kubectl get pod -l app=curl -n ambient-demo -o jsonpath='{.items[0].metadata.name}') -it -n ambient-demo --image nicolaka/netshoot -- ss -ntlp
Defaulting debug container name to debugger-nhd4d.
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 128 127.0.0.1:15080 0.0.0.0:*
LISTEN 0 128 *:15006 *:*
LISTEN 0 128 *:15001 *:*
LISTEN 0 128 *:15008 *:*
✅ Check the iptables rules setup
$ kubectl debug $(kubectl get pod -l app=curl -n ambient-demo -o jsonpath='{.items[0].metadata.name}') -it --image gcr.io/istio-release/base --profile=netadmin -n ambient-demo -- iptables-save
Defaulting debug container name to debugger-m44qc.
# Generated by iptables-save
*mangle
:PREROUTING ACCEPT [320:53261]
:INPUT ACCEPT [23753:267657744]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [23352:134432712]
:POSTROUTING ACCEPT [23352:134432712]
:ISTIO_OUTPUT - [0:0]
:ISTIO_PRERT - [0:0]
-A PREROUTING -j ISTIO_PRERT
-A OUTPUT -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -m connmark --mark 0x111/0xfff -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
-A ISTIO_PRERT -m mark --mark 0x539/0xfff -j CONNMARK --set-xmark 0x111/0xfff
-A ISTIO_PRERT -s 169.254.7.127/32 -p tcp -m tcp -j ACCEPT
-A ISTIO_PRERT ! -d 127.0.0.1/32 -i lo -p tcp -j ACCEPT
-A ISTIO_PRERT -p tcp -m tcp --dport 15008 -m mark ! --mark 0x539/0xfff -j TPROXY --on-port 15008 --on-ip 0.0.0.0 --tproxy-mark 0x111/0xfff
-A ISTIO_PRERT -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ISTIO_PRERT ! -d 127.0.0.1/32 -p tcp -m mark ! --mark 0x539/0xfff -j TPROXY --on-port 15006 --on-ip 0.0.0.0 --tproxy-mark 0x111/0xfff
COMMIT
# Completed
# Generated by iptables-save
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [175:13694]
:POSTROUTING ACCEPT [205:15494]
:ISTIO_OUTPUT - [0:0]
-A OUTPUT -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -d 169.254.7.127/32 -p tcp -m tcp -j ACCEPT
-A ISTIO_OUTPUT -p tcp -m mark --mark 0x111/0xfff -j ACCEPT
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ACCEPT
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -p tcp -m mark ! --mark 0x539/0xfff -j REDIRECT --to-ports 15001
COMMIT
💕💕💕 9주간 Istio 학습정리를 마무리 합니다. 본 Study를 통해 내적으로 많은 성장을 하였다고 자체 평가하며, 열강해주신 가시다님께 심심한 감사 말씀드립니다. 💕💕💕