Kubernetes/Network Study

Cilium CNI와 eBPF

백곰곰 2024. 10. 26. 18:55
728x90
반응형

가시다님의 Kubernetes Advanced Networking Study에 참여하게 되어, 스터디 때 다룬 주제를 정리하려고 합니다.
8주차는 Cilium CNI를 주제로 진행되었습니다.

 

BPF/eBPF

클러스터 내 iptables의 성능을 개선하기 위해 eBPF를 지원하는 CNI가 다수 있고, 사용 사례도 증가하고 있습니다.

Kubernetes 공식 문서에서도 kube-proxy를 선택 사항으로 표기하고 있습니다.

BPF(Berkeley Packet Filter)

네트워크 패킷 필터링에 사용되는 기술로 커널에서 실행되는 일종의 VM(Virtual Machine)입니다.

sandbox 환경(격리된 환경)에서 실행되어 BPF를 통해 커널이 이벤트를 트리거할 때 안전하게 실행할 수 있습니다.

커널을 다시 컴파일하지 않고도 코드를 삽입할 수 있으며, 충돌이 발생하지 않게 보장합니다.

모든 패킷 정보를 복사하지 않고 패킷을 필터링 할 수 있는 애플리케이션별 버퍼를 사용하여, 의사결정에 필요한 데이터 양을 최소화 할 수 있습니다.

tcpdump, seccomp 등에서 해당 기술을 사용합니다.

 

eBPF(extended BPF)

개선된 BPF(기존 대비 최대 4배 성능 향상)로 observability, 보안 영역 등에서 다양하게 사용됩니다.

커널 내 sandbox 형태로 실행되며 네트워크 디바이스(H/W)와 인접한 영역에서 실행할 수 있어, 효율적이고 빠른 패킷 처리가 가능합니다.

따라서, eBPF는 userspace를 거치지 않고 트래픽 제어가 가능하여, iptables에 비해 성능이 좋습니다.

https://archive.fosdem.org/2020/schedule/event/replacing_iptables_with_ebpf/attachments/slides/3622/export/events/attachments/replacing_iptables_with_ebpf/slides/3622/Cilium_FOSDEM_2020.pdf
https://cilium.io/blog/2020/11/10/ebpf-future-of-networking/

참고)

XDP(eXpress Data Path)

eBPF 아키텍처에서 고성능 패킷 처리를 수행하는데 사용하는 기술이며, hook을 통해 kernel을 우회하는 방식을 사용합니다.

네트워크 디바이스 드라이버 레벨에서 처리되어 커널의 오버헤드를 줄입니다.

10Gbps 이상의 높은 네트워크 성능 환경에서 중요합니다.

출처) Large-Scale Cloud OpenFlow 활용

 

참고)

Cilium CNI

컨테이너 관리 플랫폼(Kubernetes, Docker)에서 배포된 애플리케이션 간의 네트워크 연결을 투명하게 보호하기 위한 eBPF 기반의 소프트웨어입니다.

Cilium 구성 요소

Agent

데몬셋으로 모든 노드에서 실행되며, 네트워킹, 서비스 로드 밸런싱, Network Policy, 가시성 및 모니터링에 대한 구성 정보를 Kubernetes 또는 API를 통해 수신합니다.

 

Client(CLI)

명령줄 도구로 동일한 노드에 실행 중인 Cilium Agent의 REST API와 상호작용합니다.

agent에 대한 상태 확인 및 eBPF 맵에 접근하여 상태를 확인할 수 있습니다.

 

Cilium Operator

클러스터 전체에 대해 한 번만 처리되어야 하는 작업을 관리합니다.

패킷 전달이나 Network Policy 결정과 같은 작업을 하지 않기 때문에 일시적으로 operator를 사용하지 못하는 상황에도 클러스터는 동작할 수 있습니다.

하지만 IP 주소 관리(IPAM) 지연에 따른 새 IP 주소가 필요한 워크로드에 대한 스케쥴링이 지연될 수 있습니다.

 

Hubble

네트워크와 보안 모니터링 플랫폼 역할을 하여, Server, Relay, Client, Graphical UI 로 구성되어 있습니다.

 

Data Store

Cilium Agent 간의 상태를 저장하고 전파하는 데이터 저장소입니다.

Kubernetes CRD와 Key-Value Store(etcd)를 지원합니다.

 

참고)

Cilium 실습

이제 클러스터에 Cilium을 설치하여 네트워크 통신 흐름을 살펴보겠습니다.

참고로 Cilium의 정상적인 동작을 위해서 5.4 이상의 커널 버전이 필요합니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# k get no -owide
NAME     STATUS     ROLES           AGE     VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
k8s-s    NotReady   control-plane   4m59s   v1.30.6   192.168.10.10    <none>        Ubuntu 22.04.5 LTS   6.8.0-1015-aws   containerd://1.7.22
k8s-w1   NotReady   <none>          4m39s   v1.30.6   192.168.10.101   <none>        Ubuntu 22.04.5 LTS   6.8.0-1015-aws   containerd://1.7.22
k8s-w2   NotReady   <none>          4m40s   v1.30.6   192.168.10.102   <none>        Ubuntu 22.04.5 LTS   6.8.0-1015-aws   containerd://1.7.22
(⎈|CiliumLab:N/A) root@k8s-s:~# uname -a
Linux k8s-s 6.8.0-1015-aws #16~22.04.1-Ubuntu SMP Mon Aug 19 19:38:17 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

 

Cilium 설치
현재 CNI가 별도로 설치되어 있지 않아 파드에 IP가 할당되지 않은 상태입니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# k get po -A -owide
NAMESPACE     NAME                            READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
kube-system   coredns-55cb58b774-8x5ps        0/1     Pending   0          6m21s   <none>          <none>   <none>           <none>
kube-system   coredns-55cb58b774-qzd88        0/1     Pending   0          6m21s   <none>          <none>   <none>           <none>
kube-system   etcd-k8s-s                      1/1     Running   0          6m35s   192.168.10.10   k8s-s    <none>           <none>
kube-system   kube-apiserver-k8s-s            1/1     Running   0          6m35s   192.168.10.10   k8s-s    <none>           <none>
kube-system   kube-controller-manager-k8s-s   1/1     Running   0          6m35s   192.168.10.10   k8s-s    <none>           <none>
kube-system   kube-scheduler-k8s-s            1/1     Running   0          6m35s   192.168.10.10   k8s-s    <none>           <none>

설치

helm repo add cilium https://helm.cilium.io/
helm repo update
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 debug.enabled=true # cilium 파드에 로그 레벨을 debug 설정
--set autoDirectNodeRoutes=true # 동일 대역 내의 노드들 끼리는 상대 노드의 podCIDR 대역의 라우팅이 자동으로 설정
--set rollOutCiliumPods=true # cilium 설정 변경 시 cilium 파드 자동 재시작
--set endpointRoutes.enabled=true # 호스트에 endpoint(파드)별 개별 라우팅 설정
--set installNoConntrackIptablesRules=true # 파드 트래픽에 conntrack table 미사용
--set hubble.relay.enabled=true --set hubble.ui.enabled=true # hubble 활성화
--set ipam.mode=kubernetes --set k8s.requireIPv4PodCIDR=true # k8s IPAM 활용
--set kubeProxyReplacement=true # kube-proxy 없이 (최대한) 대처할수 있수 있게
--set ipv4NativeRoutingCIDR=192.168.0.0/16 # 해당 대역과 통신 시 IP Masq 하지 않음, 보통 사내망 대역을 지정
--set operator.replicas=1 # cilium-operator 파드 기본 1개
--set enableIPv4Masquerade=true --set bpf.masquerade=true # 파드를 위한 Masquerade , 추가로 Masquerade 을 BPF 로 처리 >> enableIPv4Masquerade=true 인 상태에서 추가로 bpf.masquerade=true 적용이 가능

설치 이후 pending 중이던 파드에 IP가 정상적으로 할당된 것을 볼 수 있습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# k get po -A -owide
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
kube-system   cilium-8wmsh                       1/1     Running   0          64s   192.168.10.102   k8s-w2   <none>           <none>
kube-system   cilium-envoy-c89pz                 1/1     Running   0          64s   192.168.10.10    k8s-s    <none>           <none>
kube-system   cilium-envoy-nphkf                 1/1     Running   0          64s   192.168.10.102   k8s-w2   <none>           <none>
kube-system   cilium-envoy-qhxqv                 1/1     Running   0          64s   192.168.10.101   k8s-w1   <none>           <none>
kube-system   cilium-operator-76bb588dbc-6d6wl   1/1     Running   0          64s   192.168.10.101   k8s-w1   <none>           <none>
kube-system   cilium-pz9jf                       1/1     Running   0          64s   192.168.10.10    k8s-s    <none>           <none>
kube-system   cilium-w5jl2                       1/1     Running   0          64s   192.168.10.101   k8s-w1   <none>           <none>
kube-system   coredns-55cb58b774-8x5ps           1/1     Running   0          10m   172.16.0.55      k8s-s    <none>           <none>
kube-system   coredns-55cb58b774-qzd88           1/1     Running   0          10m   172.16.0.36      k8s-s    <none>           <none>
kube-system   etcd-k8s-s                         1/1     Running   0          10m   192.168.10.10    k8s-s    <none>           <none>
kube-system   hubble-relay-88f7f89d4-hzddt       0/1     Running   0          64s   172.16.1.14      k8s-w2   <none>           <none>
kube-system   hubble-ui-59bb4cb67b-h5gsd         2/2     Running   0          64s   172.16.1.228     k8s-w2   <none>           <none>
kube-system   kube-apiserver-k8s-s               1/1     Running   0          10m   192.168.10.10    k8s-s    <none>           <none>
kube-system   kube-controller-manager-k8s-s      1/1     Running   0          10m   192.168.10.10    k8s-s    <none>           <none>
kube-system   kube-scheduler-k8s-s               1/1     Running   0          10m   192.168.10.10    k8s-s    <none>           <none>

또한, 노드에 cilium 관련 인터페이스(cilium_net, cilium_host)가 생긴 것을 확인할 수 있습니다.

cilium_net와 cilium_host는 pair로 연결되어 있습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# ip -c a
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: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 02:76:80:0a:67:9b brd ff:ff:ff:ff:ff:ff
    altname enp0s5
    inet 192.168.10.10/24 metric 100 brd 192.168.10.255 scope global dynamic ens5
       valid_lft 2323sec preferred_lft 2323sec
    inet6 fe80::76:80ff:fe0a:679b/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 a2:e4:59:80:d9:af brd ff:ff:ff:ff:ff:ff
    inet6 fe80::a0e4:59ff:fe80:d9af/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 ba:59:42:f5:69:9c brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.161/32 scope global cilium_host
       valid_lft forever preferred_lft forever
    inet6 fe80::b859:42ff:fef5:699c/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 76:b2:97:01:25:67 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::74b2:97ff:fe01:2567/64 scope link
       valid_lft forever preferred_lft forever
8: lxc4271a023c30a@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
    link/ether ae:11:d9:24:79:5e brd ff:ff:ff:ff:ff:ff link-netns cni-3ae82cf5-f9da-5795-edeb-25d2212deaf1
    inet6 fe80::ac11:d9ff:fe24:795e/64 scope link
       valid_lft forever preferred_lft forever
10: lxcf0d0f1149d61@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default qlen 1000
    link/ether 96:43:5c:c0:eb:77 brd ff:ff:ff:ff:ff:ff link-netns cni-3c241c5b-1539-8a44-d18f-0d42f63de8ce
    inet6 fe80::9443:5cff:fec0:eb77/64 scope link
       valid_lft forever preferred_lft forever

 

출처) http://arthurchiao.art/blog/ctrip-network-arch-evolution/

 

iptables 룰을 살펴보면 기존대비 상당이 줄어든 것을 볼 수 있습니다. 

(⎈|CiliumLab: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

 

Cilium CLI 설치

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

# 확인
(⎈|CiliumLab:N/A) root@k8s-s:~# cilium status --wait
    /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    OK
 \__/¯¯\__/    Hubble Relay:       OK
    \__/       ClusterMesh:        disabled

DaemonSet              cilium             Desired: 3, Ready: 3/3, Available: 3/3
DaemonSet              cilium-envoy       Desired: 3, Ready: 3/3, Available: 3/3
Deployment             cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
Deployment             hubble-relay       Desired: 1, Ready: 1/1, Available: 1/1
Deployment             hubble-ui          Desired: 1, Ready: 1/1, Available: 1/1
Containers:            cilium             Running: 3
                       cilium-envoy       Running: 3
                       cilium-operator    Running: 1
                       hubble-relay       Running: 1
                       hubble-ui          Running: 1
Cluster Pods:          4/4 managed by Cilium
Helm chart version:    1.16.3
Image versions         cilium             quay.io/cilium/cilium:v1.16.3@sha256:62d2a09bbef840a46099ac4c69421c90f84f28d018d479749049011329aa7f28: 3
                       cilium-envoy       quay.io/cilium/cilium-envoy:v1.29.9-1728346947-0d05e48bfbb8c4737ec40d5781d970a550ed2bbd@sha256:42614a44e508f70d03a04470df5f61e3cffd22462471a0be0544cf116f2c50ba: 3
                       cilium-operator    quay.io/cilium/operator-generic:v1.16.3@sha256:6e2925ef47a1c76e183c48f95d4ce0d34a1e5e848252f910476c3e11ce1ec94b: 1
                       hubble-relay       quay.io/cilium/hubble-relay:v1.16.3@sha256:feb60efd767e0e7863a94689f4a8db56a0acc7c1d2b307dee66422e3dc25a089: 1
                       hubble-ui          quay.io/cilium/hubble-ui-backend:v0.13.1@sha256:0e0eed917653441fded4e7cdb096b7be6a3bddded5a2dd10812a27b1fc6ed95b: 1
                       hubble-ui          quay.io/cilium/hubble-ui:v0.13.1@sha256:e2e9313eb7caf64b0061d9da0efbdad59c6c461f6ca1752768942bfeda0796c6: 1
(⎈|CiliumLab:N/A) root@k8s-s:~# cilium config viewagent-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
bpf-events-trace-enabled                          true
bpf-lb-acceleration                               disabled
...

 

참고) 특정 agent의 설정을 확인하는 방법

# cilium 데몬셋 파드 내에서 cilium 명령어로 상태 확인
export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-s  -o jsonpath='{.items[0].metadata.name}')
alias c0="kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium"
c0 status --verbose

Service에서 파드로의 분산 정보 확인도 가능합니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# c0 service list
ID   Frontend           Service Type   Backend
1    10.10.0.1:443      ClusterIP      1 => 192.168.10.10:6443 (active)
2    10.10.41.90:443    ClusterIP      1 => 192.168.10.10:4244 (active)
3    10.10.252.43:80    ClusterIP      1 => 172.16.1.14:4245 (active)
4    10.10.207.135:80   ClusterIP      1 => 172.16.1.228:8081 (active)
5    10.10.0.10:53      ClusterIP      1 => 172.16.0.36:53 (active)
                                       2 => 172.16.0.55:53 (active)
6    10.10.0.10:9153    ClusterIP      1 => 172.16.0.36:9153 (active)
                                       2 => 172.16.0.55:9153 (active)
(⎈|CiliumLab:N/A) root@k8s-s:~# k get svc -A | grep dns
kube-system   kube-dns         ClusterIP   10.10.0.10      <none>        53/UDP,53/TCP,9153/TCP   35m
(⎈|CiliumLab:N/A) root@k8s-s:~# k get po -owide -n kube-system | grep dns
coredns-55cb58b774-8x5ps           1/1     Running   0          34m   172.16.0.55      k8s-s    <none>           <none>
coredns-55cb58b774-qzd88           1/1     Running   0          34m   172.16.0.36      k8s-s    <none>           <none>

 

Hubble 설치

# UI 파드 정보 확인
kubectl get pod -n kube-system -l k8s-app=hubble-ui -o wide

# Hubble UI 웹 접속
kubectl patch -n kube-system svc hubble-ui -p '{"spec": {"type": "NodePort"}}'
HubbleUiNodePort=$(kubectl get svc -n kube-system hubble-ui -o jsonpath={.spec.ports[0].nodePort})
echo -e "Hubble UI URL = http://$(curl -s ipinfo.io/ip):$HubbleUiNodePort"

# Install Hubble Client
HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
HUBBLE_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then HUBBLE_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-${HUBBLE_ARCH}.tar.gz{,.sha256sum}
sha256sum --check hubble-linux-${HUBBLE_ARCH}.tar.gz.sha256sum
sudo tar xzvfC hubble-linux-${HUBBLE_ARCH}.tar.gz /usr/local/bin
rm hubble-linux-${HUBBLE_ARCH}.tar.gz{,.sha256sum}
(⎈|CiliumLab:N/A) root@k8s-s:~# cilium hubble port-forward &
[1] 8395
(⎈|CiliumLab:N/A) root@k8s-s:~# hubble status
Healthcheck (via localhost:4245): Ok
Current/Max Flows: 11,368/12,285 (92.54%)
Flows/s: 34.98
Connected Nodes: 3/3

(⎈|CiliumLab:N/A) root@k8s-s:~# hubble observe
Oct 26 07:23:02.925: 192.168.10.102:53610 (remote-node) -> 172.16.2.179:4240 (health) to-endpoint FORWARDED (TCP Flags: ACK)
Oct 26 07:23:02.925: 192.168.10.102:53610 (remote-node) <- 172.16.2.179:4240 (health) to-network FORWARDED (TCP Flags: ACK)
Oct 26 07:23:05.647: 192.168.10.101:54360 (host) -> 192.168.10.10:6443 (kube-apiserver) to-network FORWARDED (TCP Flags: ACK)
Oct 26 07:23:07.745: 192.168.10.101:56274 (host) -> 192.168.10.10:6443 (kube-apiserver) to-network FORWARDED (TCP Flags: ACK, PSH)
Oct 26 07:23:07.981: 192.168.10.10:45888 (kube-apiserver) <- 172.16.2.179:4240 (health) to-network FORWARDED (TCP Flags: ACK)
Oct 26 07:23:07.982: 192.168.10.10:45888 (kube-apiserver) -> 172.16.2.179:4240 (health) to-endpoint FORWARDED (TCP Flags: ACK)
Oct 26
...

NodePort 서비스를 통해 UI로 접근할 수 있습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# k get svc -A | grep hubble
kube-system   hubble-metrics   ClusterIP   None            <none>        9965/TCP                 37m
kube-system   hubble-peer      ClusterIP   10.10.41.90     <none>        443/TCP                  37m
kube-system   hubble-relay     ClusterIP   10.10.252.43    <none>        80/TCP                   37m
kube-system   hubble-ui        NodePort    10.10.207.135   <none>        80:31854/TCP             37m

 

Cilium 통신 테스트

 

파드 생성

cat <<EOF | kubectl create -f -
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
EOF

파드 확인

(⎈|CiliumLab: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          17s   172.16.0.251   k8s-s    <none>           <none>
webpod1   1/1     Running   0          17s   172.16.2.151   k8s-w1   <none>           <none>
webpod2   1/1     Running   0          16s   172.16.1.161   k8s-w2   <none>           <none>

실습을 위한 단축키 지정

# cilium 파드 이름
export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-s  -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1 -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD2=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w2 -o jsonpath='{.items[0].metadata.name}')

# 단축키(alias) 지정
alias c0="kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium"
alias c1="kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- cilium"
alias c2="kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- cilium"

alias c0bpf="kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- bpftool"
alias c1bpf="kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- bpftool"
alias c2bpf="kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- bpftool"

# 테스트 파드들 IP
NETPODIP=$(kubectl get pods netpod -o jsonpath='{.status.podIP}')
WEBPOD1IP=$(kubectl get pods webpod1 -o jsonpath='{.status.podIP}')
WEBPOD2IP=$(kubectl get pods webpod2 -o jsonpath='{.status.podIP}')

# 단축키(alias) 지정
alias p0="kubectl exec -it netpod  -- "
alias p1="kubectl exec -it webpod1 -- "
alias p2="kubectl exec -it webpod2 -- "

 

파드 통신

파드 <-> 파드, 파드 <-> 인터넷 등의 통신을 하면 hubble ui에서 통신 내역을 확인할 수 있습니다.

# netpod 네트워크 정보 확인
p0 ip -c -4 addr
p0 route -n
p0 ping -c 1 $WEBPOD1IP && p0 ping -c 1 $WEBPOD2IP
p0 curl -s $WEBPOD1IP && p0 curl -s $WEBPOD2IP
p0 curl -s $WEBPOD1IP:8080 ; p0 curl -s $WEBPOD2IP:8080
p0 ping -c 1 8.8.8.8 && p0 curl -s wttr.in/seoul
p0 ip -c neigh

 

파드 <-> 서비스 통신

출처) https://velog.io/@haruband/K8SCilium-Socket-Based-LoadBalancing-%EA%B8%B0%EB%B2%95

일반적으로 파드 -> 서비스 통신 시에는 패킷의 목적지 IP가 서비스 IP에서 대상 파드 IP로 DNAT 처리됩니다.

하지만, cilium에서는 이런 경우 파드 내부에서 소켓을 연결할 때 목적지 주소를 대상 파드 IP로 변경합니다.

때문에 DNAT 변환 및 역변환 처리가 불필요합니다.

이를 Socket-Based LoadBalancing 이라 부르고, 이는 eBPF cgroup hook을 활용합니다.

참고로 cilium 파드의 initcontainer를 통해 호스트의 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;
      rm /hostbin/cilium-mount
    env:
    - name: CGROUP_ROOT
      value: /run/cilium/cgroupv2
    - name: BIN_PATH
      value: /opt/cni/bin
    image: quay.io/cilium/cilium:v1.16.3@sha256:62d2a09bbef840a46099ac4c69421c90f84f28d018d479749049011329aa7f28
    imagePullPolicy: IfNotPresent
    name: mount-cgroup
    resources: {}
    securityContext:
      capabilities:
        add:
        - SYS_ADMIN
        - SYS_CHROOT
        - SYS_PTRACE
        drop:
        - ALL
      seLinuxOptions:
        level: s0
        type: spc_t

 

서비스 생성

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

서비스 확인

(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl get svc,ep svc
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/svc   ClusterIP   10.10.207.24   <none>        80/TCP    7m18s

NAME            ENDPOINTS                         AGE
endpoints/svc   172.16.1.161:80,172.16.2.151:80   7m18s

통신 테스트

SVCIP=$(kubectl get svc svc -o jsonpath='{.spec.clusterIP}')
while true; do kubectl exec netpod -- curl -s $SVCIP | grep Hostname;echo "-----";sleep 1;done

파드에서 tcpdump 실행 시 Service IP는 보이지 않고, 파드 IP만 확인할 수 있습니다.

추가로 확인해보면 파드 내에서 목적지 IP 자체가 바뀌어있는 것을 볼 수 있습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec netpod -- sh -c "ngrep -tW byline -d eth0 '' 'tcp port 80'"
interface: eth0 (172.16.0.251/255.255.255.255)
filter: ( tcp port 80 ) and ((ip || ip6) || (vlan && (ip || ip6)))
####
T 2024/10/26 07:57:40.855663 172.16.0.251:37054 -> 172.16.2.151:80 [AP] #4
GET / HTTP/1.1.
Host: 10.10.207.24.
User-Agent: curl/8.7.1.
Accept: */*.
.

##
T 2024/10/26 07:57:40.856590 172.16.2.151:80 -> 172.16.0.251:37054 [AP] #6
HTTP/1.1 200 OK.
Date: Sat, 26 Oct 2024 07:57:40 GMT.
Content-Length: 194.
Content-Type: text/plain; charset=utf-8.
.
Hostname: webpod1
IP: 127.0.0.1
IP: ::1
IP: 172.16.2.151
IP: fe80::8c05:68ff:fecb:ed0f
RemoteAddr: 192.168.10.10:37054
GET / HTTP/1.1.
Host: 10.10.207.24.
User-Agent: curl/8.7.1.
Accept: */*.
.

 

strace를 사용해 system call 내역을 살펴보겠습니다.

kubectl exec netpod -- strace -s 65535 -f -tt curl -s $SVCIP

curl 요청 시 connect에서는 cluster IP가 확인되지만 바로 이후에 호출된 getsockname에서는 파드 IP로 바뀐 것을 볼 수 있습니다.

이는 connect system call을 이용하여 목적지 IP를 변경하기 때문입니다.

08:12:57.832699 connect(5, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("10.10.207.24")}, 16) = -1 EINPROGRESS (Operation in progress)
08:12:57.835078 getsockname(5, {sa_family=AF_INET, sin_port=htons(46272), sin_addr=inet_addr("172.16.0.251")}, [128 => 16]) = 0

 

참고로 Socket-Based LoadBalancing 은 기존 보안 프로그램 등을 우회할 수 있기 때문에, 적용시 검토가 필요하고 비활성화도 가능합니다.

 

Cilium Network Policy 실습

참고) https://docs.cilium.io/en/stable/security/

 

실습1) L3/L4 네트워크 정책

Cilium에서는 파드 IP 대신 Label 기반으로 보안 정책을 적용합니다.

파드 및 서비스 생성

kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.16.3/examples/minikube/http-sw-app.yaml

(⎈|CiliumLab:N/A) root@k8s-s:~# k get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/deathstar-689f66b57d-2tn2p   1/1     Running   0          3m2s
pod/deathstar-689f66b57d-wkfpd   1/1     Running   0          3m2s
pod/tiefighter                   1/1     Running   0          3m2s
pod/xwing                        1/1     Running   0          3m2s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/deathstar    ClusterIP   10.10.111.242   <none>        80/TCP    3m2s
service/kubernetes   ClusterIP   10.10.0.1       <none>        443/TCP   118m

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/deathstar   2/2     2            2           3m2s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/deathstar-689f66b57d   2         2         2       3m2s

정책 생성

cat <<EOF | kubectl apply -f - 
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
EOF

정책 생성 후 아래와 같이 cli를 통해 확인이 가능합니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl get cnp
NAME    AGE
rule1   28s
(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl describe cnp rule1
Name:         rule1
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  cilium.io/v2
Kind:         CiliumNetworkPolicy
Metadata:
  Creation Timestamp:  2024-10-26T08:32:44Z
  Generation:          1
  Resource Version:    14146
  UID:                 73290ad1-3a65-4688-be2c-ac27814ca241
Spec:
  Description:  L3-L4 policy to restrict deathstar access to empire ships only
  Endpoint Selector:
    Match Labels:
      Class:  deathstar
      Org:    empire
  Ingress:
    From Endpoints:
      Match Labels:
        Org:  empire
    To Ports:
      Ports:
        Port:      80
        Protocol:  TCP
Status:
  Conditions:
    Last Transition Time:  2024-10-26T08:32:44Z
    Message:               Policy validation succeeded
    Status:                True
    Type:                  Valid
Events:                    <none>

이제 tiefighter와 xwing 파드에서 deathstar 파드에 접근해보겠습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing

tiefighter에서는 정상적으로 접근이 되지만, xwing에서는 drop되는 것을 볼 수 있습니다.

 

실습2) 도메인 기반 네트워크 정책

xwing 파드에서 api.github.com 외 다른 도메인은 접근하지 못하도록 설정합니다.

cat <<EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: alliance
      class: xwing
  egress:
  - toFQDNs:
    - matchName: "api.github.com"
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"
EOF

curl을 실행하면 api.github.com 외 다른 도메인에대한 요청은 drop되어 응답을 받지 못하는 것을 볼 수 있습니다.

(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec xwing -- curl -I -s https://api.github.com | head -1
HTTP/2 200
(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec xwing -- curl -I -s https://support.github.com | head -1
(⎈|CiliumLab:N/A) root@k8s-s:~# kubectl exec xwing -- curl -I -s https://naver.com | head -1

728x90