Cilium

Gyullbb·2024년 10월 26일
0

K8S

목록 보기
12/13

BPF/eBPF

iptables는 오랜 기간 리눅스 네트워크 필터링 및 방화벽의 핵심 역할을 해왔지만, 몇 가지 명확한 한계가 있었다.

  • 성능 저하: 방대한 규칙이 쌓일수록 검사해야하는 패킷 수가 늘어나면서 성능이 저하된다.
  • 복잡한 유지보수: 규칙이 많아질수록 관리가 어려워지고 오류 발생 가능성이 커진다.
  • 확장성의 한계: 커널과 user space 간 빈번한 전환으로 인한 오버헤드가 발생하며, 고속 네트워크 환경에서 iptables는 병목이 될 수 있다.

이러한 한계를 해결하기 위해 BPF(Berkeley Packet Filter)와 그 확장판인 eBPF(extended BPF)가 등장하였다. eBPF는 커널에서 사용자 정의 프로그램을 실행할 수 있도록 해주며, 이로 인해 기존 iptables가 가지지 못한 향상된 성능 / 유연한 프로그래밍 / 확장성 등 다양한 장점을 제공한다.

eBPF kernel hooks

eBPF는 리눅스 커널의 다양한 지점에 프로그램을 hook, 즉 연결을 해서 특정 이벤트가 발생할 때 커스텀 로직을 실행할 수 있게 해준다.
각 계층 별로 걸 수 있는 eBPF Hook 예시를 확인해본다.

1. Driver 계층


네트워크 장치 드라이버 또는 하드웨어와의 인터페이스에서 BPF 프로그램을 직접 실행한다.

1-1. XDP

위치: NIC(Network Interface Card) 드라이버 수준에서 직접 실행
용도: 초고속 패킷 처리
예시: 고성능 로드 밸런서(NIC)에서 패킷을 바로 분산 처리 가능

2. 네트워크 계층


2-1. TC(Traffic Control)

  • 위치: 네트워크 트래픽의 Ingress(수신) 및 Egress(송신) 지점
  • 용도: 패킷 필터링, QoS(품질 보장), 로드 밸런싱, 정책 기반 라우팅
  • 예시: Cilium에서 네트워크 보안 정책 구현

2-2. XDP(eXpress Data Path)

  • 위치: 네트워크 인터페이스에서 패킷 수신 직후, 커널의 네트워크 스택에 도달하기 전에 실행
  • 용도: 고성능 네트워크 필터링 및 정책 구현
  • 예시: DDoS 방어(악성 패킷이 스택에 전달되기 전에 drop), L4 로드 밸런싱(목적지 서버에 대한 초기 라우팅 수행)

3. 소켓 계층


소켓에서 발생하는 이벤트에 Hook을 걸어 애플리케이션 계층 통신 제어가 가능하다.

3-1. Socket Filtering

  • 위치: Socket의 Send/Receive 지점
  • 용도: 특정 애플리케이션 소켓에 대한 패킷을 필터링하거나 수정
  • 예시: 특정 포트나 프로토콜의 소켓 트래픽을 모니터링하거나 차단

3-2. Socket Options

  • 위치: 소켓 옵션을 설정하는 지점에 BPF 프로그램 연결
  • 용도: 애플리케이션별 커스텀 필터링 로직 구현
  • 예시: 특정 애플리케이션 트래픽에 대한 방화벽 규칙 적용

4. System call 계층


시스템 호출 인터페이스에 Hook을 걸어 프로세스와 커널 간 상호작용을 제어한다.

4-1. kprobe / kretprobe

  • 위치: 커널 함수 진입(kprobe)과 종료(kretprobe) 지점
  • 용도: 커널 함수가 호출될 때 파라미터를 추적하거나 로깅
  • 예시: sys_execve 함수에 kprobe를 걸어 프로세스 생성 시 로깅

4-2. tracepoints

  • 위치: 커널의 특정 이벤트가 발생하는 지점
  • 용도: I/O 작업, 네트워크 트래픽, 프로세스 생성 등의 이벤트 추적
  • 예시: 시스템 호출 모니터링, 특정 파일 접근 추적

5. Filesystem 및 Process 계층


파일 접근과 프로세스 이벤트에 Hook을 걸어 보안 정책을 적용하거나 모니터링한다.

5-1. LSM (Linux Security Modules) Hooks

  • 위치: 커널의 파일 접근, 프로세스 권한 변경, 네트워크 접속 등 보안 관련 지점
  • 용도: 보안 정책 구현 (e.g., SELinux, AppArmor)
  • 예시: 특정 프로세스의 파일 접근 차단, 네트워크 사용 제한

5-2. cgroup BPF

  • 위치: cgroup에 연결된 리소스 제한과 네트워크 트래픽 제어 지점
  • 용도: 특정 컨테이너나 프로세스 그룹에 대해 네트워크 및 CPU 사용을 제한
  • 예시: Kubernetes 컨테이너 네트워크 정책 설정

6. UserSpace 계층


eBPF는 커널 내에서 동작하는 프로그램이지만, userspace와 커널 간의 통신을 통해 애플리케이션 레벨의 트래픽을 추적하고 제어할 수 있다.

6-1. uprobe / uretprobe

  • 위치: userspace의 특정 함수에 Hook을 걸어 모니터링.
  • 예시: 데이터베이스 클라이언트 함수에 uprobe를 걸어 쿼리 지연 시간 측정 가능

6-2.Envoy + eBPF

Envoy와 eBPF를 연동해 애플리케이션의 L7 트래픽을 제어.

  • 예시: HTTP/gRPC 호출 추적, 보안 정책 적용.

Cilium

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를 사용할 경우 기존 커널 위에 커스텀 커널 및 로직 개발이 가능하기 때문에, 이슈 해결을 조금 더 유연하게 할 수 있지 않을까 하는 생각이 든다.

Cilium 구성요소

  • Cilium Agent : 데몬셋으로 실행, K8S API 설정으로 부터 '네트워크 설정, 네트워크 정책, 서비스 부하분산, 모니터링' 등을 수행하며, eBPF 프로그램을 관리한다.
  • Cilium Client (CLI) : Cilium 커멘드툴, eBPF maps 에 직접 접속하여 상태를 확인할 수 있다.
  • Cilium Operator : K8S 클러스터에 대한 한 번씩 처리해야 하는 작업을 관리.
  • Hubble : 네트워크와 보안 모니터링 플랫폼 역할을 하여, 'Server, Relay, Client, Graphical UI' 로 구성되어 있다.
  • Data Store : Cilium Agent 간의 상태를 저장하고 전파하는 데이터 저장소, 2가지 종류 중 선택(K8S CRDs, Key-Value Store)

Cilium 설치

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
옵션설명
rollOutCiliumPodsCilium Pods의 롤아웃을 활성화하여 새로운 버전으로의 배포를 관리한다.
routingMode패킷의 라우팅 방식을 설정하며, 기본적으로 네이티브 라우팅 모드를 사용한다.
autoDirectNodeRoutes자동으로 노드 간 라우팅을 설정하여 패킷 전송을 최적화한다.
bpf.masqueradeBPF를 사용하여 IP 마스커레이드를 활성화한다.
bpf.hostRouting호스트 라우팅 기능을 활성화하여 패킷을 직접 전달한다.
endpointRoutes.enabled엔드포인트 라우트를 활성화하여 Pod 간의 트래픽 흐름을 최적화한다.
ipam.modeIP 주소 할당 방식을 Kubernetes로 설정한다.
k8s.requireIPv4PodCIDRIPv4 Pod CIDR 요구 사항을 활성화하여 클러스터 내 IPv4 주소 사용을 보장한다.
kubeProxyReplacementCilium이 kube-proxy의 역할을 대체하도록 설정한다.
ipv4NativeRoutingCIDRIPv4 네이티브 라우팅에 사용할 CIDR 블록을 설정한다.
installNoConntrackIptablesRulesconntrack iptables 규칙을 설치하지 않도록 설정하여 Cilium의 효율성을 높인다.
hubble.ui.enabledHubble UI를 활성화하여 네트워크 모니터링 및 시각화를 지원한다.
hubble.relay.enabledHubble Relay를 활성화하여 메트릭 수집 및 이벤트 전달 기능을 지원한다.
prometheus.enabledPrometheus 모니터링을 활성화하여 메트릭 수집을 지원한다.
operator.prometheus.enabledCilium Operator의 Prometheus 모니터링 기능을 활성화한다.
hubble.metrics.enableOpenMetricsHubble 메트릭의 OpenMetrics 형식을 활성화하여 호환성을 높인다.
hubble.metrics.enabledHubble에서 수집할 메트릭과 이벤트를 정의한다.
operator.replicasCilium Operator의 복제본 수를 설정하여 고가용성을 유지한다.

Cilium 설정 및 확인

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-외부 통신을 가능하게 한다.

  • cilium_net : Cilium이 관리하는 Pod 간의 통신을 처리한다.
  • clium_host : Cilium과 호스트 간의 트래픽을 처리하는 인터페이스로, 외부와의 통신을 가능하게 한다.
  • lxc_healthcheck : 헬스체크 전용 인터페이스
(|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가 있다.

  • ciliumnodes : cilium이 설치된 클러스터의 노드에 대한 정보를 저장하는 리소스이다.
  • ciliumendpoints : 클러스터 내의 각 Pod에 대한 네트워크 정보를 저장하는 리소스이다. Cilium은 ciliumendpoints를 통해 Pod에 대한 정책 적용 여부를 모니터링하고, 필요한 정책을 동적으로 추가한다.
(|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",
    ...

Cilium 노드 간 파드 통신 확인

파드를 생성해서 노드간 파드 통신을 확인해본다.

(|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 프로그램이 패킷을 가로챈다.

  • 패킷의 목적지 IP(172.16.1.244)를 확인하고, cilium ipcache에서 해당 IP에 대한 엔드포인트 ID(55847), 목적지 Node(k8s-w1)을 조회한다.

3) 목적지 Node가 동일한 서브넷임을 인지하고 Direct Routing 모드를 적용한다.

  • 패킷은 터널링 없이 k8s-w1 노드로 전송된다.

4) k8s-w1 노드의 cilium_host 인터페이스에 패킷이 도착하면 k8s-w1노드의 cilium의 eBPF 프로그램이 패킷을 가로챈다.

  • cilium_ipcache를 조회하여 목적지가 webpod1임을 확인한다.

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에서 관련된 패킷 흐름을 확인할 수 있다.

Cilium 서비스 통신 확인

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;
}

0개의 댓글