본문 바로가기
devops/cilium

[Cilium ServiceMesh] Cilium Ingress #2

by gnobaaaar 2026. 2. 18.

 

이전 글 이어서 작성됩니다

 

테스트

데모 App을 배포합니다.

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.26/samples/bookinfo/platform/kube/bookinfo.yaml

 

 

해당 데모 앱을 연결하기 위해 ingress를 배포해줍니다.

ingressClassName: cilium 를 통해 Cilium Ingress Controller 를 가리킵니다.

 

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

 

위 Ingress 명세에는 /details 와 / 가 명시되어 있고

URL경로는 각각 details 서비스와 productpage 서비스로 흐르게됩니다.

 

서비스 진입점은 Ingress Controller 이고 이전글에서 연결했던

EXTERNAL-IP: 192.168.10.211 이 연결 IP가 됩니다. 

 

명세를 통해 생성된  Ingress는 아래와 같습니다.

외부 포트 노드 내부 포트
80 32046
443 31695

 

인입 흐름도는 아래와 같습니다.

# cilium ingress는 ebpf 동작으로 nodeport를 실제로 지나지 않습니다

Client → 192.168.10.211:80
             ↓
노드 (eth1)
             ↓
eBPF (TC ingress hook)
             ↓
Envoy

 

k8s-ctr 노드에서 아래와 같은 순서로 테스트도 가능합니다.

# 호출 확인
LBIP=$(kubectl get svc -n kube-system cilium-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $LBIP

# 실패하는 호출이 있는가?
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/details/1
curl -so /dev/null -w "%{http_code}\n" http://$LBIP/ratings

# Access the Bookinfo application
curl "http://$LBIP/productpage?u=normal"

 

 

hubble 

hubble ui

 

 

클러스터 내부 hubble에 접근하도록 포트포워딩을 열겠습니다.

cilium hubble port-forward&

 

 

hubble observe -f -t l7

L7 (HTTP, gRPC 등) 트래픽을 실시간으로 출력합니다.

 

출력된 내용은 아래와 같습니다.

$ hubble observe -f --identity ingress

Feb 18 12:22:17.019: 172.20.0.106:40867 (ingress) -> default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-endpoint FORWARDED (TCP Flags: SYN)
Feb 18 12:22:17.019: 172.20.0.106:40867 (ingress) <- default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-network FORWARDED (TCP Flags: SYN, ACK)
Feb 18 12:22:17.019: 172.20.0.106:40867 (ingress) -> default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-endpoint FORWARDED (TCP Flags: ACK)
Feb 18 12:22:17.020: 172.20.0.106:40867 (ingress) <> default/productpage-v1-54bb874995-8lmzt (ID:17264) pre-xlate-rev TRACED (TCP)
Feb 18 12:22:17.020: 127.0.0.1:59664 (ingress) -> default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) http-request FORWARDED (HTTP/1.1 GET http://192.168.10.211/productpage?u=normal)
Feb 18 12:22:17.020: 172.20.0.106:40867 (ingress) -> default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Feb 18 12:22:17.078: 172.20.0.106:40867 (ingress) <- default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-network FORWARDED (TCP Flags: ACK, PSH)
Feb 18 12:22:17.079: 127.0.0.1:59664 (ingress) <- default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) http-response FORWARDED (HTTP/1.1 200 60ms (GET http://192.168.10.211/productpage?u=normal))
Feb 18 12:22:19.079: 172.20.0.106:40867 (ingress) <- default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-network FORWARDED (TCP Flags: ACK, FIN)
Feb 18 12:22:19.079: 172.20.0.106:40867 (ingress) -> default/productpage-v1-54bb874995-8lmzt:9080 (ID:17264) to-endpoint FORWARDED (TCP Flags: ACK, FIN)

 

---

172.20.0.106:40867 (ingress)

-> Envoy가 backend로 새로 만든 TPC 연결의 source (ingress = identity)

default/productpage

-> backend

---

 

172.20.0.106은 어느 ip일까요?

Envoy에서 백엔드로 향했다면 Envoy IP이어야하는데 

노드를 확인해보니 Envoy는 192.168.10.x 대역으로 떠있습니다. (hostNetwork: true)

 

이전 글에서 Cilium은 IP 기반이 아닌 Identity 기반 정책 모델을 사용하고,
따라서 정책 평가 단계에서는 reserved:ingress identity를 별도로 적용하여 

host 트래픽과 Ingress 트래픽을 명확히 구분한다고 하였습니다.

 

이를 위해 내부적으로 Pod 네트워크 대역(예: 172.20.x.x)에서 /32 주소를 예약한다고 하였습니다.

Hubble에서 보이는 172.20.0.106은 해당 ingress identity에 매핑된 내부 endpoint IP입니다.

예약된 내부 /32 IP

 

 

hubble observe -f --identity ingress

ingress identity 관련 트래픽만 필터링

 

 

 

패킷 흐름 확인하기

Cilium Ingress -> Envoy -> Backend 흐름을 실제 패킷으로 확인해보겠습니다.

productpage 파드로 들어가는 흐름을 확인해보겠습니다.

 

productpage 위치확인

k8s-w1 노드에 있다는걸 확인했습니다.

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get pod -l app=productpage -owide
NAME                              READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
productpage-v1-54bb874995-8lmzt   1/1     Running   0          34m   172.20.1.103   k8s-w1   <none>           <none>

 

 

veth 인터페이스 정보 확인

172.20.1.103 이라는 ip로 k8s-w1 에서 veth 인터페이스를 확인해보겠습니다.

$ ip route | grep 172.20.1.103
172.20.1.103 dev lxc8eb3d4f289d8 proto kernel scope link

 

 

ngrep 으로 veth 패킷 캡처

lxc8eb3d4f289d8 (veth)로 지나가는 트래픽을 ngrep 으로 캡처하겠습니다.

ingress에 적어 두었듯이 productpage는 9080 TCP Port를 사용합니다.

ngrep -tW byline -d lxc8eb3d4f289d8 '' 'tcp port 9080'

 

외부 router 노드에서 curl 로 LBIP에 접근해봅니다.

root@router:~# curl -s http://192.168.10.211

 

흐름은 아래와 같이 들어옵니다.

Router (192.168.10.200)
    ↓
VIP (192.168.10.211)
    ↓
eBPF
    ↓
Envoy (hostNetwork)
    ↓
Linux routing
    ↓
Backend Pod

 

 

 

ngrep 정보

ngrep 호출하면 아래 몇가지 IP 정보를 확인할 수 있습니다.

veth 를 캡처하니 아래 IP들이 확인됩니다.

 

x-forwarded-for 헤더의 값이 router IP인것으로 보아

igress(envoy) 가 XFF에 client-ip 담고, 목적지 파드로 요청했다는 것을 알 수 있습니다.

 

 

 

정리하자면,

순서대로 인입 흐름을 따라가 보겠습니다.

Cilium은 외부 -> ingress(Envoy) 구간,

ingress(Envoy) -> backend 구간으로 나누어서 보면 좋습니다.

 

router에서 curl 로 요청이 들어오고 LB-IP는 192.168.10.211 입니다.

해당 VIP는 현재 leader가 k8s-w1 이고 인터페이스는 eth1으로 되어있습니다.

(지난 글을 참조하시길 바랍니다)

 

그렇게 패킷이 eth1으로 들어오면 Cilium eBPF 이 개입됩니다.

eth1
   ↓
TC ingress hook
   ↓
eBPF 프로그램 실행

 

Cilium은 VIP가 ingress 로 향하는 걸 알고있고 

VIP -> NodePort(32046) 이 아니라

TPROXY 동작과정을 통해 Envoy로 전달합니다 (world identity 구간)

bpf_sk_lookup() → Envoy 소켓
bpf_sk_assign() → TPROXY redirect

 

이후 Envoy로 들어오면, Envoy는 백엔드와 새 TCP 연결을 만듭니다.

172.20.1.103 이라는 로컬 PodCIDR로 연결해야하고 (현재 product 백엔드는 w1 로컬에 있으므로)

기본 eth0 인터페이스(10.0.2.15)를 골라서 연결합니다.

 

백엔드는 10.0.2.15를 출발지 IP로 보게되므로

Envoy는 XFF 헤더에 192.168.10.200 = router IP를 담아서 백엔드로 전달합니다.

 

해당 내용은 Hubble UI 에서도 확인가능합니다.

 

 

 

 

 

Ingress and Network Policy Example

이제 Cilium Ingress 의 identity 기반으로 외부 트래픽을 차단/허용해보겠습니다.

reserved:world / reserved:ingress identity가 정책에서 실제로 어떻게 쓰이는지를 확인해보겠습니다.

 

 

클러스터 전체(모든 네임스페이스)에 적용되는 정책

클러스터 전체에 적용되는 정책을 적용합니다.

  • endpointSelector: {} → 클러스터의 모든 엔드포인트(모든 Pod) 에 적용
  • fromEntities: cluster → 클러스터 내부에서 오는 트래픽만 허용
  • 그 외(= 외부 reserved:world)는 전부 차단
cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "external-lockdown"
spec:
  description: "Block all the traffic originating from outside of the cluster"
  endpointSelector: {}
  ingress:
  - fromEntities:
    - cluster
EOF

 

 

curl로 LB-IP에 접근하면 403 에러가 발생하는 것을 확인할 수 있습니다. (권한)

 

정책은 Envoy(Ingress)까지 전달되었지만 

정책엔진이 외부(world)에서 온 요청으로 판단되어 DROP 되게 됩니다.

 

 

 

 

allow-cidr — ingress identity에 특정 CIDR만 허용

대상은 reserved:ingress identity(= ingress 경계)

  • ingress로 들어오는 트래픽 중에서
    • router(192.168.10.200)
    • 로컬(127.0.0.1)
    • 만 허용

특정 트래픽만 허용하는 정책입니다.

적용 이후 router/로컬 curl 이 가능해집니다.

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "allow-cidr"
spec:
  description: "Allow all the traffic originating from a specific CIDR"
  endpointSelector:
    matchExpressions:
    - key: reserved:ingress
      operator: Exists
  ingress:
  - fromCIDRSet:
    # Please update the CIDR to match your environment
    - cidr: 192.168.10.200/32
    - cidr: 127.0.0.1/32
EOF

 

 

Default Deny Ingress Policy

DNS쿼리와 kube-system내의 파드 제외 to deny all traffic by default 입니다.

  • 대상: kube-system을 제외한 모든 Pod
  • 기본: 모든 egress 차단
  • 예외: kube-dns로 가는 UDP/53(DNS)만 허용

 

Ingress를 통해 들어온 요청도, 결국 ingress → backend egress가 막혀서 실패합니다.

cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "default-deny"
spec:
  description: "Block all the traffic (except DNS) by default"
  egress:
  - toEndpoints:
    - matchLabels:
        io.kubernetes.pod.namespace: kube-system
        k8s-app: kube-dns
    toPorts:
    - ports:
      - port: '53'
        protocol: UDP
      rules:
        dns:
        - matchPattern: '*'
  endpointSelector:
    matchExpressions:
    - key: io.kubernetes.pod.namespace
      operator: NotIn
      values:
      - kube-system
EOF

 

allow-ingress-egress

위에서 ingress -> backend 로의 egress가 차단되었으므로 해당 구간만 허용해줍니다.

  • 대상: reserved:ingress identity (Envoy)
  • 허용: cluster 내부로의 egress 전부
cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: allow-ingress-egress
spec:
  description: "Allow all the egress traffic from reserved ingress identity to any endpoints in the cluster"
  endpointSelector:
    matchExpressions:
    - key: reserved:ingress
      operator: Exists
  egress:
  - toEntities:
    - cluster
EOF

 

위 테스트를 순서대로 hubble observe -f --identity ingress 로

모니터링하면서 해보시길 추천드립니다.

 

 

 

 

Cilium Ingress TLS Termination은

차후 인증서 정리 때 이어서 작성해보겠습니다.