본문 바로가기

컨테이너/쿠버네티스 네트워크

[KANS] 쿠버네티스 네트워크 (16) Cilium - pod통신, service 통신

목표:

1. Cillum의 POD 통신 자세히 알아보자

2. Cillium의 Service 통신 자세히 알아보자

Cilium POD 통신 Packet Flow

  • pod 투 pod
  • Egress 
  • Ingress 

노드간 POD 통신 실습을 위해 3개의 POD를 디폴트 네임스페이스에 생성하겠습니다.

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: netpod
  labels:
    app: netpod
spec:
  nodeName: k8s-cp
  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

 

환경변수 삽입 및 확인 

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 -- "
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# c0 service list
c0: command not found
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# # 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"

# 확인
kubectl get pod -o wide
c0 status --verbose | grep Allocated -A5
c1 status --verbose | grep Allocated -A5
c2 status --verbose | grep Allocated -A5

kubectl get ciliumendpoints
kubectl get ciliumendpoints -A
c0 endpoint list
c0 bpf endpoint list
c0 map get cilium_lxc
c0 ip list
  • cilium POD 안에서도 생성된 POD 정보를 조회 c0 status
  • kubectl get ciliumendpoints -A 각 POD ip 확인 가능
  • c0 map get cilum_lxc 에서 얻는 값들 
    • Key (IP 주소:포트): Cilium에서 관리하는 각 엔드포인트의 IP 주소와 포트 번호입니다. 여기서는 :0으로 표기되어 있지만, 기본적으로 포트가 없는 IP 주소로 보입니다.
    • id: Cilium이 내부적으로 엔드포인트를 구별하기 위해 부여하는 고유 ID입니다.
    • sec_id: 보안 ID(Security ID)로, Cilium에서 정책 관리와 보안 그룹을 구별하기 위해 사용합니다. 네트워크 흐름에 대한 정책을 적용하는 데 사용됩니다.
    • flags: 현재 상태에 대한 플래그 값을 표시합니다. 여기서는 0x0000로 설정되어 있어 특별한 플래그가 없는 상태로 보입니다.
    • ifindex: 각 엔드포인트에 대한 네트워크 인터페이스의 인덱스입니다. 운영 체제에서 인터페이스를 구별하기 위해 사용됩니다.
    • mac: 엔드포인트의 MAC 주소입니다. 각 엔드포인트가 네트워크 상에서 데이터를 주고받을 때 사용하는 물리적 주소입니다.
    • nodemac: 해당 노드의 네트워크 인터페이스 MAC 주소입니다. 이 MAC 주소는 네트워크 노드 간 통신에 사용됩니다.
    • State: 현재 상태입니다. sync는 이 엔드포인트가 Cilium과 동기화 상태에 있음을 나타냅니다.
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# c0 status --verbose | grep Allocated -A5
Allocated addresses:
  172.16.0.126 (kube-system/coredns-55cb58b774-p5z9s [restored])
  172.16.0.186 (default/netpod)
  172.16.0.191 (router)
  172.16.0.234 (kube-system/coredns-55cb58b774-fj28m [restored])
  172.16.0.236 (health)
  
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# kubectl get ciliumendpoints -A
NAMESPACE     NAME                           SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
default       netpod                         7889                ready            172.16.0.186
default       webpod1                        415                 ready            172.16.2.60
default       webpod2                        415                 ready            172.16.1.87
kube-system   coredns-55cb58b774-fj28m       36492               ready            172.16.0.234
kube-system   coredns-55cb58b774-p5z9s       36492               ready            172.16.0.126
kube-system   hubble-relay-88f7f89d4-df2xq   34125               ready            172.16.1.29
kube-system   hubble-ui-59bb4cb67b-r94c8     1892                ready            172.16.1.98

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# c0 map get cilium_lxc
Key              Value                                                                                            State   Error
172.16.0.186:0   id=334   sec_id=7889  flags=0x0000 ifindex=14  mac=8E:35:21:8C:DF:CE nodemac=8E:B6:0B:E5:3A:0A   sync
172.16.0.126:0   id=658   sec_id=36492 flags=0x0000 ifindex=8   mac=2A:46:4E:F0:FD:D7 nodemac=1A:A5:90:E3:2F:BE   sync
172.16.0.234:0   id=912   sec_id=36492 flags=0x0000 ifindex=10  mac=7A:3A:E5:A2:BD:56 nodemac=DE:29:FA:C6:18:E1   sync
172.16.0.236:0   id=426   sec_id=4     flags=0x0000 ifindex=12  mac=7A:6E:A4:FF:F4:87 nodemac=AA:A6:2D:45:3E:86   sync

파드 변수지정

# 테스트 파드들 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 -- "

ping 전개 이후 확인

p0 ip -c -4 addr
p0 route -n
p0 ping -c 1 $WEBPOD1IP && p0 ping -c 1 $WEBPOD2IP

2개의 ping을 던졌는데 왜 4개의 컬럼이 존재 할까? 

 

Flow Details의 정보에 따르면 Cilium event type이 to-endpoint로 나타나고 있습니다. 이 항목은 패킷의 목적지가 Cilium에서 관리하는 특정 엔드포인트임을 나타냅니다. 즉, 이 이벤트는 패킷이 Cilium의 엔드포인트로 전달될 때 발생합니다.

ICMP 패킷의 경우 1개당 2개의 컬럼이 생기는 이유는 패킷의 **요청(Request)과 응답(Reply)**이 각각의 이벤트로 기록되기 때문입니다. 

요약하자면:

  • Cilium event type "to-endpoint": 패킷이 Cilium에서 관리하는 엔드포인트로 향하고 있음을 나타냅니다.
  • ICMP 패킷당 2개의 컬럼: 각각 요청(Request)과 응답(Reply) 패킷에 해당하는 이벤트로 기록되기 때문에 발생합니다.

curl  전개 이후 확인

HTTP 요청을 위해 curl을 사용하여 80 포트로 전송하면, 일반적으로 10개 이상의 컬럼이 생성됩니다. 이는 TCP 3-way 핸드셰이크HTTP 요청-응답 단계 때문입니다. 각각의 흐름은 다음과 같이 기록됩니다

 

  • TCP 연결 설정: 3개의 기본 핸드셰이크 이벤트 (SYN, SYN-ACK, ACK)
  • HTTP 요청 데이터 전송 (ACK-PSH) Cilium event type  없음
  • HTTP 응답 데이터 전송 (ACK-PSH) Cilium event type  to-endpoint
  • TCP 연결 해제: 3개의 해제 이벤트 (FIN, FIN-ACK, ACK)

이처럼 curl 명령어로 HTTP 요청을 보낼 때, 80 포트를 통해 연결을 설정하고 HTTP 요청과 응답을 처리하는 과정에서 총 10개의 이벤트가 발생하게 됩니다.

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

# hubble cli 확인
hubble observe --pod netpod
Oct 26 10:51:24.701: default/netpod (ID:7889) -> default/webpod1 (ID:415) to-endpoint FORWARDED (ICMPv4 EchoRequest)
Oct 26 10:51:24.701: default/netpod (ID:7889) <- default/webpod1 (ID:415) to-network FORWARDED (ICMPv4 EchoReply)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) -> default/webpod1:80 (ID:415) to-endpoint FORWARDED (TCP Flags: SYN)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) <- default/webpod1:80 (ID:415) to-network FORWARDED (TCP Flags: SYN, ACK)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) -> default/webpod1:80 (ID:415) to-endpoint FORWARDED (TCP Flags: ACK)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) -> default/webpod1:80 (ID:415) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) <> default/webpod1 (ID:415) pre-xlate-rev TRACED (TCP)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) <> default/webpod1 (ID:415) pre-xlate-rev TRACED (TCP)
Oct 26 10:57:54.722: default/netpod:43328 (ID:7889) <> default/webpod1 (ID:415) pre-xlate-rev TRACED (TCP)
Oct 26 10:57:54.723: default/netpod:43328 (ID:7889) <> default/webpod1 (ID:415) pre-xlate-rev TRACED (TCP)
Oct 26 10:57:54.723: default/netpod:43328 (ID:7889) <> default/webpod1 (ID:415) pre-xlate-rev TRACED (TCP)
Oct 26 10:57:54.723: default/netpod:43328 (ID:7889) <- default/webpod1:80 (ID:415) to-network FORWARDED (TCP Flags: ACK, PSH)
Oct 26 10:57:54.724: default/netpod:43328 (ID:7889) -> default/webpod1:80 (ID:415) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Oct 26 10:57:54.724: default/netpod:43328 (ID:7889) <- default/webpod1:80 (ID:415) to-network FORWARDED (TCP Flags: ACK, FIN)
Oct 26 10:57:54.724: default/netpod:43328 (ID:7889) -> default/webpod1:80 (ID:415) to-endpoint FORWARDED (TCP Flags: ACK)

hubble observe --pod webpod1
hubble observe --pod webpod2

# BPF maps : 목적지 파드와 통신 시 어느곳으로 보내야 될지 확인할 수 있다
c0 map get cilium_ipcache
c0 map get cilium_ipcache | grep $WEBPOD1IP

# netpod 의 LXC 변수 지정
LXC=<k8s-s의 가장 나중에 lxc 이름>
LXC=lxc335e04832afa

# 파드와 veth pair 에 IP가 없다! proxy_arp 도 없다! 하지만 GW MAC 요청 시 lxc(veth)의 MAC 으로 응답이 온다! >> eBPF Magic!
# Cilium hijacks ARP table of POD1, forces the next hop to be the peer end (host side) of the veth pair.
ip -c addr show dev $LXC

1. SYN (Synchronize)

  • 의미: TCP 연결을 시작할 때 클라이언트가 서버에 전송하는 패킷입니다.
  • 역할: 클라이언트가 서버에 연결을 요청하면서 자신이 사용할 초기 순서 번호를 보내고, 서버가 연결 요청을 받았다는 것을 알리는 역할을 합니다.
  • 흐름: 클라이언트 → 서버

2. SYN-ACK (Synchronize-Acknowledge)

  • 의미: 서버가 클라이언트의 SYN 패킷을 받았음을 확인하고, 서버도 연결을 동의한다는 응답을 보내는 패킷입니다.
  • 역할: 서버가 자신도 연결을 설정할 준비가 되었음을 알리기 위해 자신의 초기 순서 번호와 클라이언트의 순서 번호에 대한 응답을 포함합니다.
  • 흐름: 서버 → 클라이언트

3. ACK (Acknowledge)

  • 의미: 클라이언트가 서버의 SYN-ACK 패킷을 받았음을 확인하고, 최종적으로 연결 설정을 완료하는 패킷입니다.
  • 역할: 클라이언트가 서버의 응답을 받았고, 서로 연결이 완료되었음을 확정하는 패킷으로, 3-way 핸드셰이크를 마무리합니다.
  • 흐름: 클라이언트 → 서버

4. ACK-PSH (Acknowledge-Push)

  • 의미: TCP 데이터 전송 중 데이터가 포함된 ACK 패킷으로, 전송된 데이터를 즉시 상위 애플리케이션으로 푸시(전달)하라는 명령을 포함합니다.
  • 역할: ACK 플래그가 데이터 수신을 확인하는 역할을 수행하는 것에 더해, PSH 플래그는 받은 데이터를 즉시 애플리케이션 계층으로 전달하라는 신호를 보냅니다. 주로 HTTP 요청이나 응답과 같은 실제 데이터를 전송할 때 사용됩니다.
  • 흐름: 클라이언트 ↔ 서버 (서로 간 데이터 전송 시)

5. ACK-PSH (Acknowledge-Push)

  • 의미: 데이터 전송 중 데이터가 포함된 ACK 패킷입니다. PSH(푸시) 플래그는 받은 데이터를 애플리케이션 계층으로 즉시 전달하라는 명령입니다.
  • 역할: 데이터 전송 중 수신 측이 데이터를 즉시 상위 애플리케이션으로 전달하도록 합니다. 예를 들어, HTTP 요청을 보낼 때 클라이언트가 서버로 보낼 데이터가 포함된 ACK-PSH 패킷을 사용하게 됩니다.
  • 흐름: 주로 클라이언트 → 서버 또는 서버 → 클라이언트로, 실제 데이터 전송이 발생할 때 양방향으로 사용됩니다.

6. ACK-FIN (Acknowledge-Finish)

  • 의미: 연결 종료 요청을 나타내는 FIN 플래그와 수신 확인 플래그 ACK가 함께 설정된 패킷입니다. FIN은 연결을 종료하겠다는 신호이며, ACK는 이전 패킷에 대한 응답입니다.
  • 역할: 송신 측(예: 클라이언트 또는 서버)이 데이터 전송을 마치고 연결을 종료할 준비가 되었음을 알립니다. 이를 통해 연결 해제가 시작됩니다.
  • 흐름: 송신 측에서 상대방으로 전송되며, 예를 들어 클라이언트가 서버로 전송하는 경우도 있고, 서버가 클라이언트로 보내는 경우도 있습니다.

7. ACK-FIN (Acknowledge-Finish)

  • 의미: 두 번째 ACK-FIN은 연결 종료 과정에서 상대방의 FIN 패킷에 대한 응답을 나타냅니다.
  • 역할: 송신 측에서 FIN 요청을 받은 수신 측이, 자신도 연결을 종료하겠다는 신호로 FIN 플래그와 ACK 플래그를 설정하여 응답합니다. 이렇게 양쪽 모두 연결 종료 의사를 확인합니다.
  • 흐름: 수신 측에서 처음 FIN 패킷을 보낸 송신 측으로 전송되며, 양방향으로 연결 종료를 확인하는 과정입니다.

8. ACK (Acknowledge)

  • 의미: 마지막 ACK 패킷은 연결 종료의 최종 확인 응답입니다.
  • 역할: 모든 FIN 패킷에 대한 수신이 완료되었음을 확인하고, 최종 ACK 패킷을 전송하여 연결이 완전히 종료되었음을 알립니다. 이 패킷을 마지막으로 TCP 연결이 완전히 종료됩니다.
  • 흐름: 보통 FIN을 받은 측 → FIN을 처음 보낸 측으로 전송됩니다.

Cilium SERVICE 통신 Packet Flow

DNAT MAGIC

Pod1 안에서 동작하는 앱이 connect() 시스템콜을 이용하여 소켓을 연결할 때 목적지 주소가 서비스 주소(10.10.8.55)이면 소켓의 목적지 주소를 바로 백엔드 주소(10.0.0.31)로 설정한다. 이후 앱에서 해당 소켓을 통해 보내는 모든 패킷의 목적지 주소는 이미 백엔드 주소(10.0.0.31)로 설정되어 있기 때문에 중간에 DNAT 변환 및 역변환 과정이 필요없어진다.

 

서비스를 생성

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

서비스 접속 확인

# 서비스 생성 확인
kubectl get svc,ep svc

# 노드에 iptables 더이상 KUBE-SVC rule 이 생성되지 않는다!
iptables-save | grep KUBE-SVC
iptables-save | grep CILIUM

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# iptables-save | grep KUBE-SVC
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 45643 --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 45643 --on-ip 127.0.0.1 --tproxy-mark 0x200/0xffffffff
:CILIUM_OUTPUT_raw - [0:0]
:CILIUM_PRE_raw - [0:0]
-A PREROUTING -m comment --comment "cilium-feeder: CILIUM_PRE_raw" -j CILIUM_PRE_raw
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT_raw" -j CILIUM_OUTPUT_raw
-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_FORWARD - [0:0]
:CILIUM_INPUT - [0:0]
:CILIUM_OUTPUT - [0:0]
-A INPUT -m comment --comment "cilium-feeder: CILIUM_INPUT" -j CILIUM_INPUT
-A FORWARD -m comment --comment "cilium-feeder: CILIUM_FORWARD" -j CILIUM_FORWARD
-A OUTPUT -m comment --comment "cilium-feeder: CILIUM_OUTPUT" -j CILIUM_OUTPUT
-A CILIUM_FORWARD -o cilium_host -m comment --comment "cilium: any->cluster on cilium_host forward accept" -j ACCEPT
-A CILIUM_FORWARD -i cilium_host -m comment --comment "cilium: cluster->any on cilium_host forward accept (nodeport)" -j ACCEPT
-A CILIUM_FORWARD -i lxc+ -m comment --comment "cilium: cluster->any on lxc+ forward accept" -j ACCEPT
-A CILIUM_FORWARD -i cilium_net -m comment --comment "cilium: cluster->any on cilium_net forward accept (nodeport)" -j ACCEPT
-A CILIUM_FORWARD -o lxc+ -m comment --comment "cilium: any->cluster on lxc+ forward accept" -j ACCEPT
-A CILIUM_FORWARD -i lxc+ -m comment --comment "cilium: cluster->any on lxc+ forward accept (nodeport)" -j ACCEPT
-A CILIUM_INPUT -m comment --comment "cilium: ACCEPT for proxy traffic" -j ACCEPT
-A CILIUM_OUTPUT -m comment --comment "cilium: ACCEPT for proxy traffic" -j ACCEPT
-A CILIUM_OUTPUT -m comment --comment "cilium: ACCEPT for l7 proxy upstream traffic" -j ACCEPT
-A CILIUM_OUTPUT -m comment --comment "cilium: host->any mark as from host" -j MARK --set-xmark 0xc00/0xf00
:CILIUM_OUTPUT_nat - [0:0]
:CILIUM_POST_nat - [0:0]
:CILIUM_PRE_nat - [0:0]
-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

서비스 traffic 발생 확인

# 서비스IP를 변수에 지정
SVCIP=$(kubectl get svc svc -o jsonpath='{.spec.clusterIP}')

# Pod1 에서 Service(ClusterIP) 접속 트래픽 발생
kubectl exec netpod -- curl -s $SVCIP
kubectl exec netpod -- curl -s $SVCIP | grep Hostname

# 지속적으로 접속 트래픽 발생
SVCIP=$(kubectl get svc svc -o jsonpath='{.spec.clusterIP}')
while true; do kubectl exec netpod -- curl -s $SVCIP | grep Hostname;echo "-----";sleep 1;done

# 파드에서 SVC(ClusterIP) 접속 시 tcpdump 로 확인 >> 파드 내부 캡쳐인데, SVC(10.108.12.195)는 보이지 않고, DNAT 된 web-pod 의 IP가 확인! Magic!
(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# 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
13:17:07.740110 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 0
13:17:07.740501 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48804: tcp 0
13:17:07.740905 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 0
13:17:07.740950 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 74
13:17:07.741346 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48804: tcp 0
13:17:07.741917 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48804: tcp 309
13:17:07.741946 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 0
13:17:07.742169 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 0
13:17:07.742600 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48804: tcp 0
13:17:07.742617 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48804 > 172.16.1.87.80: tcp 0
13:17:08.985086 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 0
13:17:08.985530 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.2.60.80 > 172.16.0.186.57484: tcp 0
13:17:08.985578 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 0
13:17:08.985732 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 74
13:17:08.986120 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.2.60.80 > 172.16.0.186.57484: tcp 0
13:17:08.986779 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.2.60.80 > 172.16.0.186.57484: tcp 309
13:17:08.986799 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 0
13:17:08.987105 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 0
13:17:08.987488 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.2.60.80 > 172.16.0.186.57484: tcp 0
13:17:08.987509 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.57484 > 172.16.2.60.80: tcp 0
13:17:10.135721 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 0
13:17:10.136141 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48818: tcp 0
13:17:10.136200 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 0
13:17:10.136277 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 74
13:17:10.136578 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48818: tcp 0
13:17:10.136894 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48818: tcp 309
13:17:10.136916 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 0
13:17:10.137184 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 0
13:17:10.137634 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48818: tcp 0
13:17:10.137658 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48818 > 172.16.1.87.80: tcp 0
13:17:11.299708 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 0
13:17:11.300140 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48824: tcp 0
13:17:11.300177 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 0
13:17:11.300250 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 74
13:17:11.300558 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48824: tcp 0
13:17:11.301039 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48824: tcp 309
13:17:11.301056 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 0
13:17:11.301301 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 0
13:17:11.301680 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.48824: tcp 0
13:17:11.301700 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.48824 > 172.16.1.87.80: tcp 0
13:17:12.474159 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.36488 > 172.16.1.87.80: tcp 0
13:17:12.474585 eth0  In  ifindex 13 8e:b6:0b:e5:3a:0a 172.16.1.87.80 > 172.16.0.186.36488: tcp 0
13:17:12.474623 eth0  Out ifindex 13 8e:35:21:8c:df:ce 172.16.0.186.36488 > 172.16.1.87.80: tcp ^C

서비스 traffic 발생 확인

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# k get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.10.0.1     <none>        443/TCP   6d2h
svc          ClusterIP   10.10.88.91   <none>        80/TCP    8m50s

(⎈|kubernetes-admin@kubernetes:N/A) root@k8s-s:~/koo# 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.13.10:443       ClusterIP      1 => 192.168.10.10:4244 (active)
3    10.10.239.122:80      ClusterIP      1 => 172.16.1.29:4245 (active)
4    10.10.41.208:80       ClusterIP      1 => 172.16.1.98:8081 (active)
5    10.10.0.10:53         ClusterIP      1 => 172.16.0.126:53 (active)
                                          2 => 172.16.0.234:53 (active)
6    10.10.0.10:9153       ClusterIP      1 => 172.16.0.126:9153 (active)
                                          2 => 172.16.0.234:9153 (active)
7    192.168.10.10:32121   NodePort       1 => 172.16.1.98:8081 (active)
8    0.0.0.0:32121         NodePort       1 => 172.16.1.98:8081 (active)
9    10.10.88.91:80        ClusterIP      1 => 172.16.2.60:80 (active)
                                          2 => 172.16.1.87:80 (active)

9번 )  10.10.88.91이 SVC IP인데 netpod 1, 2로 DNAT 되는것을 확인할수 있다.

어떻게 되는걸까? 

Pod1 안에서 동작하는 앱이 connect() 시스템콜을 이용하여 소켓을 연결할 때 목적지 주소가 서비스 주소(10.10.88.91)이면 소켓의 목적지 주소를 바로 백엔드 주소(172.16.2.60과 172.16.1.87)로 설정한다. 이후 앱에서 해당 소켓을 통해 보내는 모든 패킷의 목적지 주소는 이미 백엔드 주소(Pod1과 Pod2)로 설정되어 있기 때문에 중간에 DNAT 변환 및 역변환 과정이 필요없어진다.

 

이게 바로 Cillium의 Magic