iptables는 오랜 기간 리눅스 네트워크 필터링 및 방화벽의 핵심 역할을 해왔지만, 몇 가지 명확한 한계가 있었다.
이러한 한계를 해결하기 위해 BPF(Berkeley Packet Filter)와 그 확장판인 eBPF(extended BPF)가 등장하였다. eBPF는 커널에서 사용자 정의 프로그램을 실행할 수 있도록 해주며, 이로 인해 기존 iptables가 가지지 못한 향상된 성능 / 유연한 프로그래밍 / 확장성 등 다양한 장점을 제공한다.
eBPF는 리눅스 커널의 다양한 지점에 프로그램을 hook, 즉 연결을 해서 특정 이벤트가 발생할 때 커스텀 로직을 실행할 수 있게 해준다.
각 계층 별로 걸 수 있는 eBPF Hook 예시를 확인해본다.
네트워크 장치 드라이버 또는 하드웨어와의 인터페이스에서 BPF 프로그램을 직접 실행한다.
위치: NIC(Network Interface Card) 드라이버 수준에서 직접 실행
용도: 초고속 패킷 처리
예시: 고성능 로드 밸런서(NIC)에서 패킷을 바로 분산 처리 가능
소켓에서 발생하는 이벤트에 Hook을 걸어 애플리케이션 계층 통신 제어가 가능하다.
시스템 호출 인터페이스에 Hook을 걸어 프로세스와 커널 간 상호작용을 제어한다.
파일 접근과 프로세스 이벤트에 Hook을 걸어 보안 정책을 적용하거나 모니터링한다.
eBPF는 커널 내에서 동작하는 프로그램이지만, userspace와 커널 간의 통신을 통해 애플리케이션 레벨의 트래픽을 추적하고 제어할 수 있다.
Envoy와 eBPF를 연동해 애플리케이션의 L7 트래픽을 제어.
Cilium은 eBPF를 기반으로 Pod 네트워크 환경과 보안을 제공하는 CNI 플러그인이다.
Cilium eBPF 는 추가적인 App 이나 설정 변경 없이 리눅스 커널을 자유롭게 프로그래밍하여 동작 가능 하다.
Kubernetes에서는 Cilium으로 100% kube-proxy replacement가 가능하다. 이로 인해 복잡한 kube-proxy, iptables, conntrack 관리와, 커널 버그로 인해 발생하는 다양한 환경 변수의 조합을 덜 고민해도 될 것으로 보인다.
최근 운영 하면서 겪은 이슈
서비스 간에 Large Payload 전송 시 Connection reset by peer 에러와 함께 TCP connection이 Resete되며 끊기는 이슈 발생
원인 : Large Payload 전송 시 Out-of-order가 발생하는 경우가 있는데, 문제가 없는 패킷임에도 conntrack버그로 인해 패킷에 INVALID 마킹이 됨. INVALID 마킹이 된 패킷이 DROP되지 않고, 정상적으로 DNAT되지 않은 상태로 노드로 전송되면서 노드는 출처가 불분명해진 패킷에 대해 Connection Reset 요청을 보냄.해결 방법 1: Kernel 6버전 이상에서는 해결되는 이슈이나, 운영상 민감한 부분이 많은 클러스터이기에 Kernel 업그레이드가 자유롭지 않음.
해결 방법 2: kube-proxy에 tcp_be_liberal=1 적용 가능, INVALID 마킹을 유연하게 하는 W/A 옵션으로 K8s 1.29부터 적용됨. 이 또한 K8s 업그레이드를 바로 하기에는 운영상 민감한 부분이 많음.
해결 방법 3: 노드에 net.netfilter.nf_conntrack_tcp_be_liberal=1 설정을 적용. INVALID 마킹을 유연하게 하는 옵션으로, 커널 자체의 문제를 개선한 것이 아니고 out-of-order 패킷을 허용하는 옵션이기 때문에 보안적으로 취약하다는 단점이 있음. 다만, 바로 별도의 업그레이드 없이 적용할 수 있는 가장 간단한 방법이기 때문에 해당 방법을 선택함.
커널 버전, 쿠버네티스 버전 업그레이드는 안정성 등의 이슈로 운영 중에 바로바로 업그레이드를 할 수 없어 문제를 가장 확실하게 해결할 수 있는 방법을 바로 적용하지 못하는 경우가 있는데, eBPF를 사용할 경우 기존 커널 위에 커스텀 커널 및 로직 개발이 가능하기 때문에, 이슈 해결을 조금 더 유연하게 할 수 있지 않을까 하는 생각이 든다.
helm install cilium cilium/cilium --version 1.16.3 --namespace kube-system \
--set k8sServiceHost=192.168.10.10 --set k8sServicePort=6443 --set debug.enabled=true \
--set rollOutCiliumPods=true --set routingMode=native --set autoDirectNodeRoutes=true \
--set bpf.masquerade=true --set bpf.hostRouting=true --set endpointRoutes.enabled=true \
--set ipam.mode=kubernetes --set k8s.requireIPv4PodCIDR=true --set kubeProxyReplacement=true \
--set ipv4NativeRoutingCIDR=192.168.0.0/16 --set installNoConntrackIptablesRules=true \
--set hubble.ui.enabled=true --set hubble.relay.enabled=true --set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns:query;ignoreAAAA,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 operator.replicas=1
옵션 | 설명 |
---|---|
rollOutCiliumPods | Cilium Pods의 롤아웃을 활성화하여 새로운 버전으로의 배포를 관리한다. |
routingMode | 패킷의 라우팅 방식을 설정하며, 기본적으로 네이티브 라우팅 모드를 사용한다. |
autoDirectNodeRoutes | 자동으로 노드 간 라우팅을 설정하여 패킷 전송을 최적화한다. |
bpf.masquerade | BPF를 사용하여 IP 마스커레이드를 활성화한다. |
bpf.hostRouting | 호스트 라우팅 기능을 활성화하여 패킷을 직접 전달한다. |
endpointRoutes.enabled | 엔드포인트 라우트를 활성화하여 Pod 간의 트래픽 흐름을 최적화한다. |
ipam.mode | IP 주소 할당 방식을 Kubernetes로 설정한다. |
k8s.requireIPv4PodCIDR | IPv4 Pod CIDR 요구 사항을 활성화하여 클러스터 내 IPv4 주소 사용을 보장한다. |
kubeProxyReplacement | Cilium이 kube-proxy의 역할을 대체하도록 설정한다. |
ipv4NativeRoutingCIDR | IPv4 네이티브 라우팅에 사용할 CIDR 블록을 설정한다. |
installNoConntrackIptablesRules | conntrack iptables 규칙을 설치하지 않도록 설정하여 Cilium의 효율성을 높인다. |
hubble.ui.enabled | Hubble UI를 활성화하여 네트워크 모니터링 및 시각화를 지원한다. |
hubble.relay.enabled | Hubble Relay를 활성화하여 메트릭 수집 및 이벤트 전달 기능을 지원한다. |
prometheus.enabled | Prometheus 모니터링을 활성화하여 메트릭 수집을 지원한다. |
operator.prometheus.enabled | Cilium Operator의 Prometheus 모니터링 기능을 활성화한다. |
hubble.metrics.enableOpenMetrics | Hubble 메트릭의 OpenMetrics 형식을 활성화하여 호환성을 높인다. |
hubble.metrics.enabled | Hubble에서 수집할 메트릭과 이벤트를 정의한다. |
operator.replicas | Cilium Operator의 복제본 수를 설정하여 고가용성을 유지한다. |
cilium-operator, cilium, cilium-envoy가 설치되어 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get node,pod,svc -A -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node/k8s-s Ready control-plane 141m v1.30.6 192.168.10.10 <none> Ubuntu 22.04.5 LTS 6.8.0-1015-aws containerd://1.7.22
node/k8s-w1 Ready <none> 140m v1.30.6 192.168.10.101 <none> Ubuntu 22.04.5 LTS 6.8.0-1015-aws containerd://1.7.22
node/k8s-w2 Ready <none> 140m v1.30.6 192.168.10.102 <none> Ubuntu 22.04.5 LTS 6.8.0-1015-aws containerd://1.7.22
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system pod/cilium-7qtr8 1/1 Running 0 100s 192.168.10.102 k8s-w2 <none> <none>
kube-system pod/cilium-envoy-g29t8 1/1 Running 0 100s 192.168.10.102 k8s-w2 <none> <none>
kube-system pod/cilium-envoy-gksj4 1/1 Running 0 100s 192.168.10.10 k8s-s <none> <none>
kube-system pod/cilium-envoy-m546t 1/1 Running 0 100s 192.168.10.101 k8s-w1 <none> <none>
kube-system pod/cilium-f6hl2 1/1 Running 0 100s 192.168.10.10 k8s-s <none> <none>
kube-system pod/cilium-operator-76bb588dbc-gx6bn 1/1 Running 0 100s 192.168.10.101 k8s-w1 <none> <none>
kube-system pod/cilium-tgkbh 1/1 Running 0 100s 192.168.10.101 k8s-w1 <none> <none>
kube-system pod/coredns-55cb58b774-7tj27 1/1 Running 0 140m 172.16.1.13 k8s-w1 <none> <none>
kube-system pod/coredns-55cb58b774-pldd7 1/1 Running 0 140m 172.16.1.217 k8s-w1 <none> <none>
kube-system pod/etcd-k8s-s 1/1 Running 0 141m 192.168.10.10 k8s-s <none> <none>
kube-system pod/hubble-relay-88f7f89d4-zpgdl 1/1 Running 0 100s 172.16.1.150 k8s-w1 <none> <none>
kube-system pod/hubble-ui-59bb4cb67b-dk4hp 2/2 Running 0 100s 172.16.1.214 k8s-w1 <none> <none>
kube-system pod/kube-apiserver-k8s-s 1/1 Running 0 141m 192.168.10.10 k8s-s <none> <none>
kube-system pod/kube-controller-manager-k8s-s 1/1 Running 0 141m 192.168.10.10 k8s-s <none> <none>
kube-system pod/kube-scheduler-k8s-s 1/1 Running 0 141m 192.168.10.10 k8s-s <none> <none>
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default service/kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 141m <none>
kube-system service/cilium-envoy ClusterIP None <none> 9964/TCP 101s k8s-app=cilium-envoy
kube-system service/hubble-metrics ClusterIP None <none> 9965/TCP 101s k8s-app=cilium
kube-system service/hubble-peer ClusterIP 10.10.77.48 <none> 443/TCP 101s k8s-app=cilium
kube-system service/hubble-relay ClusterIP 10.10.24.62 <none> 80/TCP 101s k8s-app=hubble-relay
kube-system service/hubble-ui ClusterIP 10.10.202.89 <none> 80/TCP 101s k8s-app=hubble-ui
kube-system service/kube-dns ClusterIP 10.10.0.10 <none> 53/UDP,53/TCP,9153/TCP 141m k8s-app=kube-dns
cilium_net과 cilium_host 인터페이스로 Pod간 통신 , Pod-외부 통신을 가능하게 한다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 02:95:92:79:ac:d1 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.10/24 metric 100 brd 192.168.10.255 scope global dynamic eth0
valid_lft 2194sec preferred_lft 2194sec
inet6 fe80::95:92ff:fe79:acd1/64 scope link
valid_lft forever preferred_lft forever
3: cilium_net@cilium_host: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
link/ether 02:f7:b5:46:ac:49 brd ff:ff:ff:ff:ff:ff
inet6 fe80::f7:b5ff:fe46:ac49/64 scope link
valid_lft forever preferred_lft forever
4: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
link/ether 52:5b:5b:86:6a:6d brd ff:ff:ff:ff:ff:ff
inet 172.16.0.70/32 scope global cilium_host
valid_lft forever preferred_lft forever
inet6 fe80::505b:5bff:fe86:6a6d/64 scope link
valid_lft forever preferred_lft forever
6: lxc_health@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
link/ether 7a:98:19:81:96:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::7898:19ff:fe81:964e/64 scope link
valid_lft forever preferred_lft forever
iptables규칙을 확인해보면 Cilium과 관련된 NAT체인 규칙 외에 규칙이 거의 없다. cilium 설치 시 installNoConntrackIptablesRules
옵션을 통해 conntrack iptables룰을 사용하지 않고 eBPF를 통해 패킷이 처리도록 설정을 하였기 때문에 conntrack -L로 보았을 때 기록되는 연결 상태가 거의 없는 것을 확인할 수 있다.
notrack
옵션으로 iptables 규칙을 조회해보면, 트래픽에서 conntrack을 우회하기 위한 설정(pod 네트워크 범위 트래픽 우회, L7 프록시 트래픽 우회)이 되어있음을 확인할 수 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N CILIUM_OUTPUT_nat
-N CILIUM_POST_nat
-N CILIUM_PRE_nat
-N KUBE-KUBELET-CANARY
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_nat" -j CILIUM_PRE_nat
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT_nat" -j CILIUM_OUTPUT_nat
-A POSTROUTING -m comment --comment "cilium-feeder: CILIUM_POST_nat" -j CILIUM_POST_nat
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# conntrack -L | grep -v 2379 | wc -l
conntrack v1.4.6 (conntrack-tools): 125 flow entries have been shown.
54
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables -t raw -S | grep notrack
-A CILIUM_OUTPUT_raw -d 192.168.0.0/16 -m comment --comment "cilium: NOTRACK for pod traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -s 192.168.0.0/16 -m comment --comment "cilium: NOTRACK for pod traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o lxc+ -m comment --comment "cilium: NOTRACK for proxy return traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o cilium_host -m comment --comment "cilium: NOTRACK for proxy return traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o lxc+ -m comment --comment "cilium: NOTRACK for L7 proxy upstream traffic" -j CT --notrack
-A CILIUM_OUTPUT_raw -o cilium_host -m comment --comment "cilium: NOTRACK for L7 proxy upstream traffic" -j CT --notrack
-A CILIUM_PRE_raw -d 192.168.0.0/16 -m comment --comment "cilium: NOTRACK for pod traffic" -j CT --notrack
-A CILIUM_PRE_raw -s 192.168.0.0/16 -m comment --comment "cilium: NOTRACK for pod traffic" -j CT --notrack
-A CILIUM_PRE_raw -m comment --comment "cilium: NOTRACK for proxy traffic" -j CT --notrack
cilium CRD를 살펴보면, ciliumnodes, ciliumendpoints라는 CRD가 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get crd
NAME CREATED AT
ciliumcidrgroups.cilium.io 2024-10-25T15:00:57Z
ciliumclusterwidenetworkpolicies.cilium.io 2024-10-25T15:00:58Z
ciliumendpoints.cilium.io 2024-10-25T15:00:57Z
ciliumexternalworkloads.cilium.io 2024-10-25T15:00:57Z
ciliumidentities.cilium.io 2024-10-25T15:00:57Z
ciliuml2announcementpolicies.cilium.io 2024-10-25T15:00:57Z
ciliumloadbalancerippools.cilium.io 2024-10-25T15:00:57Z
ciliumnetworkpolicies.cilium.io 2024-10-25T15:00:58Z
ciliumnodeconfigs.cilium.io 2024-10-25T15:00:57Z
ciliumnodes.cilium.io 2024-10-25T15:00:57Z
ciliumpodippools.cilium.io 2024-10-25T15:00:57Z
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get ciliumnodes
NAME CILIUMINTERNALIP INTERNALIP AGE
k8s-s 172.16.0.70 192.168.10.10 15m
k8s-w1 172.16.1.153 192.168.10.101 15m
k8s-w2 172.16.2.237 192.168.10.102 15m
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get ciliumendpoints -A
NAMESPACE NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
kube-system coredns-55cb58b774-7tj27 24672 ready 172.16.1.13
kube-system coredns-55cb58b774-pldd7 24672 ready 172.16.1.217
kube-system hubble-relay-88f7f89d4-zpgdl 37739 ready 172.16.1.150
kube-system hubble-ui-59bb4cb67b-dk4hp 42419 ready 172.16.1.214
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get pod -o wide -A | grep -v 192.168
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-55cb58b774-7tj27 1/1 Running 0 157m 172.16.1.13 k8s-w1 <none> <none>
kube-system coredns-55cb58b774-pldd7 1/1 Running 0 157m 172.16.1.217 k8s-w1 <none> <none>
kube-system hubble-relay-88f7f89d4-zpgdl 1/1 Running 0 18m 172.16.1.150 k8s-w1 <none> <none>
kube-system hubble-ui-59bb4cb67b-dk4hp 2/2 Running 0 18m 172.16.1.214 k8s-w1 <none> <none>
아래 명령어로 다양한 cilium config가 적용되어 있는 것을 확인할 수 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get cm -n kube-system cilium-config -o json | jq
{
"apiVersion": "v1",
"data": {
"agent-not-ready-taint-key": "node.cilium.io/agent-not-ready",
"arping-refresh-period": "30s",
"auto-direct-node-routes": "true",
"bpf-events-drop-enabled": "true",
"bpf-events-policy-verdict-enabled": "true",
...
파드를 생성해서 노드간 파드 통신을 확인해본다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netpod 1/1 Running 0 115s 172.16.0.110 k8s-s <none> <none>
webpod1 1/1 Running 0 115s 172.16.1.244 k8s-w1 <none> <none>
webpod2 1/1 Running 0 115s 172.16.2.198 k8s-w2 <none> <none>
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 status --verbose | grep Allocated -A5
Allocated addresses:
172.16.0.110 (default/netpod)
172.16.0.250 (health)
172.16.0.70 (router)
IPv4 BIG TCP: Disabled
IPv6 BIG TCP: Disabled
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c1 status --verbose | grep Allocated -A5
Allocated addresses:
172.16.1.106 (health)
172.16.1.13 (kube-system/coredns-55cb58b774-7tj27)
172.16.1.150 (kube-system/hubble-relay-88f7f89d4-zpgdl)
172.16.1.153 (router)
172.16.1.214 (kube-system/hubble-ui-59bb4cb67b-dk4hp)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c2 status --verbose | grep Allocated -A5
Allocated addresses:
172.16.2.198 (default/webpod2)
172.16.2.237 (router)
172.16.2.246 (health)
IPv4 BIG TCP: Disabled
IPv6 BIG TCP: Disabled
netpod에서 webpod1로 통신하는 과정을 확인해본다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# p0 ping -c 1 $WEBPOD1IP
PING 172.16.2.42 (172.16.2.42) 56(84) bytes of data.
64 bytes from 172.16.2.42: icmp_seq=1 ttl=62 time=0.514 ms
--- 172.16.2.42 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.514/0.514/0.514/0.000 ms
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# hubble observe --pod netpod
Oct 26 15:05:12.844: default/netpod (ID:46040) -> default/webpod1 (ID:39469) to-network FORWARDED (ICMPv4 EchoRequest)
Oct 26 15:05:12.844: default/netpod (ID:46040) default/webpod1 (ID:39469) to-endpoint FORWARDED (ICMPv4 EchoReply)
1) netpod에서 ping 명령어를 실행하여 webpod1의 IP 주소인 172.16.1.244로 ICMP 패킷을 전송한다.
2) 패킷이 k8s-s의 cilium_net 인터페이스로 도달하면, cilium의 eBPF 프로그램이 패킷을 가로챈다.
3) 목적지 Node가 동일한 서브넷임을 인지하고 Direct Routing 모드를 적용한다.
4) k8s-w1 노드의 cilium_host 인터페이스에 패킷이 도착하면 k8s-w1노드의 cilium의 eBPF 프로그램이 패킷을 가로챈다.
5) lxc 인터페이스의 veth 쌍과 cilium eBPF에서 생성한 목적지 MAC주소를 포함한 ARP 응답을 기반으로 패킷을 webpod1에 전달한다.
각 과정이 cilium에서는 어떤 코드로 구성되어 있는지 확인해보자.
cilium eBPF에서 패킷 유효성 검사 후 IPv4 프로토콜 패킷 처리
//cilium/bpf/bpf_lxc.c
static __always_inline int handle_ipv4_from_lxc(struct __ctx_buff *ctx, __u32 *dst_sec_identity,
__s8 *ext_err)
{
struct ct_state *ct_state, ct_state_new = {};
struct ipv4_ct_tuple *tuple;
...
// 패킷이 검증이 실패하면 패킷 DROP
if (!revalidate_data(ctx, &data, &data_end, &ip4))
return DROP_INVALID;
...
# ifdef ENABLE_HIGH_SCALE_IPCACHE
if (identity_is_world_ipv4(identity)) {
struct endpoint_info *ep;
void *data, *data_end;
struct iphdr *ip4;
if (!revalidate_data(ctx, &data, &data_end, &ip4)) {
ret = DROP_INVALID;
goto out;
}
// 패킷의 목적지 IP를 기반으로 ipcache에서 엔드포인트를 조회함.
// 엔드포인트가 존재할 경우 security id를 패킷의 identity에 할당함.
ep = __lookup_ip4_endpoint(ip4->saddr);
if (ep)
identity = ep->sec_id;
}
# endif /* ENABLE_HIGH_SCALE_IPCACHE */
//패킷의 메타데이터를 저장함
ctx_store_meta(ctx, CB_SRC_LABEL, identity);
ret = tail_call_internal(ctx, CILIUM_CALL_IPV4_CT_INGRESS, &ext_err);
break;
...
}
IPCache eBPF맵 조회
//cilium/bpf/lib/eps.h
static __always_inline __maybe_unused struct endpoint_info *
__lookup_ip4_endpoint(__u32 ip)
{
struct endpoint_key key = {};
key.ip4 = ip;
key.family = ENDPOINT_KEY_IPV4;
return map_lookup_elem(&ENDPOINTS_MAP, &key);
}
통신을 시도하면 Hubble UI에서 관련된 패킷 흐름을 확인할 수 있다.
ClusterIP 서비스를 생성해본다. iptables를 확인해보아도 더이상 KUBE-SVC룰이 생성되지 않는 것을 확인할 수 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get svc,ep svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/svc ClusterIP 10.10.254.180 <none> 80/TCP 2m1s
NAME ENDPOINTS AGE
endpoints/svc 172.16.1.79:80,172.16.2.42:80 2m1s
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables-save | grep KUBE-SVC
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables-save | grep CILIUM
:CILIUM_POST_mangle - [0:0]
:CILIUM_PRE_mangle - [0:0]
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_mangle" -j CILIUM_PRE_mangle
-A POSTROUTING -m comment --comment "cilium-feeder: CILIUM_POST_mangle" -j CILIUM_POST_mangle
-A CILIUM_PRE_mangle ! -o lo -m socket --transparent -m comment --comment "cilium: any->pod redirect proxied traffic to host proxy" -j MARK --set-xmark 0x200/0xffffffff
-A CILIUM_PRE_mangle -p tcp -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 41489 --on-ip 127.0.0.1 --tproxy-mark 0x200/0xffffffff
-A CILIUM_PRE_mangle -p udp -m comment --comment "cilium: TPROXY to host cilium-dns-egress proxy" -j TPROXY --on-port 41489 --on-ip 127.0.0.1 --tproxy-mark 0x200/0xffffffff
:CILIUM_OUTPUT_raw - [0:0]
...
netpod에서 서비스를 호출함과 동시에 netpod 내에서 tcpdump를 통해 파드 내부 통신을 캡처해본다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# while true; do kubectl exec netpod -- curl -s $SVCIP | grep Hostname;echo "-----";sleep 1;done
Hostname: webpod2
-----
Hostname: webpod2
-----
Hostname: webpod2
-----
Hostname: webpod2
-----
파드 내부에서 캡처를 했음에도 SVC IP가 보이는 것이 아니라 DNAT된 web-pod IP가 보이는 것을 알 수 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl exec netpod -- tcpdump -enni any -q
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
17:20:38.927364 eth0 Out ifindex 11 ba:11:4d:8c:87:38 172.16.0.214.60718 > 172.16.1.79.80: tcp 0
17:20:38.927861 eth0 In ifindex 11 1a:09:90:79:58:0e 172.16.1.79.80 > 172.16.0.214.60718: tcp 0
17:20:38.927916 eth0 Out ifindex 11 ba:11:4d:8c:87:38 172.16.0.214.60718 > 172.16.1.79.80: tcp 0
17:20:38.927974 eth0 Out ifindex 11 ba:11:4d:8c:87:38 172.16.0.214.60718 > 172.16.1.79.80: tcp 76
17:20:38.928318 eth0 In ifindex 11 1a:09:90:79:58:0e 172.16.1.79.80 > 172.16.0.214.60718: tcp 0
17:20:38.929140 eth0 In ifindex 11 1a:09:90:79:58:0e 172.16.1.79.80 > 172.16.0.214.60718: tcp 312
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netpod 1/1 Running 0 3h49m 172.16.0.214 k8s-s <none> <none>
webpod1 1/1 Running 0 3h49m 172.16.2.42 k8s-w1 <none> <none>
webpod2 1/1 Running 0 3h49m 172.16.1.79 k8s-w2 <none> <none>
cilium에서 확인할 수 있는 서비스 정보로, ClusterIP, Backend endpoint 정보를 확인할 수 있다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 service list
ID Frontend Service Type Backend
9 10.10.254.180:80 ClusterIP 1 => 172.16.1.79:80 (active)
2 => 172.16.2.42:80 (active)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 bpf lb list
SERVICE ADDRESS BACKEND ADDRESS (REVNAT_ID) (SLOT)
10.10.254.180:80 (1) 172.16.1.79:80 (9) (1)
10.10.254.180:80 (0) 0.0.0.0:0 (9) (0) [ClusterIP, non-routable]
10.10.254.180:80 (2) 172.16.2.42:80 (9) (2)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 map get cilium_lb4_services_v2
Key Value State Error
10.10.254.180:80 (0) 0 2 (9) [0x0 0x0] sync
10.10.254.180:80 (1) 9 0 (9) [0x0 0x0] sync
10.10.254.180:80 (2) 10 0 (9) [0x0 0x0] sync
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 map get cilium_lb4_backends_v3
Key Value State Error
10 ANY://172.16.2.42 sync
9 ANY://172.16.1.79 sync
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 map get cilium_lb4_reverse_nat
Key Value State Error
9 10.10.254.180:80 sync
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 map get cilium_lb4_reverse_sk
Key Value State Error
[172.16.2.42]:20480, 130299 [10.10.254.180]:20480, 2304
[172.16.1.79]:20480, 125195 [10.10.254.180]:20480, 2304
[172.16.1.79]:20480, 128771 [10.10.254.180]:20480, 2304
[172.16.1.79]:20480, 128778 [10.10.254.180]:20480, 2304
[172.16.1.79]:20480, 125202 [10.10.254.180]:20480, 2304
[172.16.1.79]:20480, 130277 [10.10.254.180]:20480, 2304
pod에서 connect() 시스템콜을 호출하여 소켓을 연결할 때, 목적지 주소가 서비스 주소면, 소켓의 목적지 주소를 서비스의 백엔드 주소로 변경한다.(*이후 과정은 pod - pod 통신과 동일)
해당 과정은 bpf프로그램을 cgroup에 연결해서 처리하게되는데, 이를 위해 cgroup을 pod에서 사용할 수 있도록 마운트하는 작업이 필요하다.
cilium파드의 init containers를 보면 cgroup 마운트를 수행하는 것을 볼 수 있다.
- command:
- sh
- -ec
- |
cp /usr/bin/cilium-mount /hostbin/cilium-mount;
nsenter --cgroup=/hostproc/1/ns/cgroup --mount=/hostproc/1/ns/mnt "${BIN_PATH}/cilium-mount" $CGROUP_ROOT;
connect()시스템콜을 호출하여 소켓을 연결할 때 lb4_lookup_service
를 호출해 dst_ip, dst_port에 해당하는 서비스가 존재하는지 확인한다.
//cilium/bpf/bpf_sock.c
__section("cgroup/connect4")
int cil_sock4_connect(struct bpf_sock_addr *ctx)
{
int err;
...
err = __sock4_xlate_fwd(ctx, ctx, false);
...
}
static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
struct bpf_sock_addr *ctx_full,
const bool udp_only)
{
struct lb4_key key = {
.address = dst_ip,
.dport = dst_port,
#if defined(ENABLE_SERVICE_PROTOCOL_DIFFERENTIATION)
.proto = protocol,
}
...
//서비스 존재 유무 확인
svc = lb4_lookup_service(&key, true);
}
서비스가 존재하면 해당 서비스에 연결된 백엔드 정보를 backend_slot에 저장한 후, 소켓의 목적지 주소와 포트를 백엔드 주소와 포트로 변경한다.
//cilium/bpf/bpf_sock.c
static __always_inline int __sock4_xlate_fwd(struct bpf_sock_addr *ctx,
struct bpf_sock_addr *ctx_full,
const bool udp_only)
{
...
//서비스가 존재하지 않으면 에러 반환
if (!svc)
return -ENXIO;
//서비스의 백엔드가 존재하지 않으면 에러 반환
if (svc->count == 0 && !lb4_svc_is_l7loadbalancer(svc))
return -EHOSTUNREACH;
...
if (backend_id == 0) {
backend_from_affinity = false;
//서비스에 연결된 여러 백엔드 중 하나를 backend_slot에 저장한다.
key.backend_slot = (sock_select_slot(ctx_full) % svc->count) + 1;
backend_slot = __lb4_lookup_backend_slot(&key);
...
//백엔드 ID를 이용하여 백엔드 정보를 가져온다.
backend_id = backend_slot->backend_id;
backend = __lb4_lookup_backend(backend_id);
}
...
//ReverseNAT테이블에 정보를 등록하여서 응답이 올바른 출발지로 돌아가는 것을 보장한다.
if (sock4_update_revnat(ctx_full, backend, &orig_key,
svc->rev_nat_index) < 0) {
update_metrics(0, METRIC_EGRESS, REASON_LB_REVNAT_UPDATE);
return -ENOMEM;
}
//소켓의 목적지 주소를 백엔드 IP로 설정하고, 목적지 포트도 백엔드 포트로 설정한다.
ctx->user_ip4 = backend->address;
ctx_set_port(ctx, backend->port);
return 0;
}