1. 서론
이전 포스팅에서는 istiod의 수동 방식과 pilot-discovery Sidecar Injector에 의해 자동 주입된 envoy proxy 과정에 대해서 살펴봤습니다.

Sidecar Injector에 의해 주입된 변경된 Pod를 살펴보면 istio-init이라는 init-container와 istio-proxy라는 사이드카 컨테이너 총 2개가 주입되는 것을 확인할 수 있습니다. 이번 포스팅에서는 그 중 init-container 부분에 대해서 다루어 보고자합니다.
우선 본론에 들어가기 앞서 간단히 해당 컨테이너의 역할에 대해서 살펴보면, envoy가 application과 트래픽을 주고 받고 istio control plane과 통신을 원활하게 수행하기 위한 iptables를 조작합니다.
지금부터 본격적으로 iptables 조작 과정이 어떻게 이루어지는지에 대해서 알아보겠습니다. 다만 필자가 네트워크나 서버 전문가가 아니기 때문에 틀린 부분이 있을 수 있습니다. 틀린 부분이 있다면, 꼭 알려주시기 바랍니다.
2. iptables
Kubernetes에서 네트워크 트래픽을 다루는데 있어 핵심은 iptables와 Netfilter 입니다. 이를 통해서 외부로 트래픽을 전달하기도 하고 내부 컨테이터로 트래픽을 전달합니다. istio 또한 inbound와 outbound 트래픽을 envoy로 전달하고 외부로 보내야하기 때문에 Netfilter에서 제공하는 hook을 사용하여 패킷의 흐름을 변경하며 이를 위해서 사이드카 컨테이너를 주입할 때 iptables 체인을 만들고 라우팅 규칙을 설정하는 작업을 선행합니다.
iptables와 Netfilter에 대해서는 내용이 방대하고 깊으므로 별도 학습을 권장드리며, 본 포스팅에서는 envoy의 설정과 네트워크 조작에 대해 알아보기 이전에 가볍게 iptables에서 사용되는 네트워크 체인에 대해서만 가볍게 다루어보고 넘어가겠습니다.

iptables에는 기본적으로 위와같이 5개의 체인이 등록되어있습니다. 각각의 체인은 역할이 존재하는데, 체인들의 관계와 흐름을 이해하는 것이 중요합니다. 따라서 위 그림을 기반으로 각 체인의 특징에 대해 살펴보겠습니다.
1. PREROUTING
PREROUTING은 Packet이 처음 도달하는 체인으로 패킷 내용을 조사하여 목적지가 로컬 주소인지 아닌지를 판단합니다. 이때 만약 로컬이라면, INPUT 체인으로 전달하고 다른 host로 전달되어야한다면 FORWARDING으로 전달합니다. 그리고 해당 체인을 통해서 목적지의 주소 변경(DNAT)이 가능한 것이 주요 특징 중 하나입니다.
그렇다면 DNAT는 왜 할까요?

가령 Docker를 예로 들자면, 생성한 도커 컨테이너를 외부에서 접속 가능하게 하기 위해 흔히 포트 포워딩을 수행합니다. 이때 위 그림과 같이 특정 서버 포트(8080)에 대해서 컨테이너 내부 포트와 연결시키면, 위와 같이 PREROUTING을 통해 들어온 트래픽은 DOCKER 체인으로 전달되고 그 과정에서 8080 포트로 들어온 연결에 대해서 목적지 주소를 변경하는 작업을 수행해서 이후 컨테이너에 트래픽이 전달됩니다.
2. INPUT
INPUT은 PREROUTING을 통해 전달되는 Packet이 로컬 주소의 목적지를 향할 경우에 해당 체인으로 라우팅됩니다. 만약 해당 체인을 통해 최종 목적지를 지정하면 그쪽으로 트래픽을 전달할 수 있습니다.
3. OUTPUT
OUTPUT은 외부에서 들어오는 패킷이 아니라 서버에서 생성되어 나가는 패킷이 발생될 때 트리거링되는 체인입니다.
4. FORWARDING
FORWARDING은 해당 서버로 보낸 패킷은 아니지만 외부 패킷을 외부로 전달하는 경우 설정 여부에 따라 패킷을 전달할지 여부를 결정합니다. 사례를 통해 조금 더 자세히 살펴보겠습니다.

위와 같이 2개의 서버를 연결하는 라우터로써 사용될 때 중간에 있는 서버는 외부에서 들어온 패킷을 외부로 전달하는 역할을 수행합니다. 이때 패킷을 다른 서버에 전달하기 위해서는 sysctl.conf 파일에서 net.ipv4.ip_forward 값을 1로 설정이 필요합니다. 설정이 완료되면, 해당 체인을 통해서 지정된 Gateway를 통해 목적지로 전달될 것입니다.

두 번째 사례는 위 그림과 같이 단일 서버내라도 Bridge interface로 구성되어있고 하위에 별도 Network namespace와 서로 다른 네트워크 주소로 구성된 환경에서 namespace간 통신을 수행할 때 해당 체인 설정이 필요할 수 있습니다. 가령 FORWARD 체인에 대해서 Policy가 DROP으로 되어있을 경우 출발지 IP에 대해 ACCEPT 하도록 정책을 등록할 수 있습니다.
위 상황에서 같은 서버내 통신인데도 FORWARDING 체인으로 전달되는 이유는 서로 다른 네트워크 주소를 가지고 있기 때문에 host 입장에서는 출발지와 목적지 모두가 외부 패킷이기 때문입니다.
5. POSTROUTING
POSTROUTING은 OUTPUT에서 전달된 패킷이나 FORWARDING을 통해서 전달된 패킷을 통해 네트워크 인터페이스를 통해 나갈 패킷에 대한 처리를 수행할 수 있습니다. 이때 전달된 패킷에 대하여 출발지 주소를 변경(SNAT)할 수 있습니다.

SNAT의 경우는 공인 IP와 사설 IP로 구성되었을 경우 사설 IP 대역에서 외부 인터넷 접속 시 돌아올 목적지를 공인 IP로 지정해야되기 때문에 출발지 주소를 서버의 IP로 변경해서 나갑니다. 따라서 이 경우 해당 체인을 통해 출발지 주소를 변경할 수 있습니다.
3. init conatiner
지금까지 iptables에서 사용되는 체인에 대해서 살펴봤습니다. 지금부터는 이전 포스팅에서 살펴본 istio에 의해서 주입된 사이드카 컨테이너 설정을 차근 차근 살펴보면서 어떤 것이 적용되었는지를 분석해보겠습니다.
initContainers:
- args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
image: docker.io/istio/proxyv2:1.14.2
name: istio-init
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 10m
memory: 40Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
istio에 의해서 변경된 Pod의 yaml을 살펴보면, 위와 같이 istio-init으로 명명된 init container가 포함되었으며 이 단계에서 iptables를 조작한다는 것을 알 수 있습니다.
위 코드 내용을 분할하여 자세히 살펴보겠습니다.
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
먼저 iptables를 조작하기 위해서 securityContext에서 NET_ADMIN과 NET_RAW에 대한 capabilites를 추가하고 그 외 나머지는 drop 하도록 하였습니다. 그리고 해당 container를 root 유저로 기동하는 것을 확인할 수 있습니다.
image: docker.io/istio/proxyv2:1.14.2
name: istio-init
두번째로 살펴볼 것은 proxyv2 이미지입니다. 해당 컨테이너 이미지명이 proxyv2라고 되어있지만 실제로는 envoy proxy를 기반으로 만들어진 이미지입니다. 해당 이미지에 대한 Dockerfile 명세는 아래 URL을 통해서 확인해볼 수 있습니다.
https://github.com/istio/istio/blob/master/pilot/docker/Dockerfile.proxyv2
GitHub - istio/istio: Connect, secure, control, and observe services.
Connect, secure, control, and observe services. Contribute to istio/istio development by creating an account on GitHub.
github.com
해당 이미지는 pilot-agent를 기동하는데, 이때 argument를 통해서 해당 프로그램이 어떤 동작을 수행하는지를 정의할 수 있습니다.
- args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
세번째로 살펴볼 것은 argument 입니다. argument로 istio-iptables 옵션을 전달하면 내부에서 istio-iptables.sh을 수행해서 iptables를 조작하는 작업을 수행합니다. 위 코드에서 입력된 옵션에 대해서 살펴보면 다음과 같습니다.
| 옵션 | 설명 |
| -p | Traffic을 redirect 받을 envoy의 포트 |
| -z | Inbound TCP traffic에 대해서 redirect 포트 |
| -u | proxy container의 UID |
| -m | envoy로 redirect되는 Inbound 연결에 사용되는 모드로써 REDIRECT 혹은 TPROXY 중 선택 가능 |
| -i | envoy로 redirect 시킬 CIDR IP Range로 복수개 입력 시 "," 로 구분하여 list 형태로 입력한다. 해당 값이 *일 경우 모두 outbound로 redirect 하며, 값이 없을 경우에는 모든 outbound 트래픽을 disable 한다. |
| -x | envoy로 redirect 제외 시킬 CIDR IP Range로 복수개 입력 시 "," 로 구분하여 list 형태로 입력함 |
| -b | envoy로 redirect 시킬 Inbound Port를 명시하는 것으로써 복수개 입력 시 "," 로 구분하여 list 형태로 입력함 |
| -d | envoy로 redirect 제외시킬 Inbound Port를 명시하는 것으로써 복수개 입력 시 "," 로 구분하여 list 형태로 입력함 |
위 예제에서 작성한 옵션을 기반으로 설명하면, 위 args는 다음과 같은 명령을 요구하였음을 알 수 있습니다.
1) Inboud의 경우 15006 그 외는 15001 포트로 전달한다.
2) 해당 proxy를 사용하는 컨테이너의 UID는 1337이다
3) 모든 IP로부터 전달되는 트래픽은 모두 envoy로 redirect한다.
4) envoy redirect 하는데 있어 제외대상 IP는 없다.
5) 15090, 15021, 15020 포트를 제외한 나머지 포트를 통해 들어오는 트래픽은 envoy로 redirect한다.
위 내용에서 15090, 15021, 15020 포트 등은 envoy의 헬스체크와 prometheus 연결 등에 사용하기 위한 포트로 사용되기 때문에 제외하였습니다. 그 밖에도 istio에서 사용하는 여러 포트가 있는데, 해당 내용은 공식문서를 참고하시기 바랍니다.
참고로 위 istio-iptables의 실제 동작 과정이나 위에 설명한 옵션 외에 다른 옵션이 무엇이 있는지 궁금하신 분은 istio github을 참고하시기 바랍니다.
4. iptables 룰 변경 내역 확인
이전 내용들을 토대로 init container 안에서 iptables를 조작함을 확인했습니다. 이번에는 init container를 통해서 변경된 내용을 확인하기 위해 istio-init container의 로그를 확인해보겠습니다.
[cla9@DESKTOP-FBK64T0]$ kubectl logs -c istio-init nginx
2022-08-26T00:34:38.813305Z info Istio iptables environment:
ENVOY_PORT=
INBOUND_CAPTURE_PORT=
ISTIO_INBOUND_INTERCEPTION_MODE=
ISTIO_INBOUND_TPROXY_ROUTE_TABLE=
ISTIO_INBOUND_PORTS=
ISTIO_OUTBOUND_PORTS=
ISTIO_LOCAL_EXCLUDE_PORTS=
ISTIO_EXCLUDE_INTERFACES=
ISTIO_SERVICE_CIDR=
ISTIO_SERVICE_EXCLUDE_CIDR=
ISTIO_META_DNS_CAPTURE=
INVALID_DROP=
2022-08-26T00:34:38.813346Z info Istio iptables variables:
PROXY_PORT=15001
PROXY_INBOUND_CAPTURE_PORT=15006
PROXY_TUNNEL_PORT=15008
PROXY_UID=1337
PROXY_GID=1337
INBOUND_INTERCEPTION_MODE=REDIRECT
INBOUND_TPROXY_MARK=1337
INBOUND_TPROXY_ROUTE_TABLE=133
INBOUND_PORTS_INCLUDE=*
INBOUND_PORTS_EXCLUDE=15090,15021,15020
OUTBOUND_OWNER_GROUPS_INCLUDE=*
OUTBOUND_OWNER_GROUPS_EXCLUDE=
OUTBOUND_IP_RANGES_INCLUDE=*
OUTBOUND_IP_RANGES_EXCLUDE=
OUTBOUND_PORTS_INCLUDE=
OUTBOUND_PORTS_EXCLUDE=
KUBE_VIRT_INTERFACES=
ENABLE_INBOUND_IPV6=false
DNS_CAPTURE=false
DROP_INVALID=false
CAPTURE_ALL_DNS=false
DNS_SERVERS=[],[]
OUTPUT_PATH=
NETWORK_NAMESPACE=
CNI_MODE=false
EXCLUDE_INTERFACES=
2022-08-26T00:34:38.813822Z info Writing following contents to rules file: /tmp/iptables-rules-1661474078813392800.txt3716182416
* nat
-N ISTIO_INBOUND
-N ISTIO_REDIRECT
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
COMMIT
위 코드는 istio-init container의 로그 일부를 발췌하였습니다. 많은 내용이 있지만 이 중에서 유의미한 내용만 추려서 살펴보겠습니다.
PROXY_PORT=15001
PROXY_INBOUND_CAPTURE_PORT=15006
PROXY_UID=1337
PROXY_GID=1337
INBOUND_INTERCEPTION_MODE=REDIRECT
INBOUND_PORTS_INCLUDE=*
INBOUND_PORTS_EXCLUDE=15090,15021,15020
OUTBOUND_OWNER_GROUPS_INCLUDE=*
OUTBOUND_OWNER_GROUPS_EXCLUDE=
OUTBOUND_IP_RANGES_INCLUDE=*
OUTBOUND_IP_RANGES_EXCLUDE=
위 내용들을 살펴보면, 이전에 arg 인자로 전달했던 옵션들이 정상적으로 매칭되었음을 확인할 수 있습니다. 먼저 inbound 트래픽은 15006 포트로 전달되도록 되어있고 outbound의 경우는 15001로 지정되었음을 확인할 수 있습니다.
PROXY의 UID와 GID는 1337로 이 또한 인자로 지정한 값입니다. PORT 전달 대상은 15090, 15021, 15020을 제외한 나머지 PORT를 모두 INBOUND로 전달하도록 되어있습니다.
* nat
-N ISTIO_INBOUND
-N ISTIO_REDIRECT
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
Argument로 전달된 값을 기반으로 생성된 NAT 체인 룰을 보면 위와 같이 지정된 것을 로그를 통해 확인할 수 있습니다. 그렇다면 구체적으로 어떤 값이 어떻게 변경되었을까요? 이에 대해서 조금 자세히 살펴보겠습니다.
로그 내용을 살펴보면 먼저 4개의 체인을 생성한 것을 확인할 수 있습니다. 여기서 각각의 체인은 다음과 같은 역할을 수행합니다.
ISTIO_INBOUND : PREROUTING으로 전달된 트래픽 중 tcp 트래픽 전달받음
ISTIO_REDIRECT : Outbound Traffic을 envoy의 Outbound Handler인 15001 포트로 전달함
ISTIO_IN_REDIRECT : Inbound Traffic을 envoy의 Inbound Handler인 15006 포트로 전달함
ISTIO_OUTPUT : envoy traffic을 결정하는 가장 핵심적인 체인으로 traffic 전달과 관련한 규칙이 정의되어있음.

체인을 생성하고 난 이후에는 체인 호출 및 트래픽 라우팅과 관련된 규칙을 설정하고 있습니다. 위 설정 중에서 가장 중요한 것은 ISTIO_OUTPUT이며, 해당 체인에 적용된 규칙에 의하여 그 다음에 전달할 체인이 결정됩니다.
-A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
참고로 ISTIO_OUTPUT에 적용된 규칙은 위와 같으며 해당 규칙을 조금 더 풀어서 설명하면 다음과 같습니다.
| 처리 결과 | Out Interface | 조건 |
| RETURN | loopback 인터페이스 | 출발지가 127.0.0.6 일 경우 |
| ISTIO_IN_REDIRECT | loopback 인터페이스 | 목적지가 localhost(127.0.0.1)가 아니면서 UID가 1337일 경우 |
| RETURN | loopback 인터페이스 | UID가 1337가 아닐 경우 |
| RETURN | * | UID가 1337일 경우 |
| ISTIO_IN_REDIRECT | loopback 인터페이스 | 목적지가 localhost(127.0.0.1)가 아니면서 GID가 1337일 경우 |
| RETURN | loopback 인터페이스 | GID가 1337가 아닐 경우 |
| RETURN | * | GID가 1337일 경우 |
| RETURN | * | 출발지가 localhost(127.0.0.1)일 경우 |
| ISTIO_REDIRECT | * | * |
위 규칙에 따라 외부에서 접속하는 요청을 envoy에 전달하고 다시 envoy inbound handler에서 나온 트래픽을 application에 전달합니다. 그리고 반대로 application에서 나오는 트래픽을 외부로 전달하는 역할을 수행합니다.
체인의 내용만 봐서는 트래픽이 어떤 흐름으로 어떻게 도달되는지 감이 잡히지는 않습니다. 다만 이번 포스팅은 설정과 관련된 내용만을 다루기 때문에 트래픽 전달에 대해서는 추후에 다루어보도록 하고 여기서는 위 설정 관련해서 알아야할 주요 포인트 3가지 (loopback 인터페이스, 127.0.0.6 IP 존재 이유, 1337 UID/GID)에 대해서 알아보겠습니다.
4-1 loopback 인터페이스

일반적으로 쿠버네티스를 통해 1개의 컨테이너가 포함된 POD를 배포하면 왼쪽과 같이 배포된다고 생각하지만, 실제로는 오른쪽과 같이 사용자에게는 보이지 않는 Pause 컨테이너가 포함된 형태로 배포됩니다.
여기서 Pause 컨테이너는 네트워크, IPC namespace를 생성하고 다른 컨테이너와 공유하는 역할을 수행하며, init process의 역할을 수행해서 좀비 프로세스를 방지하는 역할을 수행합니다.

그 결과 POD 내부에 여러개의 컨테이너가 생성되었을 때 내부적으로는 loopback 인터페이스를 통해 컨테이너간에 localhost로 통신이 가능합니다. 따라서 위 ISTIO_OUTPUT 규칙에서 Out Interface가 loopback인 것은 envoy proxy와 Application 통신을 위해 localhost를 사용함을 알 수 있습니다.
4-2 127.0.0.6
해당 IP의 의미에 대해 알기 전에 envoy의 cluster 타입 중 하나인 ORIGINAL_DST와 passthroughcluster에 대한 내용 이해가 필요합니다. 따라서 해당 개념 사전학습 이후에 살펴보겠습니다.
4-2-1 ORIGINAL_DST
ORIGINAL_DST는 envoy의 cluster 타입 중 하나로써, 트래픽이 들어왔을 때 목적지 주소가 envoy에 등록된 Cluster와 Endpoint 중 일치하는 것이 없을 경우에 해당 목적지 주소를 다시 매핑해서 Forwarding할 수 있도록 지원하는 기능입니다.
이를 위해서 envoy 공식 문서에 따르면 2가지 컴포넌트가 상호 작용하는 것을 알 수 있습니다.
1. Original Destination Listener Filter
2. Original Destination Cluster

Original Destination Listener Filter는 envoy 아키텍처 포스팅에서 다룬 내용으로 Listener Filters에 해당합니다. 해당 Filter는 SO_ORIGINAL_DST 소켓 정보를 읽어서 iptables에 의해 목적지 주소가 바뀌기 이전 사용자의 원래 목적지 정보를 읽는 역할을 수행합니다.

iptables를 지나게되면 envoy proxy로 트래픽이 전달되어야 되기 때문에 목적지 주소가 바뀌게 되는데, Original Destination Filter를 통과하면서 redirect된 소켓의 주소 정보를 해당 Filter에서 읽습니다.

이후 해당 정보는 Listener를 지나 Original Destination Cluster에 전해지는데, 이때 해당 트래픽의 목적지 주소를 iptables를 지나기 이전 목적지 주소로 재설정하는데 사용됩니다. 이를 통해 upstream address에 대해서 동적으로 런타임에 주소 변경이 가능한 것이 특징입니다. 그외 Original Destination Cluster에 대한 부가적인 설명은 envoy 공식 문서를 통해 참고 바랍니다.
4-2-2 PassthroughCluster

istio의 Passthrough는 서비스 메시 바깥으로 나가는 egress 트래픽에 대해서 ALLOW_ANY(default)로 지정된 경우 Service Entry로 등록하지 않은 트래픽을 envoy proxy를 통해 외부로 전달하기 위해 사용됩니다. 이를 위해서 PassthroughCluster라는 Virtual Cluster를 envoy 내부에 생성하고 envoy가 보유한 cluster 혹은 endpoint와 매치되지 않는 목적지 트래픽에 대해 해당 Cluster로 트래픽을 전달하고 후속 작업을 처리합니다. 이때 해당 PassthroughCluster의 타입은 이전에 설명한 ORIGINAL_DST로 지정되어 있어 목적지의 IP를 유지한채로 외부에 전달할 수 있습니다.
지금까지 ORIGINAL_DST와 PassthroughCluster에 대해서 설명했습니다. 그렇다면, 이제 127.0.0.6 IP가 왜 등장했는지에 대해서 살펴보겠습니다.

14443 github 이슈를 살펴보면, 이전에는 Service로 등록하지 않은 Pod의 port를 외부에서 호출했을 경우 iptables과 envoy간에 무한 루프로 인하여 트래픽이 정상적으로 전달되지 못하고 계속 반복되는 현상이 있었습니다.
두 번째 문제로는 Pod 내부 호출을 위해서 Wildcard bind 혹은 localhost를 입력하거나 Kubernetes의 downward API를 사용하여 Pod IP만을 지정하게되면, Port가 명시되어있지 않기 때문에 cluster에서 매칭되는 결과를 찾을 수가 없습니다. 따라서 이 경우에는 InboundPassthroughCluster의 경로를 따르게 되는데, 이러한 이유로 Istio가 적용된 클러스터와 그렇지 않은 클러스터에서 Pod간 통신 할 때 제약사항이 존재하게 되었습니다. 여기서 제약사항에 대한 자세한 내용은 Inbound Forwarding 문서를 참고하시기 바랍니다.
따라서, 위 두 가지 문제를 해결하기 위해서 다음과 같은 결정을 하게 됩니다.


내용을 살펴보면 Inbound Cluster 타입은 ORIGINAL_DST로 변경했으며, UpstreamBindConfig를 통해 upstream 값을 127.0.0.6으로 지정하여, localhost에 위치한 애플리케이션으로 트래픽을 전달할 수 있도록 하였습니다. 이를 통해 iptables과 envoy간의 무한 루프를 탈피하고자 했습니다.
[cla9@DESKTOP-FBK64T0]$ ip route show table local | grep 127.0.0.0/8
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
여기서 127.0.0.6은 127.0.0.0/8 즉 로컬호스트 루프백 주소에 사용하기 위한 수많은 주소 중 하나이며, 컨트리뷰터가 밝힌 수 많은 주소 중 127.0.0.6이 채택된 이유는 envoy의 Inbound 트래픽을 전달받는 포트가 15006 이기 때문입니다.
정리하자면 127.0.0.6는 envoy proxy에 트래픽이 진입한 이후에 바인딩되는 주소로써, envoy 밖으로 나간 이후 Outbound Handler를 우회하여 Pod의 Application으로 전달될 수 있도록 하는 역할을 수행합니다. 이러한 과정이 없다면, envoy에서 upstream으로 전달을 해야하는데, iptables를 지날 때 다시 envoy로 전달될 수 있기 때문에 해당 magic 주소를 임의로 추가하여 iptables에서 envoy가 아닌 application으로 트래픽을 전달하는데 목적이 있습니다. 해당 주소는 istio 코드에 IboundPassthroughClusterIpv4로 하드코딩되어있는 값이며, 자세한 내용은 github 이슈 내용을 참고 바랍니다.
4-3 1337 UID, GID
iptables에서 UID 혹은 GID가 1337로 구분되어 있는 이유는 envoy proxy와 application 트래픽을 구분하기 위한 용도입니다.
5. 마무리
이번 포스팅에서는 사이드카 컨테이너 주입 설정에서 init-container에 주입된 설정에 대해서 살펴봤습니다. init-container에서는 inbound 트래픽을 envoy proxy에게 전달하고 이를 다시 application에게 전달하기 위한 설정과 outbound 트래픽 흐름을 조작하기 위한 iptables 설정에 대해서 살펴봤습니다.
실제 트래픽이 들어왔을 때 어떤 체인을 통해 어떻게 전달되는지에 대해서는 살펴보지 않았기 때문에 해당 iptables 설정이 어떻게 쓰이는지에 대해서는 아직 잘 모를 수 있습니다. 이는 차후에 몇 가지 사례를 통해서 어떤 체인을 통해 전달되는지 살펴보겠습니다.
다음 포스팅에서는 사이드카 컨테이너인 envoy proxy 설정과 내부 구조에 대해서 살펴보겠습니다.
'MSA > Istio' 카테고리의 다른 글
| 13. [istio-internals] Pilot-agent - 사이드카 컨테이너 (1) | 2026.03.03 |
|---|---|
| 11. [istio-internals] Pilot-discovery 사이드카 컨테이너 주입 (0) | 2026.03.03 |
| 10. [istio-internals] Pilot-discovery - XDS Server (0) | 2026.03.03 |
| 9. [istio-internals] istio - Service Discovery (0) | 2026.03.03 |
| 8. [envoy-internals] Client 요청 전달 과정 이해하기 - 2 (0) | 2026.03.03 |