Cilium ServiceMesh 테스트전 개념정리
TProxy
Cilium TProxy는 L7 정책을 적용할 때 트래픽을 목적지 IP를 유지한 채 노드의 Envoy로 가로채는 메커니즘입니다.
일반적인 NAT REDIRECT 가 아닌 투명 프록시 방식으로 커널 레벨에서 트래픽을 Envoy로 보냅니다.
Envoy
Cloud Native Computing Foundation(CNCF) 공식 프로젝트
구글 IBM 리프트(Lyft)가 중심이 되어 개발하고 있는 오픈 소스 소프트웨어
C++ 로 구현된 고성능 Proxy 입니다.

아래와 같은 특징을 가집니다.
- 중앙에서 동적 설정 관리 필요
- 풍부한 API 지원
- 필터 체인 구조
- mTLS, L7 라우팅
- hot reload
- xDS 기반 제어
다만, Istio 에서의 Envoy와 Cilium에서 Envoy는 차이를 가집니다.
Istio에서 Envoy
Istio의 Sidecar 모드에서는 mesh에 참여하는 Pod 간 트래픽이 Envoy를 통과합니다.
Envoy는 L4(TCP)와 L7(HTTP/gRPC) 프록시 역할을 모두 수행하며, Control Plane인 Istiod가 xDS를 통해 Envoy 설정을 동적으로 관리합니다.
다만 Kubernetes Service 레벨의 L4 로드밸런싱은 kube-proxy가 처리합니다.
https://gnobaaaaar.tistory.com/125
[istio] envoy proxy
istio in action 3장과 블로그들을 참조했습니다. 사내에서 istio 관련 기능을 몇 가지 테스트해볼 기회가 있었습니다.이해를 위해 흐름을 간단하게 정리해보았습니다. istio envoy proxyistio envoy proxy는 파
gnobaaaaar.tistory.com
Cilium에서 Envoy
https://docs.cilium.io/en/latest/security/network/proxy/envoy/
네트워크의 모든 트래픽 제어가 목적인 Istio 의 Envoy와 달리,
Cilium은 L7 정책이 있을 때만 Envoy 사용됩니다.
Cilium의 기본 데이터플레인은 eBPF이며,
L3/L4 네트워크 기능(Service LB, 정책 등)은 Envoy 없이 처리됩니다.
L7 정책이나 HTTP/gRPC 기반 기능이 활성화된 경우에만
Node-level Envoy가 사용되며, 이때 Transparent Proxy(TPROXY) 메커니즘이 동작합니다.
동작흐름
Client가 아래와 같은 IP를 가진 특정 목적지(ex. 웹서버)로 접근했다고 하겠습니다.
Clinet -> 10.1.2.20:80
Istio Envoy가 트래픽을 제어하는 경우에는 아래와 같이
application 컨테이너의 목적지는 envoy의 outbound capture 포트인 15001으로 REDIRECT 됩니다 (NAT)
App
↓
iptables REDIRECT
↓
127.0.0.1:15001 (Envoy sidecar)
반면 Cilium은 L7 정책이 활성화된 경우
NAT 없이 목적지 IP 유지한 채 Node Envoy로 전달합니다.
(사이드카 형태의 Istio 와 달리 Cilium 은 노드 단위 파드형태로 동작합니다)
Client → 10.1.2.20:80
↓
eBPF intercept
↓
Transparent socket redirect
↓
Node Envoy (IP 그대로)
위 흐름도에서 보이는 Transparent Proxy 에 대해 확인해보겠습니다.
아래 글을 참조했습니다. 원래 글을 한번 읽어보시길 추천드립니다.
https://nuguni.tistory.com/138
[cilium] TProxy 이해해보기
Cilium 문서에서도 나오듯 TPROXY는 클라이언트 트래픽의 목적지 주소를 바꾸지 않고 프록시로 “투명하게” 가로채는 방식입니다.NAT 없이 원래 목적지(IP/포트)를 보존하므로, 앱에서 정책/로깅을
nuguni.tistory.com
Non-local Socket Binding
아까 클라이언트가 10.1.2.20:80 으로 접근하는 경우를 이야기하였습니다.
Clinet -> 10.1.2.20:80
해당 트래픽을 가져가도 프록시 호스트에는 10.1.2.20 이라는 IP주소가 없으므로
해당 주소로 bind() 가 실패하게 됩니다.
bind()는 특정 소켓을 로컬 IP:port에 연결해서
해당 IP:port로 들어오는 트래픽을 이 소켓이 받도록 커널에 등록하는 동작입니다.
리눅스는 내가 가진 IP에만 bind가 가능합니다.
ex. ip addr -> 내가 가진 네트워크 인터페이스에 IP 목록 확인가능합니다.
Envoy는 Node 네임스페이스에는 Pod IP(10.1.2.20)가 존재하지 않으므로 기본적으로 bind()가 실패합니다.
그래서 우선 비로컬 바인딩허용을 해줘서 로컬에 없는 IP도 bind()를 가능하게 해줍니다.
# IPv4에서 비로컬 바인딩 허용
sysctl -w net.ipv4.ip_nonlocal_bind=1
# 영구 반영 (예: /etc/sysctl.conf 에 추가)
echo "net.ipv4.ip_nonlocal_bind=1" >> /etc/sysctl.conf
sysctl -p
bind() 뿐만 아니라 통신을 위해 소켓(네트워크 통신을 위한 파일 디스크립터, TCP통신용 문)도 설정이 필요합니다.
소켓은 나중에 다른 글로 좀더 자세하게 다뤄보겠습니다.
어플리케이션에서 소켓을 열때 해당 소켓은 투명소켓이라고 커널에 알려줍니다.
int fd = socket(AF_INET, SOCK_STREAM, 0);
int optval = 1;
setsockopt(fd, SOL_IP, IP_TRANSPARENT, &optval, sizeof(optval));
애플리케이션이 소켓을 생성한 뒤
IP_TRANSPARENT 옵션을 설정함으로써
그 소켓이 투명 프록시용으로 동작할 수 있도록 커널에 요청합니다.
Iptables 기반 TPROXY
흐름은 아래와 같습니다.
자세하게 확인해보겠습니다.
PREROUTING
↓
TPROXY → skb->sk 세팅
↓
MARK=1
↓
ip rule → table 100
↓
local route → lo
↓
LOCAL_IN
↓
ip_local_deliver_finish()
↓
tcp_v4_rcv()
↓
이미 연결된 소켓(skb->sk)으로 바로 전달
위 링크 원본글을 참조하시길 바랍니다
패턴 A: 떠 있는 투명 소켓에 붙이기
이미 IP_TRANSPARENT로 열려 있는 소켓을 커널이 자동으로 찾아서 연결하는 방식입니다.
# DIVERT 체인 생성
iptables -t mangle -N DIVERT
# PREROUTING 단계에서 transparent 소켓이 있는 경우 DIVERT로 이동
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j DIVERT
# mark 값 세팅
iptables -t mangle -A DIVERT -j MARK --set-mark 1
# 해당 패킷은 로컬에서 수락
iptables -t mangle -A DIVERT -j ACCEPT
패턴 B) 특정 포트로 바로 TPROXY 하기
들어오는 트래픽을 특정포트 (ex.로컬 50080 포트)로 보냅니다.
-j TPROXY --on-port 50080
라우팅 정책: 규칙 기반 라우팅(PBR)
iptables에서 TPROXY를 걸었다고 끝이 아닙니다.
커널입장에서 아래 목적지를 보고 라우팅을 태우면 그만입니다.
# 언급했듯이 로컬IP 주소가 아닙니다
목적지=10.1.2.20
이 패킷은 로컬 애플리케이션으로 보내라고 설정해야합니다.
fwmark + ip rule 으로 진행됩니다.
iptables에서 fwmark 찍기
이 패킷은 특별 대상이라고 찍습니다.
-j MARK --set-mark 1
ip rule로 경로 바꾸기
mark=1이면 일반 라우팅 테이블 말고 table 100을 쓰라고 설정해 둡니다.
ip rule add fwmark 1 lookup 100
table100 에서 로컬로 강제 인입
모든 목적지를 로컬, 루프백(lo)로 처리합니다.
ip route add local 0.0.0.0/0 dev lo table 100
# 패킷이 NIC를 통해 커널에 들어오면 가장 먼저 통과하는 netfilter hook
PREROUTING
↓
TPROXY → skb->sk 세팅
↓
MARK=1
↓
ip rule → table 100
↓
local route → lo
↓
LOCAL_IN
↓
ip_local_deliver_finish()
↓
# TCP 패킷 처리
tcp_v4_rcv()
↓
이미 연결된 투명소켓(skb->sk)으로 바로 전달
Cilium에서..
Cilium 기본동작에서,
Service(LoadBalancer/NodePort/HostNetwork) 포트에 트래픽이 도착하면 eBPF가 가로챕니다.
Cilium L7 모드에서는 eBPF가 TC ingress에서
bpf_sk_lookup_ : 커널 소켓 테이블에서 투명소켓을 찾는 과정 (iptables TPROXY의 “-m socket”과 유사한 역할)
로 Envoy 소켓을 조회하고,
bpf_sk_assign() : 이 패킷을 이 소켓으로 전달하라고 지정 (iptables의 skb->sk 설정과 동일한 eBPF 동작)
로 패킷을 해당 소켓에 연결합니다.
이후 fwmark와 정책 라우팅을 이용해 패킷을 로컬 스택으로 인입시켜,
Envoy로 전달되는 과정에서는 DNAT/REDIRECT가 발생하지 않으며 목적지 IP가 유지됩니다.
블로그 글을 참고해서 확인했던 iptables TPROXY는 netfilter와 정책 라우팅을 통해 이를 구현하지만,
Cilium은 동일한 개념을 eBPF 기반 데이터패스로 재구성합니다.
두 방식의 차이는 어디에서 트래픽을 제어하느냐에 있습니다.
Cilium은 이를 eBPF 기반으로, NAT에 의존하지 않는 L7 제어를 구현합니다.
또한 유지되는 목적지 IP를 통해 로그 상 목적지 IP 왜곡 없이, 리전별 트래픽 구분을 명확하게 할 수 있는 장점이 있습니다.
참조) skb->sk
• skb = 커널이 네트워크 패킷을 들고 다니는 객체 (struct sk_buff)
• sk = 커널의 소켓 객체 (struct sock)
일반적으로 NIC에 패킷이 들어오면 아래와 같은 순서로 동작됩니다.
1. PREROUTING(초기 훅)
2. 라우팅 결정 (이 패킷이 로컬로 갈지, 포워딩될지)
3. LOCAL_IN → 프로토콜 처리(TCP면 tcp_v4_rcv())
이때 tcp_v4_rcv()는 원래 dst IP:port 기준으로 누가 받을 소켓인지를 찾아야 합니다.
이 과정에서 소켓 lookup 이 이루어 집니다 (커널 소켓 테이블에서 매칭합니다)
즉, 일반 흐름에서는 skb->sk가 미리 정해져 있지 않습니다. (= NULL인 경우가 많습니다)
하지만, Tproxy는 사전에 원래 목적지 소켓이 아닌, 투명소켓 (프록시 소켓) 으로 보내라고 설정됩니다.
이를, TPROXY → skb->sk 세팅 으로 위에서는 표현되었습니다.
https://thinking-bank.tistory.com/20
[리눅스 커널] sk_buff 구조체란?
1. sk_buff 구조체란?sk_buff(Socket Buffer)는 Linux 커널 네트워크 스택에서 패킷(프레임 포함)을 표현하는 데 사용되는 핵심 구조체입니다.이 구조체는 네트워크 계층 간에 데이터를 전달할 때 사용되며
thinking-bank.tistory.com
뭔가
쿠버네티스와 네트워크를 보는데 점점 리눅스를 많이 보게 되는 느낌...
'devops > cilium' 카테고리의 다른 글
| [Cilium ServiceMesh] Cilium Ingress #실습1 (0) | 2026.02.17 |
|---|---|
| [Cilium ServiceMesh] Cilium Ingress #개념 (0) | 2026.02.17 |
| eBPF (Cilium networking) #2 BCC tools (0) | 2025.12.19 |
| eBPF (Cilium networking) #1 (0) | 2025.12.18 |
| 8주차 - Cilium Security & Tetragon (2) | 2025.09.06 |