Ambient Mesh란
Istio Ambient Mesh는 기존 사이드카 기반 구조를 대체하는 사이드카리스(Sidecar-less) 서비스 메시 아키텍처다. 사이드카를 파드마다 주입하는 대신, 클러스터의 노드와 네임스페이스 단위로 프록시 컴포넌트를 분리 배치하여 운영의 유연성, 리소스 효율성, 보안 격리를 크게 향상시킨다.
이 새로운 방식은 두 가지 계층으로 구성된다. 첫 번째는 Secure Overlay로, ztunnel
이라는 경량 프록시를 통해 L4 수준에서 트래픽을 암호화하고 인증하는 역할을 한다. 두 번째는 L7 Processing Layer로, HTTP 라우팅이나 인증, 로깅이 필요한 경우에만 waypoint proxy
를 통해 고급 기능을 처리한다.
기존 사이드카 방식과 달리, Ambient Mesh는 애플리케이션과 메시 기능을 완전히 분리해 운영자가 서비스 중단 없이 메시를 확장·업그레이드할 수 있도록 설계되었다. 또한, 필요에 따라 L4 전용 구성에서 L7 기능이 포함된 구성으로 점진적으로 도입할 수 있으며, 사이드카 모드와도 혼합 구성이 가능하다.
구성 방식 | 사이드카 모드 | 앰비언트 모드 |
---|---|---|
프록시 배치 | Pod 내부 (Envoy) | 노드(zTunnel), 네임스페이스(Waypoint) |
트래픽 처리 | L4~L7 일괄 처리 | L4: zTunnel, L7: Waypoint Proxy |
리소스 사용 | Pod 개수만큼 증가 | 공유 자원 기반, 자동 확장 |
배포 방식 | 사이드카 주입 필요 | 비침입적 구성, 주입 없음 |
기존 사이드카 방식의 한계
기존 사이드카(Sidecar) 구조는 Istio의 핵심 기능을 각 애플리케이션 포드 옆에 Envoy 프록시를 주입하는 방식으로 제공하지만, 이로 인해 침입적인 아키텍처, 과도한 리소스 소모, 복잡한 운영 부담이라는 문제가 발생한다. 사이드카는 모든 포드에 강제로 붙기 때문에 애플리케이션의 배포나 디버깅 시 예기치 않은 트래픽 흐름을 유발할 수 있고, 각 사이드카가 독립적으로 리소스를 소비하면서 클러스터 전체의 자원 효율이 떨어진다. 또한 사이드카의 버전 업그레이드나 장애 대응 시에는 전체 워크로드의 재배포가 필요해 운영이 번거롭고 복잡해진다.
Ambient Mesh는 이러한 한계를 극복하기 위해 사이드카 없는 구조로 설계되어, 보안과 성능은 유지하면서도 더 유연하고 단순한 메시 환경을 제공한다.
Istio Ambient Mode와 Kubernetes CNI 간 충돌 해결
Ambient Mesh 아키텍처 초기에는 중요한 문제가 있었다. 다양한 Kubernetes 플랫폼과 네트워크 구현 환경에서 CNI 플러그인이 사용하는 커널 수준 리디렉션과 ztunnel의 사용자 공간 리디렉션이 충돌을 일으킬 수 있었던 것이다. 이러한 충돌을 해결하기 위해 Istio는 ztunnel이 포드와 동일한 네트워크 네임스페이스 내에서 리디렉션 소켓을 직접 생성하도록 아키텍처를 개선했다.
기존 사이드카 방식에서는 포드 내부에서 사이드카와 앱이 동일한 네임스페이스를 공유하므로 트래픽 리디렉션이 간단했다. 이를 바탕으로, Istio는 사이드카 방식처럼 ztunnel이 애플리케이션 포드의 네트워크 네임스페이스 안에서 리디렉션 소켓을 시작하도록 설계했다. 놀랍게도 Linux 소켓 API는 한 네트워크 네임스페이스 외부에서 내부로 진입하여 소켓을 리스닝할 수 있는 기능을 제공하며, 이 점이 ztunnel 구조 전환의 핵심이 되었다.
이를 위해 Istio는 ztunnel과 Istio-CNI 노드 에이전트 구조를 리팩토링했고, 다양한 Kubernetes 환경에서 프로토타입을 검증한 뒤, Pod 내부 리디렉션을 구현한 새로운 아키텍처를 upstream에 반영하였다. 이로써 ztunnel은 포드 외부에서 실행되면서도 포드 내부 네임스페이스에서 소켓을 소유하는 형태로 작동하게 되었고, 사이드카 모델과 매우 유사한 방식으로 트래픽을 처리할 수 있게 되었다.
가장 큰 장점은 이러한 방식이 eBPF든 iptables든 모든 주요 CNI 구현체와 충돌 없이 동작하며, Kubernetes 기본 네트워크 정책이 그대로 유지될 수 있다는 점이다. 덕분에 Ambient 모드는 보안성과 호환성을 모두 확보하면서도, 사이드카 없는 경량 구조의 이점을 활용할 수 있게 되었다. ztunnel은 단순한 보안 프록시를 넘어, Istio Ambient Mesh 아키텍처를 가능하게 한 기술적 핵심이라 할 수 있다.
Istio Ambient Mode, v1.24에서 정식 출시(GA)
Istio Ambient 모드는 Istio 1.24 버전부터 공식 GA(General Availability) 가 되었다.
이제 Ambient 모드를 사용하면 사이드카 없이도 Istio의 핵심 기능을 누릴 수 있다. 복잡한 프록시 배포 없이도 메시 기반의 보안 정책 적용, 트래픽 관찰, 서비스 간 통신 제어가 가능해졌다.
주요 특징은 다음과 같다:
1. 빠른 성능 (Fast)
- L7 처리가 꼭 필요한 경우에만 waypoint proxy를 사용하고, 기본 통신은 ztunnel에서 L4 수준으로 처리하기 때문에 불필요한 부하를 줄일 수 있다.
- 기존 사이드카 방식보다 평균 지연 시간이 줄어드는 효과가 보고되었으며, 내부적으로는 엣지 간 처리 단계를 통합하여 홉 수를 줄이는 방식으로 최적화되었다.
2. 보안 강화 (Secure)
- mTLS 기반의 워크로드 간 통신 암호화는 기본값으로 제공되며, compromised workload와 메시 인프라(ztunnel, waypoint)를 격리할 수 있는 아키텍처 덕분에 전체 시스템의 보안성이 높아진다.
- Envoy 기반의 안정적인 프록시 구성과 Kubernetes RBAC과 연동되는 인증/인가 체계가 기본으로 제공된다.
3. 간결한 운영 (Simple)
- 더 이상 각 워크로드마다 사이드카를 삽입하고 관리할 필요가 없기 때문에, 운영 부담이 대폭 줄어든다.
- 네트워크 정책과 트래픽 설정은 네임스페이스 단위 또는 서비스 계정 단위로 통합 적용할 수 있어 구성 복잡도도 낮다.
Istio 1.24 GA는 단순히 기능이 안정화되었다는 의미를 넘어, 프로덕션 환경에서도 안전하게 사용할 수 있는 신뢰 수준에 도달했음을 의미한다. 다양한 Kubernetes 환경과 CNI에서의 테스트, Rust 기반 ztunnel의 리팩토링, 세분화된 보안 정책 등 지금까지의 개선이 모두 이 GA 선언으로 이어진 것이다.
Istio Ambient Ztunnel
Istio Ambient Mesh의 핵심 구성 요소 중 하나는 ztunnel이라는 새로운 컴포넌트다. 이 컴포넌트는 기존 사이드카 없이도 메시 내부의 워크로드 간 통신을 보안(mTLS) 기반으로 보호해주는 역할을 한다.
기존 Istio는 각 워크로드 옆에 사이드카 프록시(Envoy)를 배치했지만, 앰비언트 모드에서는 각 노드에 공유 형태로 ztunnel을 배포한다. 모든 워크로드의 트래픽은 ztunnel을 거쳐 흐르게 되고, 이 과정에서 트래픽은 자동으로 암호화된다.
흥미로운 점은 이 ztunnel이 Rust 언어로 구현되었다는 것이다. Rust는 C/C++ 수준의 고성능을 가지면서도 메모리 안전성을 컴파일 타임에 보장한다. 덕분에 보안이 중요한 메시 계층에서 더 신뢰할 수 있는 동작을 기대할 수 있다.
Rust 기반 ztunnel의 장점은 다음과 같다:
- 낮은 리소스 사용량과 빠른 시작 시간: 단일 바이너리로 컴파일되기 때문에 컨테이너 이미지가 작고 배포도 빠르다.
- 고성능 트래픽 처리: 멀티스레딩을 효율적으로 활용하며, 수많은 워크로드 트래픽을 안정적으로 처리할 수 있다.
- 보안성과 격리성 향상: 메모리 오류나 데이터 경쟁을 방지하며, 민감한 보안 계층에서도 신뢰할 수 있는 기반을 제공한다.
결국 ztunnel은 사이드카 없이도 보안 메시를 구성할 수 있게 하면서, 성능과 보안을 모두 만족시키는 앰비언트 메시의 경량 프록시 역할을 하게 된다. 기존 Istio 사용자가 가장 부담스러워했던 사이드카의 리소스 오버헤드 문제를 크게 줄이면서, 보다 단순하고 확장 가능한 아키텍처로의 전환을 가능하게 해준다.
Ambient Mode에서 보안은 어떻게 작동할까?
앰비언트 모드에서는 보안 모델 역시 기존 사이드카 방식과는 전혀 다르게 설계되었다. 기본적으로 ztunnel이 모든 워크로드의 입출력 트래픽을 가로채어 mTLS로 암호화하며, 이를 통해 메시 내에서 제로 트러스트(Zero Trust) 보안을 구현한다.
이 아키텍처의 가장 큰 특징은 보안 계층이 워크로드와 격리된 별도의 컴포넌트(ztunnel, waypoint)에서 수행된다는 점이다. 이를 통해 사이드카 모델에서 자주 지적되던 몇 가지 문제를 해결할 수 있다:
워크로드 침해 시에도 정책은 그대로
기존 사이드카 방식은 워크로드와 Envoy 프록시가 같은 Pod 안에 있었기 때문에, 애플리케이션이 침해되면 해당 사이드카를 통해 메시 네트워크 전체가 위협받을 가능성이 있었다. 반면, 앰비언트 모드에서는 ztunnel이 별도의 Pod로 격리되어 있으며, 애플리케이션이 손상되어도 ztunnel의 보안 정책은 여전히 유효하게 동작한다.
최소 권한으로, 더 안전하게
ztunnel은 자신이 위치한 노드의 워크로드 키만 접근 가능하다. 즉, 노드 단위의 제한된 권한 모델을 따르기 때문에 공격 반경이 제한적이다. 이는 일반적인 CNI 수준의 암호화 모델과 유사하며, 전체 클러스터를 위험에 빠뜨릴 가능성을 줄인다.
또한, ztunnel은 오직 L4 계층까지만 처리하도록 제한되어 있기 때문에, 복잡한 L7 로직에서 발생할 수 있는 보안 취약점으로부터도 비교적 안전하다. 트래픽이 애플리케이션 레벨의 세부 요청을 처리하는 waypoint proxy로 넘어가는 경우에도, 이 프록시는 하나의 서비스 계정에 대해서만 작동하도록 구성 가능해 보안적 격리를 유지한다.
Envoy 대신 Rust를 선택한 이유도 보안
Rust로 구현된 ztunnel은 메모리 안전성과 thread-safe한 구조 덕분에, 런타임에서 발생하는 잠재적인 메모리 취약점이 크게 줄어든다. 특히 공유 프록시 구조에서 중요한 리소스를 안전하게 다루기 위해서는 이러한 특성이 큰 장점이다.
Istio Ambient Waypoint Proxy
Istio Ambient Mesh는 기존 사이드카 아키텍처의 복잡성과 확장성 문제를 해결하기 위해 Waypoint 프록시를 도입했다. 이 프록시는 Envoy 기반으로, 레이어 7(L7) 트래픽 처리를 담당하는 선택적 구성 요소다. 웨이포인트는 네임스페이스 또는 서비스 계정 단위로 배포되며, 각 애플리케이션 파드 외부에서 실행되기 때문에 업그레이드와 관리가 독립적이다.
특히 정책 적용 구조가 바뀌었다. 기존에는 트래픽 관련 정책은 클라이언트 사이드카에서, 보안 정책은 서버 사이드카에서 처리되었지만, Ambient 모드에서는 모든 정책을 대상(Waypoint) 프록시에서 적용한다. 이는 디버깅, 스케일링, 정책 일관성 측면에서 많은 이점을 제공한다.
Waypoint 모델은 구성 전파량을 크게 줄인다. 예를 들어 두 개의 네임스페이스에 각각 2개의 워크로드가 있을 경우:
- 사이드카 모델은 각 워크로드가 모든 목적지에 대한 구성을 알아야 하므로 총 16개의 구성이 필요
- 웨이포인트 모델은 네임스페이스당 1개의 웨이포인트만 구성되므로 총 2개만
대규모 환경에서 이 차이는 더 극명해진다. 이처럼 Waypoint 구조는 제어 플레인과 데이터 플레인 리소스 사용량(CPU, RAM, 네트워크 트래픽)을 획기적으로 줄인다.
Waypoint는 istioctl experimental waypoint generate 명령어 혹은 Kubernetes Gateway 리소스를 통해 선언적으로 배포할 수 있다. Istiod는 이 리소스를 감지하고 자동으로 프록시를 배포 및 구성한다. 이 방식은 기존의 복잡한 사이드카 설정을 대체하며 운영 편의성과 확장성을 동시에 확보할 수 있다.
$ istioctl experimental waypoint generate
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: namespace
spec:
gatewayClassName: istio-waypoint
listeners:
- name: mesh
port: 15008
protocol: HBONE
Ambient Mode 실습
실습환경구성
Istio Ambient 모드를 테스트하기 위해 kind 기반의 로컬 Kubernetes 클러스터를 구성한다. 실습은 1개의 컨트롤 플레인 노드와 2개의 워커 노드로 이루어진 3노드 클러스터 환경에서 진행한다.
kind 클러스터 생성
다양한 시각화 도구와 샘플 앱을 노출하기 위해 NodePort 포트 매핑을 추가한 컨트롤 플레인 노드, 그리고 일반 워커 노드 2대를 구성한다.
#
kind create cluster --name myk8s --image kindest/node:v1.32.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000 # Sample Application
hostPort: 30000
- containerPort: 30001 # Prometheus
hostPort: 30001
- containerPort: 30002 # Grafana
hostPort: 30002
- containerPort: 30003 # Kiali
hostPort: 30003
- containerPort: 30004 # Tracing
hostPort: 30004
- containerPort: 30005 # kube-ops-view
hostPort: 30005
- role: worker
- role: worker
networking:
podSubnet: 10.10.0.0/16
serviceSubnet: 10.200.1.0/24
EOF
# 설치 확인
docker ps
# 노드에 기본 툴 설치
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'apt update && apt install tree psmisc lsof ipset wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'; echo; done
테스트용 외부 PC 컨테이너 배포
Istio 메시 외부의 트래픽 시뮬레이션을 위해 netshoot 이미지 기반의 컨테이너(mypc)를 생성하고, kind 네트워크에 붙인다.
# kind 설치 시 kind 이름의 도커 브리지가 생성된다 : 172.18.0.0/16 대역
docker network ls
docker inspect kind
# '테스트용 PC(mypc)' 컨테이너 기동 : kind 도커 브리지를 사용
혹은 IP 지정 실행 시 에러 발생 시 아래 처럼 IP 지정 없이 실행
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity # IP 지정 없이 실행 시
docker ps
# kind network 중 컨테이너(노드) IP(대역) 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
/mypc 192.168.107.3
/myk8s-worker2 192.168.107.6
/myk8s-control-plane 192.168.107.2
/myk8s-worker 192.168.107.5
MetalLB 설치
편리한 실습을 위해, MetalLB를 설치해 서비스 타입 LoadBalancer를 지원하도록 설정한다.
실습에서는 자신의 도커 브리지 대역(예: 192.168.107.0/24)을 활용해 IP 풀을 구성하고, Layer2 방식으로 announce한다.
# MetalLB 배포
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
# 확인
kubectl get crd
kubectl get pod -n metallb-system
# IPAddressPool, L2Advertisement 설정
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default
namespace: metallb-system
spec:
addresses:
- 192.168.107.101-192.168.107.120 # 자신의 도커브릿지 네트워크 대역 사용
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
spec:
ipAddressPools:
- default
EOF
# 확인
kubectl get IPAddressPool,L2Advertisement -A
Istio 1.26 설치 (Ambient 프로파일)
Ambient 모드를 실습하기 위해, Istio 1.26 버전을 ambient 프로파일로 설치한다. 이 프로파일은 ztunnel과 기본 컨트롤 플레인만 배포되며, 사이드카 프록시는 포함되지 않는다.
Gateway API CRD도 함께 설치하고, Prometheus, Grafana, Kiali, Tracing 등 기본 모니터링 애드온을 추가한다.
# myk8s-control-plane 진입 후 설치 진행
docker exec -it myk8s-control-plane bash
-----------------------------------
# istioctl 설치
export ISTIOV=1.26.0
echo 'export ISTIOV=1.26.0' >> /root/.bashrc
curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
istioctl version --remote=false
client version: 1.26.0
# ambient 프로파일 컨트롤 플레인 배포
istioctl install --set profile=ambient --set meshConfig.accessLogFile=/dev/stdout --skip-confirmation
istioctl install --set profile=ambient --set meshConfig.enableTracing=true -y
# Install the Kubernetes Gateway API CRDs
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
# 보조 도구 설치
kubectl apply -f istio-$ISTIOV/samples/addons
kubectl apply -f istio-$ISTIOV/samples/addons # nodePort 충돌 시 한번 더 입력
# 빠져나오기
exit
-----------------------------------
# 설치 확인 : istiod, istio-ingressgateway, crd 등
kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
kubectl get crd | grep istio.io
kubectl get crd | grep -v istio | grep -v metallb
kubectl get crd | grep gateways
gateways.gateway.networking.k8s.io 2025-06-01T04:54:23Z
gateways.networking.istio.io 2025-06-01T04:53:51Z
kubectl api-resources | grep Gateway
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
gateways gw networking.istio.io/v1 true Gateway
kubectl describe cm -n istio-system istio
...
Data
====
mesh:
----
accessLogFile: /dev/stdout
defaultConfig:
discoveryAddress: istiod.istio-system.svc:15012
defaultProviders:
metrics:
- prometheus
enablePrometheusMerge: true
...
docker exec -it myk8s-control-plane istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
ztunnel-6ddsp.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
ztunnel-86ch2.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
ztunnel-m2ct2.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
docker exec -it myk8s-control-plane istioctl ztunnel-config service
# iptables 규칙 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'iptables-save'; echo; done
# NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'
# Prometheus 접속 : envoy, istio 메트릭 확인
open http://127.0.0.1:30001
# Grafana 접속
open http://127.0.0.1:30002
# Kiali 접속 : NodePort
open http://127.0.0.1:30003
# tracing 접속 : 예거 트레이싱 대시보드
open http://127.0.0.1:30004
ztunnel 상태 확인
ztunnel이 정상적으로 배포되었는지, istiod 컨트롤 플레인과의 연동 상태는 정상인지 확인한다.
istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
ztunnel-6ddsp.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
ztunnel-86ch2.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
ztunnel-m2ct2.istio-system Kubernetes IGNORED IGNORED IGNORED IGNORED IGNORED istiod-86b6b7ff7-gbjv7 1.26.0
워크로드와 서비스 ztunnel 매핑 상태 확인
Istio 1.26부터 새롭게 추가된 ztunnel-config 명령어를 활용하면, 각 워크로드와 서비스가 ztunnel을 통해 어떤 식으로 관찰되고 있는지 확인할 수 있다.
TCP”로 표기된 워크로드는 아직 waypoint proxy 설정이 없는 초기 상태라고 보면 되고, 이후 실습을 통해 waypoint proxy를 설정하면 "HBONE"으로 바뀌게 된다
istioctl ztunnel-config workload
NAMESPACE POD NAME ADDRESS NODE WAYPOINT PROTOCOL
...
istio-system grafana-65bfb5f855-2j8mp 10.10.1.5 myk8s-worker2 None TCP
istio-system istio-cni-node-2mqpq 10.10.0.5 myk8s-control-plane None TCP
istio-system istio-cni-node-phcpx 10.10.1.3 myk8s-worker2 None TCP
...
istioctl ztunnel-config service
NAMESPACE SERVICE NAME SERVICE VIP WAYPOINT ENDPOINTS
default kubernetes 10.200.1.1 None 1/1
istio-system grafana 10.200.1.194 None 1/1
istio-system istiod 10.200.1.161 None 1/1
...
ztunnel과 istio-cni-node 데몬셋 확인
Ambient Mesh가 정상 작동하려면 두 가지 핵심 컴포넌트가 각 노드에 제대로 배포되어 있어야 한다: istio-cni-node와 ztunnel. 각각의 역할은 다음과 같다.
- istio-cni-node: CNI 플러그인으로서 각 Pod의 네트워크 설정을 가로채고, ztunnel로 트래픽이 흐르도록 구성한다.
- ztunnel: Rust로 작성된 lightweight 프록시로, L4 레벨의 암호화된 터널링과 기본적인 라우팅을 수행한다.
먼저 데몬셋 배포 상태를 확인해보면 다음과 같다:
kubectl get ds -n istio-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
istio-cni-node 3 3 3 3 3 kubernetes.io/os=linux 5m35s
ztunnel 3 3 3 3 3 kubernetes.io/os=linux 5m25s
istio-cni-node 구성 상세 확인
여기서는 /opt/cni/bin
경로에 istio-cni 바이너리가 존재하는지, /etc/cni/net.d
에 CNI 설정 파일이 있는지 등을 점검하면 좋다. /var/run/istio-cni
경로는 CNI 로그, 소켓, kubeconfig 파일들이 위치한 곳이다.
컨테이너 내에서의 상태는 다음과 같은 환경 변수들로 드러난다:
• REPAIR_NODE_NAME, REPAIR_RUN_AS_DAEMON 등 네트워크 설정 복구와 관련된 변수
• /var/run/ztunnel
마운트를 통해 ztunnel과 소켓 통신이 가능하도록 설정됨
kubectl describe pod -n istio-system -l k8s-app=istio-cni-node
...
Containers:
install-cni:
Container ID: containerd://0cdb0dec8fd3d4bc41f64daec3b807eb48d1d5db3ee8314b74272d1ce5926ae7
Image: docker.io/istio/install-cni:1.26.0-distroless
Image ID: docker.io/istio/install-cni@sha256:e69cea606f6fe75907602349081f78ddb0a94417199f9022f7323510abef65cb
Port: 15014/TCP
Host Port: 0/TCP
Command:
install-cni
Args:
--log_output_level=info
State: Running
Started: Mon, 09 Jun 2025 03:32:03 +0900
Ready: True
Restart Count: 0
Requests:
cpu: 100m
memory: 100Mi
Readiness: http-get http://:8000/readyz delay=0s timeout=1s period=10s #success=1 #failure=3
Environment Variables from:
istio-cni-config ConfigMap Optional: false
Environment:
REPAIR_NODE_NAME: (v1:spec.nodeName)
REPAIR_RUN_AS_DAEMON: true
REPAIR_SIDECAR_ANNOTATION: sidecar.istio.io/status
ALLOW_SWITCH_TO_HOST_NS: true
NODE_NAME: (v1:spec.nodeName)
GOMEMLIMIT: node allocatable (limits.memory)
GOMAXPROCS: node allocatable (limits.cpu)
POD_NAME: istio-cni-node-ptw7d (v1:metadata.name)
POD_NAMESPACE: istio-system (v1:metadata.namespace)
Mounts:
/host/etc/cni/net.d from cni-net-dir (rw)
/host/opt/cni/bin from cni-bin-dir (rw)
/host/proc from cni-host-procfs (ro)
/host/var/run/netns from cni-netns-dir (rw)
/var/run/istio-cni from cni-socket-dir (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bmsmd (ro)
/var/run/ztunnel from cni-ztunnel-sock-dir (rw)
...
Volumes:
cni-bin-dir:
Type: HostPath (bare host directory volume)
Path: /opt/cni/bin
HostPathType:
cni-host-procfs:
Type: HostPath (bare host directory volume)
Path: /proc
HostPathType: Directory
cni-ztunnel-sock-dir:
Type: HostPath (bare host directory volume)
Path: /var/run/ztunnel
HostPathType: DirectoryOrCreate
cni-net-dir:
Type: HostPath (bare host directory volume)
Path: /etc/cni/net.d
HostPathType:
cni-socket-dir:
Type: HostPath (bare host directory volume)
Path: /var/run/istio-cni
HostPathType:
cni-netns-dir:
Type: HostPath (bare host directory volume)
Path: /var/run/netns
HostPathType: DirectoryOrCreate
# 노드에서 기본 정보 확인
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /opt/cni/bin'; echo; done
-rwxr-xr-x 1 root root 52428984 Jun 1 05:42 istio-cni
...
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /etc/cni/net.d'; echo; done
-rw-r--r-- 1 root root 862 Jun 1 04:54 10-kindnet.conflist
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /var/run/istio-cni'; echo; done
-rw------- 1 root root 2990 Jun 1 05:42 istio-cni-kubeconfig
-rw------- 1 root root 171 Jun 1 04:54 istio-cni.log
srw-rw-rw- 1 root root 0 Jun 1 04:54 log.sock
srw-rw-rw- 1 root root 0 Jun 1 04:54 pluginevent.sock
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'ls -l /var/run/netns'; echo; done
...
for node in control-plane worker worker2; do echo "node : myk8s-$node" ; docker exec -it myk8s-$node sh -c 'lsns -t net'; echo; done
# istio-cni-node 데몬셋 파드 로그 확인
kubectl logs -n istio-system -l k8s-app=istio-cni-node -f
ztunnel Pod 상태 및 내부 정보 확인
Pod 내부에서는 proxy ztunnel 명령어로 구동되고 있으며, HBONE 활성화 여부는 ISTIO_META_ENABLE_HBONE=true로 확인 가능하다.
kubectl get pod -n istio-system -l app=ztunnel -owide
kubectl get pod -n istio-system -l app=ztunnel
ZPOD1NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[0].metadata.name}")
ZPOD2NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[1].metadata.name}")
ZPOD3NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[2].metadata.name}")
echo $ZPOD1NAME $ZPOD2NAME $ZPOD3NAME
kubectl describe pod -n istio-system -l app=ztunnel
...
Containers:
istio-proxy:
Container ID: containerd://7903d6a0a20d5cefbe427f03e9698c43f97fe4633d6ab560a8836aad76ac087b
Image: docker.io/istio/ztunnel:1.26.0-distroless
Image ID: docker.io/istio/ztunnel@sha256:d711b5891822f4061c0849b886b4786f96b1728055333cbe42a99d0aeff36dbe
Port: 15020/TCP
Host Port: 0/TCP
Args:
proxy
ztunnel
State: Running
Started: Mon, 09 Jun 2025 03:32:12 +0900
Ready: True
Restart Count: 0
Requests:
cpu: 200m
memory: 512Mi
Readiness: http-get http://:15021/healthz/ready delay=0s timeout=1s period=10s #success=1 #failure=3
Environment:
CA_ADDRESS: istiod.istio-system.svc:15012
XDS_ADDRESS: istiod.istio-system.svc:15012
RUST_LOG: info
RUST_BACKTRACE: 1
ISTIO_META_CLUSTER_ID: Kubernetes
INPOD_ENABLED: true
TERMINATION_GRACE_PERIOD_SECONDS: 30
POD_NAME: ztunnel-m2ct2 (v1:metadata.name)
POD_NAMESPACE: istio-system (v1:metadata.namespace)
NODE_NAME: (v1:spec.nodeName)
INSTANCE_IP: (v1:status.podIP)
SERVICE_ACCOUNT: (v1:spec.serviceAccountName)
ISTIO_META_ENABLE_HBONE: true
Mounts:
/tmp from tmp (rw)
/var/run/secrets/istio from istiod-ca-cert (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vprlz (ro)
/var/run/secrets/tokens from istio-token (rw)
/var/run/ztunnel from cni-ztunnel-sock-dir (rw)
...
Volumes:
istio-token:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 43200
istiod-ca-cert:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: istio-ca-root-cert
Optional: false
cni-ztunnel-sock-dir:
Type: HostPath (bare host directory volume)
Path: /var/run/ztunnel
HostPathType: DirectoryOrCreate
...
ztunnel은 /var/run/ztunnel/ztunnel.sock
이라는 Unix 도메인 소켓을 통해 다른 컴포넌트와 통신한다. 이 소켓 내부 동작을 확인하려면 pexec이라는 krew 플러그인을 활용할 수 있다. pexec은 쿠버네티스에서 SSH 없이도 high privileges 디버깅을 가능하게 해주는 도구로, 대상 파드와 같은 노드에 privileged 컨테이너를 띄운 뒤, 네트워크·프로세스·마운트 등 다양한 네임스페이스를 공유해 실제 노드 수준에서 디버깅이 가능하다.
kubectl krew install pexec
kubectl pexec $ZPOD1NAME -it -T -n istio-system -- bash
ss -xnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
u_str ESTAB 0 0 * 44981 * 44982 users:(("ztunnel",pid=1,fd=13),("ztunnel",pid=1,fd=8),("ztunnel",pid=1,fd=6))
u_seq ESTAB 0 0 /var/run/ztunnel/ztunnel.sock 47646 * 46988
u_str ESTAB 0 0 * 44982 * 44981 users:(("ztunnel",pid=1,fd=7))
u_seq ESTAB 0 0 * 46988 * 47646 users:(("ztunnel",pid=1,fd=19))
ls -l /var/run/ztunnel
total 0
srwxr-xr-x 1 root root 0 Jun 1 04:54 ztunnel.sock
샘플 애플리케이션 배포 및 외부 통신 확인
Istio Ambient Mode의 동작 방식을 이해하기 위해, Bookinfo 샘플 애플리케이션을 배포하고 ztunnel 및 외부 게이트웨이와의 통신 흐름을 살펴본다.
먼저 Istio 디렉토리 내 samples/bookinfo 경로에 위치한 샘플 애플리케이션을 Kubernetes 클러스터에 배포한다.
docker exec -it myk8s-control-plane ls -l istio-1.26.0
total 40
-rw-r--r-- 1 root root 11357 May 7 11:05 LICENSE
-rw-r--r-- 1 root root 6927 May 7 11:05 README.md
drwxr-x--- 2 root root 4096 May 7 11:05 bin
-rw-r----- 1 root root 983 May 7 11:05 manifest.yaml
drwxr-xr-x 4 root root 4096 May 7 11:05 manifests
drwxr-xr-x 27 root root 4096 May 7 11:05 samples
drwxr-xr-x 3 root root 4096 May 7 11:05 tools
# Deploy the Bookinfo sample application:
docker exec -it myk8s-control-plane kubectl apply -f istio-1.26.0/samples/bookinfo/platform/kube/bookinfo.yaml
# 확인
kubectl get deploy,pod,svc,ep
docker exec -it myk8s-control-plane istioctl ztunnel-config service
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
docker exec -it myk8s-control-plane istioctl proxy-status
# 통신 확인 : ratings 에서 productpage 페이지
kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"
추가적으로, 외부 요청을 반복적으로 보내기 위한 디버깅용 netshoot 파드를 생성하고, 지속적인 요청을 보내면서 통신 흐름을 확인할 수도 있다.
# 요청 테스트용 파드 생성 : netshoot
kubectl create sa netshoot
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: netshoot
spec:
serviceAccountName: netshoot
nodeName: myk8s-control-plane
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 요청 확인
kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title
# 반복 요청
while true; do kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
외부에서 접속 가능한 Gateway 설정
이제 외부에서 /productpage에 접근할 수 있도록 Gateway API 리소스를 생성한다. Istio는 Gateway 리소스를 bookinfo-gateway로 정의하며, HTTPRoute 리소스를 통해 다양한 엔드포인트를 productpage 서비스로 연결한다
docker exec -it myk8s-control-plane cat istio-1.26.0/samples/bookinfo/gateway-api/bookinfo-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: bookinfo
spec:
parentRefs:
- name: bookinfo-gateway
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
docker exec -it myk8s-control-plane kubectl apply -f istio-1.26.0/samples/bookinfo/gateway-api/bookinfo-gateway.yaml
정상적으로 리소스가 생성되었다면 아래와 같이 확인할 수 있다.
kubectl get gateway
NAME CLASS ADDRESS PROGRAMMED AGE
bookinfo-gateway istio 192.168.107.101 True 165m
(⎈|kind-myk8s:default) JSX⚡️ ~/Desktop/1.Projects/istio
kubectl get HTTPRoute
NAME HOSTNAMES AGE
bookinfo 165m
(⎈|kind-myk8s:default) JSX⚡️ ~/Desktop/1.Projects/istio
kubectl get svc,ep bookinfo-gateway-istio
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/bookinfo-gateway-istio LoadBalancer 10.200.1.237 192.168.107.101 15021:31041/TCP,80:30000/TCP 165m
NAME ENDPOINTS AGE
endpoints/bookinfo-gateway-istio 10.10.1.16:15021,10.10.1.16:80 165m
(⎈|kind-myk8s:default) JSX⚡️ ~/Desktop/1.Projects/istio
kubectl get pod -l gateway.istio.io/managed=istio.io-gateway-controller -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
bookinfo-gateway-istio-6cbd9bcd49-bbsd6 1/1 Running 0 165m 10.10.1.16 myk8s-worker2 <none> <none>
외부 접속 테스트
Gateway가 노출된 외부 IP를 확인하고, 테스트 컨테이너인 mypc에서 curl을 통해 접속을 시도한다.
kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
docker exec -it mypc curl $GWLB/productpage -v
docker exec -it mypc curl $GWLB/productpage -I
# 반복 요청 : 아래 mypc 컨테이너에서 반복 요청 계속 해두기!
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
NodePort 방식으로 포트를 30000번으로 열어, 로컬에서 직접 접속할 수 있게 구성한다.
# 외부 접근 테스트를 위해 로컬 PC에서 30000번 포트를 통해 접속 가능하도록 NodePort 설정을 한다.
kubectl patch svc bookinfo-gateway-istio -p '{"spec": {"type": "LoadBalancer", "ports": [{"port": 80, "targetPort": 80, "nodePort": 30000}]}}'
kubectl get svc bookinfo-gateway-istio
open "http://127.0.0.1:30000/productpage"
# 반복 요청
while true; do curl -s http://127.0.0.1:30000/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
애플리케이션을 Ambient Mesh에 추가하기
Istio Ambient 모드에서는 사이드카 없이도 메시 기능을 활용할 수 있다. 이를 위해서는 네임스페이스나 파드에 특정 라벨을 설정해야 한다.
• istio.io/dataplane-mode=ambient
: 이 라벨이 지정된 네임스페이스나 파드는 Ambient 모드에 자동으로 포함된다.
• istio.io/dataplane-mode=none
: 이 라벨이 파드에 설정되어 있으면 Ambient 모드에서 제외된다.
Ambient 모드 적용 전후의 워크로드 상태를 istioctl ztunnel-config workload 명령어로 확인할 수 있다.
적용 전
모든 워크로드가 PROTOCOL이 TCP로 되어 있으며, 아직 Ambient 모드(HBONE)가 적용되지 않은 상태다.
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
# 출력 예시
default details-v1-766844796b-7cdm7 10.10.1.11 myk8s-worker2 None TCP
default productpage-v1-54bb874995-... 10.10.2.5 myk8s-worker None TCP
Ambient를 적용하려면 다음과 같이 네임스페이스에 라벨을 설정한다.
kubectl label namespace default istio.io/dataplane-mode=ambient
적용 후
이후 다시 ztunnel-config workload 명령어를 실행하면 일부 워크로드의 프로토콜이 HBONE으로 바뀐 것을 확인할 수 있다. 이는 해당 워크로드들이 ztunnel을 통해 Ambient 모드로 통신하고 있다는 의미다.
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
NAMESPACE POD NAME ADDRESS NODE WAYPOINT PROTOCOL
default details-v1-766844796b-7cdm7 10.10.1.11 myk8s-worker2 None HBONE
default netshoot 10.10.0.12 myk8s-control-plane None HBONE
...
Ambient 모드가 적용되면 워크로드 간 mTLS 통신이 활성화되고, 기존의 복잡한 사이드카 기반 설정과 달리, 엄청나게 간소화된 정책 구성을 확인할 수 있다.
애플리케이션 파드 내부 정보 확인
Ambient 모드에 포함된 파드 내부에서는 ztunnel 관련 정보나 메트릭을 직접 확인할 수 없다. 하지만 기본적인 네트워크 설정은 다음 명령어들을 통해 파드 내에서 확인 가능하다.
kubectl pexec $PPOD -it -T -- bash
-------------------------------------------------------
iptables-save
iptables -t mangle -S
iptables -t nat -S
ss -tnlp
ss -tnp
ss -xnp
ls -l /var/run/ztunnel
/var/run/ztunnel 경로는 존재하지만, 실제 트래픽 제어나 메트릭 정보는 ztunnel 파드에 존재한다.
ztunnel 파드에서 상세 정보 확인
ztunnel은 각 노드에 하나씩 존재하며, 해당 파드 내부에서만 실제 통신 흐름, iptables 설정, 메트릭 정보 등을 확인할 수 있다.
# ztunnel 파드 확인 : 파드 이름 변수 지정
kubectl get pod -n istio-system -l app=ztunnel -owide
kubectl get pod -n istio-system -l app=ztunnel
ZPOD1NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[0].metadata.name}")
ZPOD2NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[1].metadata.name}")
ZPOD3NAME=$(kubectl get pod -n istio-system -l app=ztunnel -o jsonpath="{.items[2].metadata.name}")
echo $ZPOD1NAME $ZPOD2NAME $ZPOD3NAME
#
kubectl pexec $ZPOD1NAME -it -T -n istio-system -- bash
-------------------------------------------------------
iptables -t mangle -S
iptables -t nat -S
ss -tnlp
ss -tnp
ss -xnp
ls -l /var/run/ztunnel
# 메트릭 정보 확인
curl -s http://localhost:15020/metrics | grep '^[^#]'
...
# Viewing Istiod state for ztunnel xDS resources
curl -s http://localhost:15000/config_dump
Ambient 모드가 적용된 이후, 통신 흐름과 보안 상태, 트래픽 메트릭을 다양한 시각화 도구를 통해 확인할 수 있다.
ztunnel-config를 통한 구성 정보 확인
Ambient Mesh에서는 istioctl ztunnel-config 명령어를 통해 서비스, 워크로드, 인증서, 연결 상태 등 다양한 정보를 조회할 수 있다.
서비스 정보 확인
docker exec -it myk8s-control-plane istioctl ztunnel-config service
NAMESPACE SERVICE NAME SERVICE VIP WAYPOINT ENDPOINTS
default bookinfo-gateway-istio 10.200.1.237 None 1/1
default details 10.200.1.150 None 1/1
...
워크로드 상세 정보 조회
docker exec -it myk8s-control-plane istioctl ztunnel-config workload --workload-namespace default --node myk8s-worker2 -o json
[
{
"uid": "Kubernetes//Pod/default/bookinfo-gateway-istio-6cbd9bcd49-bbsd6",
"workloadIps": [
"10.10.1.16"
],
"protocol": "TCP",
"name": "bookinfo-gateway-istio-6cbd9bcd49-bbsd6",
"namespace": "default",
"serviceAccount": "bookinfo-gateway-istio",
"workloadName": "bookinfo-gateway-istio",
"workloadType": "pod",
"canonicalName": "bookinfo-gateway-istio",
"canonicalRevision": "latest",
"clusterId": "Kubernetes",
"trustDomain": "cluster.local",
"locality": {},
"node": "myk8s-worker2",
"status": "Healthy",
"hostname": "",
"capacity": 1,
"applicationTunnel": {
"protocol": ""
}
},
...
인증서 확인
워크로드에 할당된 SPIFFE 기반 인증서를 확인할 수 있다.
# 결과에는 Root/Leaf 인증서 타입, 유효 기간, SPIFFE 식별자 등이 포함된다.
docker exec -it myk8s-control-plane istioctl ztunnel-config certificate --node myk8s-worker
CERTIFICATE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
spiffe://cluster.local/ns/default/sa/bookinfo-productpage Leaf Available true e35e4fbbc0b5f38b32df275f14a01e1e 2025-06-09T19:02:36Z 2025-06-08T19:00:36Z
spiffe://cluster.local/ns/default/sa/bookinfo-productpage Root Available true 1255f6e6fa41fe3ead58d591b24973fc
...
연결 정보
ztunnel이 현재 유지하고 있는 L4/HBONE 연결 정보를 출력한다. 각 커넥션은 Inbound/Outbound 방향으로 구분된다.
docker exec -it myk8s-control-plane istioctl ztunnel-config connections --node myk8s-worker --raw
WORKLOAD DIRECTION LOCAL REMOTE REMOTE TARGET PROTOCOL
productpage-v1-54bb874995-zrl85.default Inbound 10.10.2.5:9080 10.10.1.16:33552 HBONE
productpage-v1-54bb874995-zrl85.default Inbound 10.10.2.5:9080 10.10.1.16:33558 HBONE
정책 및 로깅 수준 확인
현재 적용된 정책이나 ztunnel의 로그 레벨을 조회할 수 있다.
# 정책 확인
docker exec -it myk8s-control-plane istioctl ztunnel-config policy
NAMESPACE POLICY NAME ACTION SCOPE
# 로그 확인
docker exec -it myk8s-control-plane istioctl ztunnel-config log
ztunnel-6ddsp.istio-system:
current log level is hickory_server::server::server_future=off,info
...
productpage Pod 내부에서 트래픽 리디렉션 확인
1. Pod 접속 및 iptables 확인
먼저 productpage Pod에 진입한다.
PPOD=$(kubectl get pod -l app=productpage -o jsonpath='{.items[0].metadata.name}')
kubectl pexec $PPOD -it -T -- bash
iptables-save 명령어로 전체 룰을 확인하거나, 특정 테이블만 필터링해 raw, mangle, nat 체인을 살펴본다.
2. DNS 트래픽 처리 로직 (raw + mangle)
raw 테이블
- DNS 트래픽(UDP 53)에 대해 connection tracking zone을 설정해 추적할 수 있게 한다.
- 마크(0x539) 기반 조건으로 리디렉션 여부를 제어한다.
-A ISTIO_OUTPUT -p udp -m mark --mark 0x539/0xfff -m udp --dport 53 -j CT --zone 1
-A ISTIO_PRERT -p udp -m mark ! --mark 0x539/0xfff -m udp --sport 53 -j CT --zone 1
mangle 테이블
- conntrack 마크(0x111)를 복원하고,
- 특정 마크가 붙은 트래픽에 대해 이후 체인에서 리디렉션 예외로 처리할 수 있게 준비한다.
-A ISTIO_OUTPUT -m connmark --mark 0x111/0xfff -j CONNMARK --restore-mark ...
-A ISTIO_PRERT -m mark --mark 0x539/0xfff -j CONNMARK --set-xmark 0x111/0xfff
3. 트래픽 리디렉션 로직 (nat)
DNS 리디렉션
- UDP 및 TCP 기반 DNS 요청을 Envoy DNS 프록시 포트인 15053으로 리디렉션한다.
-A ISTIO_OUTPUT ! -o lo -p udp --dport 53 -j REDIRECT --to-ports 15053
-A ISTIO_OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports 15053
일반 TCP 트래픽
- 마크나 특정 IP(예: 169.254.7.127)가 붙지 않은 경우, 다음 포트로 리디렉션됨:
- 15001: outbound
- 15006: inbound
- 15008: 프로메테우스 등 보안 예외 대상
-A ISTIO_OUTPUT -p tcp --dport != 15001 -j REDIRECT --to-ports 15001
-A ISTIO_PRERT -p tcp --dport != 15008 -j REDIRECT --to-ports 15006
4. 리디렉션 대상 포트 리스닝 확인
ss -tnlp
결과 예시:
- 15001, 15006, 15008, 15053 포트가 열려 있으며 Envoy에서 리디렉션된 트래픽을 수신한다.
- 9080은 앱 자체(gunicorn)가 수신 중.
5. 암호화 여부 확인
다음 명령으로 ztunnel의 포트인 15008 또는 Envoy의 15001, 15006 포트를 모니터링하여 평문 트래픽 여부를 확인한다.
apk update && apk add ngrep
ngrep -tW byline -d eth0 '' 'tcp port 15008'
ngrep -tW byline -d eth0 '' 'tcp port 15001'
ngrep -tW byline -d eth0 '' 'tcp port 15006'
이때 평문 데이터가 잡히지 않는다면 ztunnel을 통한 mTLS 암호화가 적용되고 있음을 의미한다.
6. ztunnel 확인
ls -l /var/run/ztunnel
해당 디렉터리가 존재하면 ambient 모드에서 ztunnel 프로세스가 해당 pod에 주입되어 있음을 나타낸다.
Ambient Mesh에서 mTLS 활성 여부 검증하기
Istio Ambient 모드에서는 ztunnel을 통해 워크로드 간 트래픽이 암호화된다. 다음 방법들을 통해 mTLS가 실제로 적용되었는지 확인할 수 있다.
우선, 워크로드가 HBONE 프로토콜을 사용하는지 확인한다. HBONE은 Ambient Mesh 구조에서 사용하는 방식으로, 설정되어 있다면 기본적으로 mTLS가 적용된 상태다.
docker exec -it myk8s-control-plane istioctl ztunnel-config workload
NAMESPACE POD NAME ADDRESS NODE WAYPOINT PROTOCOL
default bookinfo-gateway-istio-6cbd9bcd49-bbsd6 10.10.1.16 myk8s-worker2 None TCP
default cnsenter-kad0b53iju 10.10.2.7 myk8s-worker None HBONE
default details-v1-766844796b-7cdm7 10.10.1.11 myk8s-worker2 None HBONE
ztunnel 로그로 mTLS 피어 ID 확인
mTLS가 적용되었다면 ztunnel 로그에 트래픽 송수신 정보와 함께 spiffe:// 형식의 인증 주체 ID(SPIFFE ID)가 기록된다.
#
kubectl -n istio-system logs -l app=ztunnel | grep -E "inbound|outbound"
2024-08-21T15:32:05.754291Z info access connection complete src.addr=10.42.0.9:33772 src.workload="curl-7656cf8794-6lsm4" src.namespace="default"
src.identity="spiffe://cluster.local/ns/default/sa/curl" dst.addr=10.42.0.5:15008 dst.hbone_addr=10.42.0.5:9080 dst.service="details.default.svc.cluster.local"
dst.workload="details-v1-857849f66-ft8wx" dst.namespace="default" dst.identity="spiffe://cluster.local/ns/default/sa/bookinfo-details"
direction="outbound" bytes_sent=84 bytes_recv=358 duration="15ms"
...
tcpdump로 패킷 암호화 여부 확인
쿠버네티스 노드 또는 ztunnel이 주입된 pod 내부에서 tcpdump로 HBONE 포트(15008)와 앱 포트(예: 9080)를 감시하여 암호화 여부를 확인할 수 있다.
- 15008 포트를 통해 주고받는 트래픽에서 평문 데이터가 보이지 않는다면 mTLS가 적용된 상태
- 반대로 평문 텍스트(HTTP 헤더 등)가 보인다면 암호화되지 않았거나 ambient가 적용되지 않은 워크로드일 수 있음
# 워크로드 이름 확인
DPOD=$(kubectl get pods -l app=details -o jsonpath="{.items[0].metadata.name}")
# tcpdump 실행
kubectl pexec $DPOD -it -T -- sh -c 'tcpdump -nAi eth0 port 9080 or port 15008'
이 실습을 통해 HBONE 여부로 ambient 적용 상태를 판단하고, SPIFFE ID 및 암호화 트래픽 여부로 실제 mTLS가 작동하고 있는지를 확인할 수 있다.
L4 보안 정책 적용하기
Istio의 L4 보안 정책은 Ambient Mesh 모드에서 ztunnel이 직접 지원한다.앰비언트 구조에서는 ztunnel과 웨이포인트 프록시(Waypoint Proxy) 가 계층화되어 있어, 특정 워크로드에 대해 L7 기능(HTTP 라우팅, L7 RBAC 등)을 적용할지 선택할 수 있다.
만약 L7 기반의 정책이나 트래픽 라우팅 기능이 필요하다면 워크로드에 웨이포인트를 배포하면 된다. 이처럼 정책이 ztunnel(L4)과 웨이포인트(L7) 양쪽에서 시행될 수 있기 때문에 각 레벨에 적용되는 정책을 구분하고 명확히 이해할 필요가 있다.
L4 Authorization Policy 적용
다음 정책은 productpage 서비스에 대해 netshoot 서비스 계정만 접근을 허용하는 예제다.
# L4 Authorization Policy 신규 생성
# Explicitly allow the netshoot and gateway service accounts to call the productpage service:
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: productpage-viewer
namespace: default
spec:
selector:
matchLabels:
app: productpage
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/default/sa/netshoot
EOF
# L4 Authorization Policy 생성 확인
kubectl get authorizationpolicy
NAME AGE
productpage-viewer 5s
# 적용 후 ztunnel 로그에서 정책 적용 여부를 확인
kubectl logs ds/ztunnel -n istio-system -f | grep -E RBAC
Found 3 pods, using pod/ztunnel-6ddsp
2025-06-08T22:20:50.603334Z info xds:xds{id=8} handling RBAC update productpage-viewe
정책 동작 확인
# L4 Authorization Policy 동작 확인
## 거부 동작 확인 (외부 요청)
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
2025-06-09 07:24:50
## 허용 동작 확인 (netshoot에서 요청)
kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title
while true; do kubectl exec -it netshoot -- curl -sS productpage:9080/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
<title>Simple Bookstore App</title>
정책 업데이트
추가로 gateway 서비스 계정도 허용 대상에 포함:
# L4 Authorization Policy 업데이트
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: productpage-viewer
namespace: default
spec:
selector:
matchLabels:
app: productpage
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/default/sa/netshoot
- cluster.local/ns/default/sa/bookinfo-gateway-istio
EOF
kubectl logs ds/ztunnel -n istio-system -f | grep -E RBAC
# 허용 확인!
GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
while true; do docker exec -it mypc curl $GWLB/productpage | grep -i title ; date "+%Y-%m-%d %H:%M:%S"; sleep 1; done
<title>Simple Bookstore App</title>
'Kubernetes > Istio' 카테고리의 다른 글
Istio 시리즈 # 12 - Istio와 가상머신 통합하기(VM Support) (2) | 2025.06.01 |
---|---|
Istio 시리즈 # 11 - EnvoyFilter로 요청 처리 로직 확장하기 (0) | 2025.05.25 |
Istio 시리즈 # 10 - Istio로 구현하는 다중 클러스터 서비스 메시 (Multi-Cluster) (0) | 2025.05.25 |
Istio 시리즈 # 9 – Istio 성능 튜닝 가이드(Istio Tuning) (0) | 2025.05.18 |
Istio 시리즈 # 8 - Istio 트러블 슈팅 가이드(Istio Troubleshooting) (1) | 2025.05.17 |