..

kans3) cilium에 관하여

cilium에 관하여. ..🐝

이번 주차의 주제는 cilium이였다. 몇 년 전에도 해당 주제를 들은 적이 있었는데, 그때보다 더 알아들은 것도 있지만 그렇다고 아직도 이해를 완벽하게 했다고 할 수는 없다.. 이번 주차에 들으면서 공부하고 알게된 것들에 대해 정리해보았다. cilium의 등장 배경부터 이야기하면, 기존의 쿠버네티스에서의 네트워크 통신은 모두 user space까지 들어가서 iptables를 통해야만 했다. 그래서 설치하고 보면, 수많은 iptables 규칙이 있고 pod를 하나 생성하는 것에 따라 수많은 규칙이 생기게 되었다. (calico cni 설치해서 확인해봤을떄에는 120개 가량의 필터가 늘었었음) 또 iptables 특성상 위에서부터 일치하는 규칙에 패킷을 전달하게 되기 때문에 복잡도가 확 늘어난다. 그래서 이전에 kube-proxy에서 ipvs proxy mode가 등장하게 된 것도 이 때문이였고 kube-proxy를 사용하지 않고 ebpf를 사용하는 cilium도 등장하게 되었다.

ebpf

ebpf는 확장된 bpf(Berkeley Packet Filter)로 레지스터 등이 증가하여 기존의 bpf보다 성능이 좋아졌다. 🔗 bpf는 커널 코드에 훅을 걸어 동작한다. 찾아봤을 때, 패킷을 분석하고 필터링하기 위해 설계되었던 것이고, 커널 내부에서 하나의 샌드박스와 같은 환경(in-kernel virtual machine)에서 실행된다. iptables는 규칙에 따라 user space에서 패킷을 열심히 처리하지만, bpf는 커널레벨에서 수행되기 때문에 보다 빠르게 패킷이 처리된다.

cilium

cilium은 리눅스 컨테이너 관리 플랫폼을 사용하여 배포된 서비스 간의 네트워크 연결을 제공하는 오픈 소스 소프트웨어이다. 커널에서 동작하는 ebpf기반인 cilium은 모든 패킷을 가로채기 위해 ingress tc hooks을 사용한다. 🔗 tc(traffic control)의 ingress 훅에 연결된 bpf프로그램은 네트워크 인터페이스와 연결되지만, 네트워크 스택이 패킷을 초기에 처리한 이후 실행된다. (초기라 함은.. 아래 그림에서 보면, l2로 이해 할 수 있다. l2를 지나 tc ingress가 있음). bpf는 패킷의 메타데이터에는 접근이 가능하기 때문에 L3와 L4 엔드포인트 정책을 적용하거나 로컬 노드에서 트래픽을 특정 엔드포인트로 리다이렉트 시킬 수 있다. 그러나 L7 트래픽의 경우, 보다 복잡한 처리를 위해 envoy와 같은 프록시가 필요하기 때문에 어쩔 수 없이 iptables를 타게 된다. 전체 아키텍처를 보면 iptables 대신 커널에서 bpf 훅을 통해 트래픽을 제어하고, 기존의 conntrack 테이블 대신 bpf map에 기록이 남는다.

1-1

1-0 이미지 출처 🔗

테스트

실습 환경

테스트 환경은 가시다님이 작성해둔 스택 파일을 사용했다.

❯ curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-8w.yaml
❯ aws cloudformation deploy --template-file kans-8w.yaml --stack-name mylab --parameter-overrides KeyName=nyoung SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
❯ aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2
43.203.197.16

cilium 설치

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# helm repo add cilium https://helm.cilium.io/
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# helm repo update
"cilium" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "cilium" chart repository
Update Complete. ⎈Happy Helming!⎈

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# 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 --set ipMasqAgent.enabled=true
NAME: cilium
LAST DEPLOYED: Sun Oct 27 04:33:24 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You have successfully installed Cilium with Hubble Relay and Hubble UI.

# 설정한 옵션들을 몇가지 살펴보자면, 
--set k8sServiceHost=192.168.10.10 # cilium이 연결할 controlplane node ip 
--set k8sServicePort=6443 # kubernetes API 서버 포트를 지정 (기본 포트는 6443).
--set debug.enabled=true # cilium의 디버그 모드 활성화
--set rollOutCiliumPods=true # cilium pod가 새 설정 시 자동 재시작
--set routingMode=native # 네이티브 라우팅 모드를 사용 (cilium에는 네이티브 라우팅 모드와 터널모드가 존재)
--set autoDirectNodeRoutes=true  # 각 노드에 대해 직접 라우팅을 활성화
--set bpf.masquerade=true # eBPF 마스커레이드 사용. 클러스터 외부로 나갈 경우 snat 처리
--set bpf.hostRouting=true # eBPF를 사용하여 호스트 네트워크 라우팅을 활성화
--set endpointRoutes.enabled=true # 노드에서 endpoint(pod)별 라우트를 활성화
--set ipam.mode=kubernetes # kubernetes에서 제공하는 ipam을 사용
--set k8s.requireIPv4PodCIDR=true # pod가 ipv4 cidr 사용 가능 할 때까지 cillium 기다림
--set kubeProxyReplacement=true # kube-proxy의 기능을 cilium이 대체
--set ipv4NativeRoutingCIDR=192.168.0.0/16 # snat하지 않을 내부 주소
--set installNoConntrackIptablesRules=true # no-conntrack iptables 규칙을 설치하여 연결 추적을 우회
--set hubble.ui.enabled=true # hubble ui 활성화(relay로 수집한 데이터를 보고 분석하는데 사용)
--set hubble.relay.enabled=true # hubble relay 활성화(각 node에서 pod에 대한 네트워크 분석 데이터 수집)
--set prometheus.enabled=true # prometheus 모니터링
--set operator.prometheus.enabled=true # cilium 오퍼레이터의 Prometheus 메트릭 수집을 활성화
--set hubble.metrics.enableOpenMetrics=true # hubble 메트릭 수집 활성화
--set hubble.metrics.enabled="{...}" # Hubble 메트릭 수집 항목 정의
--set operator.replicas=1 # cilium-operator 단일 인스턴스 운영
--set ipMasqAgent.enabled=true # ip masquerade agent를 활성화. 클러스터 외부로 나가는 트래픽의 소스 snat

설치 후 확인

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k get ciliumnodes
NAME     CILIUMINTERNALIP   INTERNALIP       AGE
k8s-s    172.16.0.190       192.168.10.10    74m
k8s-w1   172.16.2.107       192.168.10.101   74m
k8s-w2   172.16.1.224       192.168.10.102   74m

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k get ciliumendpoints -A
NAMESPACE     NAME                           SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
kube-system   coredns-55cb58b774-7lsz7       29265               ready            172.16.0.41
kube-system   coredns-55cb58b774-flwn9       29265               ready            172.16.0.7
kube-system   hubble-relay-88f7f89d4-lzwjh   9167                ready            172.16.1.104
kube-system   hubble-ui-59bb4cb67b-7vlmz     35196               ready            172.16.1.230

# bpf 사용을 위해 확인 
# xdp 지원 드라이버 (https://docs.cilium.io/en/stable/bpf/progtypes/#xdp-drivers)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# ethtool -i ens5
driver: ena
version: 6.8.0-1015-aws
...

# iptables를 확인해보면 기본 리소스가 다 떠있는데에도 엄청 많이 줄어들었다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables -t nat -S | wc -l
11
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# iptables -t filter -S | wc -l
24

cilium cli 설치

# cilium cli 설치
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# CLI_ARCH=amd64
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
cilium-linux-amd64.tar.gz: OK
cilium
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}

# cilium cli로 status 확인
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cilium status --wait
    /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    OK
 \__/¯¯\__/    Hubble Relay:       OK
    \__/       ClusterMesh:        disabled
...
    
# cilium 설정 값들을 확인 가능
⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cilium config view

# cilium pod 내의 cilium agent container에 명령어 실행 전.. 너무 기니까 alias 등록
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# alias c0="kubectl exec -it cilium-88nkj -n kube-system -c cilium-agent"

# cilium endpoint 상태, 설정 확인
# 현재 아무것도 설정되지 않아 모두 Disabled로 표시된다. 
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                                                  IPv6   IPv4           STATUS
           ENFORCEMENT        ENFORCEMENT
142        Disabled           Disabled          1          k8s:node-role.kubernetes.io/control-plane                                                          ready
                                                           k8s:node.kubernetes.io/exclude-from-external-load-balancers
                                                           reserved:host
297        Disabled           Disabled          29265      k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system          172.16.0.41    ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
788        Disabled           Disabled          29265      k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system          172.16.0.7     ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
3034       Disabled           Disabled          4          reserved:health                                                                     172.16.0.196   ready



# cilium이 관리하는 각 엔드포인트의 ebpf 상태 확인
# 두번째의 id는 security id로 네트워크 정책 기반으로 트래픽 제어 시 사용된다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium bpf endpoint list
IP ADDRESS        LOCAL ENDPOINT INFO
172.16.0.196:0    id=3034  sec_id=4     flags=0x0000 ifindex=12  mac=6A:E3:6A:FF:33:E0 nodemac=7E:64:F3:57:23:7D
172.16.0.190:0    (localhost)
172.16.0.7:0      id=788   sec_id=29265 flags=0x0000 ifindex=10  mac=AE:FA:30:78:0C:F9 nodemac=6E:A4:DB:E4:9A:46
192.168.10.10:0   (localhost)
172.16.0.41:0     id=297   sec_id=29265 flags=0x0000 ifindex=8   mac=D6:BB:72:3B:80:C5 nodemac=26:59:D7:B7:99:68

192.168.10.10:0 (localhost) > pod가 올라가 있는 node ip 172.16.0.190:0 (localhost) > 이게 누구인지 찾아가보면 . .

노드에서 ip를 확인해보면, 172.16.0.190:0 은 cilium_host interface다.

cilium에서는 veth pair로 컨테이너 네트워크를 구현되어서 proxy arp를 사용하지 않는다. 바로 서로에게 전달되어, ip를 가지고 누구냐고 묻는 과정이 생략되어 빠르다. 그리고 proxy arp도 사용하지 않아 보안적으로도 위험 요소가 사라진다.

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# ip -c a
...
3: cilium_net@cilium_host: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
    link/ether 0a:9f:d1:0d:f9:36 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::89f:d1ff:fe0d:f936/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 0a:e0:e2:dc:52:d7 brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.190/32 scope global cilium_host
       valid_lft forever preferred_lft forever
    inet6 fe80::8e0:e2ff:fedc:52d7/64 scope link
       valid_lft forever preferred_lft forever
...

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cat /proc/sys/net/ipv4/conf/cilium_net/proxy_arp
0
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cat /proc/sys/net/ipv4/conf/cilium_host/proxy_arp
0

hubble web ui 들어가기 위해 nodeport로 변경

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl patch -n kube-system svc hubble-ui -p '{"spec": {"type": "NodePort"}}'
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# HubbleUiNodePort=$(kubectl get svc -n kube-system hubble-ui -o jsonpath={.spec.ports[0].nodePort})
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# echo -e "Hubble UI URL = http://$(curl -s ipinfo.io/ip):$HubbleUiNodePort"
service/hubble-ui patched
Hubble UI URL = http://43.203.197.16:30389

# hubble client 설치
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# HUBBLE_ARCH=amd64
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# if [ "$(uname -m)" = "aarch64" ]; then HUBBLE_ARCH=arm64; fi
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# curl -L --fail --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-${HUBBLE_ARCH}.tar.gz{,.sha256sum}
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# sha256sum --check hubble-linux-${HUBBLE_ARCH}.tar.gz.sha256sum
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# sudo tar xzvfC hubble-linux-${HUBBLE_ARCH}.tar.gz /usr/local/bin
hubble-linux-amd64.tar.gz: OK
hubble
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# rm hubble-linux-${HUBBLE_ARCH}.tar.gz{,.sha256sum}

# web ui로 접근 할 수 있게 포트포워딩 실행
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cilium hubble port-forward &
[1] 24466
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# hubble status
Healthcheck (via localhost:4245): Ok
Current/Max Flows: 12,285/12,285 (100.00%)
Flows/s: 39.22
Connected Nodes: 3/3

2-0

테스트

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cat testpod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: netpod
  labels:
    app: netpod
spec:
  nodeName: k8s-s
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod1
  labels:
    app: webpod
spec:
  nodeName: k8s-w1
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: webpod2
  labels:
    app: webpod
spec:
  nodeName: k8s-w2
  containers:
  - name: container
    image: traefik/whoami
  terminationGracePeriodSeconds: 0

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k apply -f testpod.yaml
pod/netpod created
pod/webpod1 created
pod/webpod2 created

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k get po -owide
NAME      READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
netpod    1/1     Running   0          99s   172.16.0.251   k8s-s    <none>           <none>
webpod1   1/1     Running   0          99s   172.16.2.139   k8s-w1   <none>           <none>
webpod2   1/1     Running   0          99s   172.16.1.235   k8s-w2   <none>           <none>

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl get ciliumendpoints -A
NAMESPACE     NAME                           SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
default       netpod                         11370               ready            172.16.0.251
default       webpod1                        19520               ready            172.16.2.139
default       webpod2                        19520               ready            172.16.1.235
kube-system   coredns-55cb58b774-7lsz7       29265               ready            172.16.0.41
kube-system   coredns-55cb58b774-flwn9       29265               ready            172.16.0.7
kube-system   hubble-relay-88f7f89d4-lzwjh   9167                ready            172.16.1.104
kube-system   hubble-ui-59bb4cb67b-7vlmz     35196               ready            172.16.1.230

# c0은 k8s-s 노드에 위치하고 있어서, 해당 노드에 위치한 netpod의 ip endpoint가 확인
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium bpf endpoint list
IP ADDRESS        LOCAL ENDPOINT INFO
172.16.0.196:0    id=3034  sec_id=4     flags=0x0000 ifindex=12  mac=6A:E3:6A:FF:33:E0 nodemac=7E:64:F3:57:23:7D
172.16.0.190:0    (localhost)
172.16.0.7:0      id=788   sec_id=29265 flags=0x0000 ifindex=10  mac=AE:FA:30:78:0C:F9 nodemac=6E:A4:DB:E4:9A:46
172.16.0.251:0    id=663   sec_id=11370 flags=0x0000 ifindex=14  mac=12:52:E3:7E:87:4F nodemac=2E:E6:8A:A2:06:1E
192.168.10.10:0   (localhost)
172.16.0.41:0     id=297   sec_id=29265 flags=0x0000 ifindex=8   mac=D6:BB:72:3B:80:C5 nodemac=26:59:D7:B7:99:68


# alias 지정
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# NETPODIP=$(kubectl get pods netpod -o jsonpath='{.status.podIP}')
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# WEBPOD1IP=$(kubectl get pods webpod1 -o jsonpath='{.status.podIP}')
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# WEBPOD2IP=$(kubectl get pods webpod2 -o jsonpath='{.status.podIP}')
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# alias p0="kubectl exec -it netpod  -- "
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# alias p1="kubectl exec -it webpod1 -- "
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# alias p2="kubectl exec -it webpod2 -- "

# netpod에서 webpod1,webpod2로 호출
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# p0 ping -c 1 $WEBPOD1IP && p0 ping -c 1 $WEBPOD2IP
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# p0 curl -s $WEBPOD1IP && p0 curl -s $WEBPOD2IP

3-0

# hubble cli로 확인
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# hubble observe --pod netpod
Oct 26 23:27:40.843: default/netpod:54492 (ID:11370) <> default/webpod1 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:27:40.844: default/netpod:54492 (ID:11370) <> default/webpod1 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:27:40.844: default/netpod:54492 (ID:11370) <> default/webpod1 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:27:40.844: default/netpod:54492 (ID:11370) <- default/webpod1:80 (ID:19520) to-network FORWARDED (TCP Flags: ACK, PSH)
Oct 26 23:27:40.844: default/netpod:54492 (ID:11370) -> default/webpod1:80 (ID:19520) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Oct 26 23:27:40.844: default/netpod:54492 (ID:11370) <- default/webpod1:80 (ID:19520) to-network FORWARDED (TCP Flags: ACK, FIN)
Oct 26 23:27:40.845: default/netpod:54492 (ID:11370) -> default/webpod1:80 (ID:19520) to-endpoint FORWARDED (TCP Flags: ACK)
Oct 26 23:28:19.003: default/netpod:47616 (ID:11370) <> default/webpod2 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:28:19.003: default/netpod:47616 (ID:11370) <> default/webpod2 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:28:19.003: default/netpod:47616 (ID:11370) <> default/webpod2 (ID:19520) pre-xlate-rev TRACED (TCP)
Oct 26 23:28:19.003: default/netpod:47616 (ID:11370) <- default/webpod2:80 (ID:19520) to-network FORWARDED (TCP Flags: ACK, PSH)
Oct 26 23:28:19.003: default/netpod:47616 (ID:11370) <- default/webpod2:80 (ID:19520) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Oct 26 23:28:19.004: default/netpod:47616 (ID:11370) -> default/webpod2:80 (ID:19520) to-network FORWARDED (TCP Flags: ACK, FIN)
Oct 26 23:28:19.004: default/netpod:47616 (ID:11370) -> default/webpod2:80 (ID:19520) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
...

# WEBPOD1에 대한 cache가 남아있음
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium map get cilium_ipcache | grep $WEBPOD1IP
172.16.2.139/32     identity=19520 encryptkey=0 tunnelendpoint=192.168.10.101, flags=<none>   sync


# bpftool로 ebpf에
# lxcb50b1869cfd2은 ip a 명령어 실행 시 가장 하위에 생긴 interface
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- bpftool net show | grep lxcb50b1869cfd2
lxcb50b1869cfd2(14) tcx/ingress cil_from_container prog_id 1361 link_id 27
lxcb50b1869cfd2(14) tcx/egress cil_to_container prog_id 1356 link_id 28

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- bpftool prog show id 1361
1361: sched_cls  name cil_from_container  tag f4a78938bf290167  gpl
	loaded_at 2024-10-26T23:11:00+0000  uid 0
	xlated 728B  jited 545B  memlock 4096B  map_ids 187,66
	btf_id 381
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- bpftool prog show id 1356
1356: sched_cls  name cil_to_container  tag 2c1290820dba04eb  gpl
	loaded_at 2024-10-26T23:11:00+0000  uid 0
	xlated 1712B  jited 1015B  memlock 4096B  map_ids 66,187
	btf_id 376
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- bpftool map list
66: percpu_hash  name cilium_metrics  flags 0x1
	key 8B  value 16B  max_entries 1024  memlock 19384B
187: prog_array  name cilium_calls_00  flags 0x0
	key 4B  value 4B  max_entries 50  memlock 720B
	owner_prog_type sched_cls  owner jited
...

ip 주소도 없는 lxc어쩌고가 어떻게 통신을 하는 것일까.. 이것에 대해서는 가장 밑에서 쓰겠다.

pod > pod

netpod > svc > webpod1,webpod2

# svc 생성
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Service
metadata:
  name: svc
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 80
  selector:
    app: webpod
  type: ClusterIP
EOF
service/svc created

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.10.0.1       <none>        443/TCP   7h22m
svc          ClusterIP   10.10.249.114   <none>        80/TCP    39s

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# kubectl exec netpod -- curl -s   10.10.249.114 | grep Hostname
Hostname: webpod2


# while문으로 계속 curl을 날리고 netpod에서 확인
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# while true; do kubectl exec netpod -- curl -s  10.10.249.114| grep Hostname;echo "-----";sleep 1;done

# netpod에서 패킷을 떠보면, svc 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
23:43:19.043325 eth0  Out ifindex 13 12:52:e3:7e:87:4f 172.16.0.251.37352 > 172.16.1.235.80: tcp 0
23:43:19.043605 eth0  In  ifindex 13 2e:e6:8a:a2:06:1e 172.16.1.235.80 > 172.16.0.251.37352: tcp 0
23:43:19.043649 eth0  Out ifindex 13 12:52:e3:7e:87:4f 172.16.0.251.37352 > 172.16.1.235.80: tcp 0
23:43:19.043767 eth0  Out ifindex 13 12:52:e3:7e:87:4f 172.16.0.251.37352 > 172.16.1.235.80: tcp 76
23:43:19.043887 eth0  In  ifindex 13 2e:e6:8a:a2:06:1e 172.16.1.235.80 > 172.16.0.251.37352: tcp 0
23:43:19.044556 eth0  In  ifindex 13 2e:e6:8a:a2:06:1e 172.16.1.235.80 > 172.16.0.251.37352: tcp 312

# 위에서 목적지로 찍히는 ip를 확인해보면, webpod2..!
# pod를 나가기도 전에 이미 dnat이 되어서 바로 목적지 pod ip를 찍고 가게된다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# k get po -owide
NAME      READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
netpod    1/1     Running   0          34m   172.16.0.251   k8s-s    <none>           <none>
webpod1   1/1     Running   0          34m   172.16.2.139   k8s-w1   <none>           <none>
webpod2   1/1     Running   0          34m   172.16.1.235   k8s-w2   <none>           <none>

# cilium에서 관리하는 서비스
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium service list
ID   Frontend              Service Type   Backend
...
9    10.10.249.114:80      ClusterIP      1 => 172.16.2.139:80 (active)
                                          2 => 172.16.1.235:80 (active)
                                          
# cilium bpf에서 관리하는 lb 목록 
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~# c0 -- cilium bpf lb list | grep 10.10.249.114
10.10.249.114:80 (0)      0.0.0.0:0 (9) (0) [ClusterIP, non-routable]
10.10.249.114:80 (2)      172.16.1.235:80 (9) (2)
10.10.249.114:80 (1)      172.16.2.139:80 (9) (1)

arp 동작 시, bpf 따라가보기

확인한 파일은 아래 두 파일

# ARP 요청 감지 (bpf_lxc.c 위치)
# 먼저 handle_xgress 함수에서 ARP 패킷을 감지하고 CILIUM_CALL_ARP로 tail call을 수행하며 해당 함수는 종료된다.
__section_entry
int cil_from_container(struct __ctx_buff *ctx)
{
	__u16 proto;
	__u32 sec_label = SECLABEL;
	__s8 ext_err = 0;
	int ret;

	bpf_clear_meta(ctx);
	ctx->queue_mapping = 0;

	send_trace_notify(ctx, TRACE_FROM_LXC, sec_label, UNKNOWN_ID,
			  TRACE_EP_ID_UNKNOWN, TRACE_IFINDEX_UNKNOWN,
			  TRACE_REASON_UNKNOWN, TRACE_PAYLOAD_LEN);

	if (!validate_ethertype(ctx, &proto)) {
		ret = DROP_UNSUPPORTED_L2;
		goto out;
	}

	switch (proto) {
#elif defined(ENABLE_ARP_RESPONDER)
	case bpf_htons(ETH_P_ARP):
		ret = tail_call_internal(ctx, CILIUM_CALL_ARP, &ext_err);
		break;

	default:
		ret = DROP_UNKNOWN_L3;
	}


# ARP 요청 검증 (bpf_lxc.c 위치)
# __section_tail에서 CILIUM_CALL_ARP을 따라가서 함수 확인
# ipv4 gw와 node mac주소를 arp 응답으로 보낸다

# 원래는 lxc ip랑 목적지가 같으면 응답 안하는데, 
# 여기서 lxc에는 ip가 없으니까 mac주소나 다른 필드에 대해서 유효성 검사한다.
# 모든 조건이 충족되어 arp_respond로 향한다.

__section_tail(CILIUM_MAP_CALLS, CILIUM_CALL_ARP)
int tail_handle_arp(struct __ctx_buff *ctx)
{
	union macaddr mac = THIS_INTERFACE_MAC;
	union macaddr smac;
	__be32 sip;
	__be32 tip;

	if (!arp_validate(ctx, &mac, &smac, &sip, &tip))
		return CTX_ACT_OK;

	if (tip == LXC_IPV4)
		return CTX_ACT_OK;

	return arp_respond(ctx, &mac, tip, &smac, sip, 0);
}


# ARP 응답 준비 및 전송 (arp.h 에 위치)
# arp_prepare_response 함수를 호출하여, 위에서 리턴받은 값들을 넣어 응답 패킷을 구성
# 준비되면 ctx_redirect를 통해 패킷을 리다이렉트
static __always_inline int
arp_respond(struct __ctx_buff *ctx, union macaddr *smac, __be32 sip,
	    union macaddr *dmac, __be32 tip, int direction)
{
	int ret = arp_prepare_response(ctx, smac, sip, dmac, tip);

	if (unlikely(ret != 0))
		goto error;

	cilium_dbg_capture(ctx, DBG_CAPTURE_DELIVERY,
			   ctx_get_ifindex(ctx));
	return ctx_redirect(ctx, ctx_get_ifindex(ctx), direction);

error:
	return send_drop_notify_error(ctx, UNKNOWN_ID, ret, CTX_ACT_DROP, METRIC_EGRESS);
}


# ARP 응답 준비 (arp.h 에 위치)
# 위에서 ARP 응답 준비 할 때 호출되는 함수
static __always_inline int
arp_prepare_response(struct __ctx_buff *ctx, union macaddr *smac, __be32 sip,
		     union macaddr *dmac, __be32 tip)
{
	__be16 arpop = bpf_htons(ARPOP_REPLY);

	if (eth_store_saddr(ctx, smac->addr, 0) < 0 ||
	    eth_store_daddr(ctx, dmac->addr, 0) < 0 ||
	    ctx_store_bytes(ctx, 20, &arpop, sizeof(arpop), 0) < 0 ||
	    /* sizeof(macadrr)=8 because of padding, use ETH_ALEN instead */
	    ctx_store_bytes(ctx, 22, smac, ETH_ALEN, 0) < 0 ||
	    ctx_store_bytes(ctx, 28, &sip, sizeof(sip), 0) < 0 ||
	    ctx_store_bytes(ctx, 32, dmac, ETH_ALEN, 0) < 0 ||
	    ctx_store_bytes(ctx, 38, &tip, sizeof(tip), 0) < 0)
		return DROP_WRITE_ERROR;

	return 0;
}