9W - 앰비언트 헬름 설치, 각종 리소스 실습

개요

이전 노트에서 딥하게 앰비언트 모드의 이론과 동작 원리에 대해 파고들었다.
이번 노트에서는 이 동작들을 실제로 검증한다.
먼저 세팅을 한 이후에 실제로 트래픽이 흘러가는지 추적한다.
이후에는 이스티오의 각종 기능을 어떻게 적용하면 되는지 알아본다.

기본 클러스터 세팅

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
  extraMounts:
  - hostPath: ./pcap
    containerPath: /pcap
- role: worker
  extraMounts:
  - hostPath: ./pcap
    containerPath: /pcap
- role: worker
  extraMounts:
  - hostPath: ./pcap
    containerPath: /pcap
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

# MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
# The status of Deployment doesn't ensure the readiness of its Pods.
#kubectl -n metallb-system wait --for condition=available  deployment controller --timeout=60s
kubectl -n metallb-system wait --for condition=Ready  po --timeout=60s --all=true
sleep 1
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.101-172.18.255.120
EOF
cat << EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
EOF

# external client
docker run -d --rm --name mypc --network kind nicolaka/netshoot sleep infinity

alias de='docker exec -ti mypc '

기본 세팅은 KIND로, 멀티 노드 세팅을 한다.
MetalLB를 통해 로드 밸런서 서비스가 정상 세팅되도록 만들었다.

헬름 기반 앰비언트 모드 구축

앰비언트 모드는 버전 관리를 명확하게 하기 위해 헬름 기반 설치가 권장된다.
헬름 설치는 istioctl의 방식과 조금 다르게 몇 가지 순서를 두고 설치를 진행해야 한다.

단계가 늘어나서 좀 복잡해 보이지만, 결국 컴포넌트들을 하나 하나 세팅하는 것일 뿐이다.

################################################################################
# Install Istio Ambient
################################################################################
helm repo add istio https://istio-release.storage.googleapis.com/charts
#helm repo update

helm install istio-base istio/base -n istio-system --create-namespace --wait --set meshConfig.accessLogFile=/dev/stdout --set variant=debug
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
helm install istiod istio/istiod --namespace istio-system --set profile=ambient --wait  --set variant=debug
# If there's too much resource used during installing istio-cni, the pod for the master node fails.
# Error says there's too many open files, and once it starts to fail, it never recovered.
sleep 5
helm install istio-cni istio/cni -n istio-system --set profile=ambient --wait --set variant=debug
helm install ztunnel istio/ztunnel -n istio-system --wait --set variant=debug

중간에 일부러 sleep을 부여했다.
이렇게 한 이유는 헬름 설치가 컴포넌트 배치의 모든 준비 상태, 내부 프로세스의 초기화 상태까지 고려하지는 않기 때문이다.

추후 탐구

image.png
해당 지연을 부여하지 않고 진행할 때 지속적으로 컨트롤 플레인 istio-cni에서 too many open files 에러가 나왔다.
image.png
istio-cni에 ulimit 해보면 제한이 안 걸려있던데 왜 이렇게 되는 건지 잘은 모르겠다.
구체적으로 해당 에러는 watch 핸들의 최대 허용 수가 넘어갈 때 발생한다고 한다.[1]
이상한 건, 해당 에러가 한 번 뜨면 클러스터를 날려먹기 전까지 문제가 해결되지 않기도 했다는 것.
구체적으로 해당 에러는 istio-cni가 cri의 플러그인 설정 파일에 있는 다른 cni(kindnet)의 설정 파일에 쓰기 작업을 할 때 발생했다.
혹시 개별 파일에 접근 가능한 프로세스 갯수도 노드 별 전역 제한을 걸 수 있는 건가 했는데 이 부분은 확실치 않다.
당장은 컨트롤 플레인에서만 발생했다는 점에서 노드 내 자원의 리소스 경합이 일어났다고 판단을 내렸다.
몇 초만 지연을 부여해도 해당 문제는 해결됐다.

추가적으로 variant에 debug를 세팅하면 배치되는 컨테이너 이미지들이 전부 debug용으로 세팅된다.
앰비언트의 기본 이미지는 distroless로 설정돼있어 테스트에 있어서는 매우 귀찮음을 유발하기에 이렇게 바꿔줬다.
(참고로 가능한 값이 distroless, debug 두 개 뿐이다.)

다음은 기타 세팅이다.

################################################################################
# Install Observability tools
################################################################################
kubectl apply -f samples/addons
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}]}}'

kubectl apply -f bookinfo.yaml
#kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
kubectl apply -f samples/bookinfo/gateway-api/bookinfo-gateway.yaml

kubectl apply -f debug.yaml

GWLB=$(kubectl get svc bookinfo-gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

istioctl을 설치할 때 기본으로 제공되는 관측가능성 도구와 북인포 예제를 사용했다.
여기에 디버깅용 넷슛 파드를 추가적으로 배치했는데, 조금의 커스텀을 넣었다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: debug
  labels:
    role: debug
---
apiVersion: v1
kind: Pod
metadata:
  name: debug
spec:
  serviceAccountName: debug
  volumes:
    - name: local
      hostPath:
        path: "/pcap"
  containers:
    - name: debug
      image: nicolaka/netshoot
      command: [sh, -c, "sleep 60d"]
      volumeMounts:
        - mountPath: /pcap
          name: local
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app: productpage
  terminationGracePeriodSeconds: 0

바로 이렇게 안티 어피니티를 넣어서 넷슛 파드로 트래픽을 쏠 서비스와 의도적으로 다른 노드에 배치되게 했다.
추가적으로 트래픽 캡쳐를 위해 볼륨 마운팅해서 내 로컬에서 pcap 파일을 쉽게 열어볼 수 있도록 세팅했다.
해당 마운팅은 북인포 예제의 productpage 서비스에도 세팅했다.

설치 확인

기본적인 설치가 완료되면 이렇게 istio-system에 여러 컴포넌트들이 배치된다.
image.png
키알리로 확인해보면 details에 아직 메시의 관리를 받지 않는다는 정보도 출력해준다.
image.png
게이트웨이의 경우 이스티오의 게이트웨이 클래스를 통해 배포가 됐기 때문에 메시의 일원으로 취급은 되고 있다.
image.png
참고로 현재 이스티오에서는 gateway api에 대한 기본적인 지원을 해주기 때문에 설치 시 위의 클래스들도 알아서 배치해버린다.

컴포넌트 정보 확인

본격적으로 컴포넌트들의 상세 상태를 확인해본다.
먼저 istio-cni를 보면 다음과 같이 cni 파일을 읽어서 해당 파일에 자신의 플러그인 정보를 주입힌다.
image.png
(참고로 지연을 주지 않고 세팅했을 때 이 cni 파일에 쓰기 작업이 들어가는 부분에서 에러가 발생했다.)
추가적으로 ztunnel과 연결이 수립됐을 때 해당 정보도 출력하는 것을 볼 수 있다.

k -n istio-system get daemonsets.apps istio-cni-node -ojson | yq '.spec.template.spec.volumes[].hostPath'

image.png
istio-cni는 노드의 여러 경로를 마운팅하고 있다.
네임스페이스 관리를 해야 하다보니 netns나 프로세스 디렉토리까지, 다양하게 마운팅을 해두는 것을 볼 수 있다.
image.png
여기에 양식을 확인해보면 NET_ADMIN, SYS_ADMIN과 같은 막강한 권한을 가지고 있다.

위험한 역할을 istio-cni가 거의 다 수행해주는 덕에 ztunnel은 상대적으로는 적은 권한을 가진다.
image.png
라곤 하지만 SYS_ADMIN을 가지고 있는 것은 똑같다.
네임스페이스에 진입하는 setns 함수는 CAP_SYS_ADMIN을 가져야 하다보니 어쩔 수 없는 부분으로 보인다.[2]
image.png
그래도 호스트패스 볼륨으로 가지는 것은 istio-cni와 통신할 때 사용하는 통로밖에 없다.

ztunnel이 열고 있는 소켓들은 무엇이 있는가?

keti -n istio-system ztunnel-fn7n4 -- ss -ntlp 

image.png
설정을 받는 포트로는 위의 것들을 열어뒀다.
image.png
여기에 istio-cni와 통신하는 창구로 ztunnel 소켓 파일도 있는 것을 확인할 수 있다.
image.png
해당 파일은 istio-cni와 ztunnel이 노드 호스트패스에 배치해두고 사용한다.

ztunnel은 엔보이와 동일하게 디버깅을 위해 15000포트를 열어두고 있다.

keti -n istio-system ztunnel-qlw9h -- curl localhost:15000/config_dump

image.png
엔보이랑 비교하면 정말 선녀 같은 설정 덤프..
재밌는 점은 워크로드 설정 부분을 보면 ztunnel, istio-cni, istiod 등 그냥 클러스터에 존재하는 모든 파드들을 워크로드 리스트에 저장시켜둔다는 것.
이 중에서 프로토콜 정보만 딸깍 바꾸면 그때부터 메시 내부 트래픽이 되도록 세팅된다.

메시 정보 확인

앰비언트 모드에서도 마찬가지로 istioctl을 이용해 메시 정보를 확인할 수 있다.
다만 조금 새로운 명령어들을 써줘야만 한다.

istioctl proxy-status
istioctl ztunnel-config workload
istioctl ztunnel-config service
istioctl waypoint list

이런 식으로 말이다.
image.png
먼저 proxy status를 보면 예제로서 세운 게이트웨이 워크로드의 프록시가 잡히고, ztunnel 들이 추적된다.
여기에 대고 이전 처럼 proxy-config를 할 수 있을까?
image.png
일반적인 대상들에 대해서는 불가능하다.
애초에 proxy-config 명령어는 대상 파드에 exec 명령으로 15000포트에 config_dump를 떠오는 방식으로 동작한다.
그러니 사이드카 모드에서만 통용되던 명령어인 것이다.

그렇지만 gateway 워크로드는 여전히 엔보이로 만들어지기에 문제 없이 추적할 수 있다.

게이트웨이 추적

이 게이트웨이 리소스는 게이트웨이 API로 설정된 게이트웨이라는 점에서 차이가 있어서, 비단 앰비언트 모드이기에 발생하는 설정 간 차이가 있지는 않을 것이다.
차이라고 한다면 ztunnel이 워크로드 상태를 가지고 있기 때문에 발생하는 차이가 있을 것으로 보인다.

먼저 리스너 설정 정보를 보자.
image.png
게이트웨이 리소스로 설정된 80 포트가 열려 있고, 이외에는 그냥 connect_originate라는 클러스터로 연결된다.
게이트웨이 리소스로 열린 라우트 설정은 라우트로 확인할 수 있다.
image.png
값들이 거의 존재하지 않는다..
image.png
엔보이에 적용된 설정들은 전부 HTTPRoute 리소스의 정보만 반영돼있다.

클러스터 설정을 보면 본격적으로 재밌는 클러스터가 눈에 띈다.
image.png
HBONE 터널을 사용하는 클러스터..
image.png
해당 클러스터에 매칭되면 게이트웨이 엔드포인트로 트래픽을 넘긴다.
클러스터 내부 트래픽과 더불어 외부 트래픽을 전부 처리하기 위한 설정으로 보인다.

다음으로는 기본으로 트래픽이 흘러들어갈 클러스터였던 connect_originate를 본다.
image.png
해당 클러스터의 설정에 대해 게이트웨이는 일반적인 프록시 역할을 수행하는 것으로 보인다.
커넥트 연결을 받고 이로부터 웨이포인트의 15008 포트로, 즉 hbone으로 연결해서 트래픽을 전달하는 것으로 보인다.

앰비언트 모드 세팅

본격적으로 앰비언트 모드를 세팅하면 발생하는 일을 살펴보자.
먼저 앰비언트 ztunnel을 보면 모든 워크로드와 서비스의 정보를 이미 알고 있다.
image.png
현재는 프로토콜이 TCP로, ztunnel이 일절 트래픽에 관여하지 않고 있는 상태이다.

기본 앰비언트 적용

여기에서 메시에 들어가게 하는 것은 매우 간단하다!

kubectl label namespace default istio.io/dataplane-mode=ambient

일단 앰비언트 모드를 세팅하는 것은 너무나도 간단하다.
사이드카 모드였다면 파드를 재기동했어야 했겠지만, 앰비언트는 워크로드에 일절 영향을 주지 않는다.
image.png
설정하자마자 default 네임스페이스에 있는 파드들의 protocol이 HBONE으로 표시되는 것을 확인할 수 있다.

image.png
해당 파드들이 배치된 노드의 istio-cni 로그를 확인해보면 허겁지겁 iptables를 세팅하는 것을 확인할 수 있다.
image.png
여기에 ztunnel에게 해당 파드 받아라 하는 내용까지 로그로 담긴다.
그럼 ztunnel 로그도 한번 확인해보자.
image.png
여기에서도 어떤 파드에 설정하라 받았다, 프록시 시작한다 간결하게 출력해준다.
그리고 어떤 리스너들이 설정됐는지 로그로 남겨준다.

image.png
설정이 적용된 파드에 들어가서 iptables를 확인해보면 조작이 일어난 것을 확인할 수 있다.
참고로 pexec은 강한 권한을 가진 파드를 실행시켜서 원하는 파드의 네임스페이스에 접근해 원하는 조작을 수행하는 kubectl 플러그인이다.
나는 조금 더 정확하게 해당 컨테이너의 네임스페이스의 모든 것을 확인하고 싶어서 직접 들어가서 확인을 진행했다.

# 노드 진입 후
DEBUG_PID=$(ps -ef | grep sleep | head -n 1 | awk '{print $2}')
# pid 확인
nsenter -t $DEBUG_PID -a

디버깅용 파드에 명령어를 sleep으로 주었기 때문에 sleep을 실행하는 프로세스를 찾아 들어갔다.
image.png
리스닝 소켓 정보를 뜯어보니 ztunnel이 열어둔 포트 경로가 확인된다.
그러나 프로세스의 정보는 확인할 수 없었다.
/proc에 들어가서 찾아보려고 해도 해당 소켓 파일의 경로를 찾기가 어려웠다.
내부 트래픽 경로는 분명하게 수정되는 동시에 워크로드는 알기 힘든 상황에 놓이게 되는 것 같다.

대충 시도해 본 것들은..

find /run /var/run /tmp /proc -type s

직접 들어가서 전부 뒤져보기 등..

생각해보면 이렇게 소켓의 정보를 확인하기 어려운 게 당연한 것 같기도 한 것이, 한 파드 내의 두 컨테이너 간 연결을 추적할 때도 결국 마찬가지의 상황에 놓인다.
두 컨테이너가 공유하는 것은 네트워크 네임스페이스 뿐이기 때문에 소켓 정보를 볼 수는 있어도 실제 파일은 찾을 수가 없는 것과 같은 것이다.

기본 gateway api 리소스 확인

2주차에도 한번 본 적 있는 리소스이지만, 앰비언트에서는 이제 필수적인 방식인 게이트웨이 api 리소스 양식을 살펴본다.

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

게이트웨이 리소스에서는 어떤 포트를 수신할 것인지 세팅하고 자신에게 걸 수 있는 라우트 리소스를 제한한다.
listeners[].hostname 필드를 설정하면 수신할 호스트 이름도 지정하는 게 가능하나, 이 예시에서는 그냥 모든 호스트를 받을 수 있도록 설정됐다.
즉 해당 게이트웨이의 ip로 들어오는 모든 80번 포트로의 요청을 받아낸다.

라우트 리소스에서는 해당 게이트웨이를 먼저 부모로 지정한다.
이후 세부 룰을 지정하고, 해당 라우트 룰을 통해 트래픽이 흐를 서비스를 지정한다.

적용된 리소스를 확인해본다.

k get gtw
k get httproute

이스티오 게이트웨이의 alias가 gw라, gtw가 약자다..(불-편)
심지어 라우트는 별칭도 없다..
image.png
아무튼 적당히 기다리다보면 해당 리소스들이 제대로 생성된다.
image.png
게이트웨이는 해당 워크로드가 제대로 준비되기 전까지 programmed가 False로 잡혀있는데 이거 생각보다 시간이 좀 걸린다.

트래픽 경로 분석

세팅은 이걸로 이미 끝났고, 이제 그냥 평소처럼 트래픽을 날리기만 하면 된다.
이제부터는 앰비언트 모드에서 일어나는 트래픽의 흐름을 추적해본다.

keti debug -- curl productpage:9080

ztunnel 레벨의 경로만 분석하면, 웨이포인트는 그저 한 홉이 증가하는 것에 불과하다.
그래서 그다지 어려운 레벨은 아니라 ztunnel 쪽에서 일어나는 위주로 정리한다.

간단한 트래픽에 대한 ztunnel 로그는 다음과 같이 나타난다.
image.png
출발지(debug)의 ztunnel의 로그는 다음과 같다.
목적지가 15008, 즉 hbone 포트였고 그 안 속 패킷은 9080 포트를 가리키고 있다.
또한 출발지 측에서 "나가는" 트래픽에 대한 로그이기 때문에 direction=outbound로 찍히는 것이 확인된다.
image.png
반대로 목적지(productpage)의 ztunnel 로그는 이렇게 남는다.
표시되는 src, dst 자체의 정보는 비슷한데, 눈 여겨 볼 지점 중 하나는 출발지의 임시 포트가 다르다는 것이다.
출발지에서 찍힌 임시 포트는 앱 컨테이너가 날린 트래픽에 대한 포트 정보이나, 목적지에서 찍히는 임시 포트는 출발지의 ztunnel이 날린 트래픽에 대한 포트 정보가 남기 때문에 이러한 결과가 나온다.
추가로 트래픽이 들어온 측의 로그라 direction=inbound가 나오고 있다.

iptables 분석

이 부분은 이전에 정리해둔 문서를 다시 가져온다.
애초에 해당 내용이 실질적으로 실습을 진행하면서 해당 정보를 분석하여 정리한 내용이다.

세팅은 이런 식으로 해서 패킷 카운트를 확인했다.

TABLE="mangle nat"
CHAIN="PREROUTING ISTIO_PRERT OUTPUT ISTIO_OUTPUT"
watch -d "for i in $TABLE ; do for j in $CHAIN ; do iptables -t \$i -n -v -L \$j; echo; done; echo; done"

먼저 mangle 테이블은 이렇게 생겼다.

Chain PREROUTING (policy ACCEPT 675 packets, 158K bytes)
 pkts bytes target     prot opt in     out     source               destination         
  675  158K ISTIO_PRERT  0    --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain ISTIO_PRERT (1 references)
 pkts bytes target     prot opt in     out     source               destination         
   48  3749 CONNMARK   0    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x539/0xfff CONNMARK xset 0x111/0xfff

Chain OUTPUT (policy ACCEPT 688 packets, 107K bytes)
 pkts bytes target     prot opt in     out     source               destination         
  688  107K ISTIO_OUTPUT  0    --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain ISTIO_OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination         
  346 75875 CONNMARK   0    --  *      *       0.0.0.0/0            0.0.0.0/0            connmark match  0x111/0xfff CONNMARK restore

이 테이블 정보를 이해하기 위한 몇 가지 지식이 있다.

iptables에서 match a / b 의 의미는 패킷 c에 대해 c & b == a를 묻는 것이다.
즉 AND연산을 하는 것인데, 0xfff(1로 채워진 12비트)에 AND 연산을 한다는 건 그냥 하위 12 비트가 a와 같냐고 묻는 것과 같다.

connmark와 mark가 구제적으로 다르다는 사실에 유의해야 한다.
connmark는 컨트랙 모듈이 관리하는 커넥션 엔트리에 마킹되는 값으로, 커널 내부에서 패킷 구조체에 마킹되는 값과 다르다.

룰 해석 자체는 그렇게 어렵지는 않다.
들어오는 트래픽에 대해서는 마크가 0x539일 경우 커넥션 마크를 0x111로 세팅한다.
그리고 나가는 트래픽의 커넥션 마크에 0x111이 세팅되어 있다면 해당 값을 일반 마크에도 세팅한다.
이 마킹 정보들은 이후 nat 테이블에서 사용된다.
여기에서 0x539는 ztunnel측에서 세팅해주는 값으로 십진수로는 1337이다.

다음으로 nat 테이블은 이런 식으로 구성된다.

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ISTIO_PRERT  0    --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain ISTIO_PRERT (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       169.254.7.127        0.0.0.0/0            tcp
    0     0 REDIRECT   6    --  *      *       0.0.0.0/0           !127.0.0.1            tcp dpt:!15008 mark match ! 0x539/0xfff redir ports 15006

Chain OUTPUT (policy ACCEPT 1 packets, 60 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   49  3756 ISTIO_OUTPUT  0    --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain ISTIO_OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            169.254.7.127        tcp
   24  2256 REDIRECT   17   --  *      !lo     0.0.0.0/0            0.0.0.0/0            mark match ! 0x539/0xfff udp dpt:53 redir ports 15053
    0     0 REDIRECT   6    --  *      *       0.0.0.0/0           !127.0.0.1            tcp dpt:53 mark match ! 0x539/0xfff redir ports 15053
    0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x111/0xfff
    0     0 ACCEPT     0    --  *      lo      0.0.0.0/0           !127.0.0.1           
   24  1440 REDIRECT   6    --  *      *       0.0.0.0/0           !127.0.0.1            mark match ! 0x539/0xfff redir ports 15001

이전 사이드카 모드 때의 iptables와 비교하면 꽤나 간결한 편이다.

테이블이 두 개 뿐이라 해석 자체는 상대적으로 쉽다.
먼저 들어오는 패킷에 대해 169.254.7.127에서 왔다면 무조건 통과시킨다.
해당 주소는 kubelet에서 오는 헬스체크 프로브에 대해 ztunnel이 SNAT 시키는 주소로, 이를 통해 헬스체크 패킷은 정상적으로 동작하게 만든다.
그리고 다음은 15006으로 리다이렉트를 시키는 조건이 적혀있는데, 간단하게 말하자면 그냥 앱 컨테이너에서 나가는 요청을 낚아채는 것이다.
사이드카 모드와 달리, 이제는 룰에 명시되지 않은 나머지 패킷들이 그냥 통과한다는 것을 유의할 필요가 있다.

패킷 확인

다음으로는 실제로 패킷을 캡쳐해서 어떻게 동작이 일어나는지 확인한다.
패킷을 실제로 뜯어보면 어떻게 트래픽이 이동했는지 감을 잡는데 도움된다.

아래 명령어는 전부 각 파드의 네임스페이스에 들어간 뒤에 캡쳐를 떴다.
참고로 productpage 파드에 들어가면 tcpdump 패키지를 설치해야 한다.

# curl to same node
tcpdump -i any -c 100 -w /pcap/same-node.pcap
# dump from debug pod
tcpdump -i any -c 100 -w /pcap/outbound-to-pod.pcap
# dump from productpage pod
tcpdump -i any -c 100 -w /pcap/inbound-from-pod.pcap

아웃바운드 트래픽

먼저 debug 파드 쪽에서 아웃바운드 트래픽을 캡쳐한 결과이다.
image.png
해당 트래픽을 포트를 기준으로 단계적으로 보자.

결국 앱 컨테이너의 요청은 ztunnel로 들어가고, ztunnel이 실제 요청을 날리는데 이 요청은 목적지 파드가 있는 곳의 ztunnel인 것이다.
그리고 그 요청은 15008 포트, HBONE을 통해 이뤄진다.
재밌는 것은 46048포트로 나가는 트래픽은 ztunnel이 날리는 트래픽인데, 파드의 네임스페이스에서 찍힌다는 것이다.
image.png
HBONE으로 한창 트래픽이 오가고, 이후에 ztunnel은 앱 컨테이너에 응답을 돌려준다.
물론 응답은 원래 목적지인 productpage 9080포트에서 온 것처럼 표시된다.

인바운드 트래픽

다음은 요청을 받았던 productpage에서 뜬 덤프이다.
image.png
이쪽은 플로우 그래프가 훨씬 단순하다.

image.png

최종적으로 정리하자면 현재 트래픽의 흐름은 이런 식으로 진행됐다.

ztunnel이 어떻게 워크로드 간 트래픽을 전달하는지 이제 명확하게 알 수 있다!

마지막으로 HBONE이 왜 터널인지 살짝 알 수 있는 대목으로, 반복적으로 요청을 보낸 패킷의 모습을 담아본다.
단순하게 위의 패킷을 뜨기 위해 debug 에서 productpage로 요청을 날린 것을 두어 번 반복했다.
image.png
앱 컨테이너에서의 요청 자체는 56436 임시 포트로 아무거나 부여받은 것이 확인된다.
그러나 15008로 향하는 출발지 ztunnel은 이전에 사용했던 46048포트를 그대로 사용하고 있다.
이는 HBONE 연결이 지속적으로 연결돼있으며, 추가적인 통신에 그대로 재사용된다는 것을 의미한다.
(conntrack -L을 찍어보면 만료 기한이 어마무시하게 길게 잡혀 있다..)
이후 트래픽은 원래도 0x539 마킹이 돼 있어 iptables에 영항을 받지 않겠지만 아예 컨트랙을 통해 추적되는 트래픽이기 때문에 iptables의 nat 테이블은 아예 거치지 않는 결과가 나오게 될 것이다.

웨이포인트 적용

웨이포인트는 전적으로 게이트웨이 api를 이용해 만들도록 제한되어 있다.
먼저 게이트웨이 역할을 할 워크로드를 만들고 버츄얼 서비스로 설정하는 식은 불가능하다.
게이트웨이 리소스를 만드는 방법을 매우 간단하게 지원을 해주고 있기에 안 써봤다고 해도 어려울 것은 없다.

istioctl waypoint generate --for service
istioctl waypoint apply --for service

image.png
보다시피 적용되는 설정은 이게 끝이다!
웨이포인트는 ztunnel로부터만 트래픽을 받는데 항상 HBONE 통신을 하므로 해당 부분에 대한 리스너만 설정이 되면 끝인 것이다..
그래서 사실 위와 같이 명령어의 도움을 빌리지 않고 직접 다른 이름을 가진 웨이포인트를 마음껏 만들 수 있다.
gatewayClass 이름을 istio-waypoint로 지정해주기만 하면 해당 게이트웨이로 인해 생성될 엔보이를 웨이포인트로 사용할 수 있다.
라벨 부분은 워크로드로 향하는 트래픽에 대해서도 적용할지, 서비스를 거쳐서 가는 트래픽만 적용할지 등을 나타내는 설정이다.

참고로 실제 배치되는 웨이포인트 워크로드 양식은 이렇게 된다.
image.png

그러나 웨이포인트가 배치된다고 해당 네임스페이스의 트래픽이 전부 웨이포인트를 경유하게 되는 것은 아니다.
image.png
보다시피 istioctl zc service/workload를 찍어보면 웨이포인트 컬럼에 None이라고 적힌 것을 보면 알 수 있다.
이제 경유를 받게 하고 싶은 서비스나 워크로드를 지정하는 작업을 해야 한다.
이 작업도 어려울 것 없이, 네임스페이스나 해당 서비스에 라벨을 지정하기만 하면 된다.

kubectl label ns default istio.io/use-waypoint=waypoint

라벨로 istio.io/use-waypoint 에 만들어진 웨이포인트의 이름을 적어주기만 하면 된다!
istioctl waypoint apply를 할 시 기본 만들어지는 게이트웨이 리소스의 이름이 waypoint라 라벨 값에 waypoint라고 적는다는 것에 유의하자.
image.png
이제 ztunnel에서는 위 서비스로 트래픽이 가야 하는 경우에 웨이포인트를 경유시킬 것이다.
image.png
json으로 뜯어보면 기본적인 서비스에 대한 정보에 더불어 웨이포인트의 경로가 명시된다.

이제 본격적으로 웨이포인트로 동작하는 엔보이의 설정이 어떻게 돼있는지 뜯어본다.
image.png
먼저 리스너로, 위의 게이트웨이와 마찬가지로 대상이 되는 서비스만 리스너로 받는다.
여기에 만약 각종 L7 기능을 넣게 된다면 비로소 라우트 설정이 들어가게 될 것이다.
image.png
엔보이 설정 상으로는 15008, 즉 HBONE 포트로 들어오는 트래픽에 대해 인터널 리스너를 설정한다.
이때 인터널 리스너가 INBOUND, 즉 들어오는 방향으로만 그대로 내보낸다는 것이 바로 목적지 기반 정책 제어를 하는 웨이포인트의 큰 특징이다.
참고로 설정을 더 뜯어보면 15008을 수신하는 리스너를 보면 막상 클러스터가 설정돼있다.
image.png
해당 클러스터 이름은 main_internal인데 이게 리스너 설정에 보이는 내부 리스너이다.

다시 리스너 설정으로 돌아와 main_internal 리스너를 보면 아래와 같이 여러 서비스에 대한 매칭을 걸고 있다.
image.png

설정 방식 차이에 대한 사견

기존 엔보이 설정은 내부 리스너를 설정할 때 리스너에 useOriginDst를 설정하여 내부 리스너가 사용되도록 설정했다.
그런데 여기에서는 클러스터로 내부 리스너를 등록하는 식으로 설정되는 것 같은데, 이런 식으로 설정이 가능한지는 처음 알았다.
HBONE 통신을 하며 내부 패킷을 뜯어낸 이후에야 대상이 될 리스너를 설정할 수 있기 때문에 아무래도 패킷을 복호화하는 과정이 먼저 필요할 것이다.
그래서 관련된 필터체인을 거치는 과정이 먼저 들어간 이후 트래픽을 보낼 리스너를 설정할 수 있게 되는 건데, 엔보이의 기존 내부 리스너 설정 방식에서는 지원하지 않는 방법이기 때문에 가상의 클러스터로 내부 리스너를 가리키도록 설정한 것으로 보인다.
아울러 웨이포인트 엔보이는 ztunnel에서 오는 트래픽은 15008 포트로 받지만, 다른 방식으로 들어오는 트래픽에 대해서는 connect_originate 리스너로 수신해야 한다.
이러한 트래픽도 처리하기 위해서 로직 변경이 일어난 게 아닐까 한다.
애초에 엔보이 잘알이라고 자신할 수 없기에, 최대한 드러난 현상을 이해하는데 초점을 맞췄으니 상당한 뇌피셜이란 것을 참고하자.

image.png
엔드포인트 설정을 보면 아예 scheme이 envoy로 박힌 main_internal을 확인할 수 있다.

그럼 트래픽을 실제로 보내면 어떻게 동작하는가?

keti debug -- curl productpage:9080

이렇게 보내고 나서 또다시 각 ztunnel의 로그를 확인해보자.
image.png
먼저 debug 쪽의 ztunnel 로그를 보면 waypoint로 보낸 요청이 outbound로 잡히는 것을 확인할 수 있다.
image.png
반대로 받은 측의 ztunnel에서는 waypoint로부터 inbound 요청을 받았다.

일일히 패킷을 뜨지 않았지만 웨이포인트 엔보이의 설정 정보와 ztunnel의 기본 동작 방식을 생각하면 실제 경로가 어떻게 이뤄졌는지 쉼게 짐작할 수 있다.

ztunnel들이 요청을 보낼 때 사용된 포트는 패킷을 뜯지 않으면 모른다.
다만 위의 로그 정보만 보더라도 이런 식으로 트래픽이 경유했을 것은 쉽게 파악할 수 있다.

키알리 확인

트래픽 경로에 대한 추적은 키알리로 마지막으로 간단하게 시각화하고 끝내고자 한다.

watch -d curl http://172.18.255.101/productpage -o /dev/null -v

image.png
기본적으로 키알리를 보게 되면 서비스 간, 그리고 내부 워크로드의 트래픽이 어떻게 흘러가는지 파악할 수 있다.
그런데 여기에서 Display 버튼에서 waypoint 프록시를 보도록 세팅하게 되면..
image.png
굉장히 어지럽게 나오기 시작한다!
서비스 기능에 맞춰 흘러가는 트래픽만 보고 싶다면 상단에서 아예 http 트래픽만 켜거나, 아니면 서비스 그래프로 보면 된다.
실제 트래픽의 흐름은 tcp로 파란색 줄로 출력되고 있는 것을 확인할 수 있다.
image.png
productpage에서 웨이포인트로 가는 tcp 트래픽 라인을 잡아보면 details와 reivews가 호스트로 추적된다.
한 가지 키알리를 보며 아쉬웠던 것은 웨이포인트의 신원을 이용해 통신을 한다는 정보를 확인할 방법이 없다는 것.

아무튼 현재 트래픽의 흐름에만 초점을 맞춰서 도식화를 해보면 다음과 같은 모양이 된다.

네임스페이스 단위로 웨이포인트를 사용하도록 설정했기 때문에 모든 트래픽이 웨이포인트를 경유하고 있는 것을 볼 수 있다.
그러나 딱 한 가지, 게이트웨이에서 productpage로 가는 요청은 웨이포인트를 거치지 않는다.
이전에 봤던 사항으로, ztunnel에서 출발하는 트래픽이 아닌 이상 웨이포인트를 거치지 않는다는 것을 여기에서 확인할 수 있는 것이다.
게이트웨이는 일반 엔보이기 때문에 ztunnel이 트래픽을 캡쳐하지 않는다.
그 결과 웨이포인트를 거치지 않고 트래픽이 productpage로 전달돼버렸다.

트래픽 제어, 인가 설정

마지막으로 이스티오의 기본적인 기능들을 사용해보자.
L4 레벨에서 인가 정책을 설정하고, 그 다음 L7의 트래픽 제어와 인가 정책까지 설정하는 식으로 도전해볼 것이다.
웨이포인트가 있고 없고의 차이를 보기 위해 일단 웨이포인트 라벨을 없앴다.

kubectl label ns default istio.io/use-waypoint-

그리고 reviews 서비스로 들어가는 다음의 요청들을 만들었다.
참고로 미리 test 네임스페이스를 만들고 debug 파드를 만들어두자.

ADDRESS="reviews.default:9080/reviews/1"
#  ADDRESS="details.default:9080/details/1"
execute_test() {
  keti debug -- curl $ADDRESS
  printf "\n---------\n"
  curl http://$GWLB/api/v1/products/1/reviews -s
  printf "\n---------\n"
  keti -n test debug -- curl $ADDRESS
}
execute_test

debug에서 날리는 요청들은 헤더 내용들을 쉽게 커스텀할 수 있게 하기 위한 요청이다.
일반 curl은 productpage가 요청을 날리게 하기 위해 넣었다.
마지막으로 다른 네임스페이스에서 요청을 날릴 수 있는지도 확인한다.
image.png
요코로롬 기본적으로는 모든 응답이 성공한다.

Peer Authentication - mtls 제한

일단 첫번째로 test 네임스페이스의 debug가 요청하는 걸 보니 배알이 꼴린다!
이건 인가 정책에서 ALLOW 제한을 걸어서 막을 수도 있다.
그러나 ALLOW는 잘못 설정하는 순간 웬만한 모든 다른 요청을 막아버리기 때문에 사용에 주의할 필요가 있다.

생각해보면 어차피 test 네임스페이스에는 앰비언트가 적용돼있지 않은 관계로 요청은 정말 순수하게 평문으로 날아간다.

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
 name: strict
spec:
  mtls:
    mode: STRICT
  selector:
    matchLabels:
      app: reviews

그렇기 때문에 Istio PeerAuthentication를 STRICT로 설정하는 것만으로 해당 요청을 막아버릴 수 있다.

해당 설정을 적용하면 ztunnel 로그에 관련한 설정이 적용됐다는 내용이 나온다.
image.png
그리고 이 내용은 istioctl을 통해서도 확인할 수 있다.
image.png
해당 정책을 보면, 그냥 deny이다..
재밌는 점은 istio-system 네임스페이스, 즉 전역으로 설정이 될 것처럼 생겼다는 것.
그리고 내가 설정한 이름으로 이름이 정해지지도 않았다.
image.png
이 정책이 진짜 어디 적용되는지는 각 워크로드를 들어가보면 알 수 있다.
정책은 제대로 reviews에만 적용된다.
image.png
test 네임스페이스의 요청은 바로 막혀버리는 것을 확인할 수 있다.

L4 Authorization Policy

이제 L4 레벨의 인가 정책을 설정해본다.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-review-debug
spec:
  selector:
    matchLabels:
      app: reviews
#  action: ALLOW
  action: DENY
  rules:
    - from:
        - source:
            principals:
              - cluster.local/ns/default/sa/debug

기본 네임스페이스의 debug에서 들어오는 요청은 허용하지 않도록 설정해본다.
image.png
이번에도 해당 이름의 정책이 설정됐다.
이름과 설정 정보가 이상한? 건 Peer Authentication 쪽이라는 걸 확실하게 알 수 있는 대목.
해당 리소스의 설정을 적용할 때 ztunnel의 정책 양식으로 바꾸기 위해 istiod가 자체적으로 변형을 조금 가하는 것으로 보인다.

image.png
아무튼 확실하게 기본 네임스페이스의 debug에서만 요청이 거부된다.

L7 Authorization Policy

이제부터는 웨이포인트가 있어야만 적용될 수 있는 설정들을 넣어본다.
특정 http 헤더가 들어있으면 요청을 거부하도록 세팅해보자.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-with-header
spec:
#  selector:
#    matchLabels:
#      app: reviews
  targetRef:
    kind: Service
    name: reviews
  action: DENY
  rules:
    - when:
        - key: request.headers[deny]
          values:
            - "true"

deny란 헤더가 true로 설정되어 들어오면 해당 요청은 거부해본다.
주의할 점으로, L7 레벨의 정책을 적용할 때 이스티오에서는 반드시 targetRef를 통해 설정하도록 제한한다.[3]
앰비언트 모드만을 위한 리소스가 아니다보니 이런 식으로 사용방법에 대한 제한이 생겼음에도 해당 부분에 대해 제대로 에러를 내뱉지 않는다..

그래서 이걸 설정하고 실제로 트래픽을 날려도 해당 요청은 성공한다.

  keti debug -- curl $ADDRESS -H "deny: true"

image.png

이제 웨이포인트를 사용해야할 순간이 왔다!

k label svc reviews istio.io/use-waypoint=waypoint

적용하는 것이 쉽고 빠르다는 것은 분명한 앰비언트의 장점인 것 같다.
image.png
보다시피 처음에는 무리 없이 성공했던 요청이 서비스에 라벨을 다는 것만으로 순식간에 적용됐다.

번외 - targetRef를 사용하지 않았을 때 디버깅

참고로 셀렉터를 이용해서 설정을 했을 때의 상황을 캡쳐했다.
image.png
잘못 세팅한 예시이나, 해당 부분에 대한 어떠한 경고도 주어지지 않는다.
그나마 이를 빠르게 판단할 수 있는 방법은 ztunnel에 설정이 들어갔는지 보는 것이라 생각한다.
웨이포인트와 ztunnel에 들어가는 설정은 명확하게 구분된다.
즉 웨이포인트에 들어가야 할 설정은 ztunnel에는 들어가서는 안 된다.
image.png
이렇게 설정 정보가 들어갔다면 설정을 잘못한 거니까 후딱 수정해주자.

트래픽 제어

마지막으로는 트래픽 제어를 해본다.
이 부분은 빠르게 하고 싶어서 그냥 문서의 예제를 가져다 썼다.[4]

apiVersion: v1
kind: Service
metadata:
  name: reviews-v1
spec:
  ports:
    - port: 9080
      name: http
  selector:
    app: reviews
    version: v1
---
apiVersion: v1
kind: Service
metadata:
  name: reviews-v2
spec:
  ports:
    - port: 9080
      name: http
  selector:
    app: reviews
    version: v2
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: reviews
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

사전 작업으로 먼저 버전 별 서비스 리소스를 만들었다.
그 이후 리뷰로 들어가는 트래픽에 대해서는 각 서비스로 트래픽을 가중치에 기반해 보내도록 만드는 설정을 넣는다.

웨이포인트의 엔보이 라우트 설정을 뜯어보면 해당 설정이 적용된 것을 확인할 수 있다.
image.png
새로운 라우트가 추가되고 이 라우트는 트래픽을 분산하여 클러스터로 보낸다.
image.png
설정을 일일히 다시 뜯어보지는 않겠다.
다만 main_internal 리스너에서 구체적으로 해당 라우트로 트래픽을 보낼 수 있는 것은 main_internal에 설정된 filterChainMatcher를 통해 이뤄진다.
image.png
포트가 9080인 경우 inbound-vip|9080|http 필터로 요청을 수행하도록 설정된 것을 확인할 수 있다.

이제 100번 정도 요청을 때려보자!

for i in {1..100};
do
  keti debug -- curl $ADDRESS | jq '.podname'
done | sort | uniq -c

image.png
보다시피 90:10 정도로 트래픽이 제대로 분산된 것을 확인할 수 있다.

그러나 이 설정은 어디까지나 웨이포인트를 경유할 수 있는 트래픽에 대해서만 설정된다.
즉 ztunnel을 거치지 않는, 앰비언트 설정이 들어가지 않은 곳으로부터 트래픽이 날아가면 해당 설정은 무용지물이라는 것이다.

for i in {1..100};
do
  keti -n test debug -- curl $ADDRESS | jq '.podname'
done | sort | uniq -c

image.png
보다시피.. test 네임스페이스에서 보낸 요청은 해당 설정이 적용되지 않았다!

게이트웨이에서 보내는 요청 역시 마찬가지이다.

cat <<EOF | kaf -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: test
spec:
  parentRefs:
  - name: bookinfo-gateway
  rules:
  - matches:
      - path:
          type: PathPrefix
          value: /reviews/1
    backendRefs:
      - name: reviews
        port: 9080
EOF

for i in {1..100};
do
  curl http://$GWLB/reviews/1 -s | jq '.podname'
done | sort | uniq -c

아예 게이트웨이에서 바로 reviews로 트래픽을 날릴 수 있도록 httproute를 추가했다.
image.png
이 트래픽 역시 ztunnel에서 날아가는 것이 아니므로, 웨이포인트의 트래픽 제어는 먹히지 않는다.
image.png
참고로 위 httproute는 정상적으로 설정이 적용된 상태이다.

결론

앰비언트 모드는 분명 사용법이 간단해서 가볍다는 인상을 받았다.
그러나 동시에 앰비언트 모드는 기존 모드와의 호환성을 위해서 많은 부분에서 엄격함을 포기한 것 같은 생각도 계속 든다.
당장 정책 관련 리소스에 targetRef를 설정해야 한다던가 하는 부분도 그렇다.
그리고 웨이포인트의 설정은 웨이포인트를 경유해야만 적용되는데 사실 이를 경유하기 위한 조건이 은근히 까다롭다.
사이드카 프록시, 게이트웨이에서 출발하는 트래픽을
앰비언트는 적응하기 전까지 굉장한 휴먼 에러를 발생시킬 것이라고 생각한다.
이러한 부분들은 차차 개선되어갈지 모르겠다.

번외 - ztunnel이 대신 보내는 트래픽의 네임스페이스에 대해

처음 패킷을 뜯어볼 때 너무 의외여서 감을 잡는데 오래 걸린 부분이 있다.
ztunnel에서 다른 ztunnel의 15008포트로 날린 것으로 보이는 트래픽이 파드 내부에서 포착됐기 때문.
이것 때문에 이게 무슨 상황인가, 너무 헷갈렸다.
아키텍처 설명 글을 보면 ztunnel은 파드 네임스페이스에 리스너 소켓만 생성한다고 했던 것 같은데, 그것만으로는 도저히 이 상황이 이해되지 않았다.
단순히 리스너 소켓만 만들어서 받아내는 것이 아닌 것으로 보인다.
요청을 처리하는 소켓을 둘 때 setns를 한 것이 이후 요청을 처리할 때도 전파된다고 해석해야 할까?
혹시 소켓이 양방향 통신을 할 수 있는 거라도 되나 싶었는데, 임의의 포트를 받는다는 것은 결국 클라 측에서 connect를 하는 동작으로 해석해야 한다고 본다.
애초에 tcp 리스닝 소켓으로 새로 요청을 한다는 발상도 말이 안 되고, 된다 한들 어떻게 인터페이스를 지나는 시점에 ip와 포트를 조작할 수 있는 것인지 해석이 되지 않는다.

내가 소켓에 대해 잘 이해를 못해서 그런 건가 싶어서 관련 글들을 찾아본 이후에, ztunnel이 실제로 요청을 파드의 네임스페이스 자원을 활용해서 보낸다고 판단했다.
그래서 코드를 조금 분석을 시도했다.
여태 추적한 내용을 정리하자면..
image.png
아웃바운드 인스턴스를 만들 때 리스너를 가져온다.[5]
이 코드는 비동기로 실행되는데, 이때 실행되는 코드 블록 단위로 네임스페이스가 세팅되는 것이 아닌가 생각한다.
image.png
해당 인스턴의 run 메소드는 리스너 소켓의 accecpt를 함과 동시에 OuboundConnection을 세팅한다.
image.png
그리고 OutboundConnection의 proxy_to 메소드가 HBONE으로 보내던, 웨이포인트로 보내던, 그냥 보내던 동작을 한다.
image.png
리스너를 가져올 때 bind 동작은 내부에 configure 메소드를 먼저 동작시킨다.[6]
image.png
해당 메서드가 네임스페이스에 들어가 소켓을 세팅한다.

잠시 여담으로 이 부분에서 소켓을 세팅하면서 패킷 마킹 작업이 일어나는 것을 확인할 수 있다.
해당 코드를 따라 들어가다보면 InPodMark로 1337을 설정하는 것을 볼 수 있다.
(1337을 16진수로 하면 0x539이다.)
image.png
netns.run 메서드는 간단하게 setns를 할 뿐이다.[7]

코드로만 봐서 사실 명확하게 판단을 내리기는 어렵다.
하지만 앞서 패킷을 뜯어본 바 파드의 네임스페이스에서 추적될 수 있도록 패킷 정보는 분명하게 출력되고 있다.
인터페이스 역시 네임스페이스 단위로 분리가 되는 것은 명확하다.
그렇다면 결국 아웃바운드 트래픽을 받아 ztunnel이 만드는 새로운 요청 역시 해당 네임스페이스에서 실행되는 것이라는 것이 내 결론이다.
이 부분을 다시 정리하자면, ztunnel은 사실 파드의 네임스페이스에 리스너 소켓만 만들고 더 이상 관여를 하지 않는 것이 아니다.
오히려 ztunnel 자체가 이루는 요청도 파드에서 나가는 것처럼 네트워크 영역을 세팅한다.

한편으로 생각해봤을 때는 이 동작은 매우 자연스러운 방식이다.
왜냐하면 ztunnel이 전송 간 암호화를 한다는 것은 결국 파드 바깥으로 트래픽이 나가는 순간 패킷이 암호화돼야 한다는 것을 뜻한다.
그렇다면 그러한 암호화된 패킷은 파드의 네트워크 네임스페이스를 통해서 나가는 것이 응당 자연스럽고, 목적에 부합한 동작이다.

이렇게까지 내가 의문을 가지게 된 것은 이 그림 때문이다.[8]
이 그림만 봐서는 꼭 소켓 하나로 모든 통신을 감당해낸다는 것처럼 보인다.
하지만 막상 패킷을 캡쳐했을 때 임의의 포트가 추적됐기 때문에 이 부분을 이해하고자 한참 삽질했다.
현재 내 결론이 맞다면, 이 그림 그린 사람은 살짝 혼나야 된다

이전 글, 다음 글

다른 글 보기

이름 index noteType created
1W - 서비스 메시와 이스티오 1 published 2025-04-10
1W - 간단한 장애 상황 구현 후 대응 실습 2 published 2025-04-10
1W - Gateway API를 활용한 설정 3 published 2025-04-10
1W - 네이티브 사이드카 컨테이너 이용 4 published 2025-04-10
2W - 엔보이 5 published 2025-04-19
2W - 인그레스 게이트웨이 실습 6 published 2025-04-17
3W - 버츄얼 서비스를 활용한 기본 트래픽 관리 7 published 2025-04-22
3W - 트래픽 가중치 - flagger와 argo rollout을 이용한 점진적 배포 8 published 2025-04-22
3W - 트래픽 미러링 패킷 캡쳐 9 published 2025-04-22
3W - 서비스 엔트리와 이그레스 게이트웨이 10 published 2025-04-22
3W - 데스티네이션 룰을 활용한 네트워크 복원력 11 published 2025-04-26
3W - 타임아웃, 재시도를 활용한 네트워크 복원력 12 published 2025-04-26
4W - 이스티오 메트릭 확인 13 published 2025-05-03
4W - 이스티오 메트릭 커스텀, 프로메테우스와 그라파나 14 published 2025-05-03
4W - 오픈텔레메트리 기반 트레이싱 예거 시각화, 키알리 시각화 15 published 2025-05-03
4W - 번외 - 트레이싱용 심플 메시 서버 개발 16 published 2025-05-03
5W - 이스티오 mTLS와 SPIFFE 17 published 2025-05-11
5W - 이스티오 JWT 인증 18 published 2025-05-11
5W - 이스티오 인가 정책 설정 19 published 2025-05-11
6W - 이스티오 설정 트러블슈팅 20 published 2025-05-18
6W - 이스티오 컨트롤 플레인 성능 최적화 21 published 2025-05-18
8W - 가상머신 통합하기 22 published 2025-06-01
8W - 엔보이와 iptables 뜯어먹기 23 published 2025-06-01
9W - 앰비언트 모드 구조, 원리 24 published 2025-06-07
9W - 앰비언트 헬름 설치, 각종 리소스 실습 25 published 2025-06-07
7W - 이스티오 메시 스케일링 26 published 2025-06-09
7W - 엔보이 필터를 통한 기능 확장 27 published 2025-06-09

관련 문서

지식 문서, EXPLAIN

이름4is-folder생성 일자
E-앰비언트 모드에서 메시 기능 활용false2025-06-07 20:56
E-앰비언트 모드 헬름 세팅false2025-06-03 19:27
E-앰비언트 ztunnel 트래픽 경로 분석false2025-06-07 20:36
앰비언트 모드false2025-06-02 14:51

기타 문서

Z0-연관 knowledge, Z1-트러블슈팅 Z2-디자인,설계, Z3-임시, Z5-프로젝트,아카이브, Z8,9-미분류,미완
이름3코드타입생성 일자
I-ztunnel이 다른 네임스페이스에서 요청 보내는 코드 분석Z1topic/idea2025-06-07 20:44
I-다른 네임스페이스 같은 포트 리스닝 서버 구현Z1topic/idea2025-06-07 19:39
9W - 앰비언트 모드 구조, 원리Z8published2025-06-07 19:17

참고


  1. https://blog.pages.kr/3006 ↩︎

  2. https://linuxias.github.io/container/namespace/3.user_namespace/ ↩︎

  3. https://istio.io/latest/docs/ambient/usage/l7-features/#security ↩︎

  4. https://istio.io/latest/docs/ambient/getting-started/manage-traffic/ ↩︎

  5. https://github.com/istio/ztunnel/blob/master/src/proxy/outbound.rs ↩︎

  6. https://github.com/istio/ztunnel/blob/master/src/inpod/config.rs#L107 ↩︎

  7. https://github.com/istio/ztunnel/blob/master/src/inpod/netns.rs ↩︎

  8. https://istio.io/latest/docs/ambient/architecture/traffic-redirection/ ↩︎