Cilium - Cilium Service Mesh(1)-Cilium-Ingress

Gyullbb·2025년 8월 23일
0

K8S

목록 보기
19/22

Cilium Service Mesh

Cilium Service Mesh는 기존의 사이드카 프록시(Envoy 등)를 강제적으로 사용하지 않고, eBPF를 활용해 커널 레벨에서 네트워크 및 애플리케이션 계층 트래픽을 제어하는 서비스 메시 구조를 제공한다.
이를 통해 애플리케이션 성능 손실을 최소화하면서도 L3~L7 보안 정책, 트래픽 관리, 모니터링 기능을 통합적으로 제공한다.

L3(Service-to-Service, IP 기반)

Cilium은 eBPF를 활용하여 Pod IP, Service IP 단위로 정책을 제어한다.
Kubernetes NetworkPolicy보다 확장된 CiliumNetworkPolicy(CNP)를 통해 CIDR, Label, Namespace 기반 정책을 정의한다.
L3 계층에서는 기본적으로 service-to-service 접근 제어를 수행하며, 클러스터 외부/내부 트래픽 모두 제어할 수 있다.

L7(HTTP/gRPC 등 애플리케이션 계층)

Cilium은 eBPF 기반 L7 필터링 기능을 제공하며 요청 단위(Path, Method, Header 등)까지 제어할 수 있는 L7 정책을 정의할 수 있다.
예를 들어 /admin 경로는 차단하고 /api 경로만 허용하는 세밀한 접근 제어가 가능하며, L7 로깅 및 메트릭을 통해 서비스 간 호출 패턴을 관찰하고, 보안 이상 탐지에도 활용한다.

TPROXY(Transparent Proxy)

기존 사이드카 방식과 달리, Transparent Proxy(TPROXY)를 활용해 Pod 네트워크 스택에서 바로 L7 트래픽을 가로챈다.
이 방식은 iptables/nftables 레벨에서 L7 프록시로 트래픽을 리다이렉션하는 것이 아니라, eBPF 프로그램이 직접 TPROXY를 통해 커널 내부에서 트래픽을 전달한다.
결과적으로 사이드카 없이도 L7 레벨 정책과 관찰 기능을 적용할 수 있으며, CPU/메모리 오버헤드가 크게 줄어든다.
특히, Envoy와 같은 별도의 프록시 프로세스를 모든 Pod에 배치하지 않고 노드 단위 공유 L7 프록시를 활용할 수 있다는 점에서 성능 및 관리 효율성이 크다.

K8S Ingress Support

Cilium Ingress 기본 동작

ingressClassName: cilium 을 사용하여 표준 Kubernetes Ingress 리소스를 지원한다.
Path 기반 라우팅과 TLS Termination을 제공하며, 하위 호환성을 위해 kubernetes.io/ingress.class: cilium 주석(annotation)도 지원한다.
Ingress 컨트롤러는 LoadBalancer 타입의 Service를 생성하므로, 환경에서 LoadBalancer 지원이 필요하다. (필요 시 NodePort 또는 HostNetwork 모드로도 노출 가능)

LoadBalancer 모드

Dedicated: Ingress 리소스별로 전용 LoadBalancer를 생성한다. (충돌 방지에 유리)
Shared: 모든 Ingress 리소스가 하나의 LoadBalancer를 공유한다. (리소스 절약 가능)
모드 변경 시 새로운 LoadBalancer IP가 할당되므로, 기존 활성 연결이 끊어질 수 있다.

필수 조건

NodePort 활성화: nodePort.enabled=true 또는 kubeProxyReplacement=true 필요
L7 Proxy 활성화: l7Proxy=true (기본값)
Ingress 컨트롤러는 기본적으로 LoadBalancer Service를 생성하므로, NodePort/HostNetwork 대안 구성이 필요할 수 있다.

Source IP Visibility

Envoy는 기본적으로 HTTP 연결의 Source IP를 X-Forwarded-For 헤더에 추가한다.
externalTrafficPolicy와 관계없이, Cilium Ingress는 항상 TPROXY를 사용하여 Envoy에 전달하므로 Source IP가 유지된다.
즉, 일반적인 Ingress 컨트롤러와 달리 externalTrafficPolicy: Local 설정이 없어도 클라이언트 IP를 확인할 수 있다.

TPROXY(Transparent Proxy)

Cilium Ingress를 정확히 이해하기 위해서는 TPROXY에 대한 개념이해가 필요하다.
TPROXY란 커널 레벨에서 원래 목적지 IP/Port를 바꾸지 않고 소켓에 트래픽을 직접 할당할 수 있는 기능을 의미한다.

일반 REDIRECT(DNAT)와 달리, 클라이언트가 보낸 원래 목적지 정보(IP/Port)가 유지되므로, 백엔드로 원래 목적지 정보를 그대로 전달할 수 있다.
Envoy 같은 L7 프록시가 들어오는 트래픽을 목적지 주소 그대로 전달할 수 있기 때문에 L7 레벨 정책/라우팅을 적용할 수 있다.

TPROXY를 통해 트래픽이 전달되는 과정에 대해 확인해보자.

1) 패킷 마킹

L7 정책이 걸린 패킷이 들어오면 Cilium에 의해 MARK_MAGIC_TO_PROXY 즉, 0x0200가 마킹된다.

//cilium/bpf/lib/proxy.h
#ifdef ENABLE_TPROXY
	if (!from_host)
		ctx->mark |= MARK_MAGIC_TO_PROXY;
	else
#endif
		ctx->mark = MARK_MAGIC_TO_PROXY | proxy_port << 16;

	cilium_dbg_capture(ctx, DBG_CAPTURE_PROXY_PRE, proxy_port);

//cilium/bpf/lib/common.h
#define MARK_MAGIC_TO_PROXY		0x0200

2) Cilium Proxy Redirection

Iptables를 통해 마킹된 패킷을 Cilium Host Proxy로 Redirection한다.
Node에서 확인해보면 pod에서 나가는 egress 트래픽의 경우, Cilium proxy port인 35531로 Iptables에 의해 패킷이 보내짐을 알 수 있다.

(|HomeLab:N/A) root@k8s-ctr:~# iptables -t mangle -S | grep -i proxy
...
-A CILIUM_PRE_mangle -p tcp -m mark --mark 0xfb820200 -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 33531 --on-ip 127.0.0.1 --tproxy-mark 0x200/0xffffffff
-A CILIUM_PRE_mangle -p udp -m mark --mark 0xfb820200 -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 33531 --on-ip 127.0.0.1 --tproxy-mark 0x200/0xffffffff

(|HomeLab:N/A) root@k8s-ctr:~# cat /var/run/cilium/state/proxy_ports_state.json
{"cilium-dns-egress":{"type":"dns","ingress":false,"port":33531}}

(|HomeLab:N/A) root@k8s-ctr:~# ss -nltup | grep cilium
udp   UNCONN 0      0           127.0.0.1:33531      0.0.0.0:*    users:(("cilium-agent",pid=5430,fd=48))

아래 실습에서 확인을 해보겠지만, cilium-ingress 활성화 상태에서 cilium클래스의 Ingress리소스를 생성하면 cilium daemonset에 의해, cilium-ingress관련 iptables 규칙이 새롭게 생성된다.

(|HomeLab:N/A) root@k8s-ctr:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 MARK       0    --  *      !lo     0.0.0.0/0            0.0.0.0/0            socket --transparent mark match ! 0xe00/0xf00 mark match ! 0x800/0xf00 /* cilium: any->pod redirect proxied traffic to host proxy */ MARK set 0x200
2        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0xfb820200 /* cilium: TPROXY to host cilium-dns-egress proxy */ TPROXY redirect 127.0.0.1:33531 mark 0x200/0xffffffff
3        0     0 TPROXY     17   --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0xfb820200 /* cilium: TPROXY to host cilium-dns-egress proxy */ TPROXY redirect 127.0.0.1:33531 mark 0x200/0xffffffff
4        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x17380200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:14359 mark 0x200/0xffffffff
5        0     0 TPROXY     17   --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x17380200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:14359 mark 0x200/0xffffffff

redirect port에 대해 확인을 해보면 cilium-envoy의 port이다.

(|HomeLab:N/A) root@k8s-ctr:~# ss -nltup | grep cilium | grep 14359
tcp   LISTEN 0      4096        127.0.0.1:14359      0.0.0.0:*    users:(("cilium-envoy",pid=15257,fd=63))
tcp   LISTEN 0      4096        127.0.0.1:14359      0.0.0.0:*    users:(("cilium-envoy",pid=15257,fd=56))
tcp   LISTEN 0      4096        127.0.0.1:14359      0.0.0.0:*    users:(("cilium-envoy",pid=15257,fd=55))
tcp   LISTEN 0      4096        127.0.0.1:14359      0.0.0.0:*    users:(("cilium-envoy",pid=15257,fd=52))

Ingress로 들어오는 트래픽의 경우, Iptables Rule에 의해 cilium-envoy로 패킷이 보내진다.

3) Socket 조회

현재 연결되어 있는 소켓이 없다면 신규로 튜플을 구성하여 커널에서 조회한다.

//cilium/bpf/lib/proxy.h
static __always_inline int							\
NAME(struct __ctx_buff *ctx, const CT_TUPLE_TYPE * ct_tuple,			\
     __be16 proxy_port, void *tproxy_addr)					\
{										\
	struct bpf_sock_tuple *tuple = (struct bpf_sock_tuple *)ct_tuple;	\
	__u8 nexthdr = ct_tuple->nexthdr;					\
	__u32 len = sizeof(tuple->SK_FIELD);					\
	__u16 port;								\
	int result;								\
...						\
	/* 로컬에 연결된 Socket이 없다면 새로운 TPROXY Socket을 할당한다. */ \
  /* 목적지 포트를 proxy_port 즉 cilium proxy port로 설정한다. */
	tuple->SK_FIELD.dport = proxy_port;	 \
  /* 출발지 포트는 wildcard처리를 한다. */
	tuple->SK_FIELD.sport = 0;	\
  /* 목적지 주소를 TPROXY 소켓이 바인딩된 로컬 IP로 지정한다. 일반적으로 127.0.0.1 혹은 ::1 같은 루프백 주소로 들어온다. */
	memcpy(&tuple->SK_FIELD.daddr, tproxy_addr, sizeof(tuple->SK_FIELD.daddr)); \
  /* 출발지 주소는 wildcard처리를 한다. */
	memset(&tuple->SK_FIELD.saddr, 0, sizeof(tuple->SK_FIELD.saddr));	\
	cilium_dbg3(ctx, DBG_LOOKUP_CODE,					\
		    tuple->SK_FIELD.SADDR_DBG, tuple->SK_FIELD.DADDR_DBG,	\
		    combine_ports(tuple->SK_FIELD.dport, tuple->SK_FIELD.sport));	\
	result = assign_socket(ctx, tuple, len, nexthdr, false);		\
	if (result == CTX_ACT_OK)						\
		goto out;	\
...
}

4) Socket 바인딩

구성한 튜플을 기반으로 커널에서 TRPROXY 리스닝 소켓을 조회한 후, 패킷을 소켓에 직접 바인딩한다.
즉, Cilium에 의해 해당 패킷이 iptables REDIRECT/DNAT없이 TPROXY 소켓에 직접 바인딩 된다. 이 단계에서 커널 TCP/IP 스택을 거치지 않고 Envoy 소켓으로 직접 바인딩되므로, NAT 없이 원래 목적지 IP/Port를 유지한 채로 L7 처리가 가능하다.

//cilium/bpf/lib/proxy.h
assign_socket_tcp(struct __ctx_buff *ctx,
		  struct bpf_sock_tuple *tuple, __u32 len, bool established)
{
	int result = DROP_PROXY_LOOKUP_FAILED;
	struct bpf_sock *sk;
	__u32 dbg_ctx;

/* tuple과 일치하는 TCP 소켓을 커널에서 조회한다.*/
	sk = skc_lookup_tcp(ctx, tuple, len, BPF_F_CURRENT_NETNS, 0);
	if (!sk)
		goto out;
...
  /* 패킷을 소켓에 직접 바인딩한다. */
	result = sk_assign(ctx, sk, 0);
...

Cilium Envoy, Cilium-ingress 설정 확인

cilium 설치 명령어

helm install cilium cilium/cilium --version $2 --namespace kube-system \
--set k8sServiceHost=192.168.10.100 --set k8sServicePort=6443 \
--set ipam.mode="cluster-pool" --set ipam.operator.clusterPoolIPv4PodCIDRList={"172.20.0.0/16"} --set ipv4NativeRoutingCIDR=172.20.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=true --set endpointRoutes.enabled=true --set directRoutingSkipUnreachable=true \
--set kubeProxyReplacement=true --set bpf.masquerade=true --set installNoConntrackIptablesRules=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set hubble.enabled=true --set hubble.relay.enabled=true --set hubble.ui.enabled=true \
--set hubble.ui.service.type=NodePort --set hubble.ui.service.nodePort=30003 \
--set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload\,traffic_direction}" \
--set ingressController.enabled=true --set ingressController.loadbalancerMode=shared --set loadBalancer.l7.backend=envoy \
--set localRedirectPolicy=true --set l2announcements.enabled=true \
--set operator.replicas=1 --set debug.enabled=true >/dev/null 2>&1

k8s ingress support 설정 확인을 해보자.

Cilium을 설치할 때 ingressController를 true로 선택을 하였기 때문에 각 워커 노드별로 Cilium이 Ingress 용도로 예약한 IP가 존재한다.
이 IP는 실제 Pod가 가진 IP가 아니라, Cilium 에이전트가 관리하는 가상 IP이며, Ingress로 들어온 트래픽이 Envoy(L7 Proxy)로 전달될 때 식별 목적으로 사용된다.

(|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep -E '^loadbalancer|l7'
enable-l7-proxy                                   true
loadbalancer-l7                                   envoy
loadbalancer-l7-algorithm                         round_robin
loadbalancer-l7-ports

(|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium ip list | grep ingress
172.20.0.147/32     reserved:ingress
172.20.1.202/32     reserved:ingress

Cilium Envoy는 hostNetwork로 각 노드에 한 대씩 daemonset으로 뜨며, 각 노드의 9964 포트를 listen하고 있다.
Cilium Envoy 서비스의 Endpoint로 Cilium Envoy pod의 9964포트가 잡혀있다.
Envoy는 Cilium Agent와 연결되며 admin.sock 유닉스 소켓을 통해 Cilium Agent ↔ Envoy 간 상태 조회/제어가 가능하다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc,ep -n kube-system cilium-envoy
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
service/cilium-envoy   ClusterIP   None         <none>        9964/TCP   18h

NAME                     ENDPOINTS                                 AGE
endpoints/cilium-envoy   192.168.10.100:9964,192.168.10.101:9964   18h

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod -n kube-system -l k8s-app=cilium-envoy -owide
NAME                 READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
cilium-envoy-hw9kw   1/1     Running   0          17h   192.168.10.100   k8s-ctr   <none>           <none>
cilium-envoy-nvvw8   1/1     Running   0          17h   192.168.10.101   k8s-w1    <none>           <none>

(|HomeLab:N/A) root@k8s-ctr:~# kubectl describe pod -n kube-system -l k8s-app=cilium-envoy
...
Volumes:
  envoy-sockets:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/cilium/envoy/sockets
    HostPathType:  DirectoryOrCreate
  envoy-artifacts:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/cilium/envoy/artifacts
    HostPathType:  DirectoryOrCreate
  envoy-config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      cilium-envoy-config
    Optional:  false
  bpf-maps:
    Type:          HostPath (bare host directory volume)
    Path:          /sys/fs/bpf
    HostPathType:  DirectoryOrCreate
...

(|HomeLab:N/A) root@k8s-ctr:~# ls -al /var/run/cilium/envoy/sockets
total 0
drwxr-xr-x 3 root root 120 Aug 22 19:30 .
drwxr-xr-x 4 root root  80 Aug 22 19:29 ..
srw-rw---- 1 root 1337   0 Aug 22 19:30 access_log.sock
srwxr-xr-x 1 root root   0 Aug 22 19:29 admin.sock
drwxr-xr-x 3 root root  60 Aug 22 19:30 envoy
srw-rw---- 1 root 1337   0 Aug 22 19:30 xds.sock

(|HomeLab:N/A) root@k8s-ctr:/var/run/cilium/envoy/sockets# kubectl exec -it -n kube-system ds/cilium-envoy -- cat /var/run/cilium/envoy/bootstrap-config.json > config.json

(|HomeLab:N/A) root@k8s-ctr:~# cat config.json | jq
        "connectTimeout": "2s",
        "loadAssignment": {
          "clusterName": "/envoy-admin",
          "endpoints": [
            {
              "lbEndpoints": [
                {
                  "endpoint": {
                    "address": {
                      "pipe": {
                        "path": "/var/run/cilium/envoy/sockets/admin.sock"
                      }
                    }
                  }
                }
              ]
            }
          ]
        },
        "name": "/envoy-admin",
        "type": "STATIC"
      }
    ],
    "listeners": [
      {
        "address": {
          "socketAddress": {
            "address": "0.0.0.0",
            "portValue": 9964
          }

Cilium ingress의 경우 외부 트래픽을 받아들이기 위하여 LoadBalancer타입으로 생성된다.
endpoint로 잡힌 192.192.192.192:9999는 외부에서 들어온 패킷이 Cilium eBPF 경로를 타고 Envoy로 리디렉션되기 위한 가상 endpoint로, 실제 이 IP로 통신되지는 않는다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc,ep -n kube-system cilium-ingress
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/cilium-ingress   LoadBalancer   10.96.132.142   <pending>     80:30861/TCP,443:31103/TCP   18h

NAME                       ENDPOINTS              AGE
endpoints/cilium-ingress   192.192.192.192:9999   18h

노드의 /sys/fs/bpf/cilium은 Cilium이 eBPF 오브젝트들을 올려두는 가상 파일시스템이다.
ControlPlane 노드의 /sys/fs/bpf/cilium 하위 트리 파일을 통해 구성 방식을 확인해보자.

/sys/fs/bpf/cilium
├── devices
...
│   ├── eth0
│   │   └── links
│   │       ├── cil_from_netdev
│   │       └── cil_to_netdev

(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system exec -it ds/cilium -- cilium endpoint list
1215       Disabled           Disabled          28440      k8s:app.kubernetes.io/name=hubble-ui                                                       172.20.0.36    ready
                                                           k8s:app.kubernetes.io/part-of=cilium
                                                           k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=hubble-ui
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=hubble-ui
...                                                           

devices/eth0/links/cil_* 등은 노드 네트워크 인터페이스 입출구에 붙은 eBPF hook으로, Cilium이 커널 네트워크 스택의 RX/TX 경로에 끼어들어 외부에서 들어오는 패킷을 Envoy로 보내기 위해 TPROXY 동작을 삽입한다.
(예시 - 외부 클라이언트가 Ingress LB IP로 접속하면 eth0 RX → cil_from_netdev → eBPF → Envoy(TPROXY) 흐름을 탐.)

  • cil_from_netdev : NIC에서 들어온 패킷을 Cilium datapath로 끌어들임.
  • cil_to_netdev : Cilium datapath에서 나온 패킷을 실제 NIC로 흘려보냄.

L2 Announcement 설정 및 Cilium-Ingress에 EX-IP 설정

외부에서 Cilium-Ingress를 통해 호출하는 테스트를 위해 LB IPAM 및 L2 Announcement 설정을 추가한다.

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2" 
kind: CiliumLoadBalancerIPPool
metadata:
  name: "cilium-lb-ippool"
spec:
  blocks:
  - start: "192.168.10.211"
    stop:  "192.168.10.215"
EOF

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  interfaces:
  - eth1
  externalIPs: true
  loadBalancerIPs: true
EOF

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc,ep cilium-ingress -n kube-system
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
service/cilium-ingress   LoadBalancer   10.96.132.142   192.168.10.211   80:30861/TCP,443:31103/TCP   18h

NAME                       ENDPOINTS              AGE
endpoints/cilium-ingress   192.192.192.192:9999   18h

#현재 L2 Announcement설정에 의한 리더 노드 = k8s-w1
(|HomeLab:N/A) root@k8s-ctr:~# kubectl -n kube-system get lease | grep "cilium-l2announce"
cilium-l2announce-kube-system-cilium-ingress   k8s-w1                                                                      55s

외부와 내부 모두에서 cilium-ingress의 외부 IP를 통해 통신이 가능하다.

(|HomeLab:N/A) root@k8s-ctr:~# LBIP=$(kubectl get svc -n kube-system cilium-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
(|HomeLab:N/A) root@k8s-ctr:~# echo $LBIP
192.168.10.211
(|HomeLab:N/A) root@k8s-ctr:~# arping -i eth1 $LBIP -c 2
ARPING 192.168.10.211
60 bytes from 08:00:27:b0:11:69 (192.168.10.211): index=0 time=249.708 usec
60 bytes from 08:00:27:b0:11:69 (192.168.10.211): index=1 time=315.533 usec

--- 192.168.10.211 statistics ---
2 packets transmitted, 2 packets received,   0% unanswered (0 extra)


root@router:~# LBIP=192.168.10.211
root@router:~# arping -i eth1 $LBIP -c 2
ARPING 192.168.10.211
60 bytes from 08:00:27:b0:11:69 (192.168.10.211): index=0 time=533.017 usec
60 bytes from 08:00:27:b0:11:69 (192.168.10.211): index=1 time=270.677 usec
^C
--- 192.168.10.211 statistics ---
2 packets transmitted, 2 packets received,   0% unanswered (0 extra)
rtt min/avg/max/std-dev = 0.271/0.402/0.533/0.131 ms

Ingress HTTP Example

Istio 예제로 유명한 bookinfo로 실습을 진행한다.
배포 후 확인을 해보면 Istio 실습과는 다르게 Sidecar Container가 없다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod,svc,ep
NAME                                  READY   STATUS    RESTARTS   AGE
pod/details-v1-766844796b-bfgg4       1/1     Running   0          53s
pod/productpage-v1-54bb874995-579qn   1/1     Running   0          53s
pod/ratings-v1-5dc79b6bcd-csprv       1/1     Running   0          53s
pod/reviews-v1-598b896c9d-dnqln       1/1     Running   0          53s
pod/reviews-v2-556d6457d-46p47        1/1     Running   0          53s
pod/reviews-v3-564544b4d6-b5bps       1/1     Running   0          53s

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/details       ClusterIP   10.96.159.26    <none>        9080/TCP   53s
service/kubernetes    ClusterIP   10.96.0.1       <none>        443/TCP    24h
service/productpage   ClusterIP   10.96.28.207    <none>        9080/TCP   53s
service/ratings       ClusterIP   10.96.106.180   <none>        9080/TCP   53s
service/reviews       ClusterIP   10.96.212.117   <none>        9080/TCP   53s

NAME                    ENDPOINTS                                              AGE
endpoints/details       172.20.1.142:9080                                      53s
endpoints/kubernetes    192.168.10.100:6443                                    24h
endpoints/productpage   172.20.1.154:9080                                      53s
endpoints/ratings       172.20.1.21:9080                                       53s
endpoints/reviews       172.20.1.139:9080,172.20.1.186:9080,172.20.1.26:9080   53s

Ingress를 배포하여 통신을 확인해본다.
Ingress IP는 cilium-ingress 서비스의 외부 IP로 지정된다.

(|HomeLab:N/A) root@k8s-ctr:~# cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: basic-ingress
  namespace: default
spec:
  ingressClassName: cilium
  rules:
  - http:
      paths:
      - backend:
          service:
            name: details
            port:
              number: 9080
        path: /details
        pathType: Prefix
      - backend:
          service:
            name: productpage
            port:
              number: 9080
        path: /
        pathType: Prefix
EOF

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ingress
NAME            CLASS    HOSTS   ADDRESS          PORTS   AGE
basic-ingress   cilium   *       192.168.10.211   80      74s

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc -n kube-system cilium-ingress
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
cilium-ingress   LoadBalancer   10.96.132.142   192.168.10.211   80:30861/TCP,443:31103/TCP   24h

(|HomeLab:N/A) root@k8s-ctr:~# kubectl describe ingress
...
Address:          192.168.10.211
Ingress Class:    cilium
Rules:
  Host        Path  Backends
  ----        ----  --------
  *
              /details   details:9080 (172.20.1.142:9080)
              /          productpage:9080 (172.20.1.154:9080)
(|HomeLab:N/A) root@k8s-ctr:~# LBIP=$(kubectl get svc -n kube-system cilium-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
(|HomeLab:N/A) root@k8s-ctr:~# echo $LBIP
192.168.10.211
(|HomeLab:N/A) root@k8s-ctr:~# curl -so /dev/null -w "%{http_code}\n" http://$LBIP/
200

(|HomeLab:N/A) root@k8s-ctr:~# curl -so /dev/null -w "%{http_code}\n" http://$LBIP/details/1
200

(|HomeLab:N/A) root@k8s-ctr:~# curl -so /dev/null -w "%{http_code}\n" http://$LBIP/ratings
404

(|HomeLab:N/A) root@k8s-ctr:~# hubble observe -f -t l7
Aug 23 10:52:09.731: 192.168.10.200:43330 (ingress) -> default/productpage-v1-54bb874995-579qn:9080 (ID:49314) http-request FORWARDED (HTTP/1.1 GET http://192.168.10.211/)
Aug 23 10:52:09.732: 192.168.10.200:43330 (ingress) <- default/productpage-v1-54bb874995-579qn:9080 (ID:49314) http-response FORWARDED (HTTP/1.1 200 2ms (GET http://192.168.10.211/))
Aug 23 10:52:37.857: 192.168.10.200:43452 (ingress) -> default/details-v1-766844796b-bfgg4:9080 (ID:20097) http-request FORWARDED (HTTP/1.1 GET http://192.168.10.211/details/1)
Aug 23 10:52:37.860: 192.168.10.200:43452 (ingress) <- default/details-v1-766844796b-bfgg4:9080 (ID:20097) http-response FORWARDED (HTTP/1.1 200 3ms (GET http://192.168.10.211/details/1))
Aug 23 10:53:11.823: 192.168.10.200:47462 (ingress) -> default/productpage-v1-54bb874995-579qn:9080 (ID:49314) http-request FORWARDED (HTTP/1.1 GET http://192.168.10.211/ratings)
Aug 23 10:53:11.832: 192.168.10.200:47462 (ingress) <- default/productpage-v1-54bb874995-579qn:9080 (ID:49314) http-response FORWARDED (HTTP/1.1 404 10ms (GET http://192.168.10.211/ratings))

Pod가 뜨는 WorkerNode에서 veth 트래픽을 캡처하여 TPROXY로 인해 Client IP가 보존되는 것을 확인해본다.

root@k8s-w1:~# PROID=172.20.1.154

root@k8s-w1:~# ip route | grep $PROID
172.20.1.154 dev lxc24276eae3fab proto kernel scope link

root@k8s-w1:~# PROVETH=lxc24276eae3fab

root@k8s-w1:~# ngrep -tW byline -d $PROVETH '' 'tcp port 9080'
lxc24276eae3fab: no IPv4 address assigned: Cannot assign requested address
interface: lxc24276eae3fab
filter: ( tcp port 9080 ) and ((ip || ip6) || (vlan && (ip || ip6)))
####
T 2025/08/23 19:56:14.533041 10.0.2.15:56674 -> 172.20.1.154:9080 [AP] #4
GET / HTTP/1.1.
host: 192.168.10.211.
user-agent: curl/8.5.0.
accept: */*.
x-forwarded-for: 192.168.10.200. # -> XFF에 client-ip가 담김.
x-forwarded-proto: http.
x-envoy-internal: true.
x-request-id: eabadda9-e958-4038-8446-aa5c5807c527.
.

##
T 2025/08/23 19:56:14.548321 172.20.1.154:9080 -> 10.0.2.15:56674 [AP] #6
HTTP/1.1 200 OK.
Server: gunicorn.
Date: Sat, 23 Aug 2025 10:56:14 GMT.
Connection: keep-alive.
Content-Type: text/html; charset=utf-8.
Content-Length: 2080.
...

추가로 Ingress로 들어오는 패킷이 TPROXY를 타고 cilium-envoy로 향하는 과정을 확인할 수 있다.

WorkerNode의 mangle IPTABLES를 확인하면 ingress로 들어오는 트래픽은 127.0.0.1의 10061로 향하라는 규칙이 있음을 확인할 수 있는데, 이는 cilium-envoy의 TCP 포트이다.

root@k8s-w1:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num   pkts bytes target     prot opt in     out     source               destination
...
4        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x4d270200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:10061 mark 0x200/0xffffffff
5        0     0 TPROXY     17   --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x4d270200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:10061 mark 0x200/0xffffffff

root@k8s-w1:~# ss -nltup | grep 10061
tcp   LISTEN 0      4096        127.0.0.1:10061      0.0.0.0:*    users:(("cilium-envoy",pid=11198,fd=63))
tcp   LISTEN 0      4096        127.0.0.1:10061      0.0.0.0:*    users:(("cilium-envoy",pid=11198,fd=56))
tcp   LISTEN 0      4096        127.0.0.1:10061      0.0.0.0:*    users:(("cilium-envoy",pid=11198,fd=55))
tcp   LISTEN 0      4096        127.0.0.1:10061      0.0.0.0:*    users:(("cilium-envoy",pid=11198,fd=52))

외부에서 LB를 호출시키면 IPTABLES의 TPROXY to host kube-system/cilium-ingress/listener proxy 규칙의 pkts가 하나씩 증가함을 확인할 수 있다.

root@k8s-w1:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num   pkts bytes target     prot opt in     out     source               destination
...
4        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x4d270200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:10061 mark 0x200/0xffffffff

root@router:~# curl -so /dev/null -w "%{http_code}\n" http://$LBIP/
200

root@k8s-w1:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
num   pkts bytes target     prot opt in     out     source               destination
...
4        1    60 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x4d270200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:10061 mark 0x200/0xffffffff

Dedicated Mode

이번에는 Ingress를 dedicated 모드로 생성해본다. Dedicated모드로 생성하게 되면 Ingress 리소스 별로 전용 LoadBalancer가 생성된다.

# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

(|HomeLab:N/A) root@k8s-ctr:~# cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webpod-ingress-nginx
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.webpod.local
    http:
      paths:
      - backend:
          service:
            name: webpod
            port:
              number: 80
        path: /
        pathType: Prefix
EOF
ingress.networking.k8s.io/webpod-ingress created

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ingress
NAME             CLASS    HOSTS   ADDRESS          PORTS   AGE
basic-ingress    cilium   *       192.168.10.211   80      19m
webpod-ingress   cilium   *       192.168.10.212   80      14s

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get svc,ep cilium-ingress-webpod-ingress
NAME                                    TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
service/cilium-ingress-webpod-ingress   LoadBalancer   10.96.181.231   192.168.10.212   80:31255/TCP,443:31157/TCP   28s

NAME                                      ENDPOINTS              AGE
endpoints/cilium-ingress-webpod-ingress   192.192.192.192:9999   28s

#현재 L2 Announcement설정에 의한 리더 노드 = k8s-ctr
(|HomeLab:N/A) root@k8s-ctr:~# kubectl get lease -n kube-system | grep ingress
cilium-l2announce-default-cilium-ingress-webpod-ingress   k8s-ctr                                                                     60s

위 실습과 동일하게 Pod가 뜨는 Node에서 veth 트래픽을 캡처하여 TPROXY로 인해 Client IP가 보존되는 것을 확인해본다.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod -l app=webpod -owide
NAME                      READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES
webpod-697b545f57-8qgpp   1/1     Running   0          76s   172.20.0.192   k8s-ctr   <none>           <none>
webpod-697b545f57-gkzn6   1/1     Running   0          76s   172.20.1.230   k8s-w1    <none>           <none>

(|HomeLab:N/A) root@k8s-ctr:~# ip link | grep 172.20.0.192
(|HomeLab:N/A) root@k8s-ctr:~# WEBIP=172.20.0.192
(|HomeLab:N/A) root@k8s-ctr:~# ip route | grep $WEBIP
172.20.0.192 dev lxc02bb82001369 proto kernel scope link
(|HomeLab:N/A) root@k8s-ctr:~# WPODVETH=lxc02bb82001369

root@router:~# LB2IP=192.168.10.212
root@router:~# curl -so /dev/null -w "%{http_code}\n" http://$LB2IP/
200
(|HomeLab:N/A) root@k8s-ctr:~# ngrep -tW byline -d $WPODVETH '' 'tcp port 80'
lxc02bb82001369: no IPv4 address assigned: Cannot assign requested address
interface: lxc02bb82001369
filter: ( tcp port 80 ) and ((ip || ip6) || (vlan && (ip || ip6)))
...
T 2025/08/23 21:40:53.991963 172.20.0.192:80 -> 10.0.2.15:36594 [AP] #6
HTTP/1.1 200 OK.
Date: Sat, 23 Aug 2025 12:40:53 GMT.
Content-Length: 341.
Content-Type: text/plain; charset=utf-8.
.
Hostname: webpod-697b545f57-8qgpp
IP: 127.0.0.1
IP: ::1
IP: 172.20.0.192
IP: fe80::5c51:1ff:fee8:c410
RemoteAddr: 10.0.2.15:36594
GET / HTTP/1.1.
Host: 192.168.10.212.
User-Agent: curl/8.5.0.
Accept: */*.
X-Envoy-Internal: true.
X-Forwarded-For: 192.168.10.200.
X-Forwarded-Proto: http.
X-Request-Id: 135499d1-ed5d-457b-9815-8b5fb49d63c9.
.

###

여기서 하나 확인할 수 있는 점은 L2 Announcement 설정으로 인해 k8s-w1에 떠있는 파드를 호출하더라도 k8s-ctr을 거쳐서 k8s-w1로 패킷이 향하는 것인데, 이 때문에 RemoteAddr의 모습이 다르다.

리더 노드인 k8s-ctr위의 Pod의 경우 L2 leader Node에서 바로 패킷을 전달하기 때문에, pod가 바라보는 RemoteAddr은 k8s-ctr의 첫 번째 NIC IP이다.

k8s-w1위의 Pod의 경우 L2 leader Node(k8s-ctr)에서 k8s-w1로 패킷이 전달되기 때문에, RemoteAddr이 Leader Node를 거친 src IP 즉, Ingress의 EXT-IP이다.

하지만, TPROXY로 인해 실제 Client IP가 보존되기 때문에 X-Forwarded-For의 값은 외부 노드 Router의 eth1 IP와 동일하다.

# k8s-ctr
(|HomeLab:N/A) root@k8s-ctr:~# ngrep -tW byline -d $WPODVETH '' 'tcp port 80'
...
Hostname: webpod-697b545f57-8qgpp
IP: 127.0.0.1
IP: ::1
IP: 172.20.0.192
IP: fe80::5c51:1ff:fee8:c410
RemoteAddr: 10.0.2.15:36594
GET / HTTP/1.1.
Host: 192.168.10.212.
User-Agent: curl/8.5.0.
Accept: */*.
X-Envoy-Internal: true.
X-Forwarded-For: 192.168.10.200.
X-Forwarded-Proto: http.
X-Request-Id: 135499d1-ed5d-457b-9815-8b5fb49d63c9.

# k8s-w1
root@k8s-w1:~# ngrep -tW byline -d $WPODVETH '' 'tcp port 80'
...
Hostname: webpod-697b545f57-gkzn6
IP: 127.0.0.1
IP: ::1
IP: 172.20.1.230
IP: fe80::8c39:e6ff:fe38:4d79
RemoteAddr: 172.20.0.147:35985
GET / HTTP/1.1.
Host: 192.168.10.212.
User-Agent: curl/8.5.0.
Accept: */*.
X-Envoy-Internal: true.
X-Forwarded-For: 192.168.10.200.
X-Forwarded-Proto: http.
X-Request-Id: 7958092f-8b81-47a1-81c3-31d3e6725660.

ingress-Nginx와 cilium Ingress 공존 가능

ingress-nginx와 cilium Ingress는 공존이 가능하다.

# Ingress-Nginx 컨트롤러 설치
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx --create-namespace -n ingress-nginx

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ingressclasses.networking.k8s.io
NAME     CONTROLLER                     PARAMETERS   AGE
cilium   cilium.io/ingress-controller   <none>       26h
nginx    k8s.io/ingress-nginx           <none>       51s

# ingress 설정
cat << EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webpod-ingress-nginx
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.webpod.local
    http:
      paths:
      - backend:
          service:
            name: webpod
            port:
              number: 80
        path: /
        pathType: Prefix
EOF

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get ingress
NAME                   CLASS    HOSTS                ADDRESS          PORTS   AGE
basic-ingress          cilium   *                    192.168.10.211   80      44m
webpod-ingress         cilium   *                    192.168.10.212   80      24m
webpod-ingress-nginx   nginx    nginx.webpod.local   192.168.10.213   80      12s

RemoteAddr

해당 LB를 호출하여 통신 상태를 확인해본다.
k8s-ctr, k8s-w1모두 RemoteAddr이 ingress-nginx-controller의 Pod IP임을 알 수 있다.

root@router:~# LB3IP=192.168.10.213
root@router:~# curl -H "Host: nginx.webpod.local" $LB3IP

# k8s-ctr
(|HomeLab:N/A) root@k8s-ctr:~# ngrep -tW byline -d $WPODVETH '' 'tcp port 80'
Hostname: webpod-697b545f57-8qgpp
IP: 127.0.0.1
IP: ::1
IP: 172.20.0.192
IP: fe80::5c51:1ff:fee8:c410
RemoteAddr: 172.20.1.35:53392
GET / HTTP/1.1.
Host: nginx.webpod.local.
User-Agent: curl/8.5.0.
Accept: */*.
X-Forwarded-For: 192.168.10.200.
X-Forwarded-Host: nginx.webpod.local.
X-Forwarded-Port: 80.
X-Forwarded-Proto: http.
X-Forwarded-Scheme: http.
X-Real-Ip: 192.168.10.200.
X-Request-Id: 02a94cc39febb17e48af05ccff0d4f9e.
X-Scheme: http.

# k8s-w1
root@k8s-w1:~# ngrep -tW byline -d $WPODVETH '' 'tcp port 80'
Hostname: webpod-697b545f57-gkzn6
IP: 127.0.0.1
IP: ::1
IP: 172.20.1.230
IP: fe80::8c39:e6ff:fe38:4d79
RemoteAddr: 172.20.1.35:35976
GET / HTTP/1.1.
Host: nginx.webpod.local.
User-Agent: curl/8.5.0.
Accept: */*.
X-Forwarded-For: 192.168.10.200.
X-Forwarded-Host: nginx.webpod.local.
X-Forwarded-Port: 80.
X-Forwarded-Proto: http.
X-Forwarded-Scheme: http.
X-Real-Ip: 192.168.10.200.
X-Request-Id: 7adfa99642ab225d4dd4c4c22b3d8408.
X-Scheme: http.

(|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod -n ingress-nginx -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
ingress-nginx-controller-67bbdf7d8d-f9bmk   1/1     Running   0          15m   172.20.1.35   k8s-w1   <none>           <none>

TPROXY

TPROXY를 타는지를 확인을 해보면, nginx class의 Ingress의 경우 TPROXY 커널 기능을 사용하지 않는 것을 확인할 수 있다.

# nginx class Ingress 호출
(|HomeLab:N/A) root@k8s-ctr:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
...
4        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x17380200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:14359 mark 0x200/0xffffffff
6        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x94480200 /* cilium: TPROXY to host default/cilium-ingress-default-webpod-ingress/listener proxy */ TPROXY redirect 127.0.0.1:18580 mark 0x200/0xffffffff

root@router:~# curl -H "Host: nginx.webpod.local" $LB3IP

# pkts 변화 없음.
Chain CILIUM_PRE_mangle (1 references)
...
4        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x17380200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:14359 mark 0x200/0xffffffff
6        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x94480200 /* cilium: TPROXY to host default/cilium-ingress-default-webpod-ingress/listener proxy */ TPROXY redirect 127.0.0.1:18580 mark 0x200/0xffffffff
# cilium class Ingress 호출
root@router:~# curl -so /dev/null -w "%{http_code}\n" http://$LB2IP/

# cilium-ingress listener TPROXY pkts 1 증가
(|HomeLab:N/A) root@k8s-ctr:~# (⎈|HomeLab:N/A) root@k8s-ctr:~# sudo iptables -t mangle -L CILIUM_PRE_mangle --line-numbers -n -v
Chain CILIUM_PRE_mangle (1 references)
...
4        1    60 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x94480200 /* cilium: TPROXY to host default/cilium-ingress-default-webpod-ingress/listener proxy */ TPROXY redirect 127.0.0.1:18580 mark 0x200/0xffffffff
6        0     0 TPROXY     6    --  *      *       0.0.0.0/0            0.0.0.0/0            mark match 0x17380200 /* cilium: TPROXY to host kube-system/cilium-ingress/listener proxy */ TPROXY redirect 127.0.0.1:14359 mark 0x200/0xffffffff

Hubble Observe

webpod에 대해 hubble observe로 모니터링을 해본다.

nginx class Ingress
(|HomeLab:N/A) root@k8s-ctr:~#  kubectl exec -n kube-system -c cilium-agent -it ds/cilium -- cilium-dbg endpoint list | grep webpod
3644       Disabled           Disabled          59436      k8s:app=webpod                                                                        172.20.1.230   ready

# nginx class Ingress 호출
root@router:~# curl -H "Host: nginx.webpod.local" $LB3IP

(|HomeLab:N/A) root@k8s-ctr:~# hubble observe -f  --protocol tcp --from-identity  59436
Aug 23 13:43:51.260: ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk:34564 (ID:47827) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-endpoint FORWARDED (TCP Flags: SYN, ACK)
Aug 23 13:43:51.261: default/webpod-697b545f57-8qgpp:80 (ID:59436) <> ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk (ID:47827) pre-xlate-rev TRACED (TCP)
Aug 23 13:43:51.261: default/webpod-697b545f57-8qgpp:80 (ID:59436) <> ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk (ID:47827) pre-xlate-rev TRACED (TCP)
Aug 23 13:43:51.272: ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk:34564 (ID:47827) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Aug 23 13:43:51.337: ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk:34564 (ID:47827) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: SYN, ACK)
Aug 23 13:43:51.344: ingress-nginx/ingress-nginx-controller-67bbdf7d8d-f9bmk:34564 (ID:47827) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK, PSH)
  • ingress-nginx-controller Pod(k8s-w1 위)에서 백엔드(webpod)로 TCP SYN 전달
  • default/webpod Pod(k8s-ctr 위)에서 ACK, PSH로 응답
  • ingress-nginx-controller Pod로 TO-NETWORK FORWARDED
  • Nginx가 처리한 후 응답 패킷이 클라이언트로 나가도록 eBPF가 라우팅

특징

  • Envoy/TPROXY 없음
  • TCP 흐름 : Nginx Pod → Backend Pod → Nginx Pod → 클라이언트
  • Nginx가 L7 인지, Host 기반 라우팅 수행
  • eBPF는 Pod→Pod와 Pod→Network 경로만 담당
cilium class Ingress
# cilium class Ingress 호출
root@router:~# curl -so /dev/null -w "%{http_code}\n" http://$LB2IP/

(|HomeLab:N/A) root@k8s-ctr:~# hubble observe -f  --from-identity  59436
# k8s-w1위의 webpod 호출됨
Aug 23 14:29:24.934: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: SYN, ACK)
Aug 23 14:29:24.935: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: ACK, PSH)
Aug 23 14:29:24.935: 192.168.10.200:40780 (ingress) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) http-response FORWARDED (HTTP/1.1 200 1ms (GET http://192.168.10.212/))
Aug 23 14:29:39.941: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: ACK)
Aug 23 14:29:55.301: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: ACK)
Aug 23 14:30:10.661: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: ACK)
Aug 23 14:30:24.937: 10.0.2.15:59674 (host) <- default/webpod-697b545f57-gkzn6:80 (ID:59436) to-stack FORWARDED (TCP Flags: ACK, FIN)

# k8s-ctr위의 webpod 호출됨
Aug 23 14:30:28.342: 192.168.10.200:42700 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) http-response FORWARDED (HTTP/1.1 200 4ms (GET http://192.168.10.212/))
Aug 23 14:30:28.416: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: SYN, ACK)
Aug 23 14:30:28.418: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK, PSH)
Aug 23 14:30:43.614: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK)
Aug 23 14:30:58.975: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK)
Aug 23 14:31:14.334: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK)
Aug 23 14:31:28.420: 172.20.1.202:38801 (ingress) <- default/webpod-697b545f57-8qgpp:80 (ID:59436) to-network FORWARDED (TCP Flags: ACK, FIN)

1) 외부 요청

  • 외부 Router → Ingress LB IP 호출
  • L2 Announcement: 트래픽이 리더 노드인 k8s-w1로 전달
  • eBPF LB + TPROXY: 패킷이 Envoy 소켓(TPROXY)으로 리다이렉트
  • Envoy가 L7 요청을 파싱하고, 적절한 backend Pod 선택

2) 요청 패킷 전달 (Envoy -> webpod)
Envoy가 선택한 Pod로 요청 전달
2-1) Backend Pod가 k8s-w1(리더 노드)에 있는 경우

  • Envoy → 동일 노드 webpod 로 전달
  • 요청 패킷 경로: router → k8s-w1 Node → webpod(k8s-w1)

2-2) Backend Pod가 k8s-ctr(리모트 노드)에 있는 경우

  • Envoy가 요청 패킷을 remote Pod IP로 전달
  • 요청 패킷 경로: router → Ingress EXT-IP → k8s-ctr webpod

3) 응답 패킷 전달 (WebPod → Client)
3-1) Backend Pod가 k8s-w1(리더 노드)에 있는 경우

  • 응답 경로: webpod → k8s-w1 host stack (Envoy, to-stack) → router
  • TPROXY가 Host Stack으로 redirect, Envoy가 HTTP 응답 처리

3-2) Backend Pod가 k8s-ctr(리모트 노드)에 있는 경우

  • 응답 경로: webpod(k8s-ctr) → k8s-ctr host stack → k8s-w1 Envoy (to-network) → client
  • k8s-ctr에서 다시 k8s-w1로 응답이 돌아오는 과정에서 to-network message, Envoy가 HTTP 응답 처리

0개의 댓글