쿠버네티스 일반/Network

쿠버네티스 칼리코 네트워크

Jerry_이정훈 2022. 1. 23. 05:05
728x90

오늘은 쿠버네티스 칼리코(calico) CNI에 관하여 정리한다. 가시다님 네트워크 스터디 3주차 예습 내용이다. 1) 같은 노드 내 파드 - 파드 통신 2) 파드와 외부 인터넷 통신 3) 다른 노드의 파드와 파드 통신 내용 실습한 걸 정리하였다.

 

가시다님, 3주차 스터디

 

실습 환경은 아래와 같다. 3대의 컨트롤 플레인 + 워커 노드 + 1대의 워커 노드. 쿠버 버전 1.22.4, CRI containerd.

[spkr@erdia22 ~ (ubun81:default)]$ kgn # k get node -o wide
NAME        STATUS   ROLES                  AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
ubun20-81   Ready    control-plane,master   3d21h   v1.22.4   172.17.29.81   <none>        Ubuntu 20.04.2 LTS   5.4.0-65-generic   containerd://1.5.8
ubun20-82   Ready    control-plane,master   3d21h   v1.22.4   172.17.29.82   <none>        Ubuntu 20.04.2 LTS   5.4.0-65-generic   containerd://1.5.8
ubun20-83   Ready    control-plane,master   3d21h   v1.22.4   172.17.29.83   <none>        Ubuntu 20.04.2 LTS   5.4.0-65-generic   containerd://1.5.8
ubun20-84   Ready    <none>                 3d21h   v1.22.4   172.17.29.84   <none>        Ubuntu 20.04.2 LTS   5.4.0-65-generic   containerd://1.5.8

칼리코 버전은 v3.21.4

[spkr@erdia22 ~ (ubun81:default)]$ calicoctl version
Client Version:    v3.21.4
Git commit:        220d04c94
Cluster Version:   v3.21.4
Cluster Type:      kubespray,bgp,kubeadm,kdd,k8s

1. 칼리코에서 같은 노드 내 파드와 파드 간 통신 흐름을 알아보자.

 

결론을 먼저 말하면 전체 흐름은 아래와 같다. 같은 노드 내 파드가 생성되면 칼리코가 각 파드 별로 가상 인터페이스 veth를 생성한다. 그리고 호스트 라우팅 테이블에 각 파드 IP에 가상 인터페이스(veth)가 할당된다. 해당 라우팅 테이블을 따라 같은 노드 내 서로 다른 파드는 정상적으로 통신한다.

같은 노드(ubun20-84) 내 2개의 파드를 배포하였다.

[spkr@erdia22 ~ (ubun81:default)]$ kgp
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          9h    10.233.110.24   ubun20-84   <none>           <none>
pod2   1/1     Running   0          9h    10.233.110.25   ubun20-84   <none>           <none>

마스터 노드에서 확인하면 칼리코가 생성하는 endpoint 인터페이스를 확인할 수 있다. veth(Virtual, 베스) 가상 인터페이스가 보인다.

root@ubun20-81:~# calicoctl get workloadendpoints
WORKLOAD   NODE        NETWORKS           INTERFACE
pod1       ubun20-84   10.233.110.24/32   calice0906292e2
pod2       ubun20-84   10.233.110.25/32   calibd2348b4f67

파드가 생성된 워커 노드에서 확인하면 파드가 생성되면 위에서 확인한 veth를 볼 수 있다. 즉 파드가 생성되면 칼리코가 노드에 가상 인터페이스를 생성한다. 

root@ubun20-84:~# ip -c link
33: calice0906292e2@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-61f99ebe-c314-d555-fa1d-0239ed361782
34: calibd2348b4f67@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-0fd3099e-6229-4f97-6f82-f241a4432d99

칼리코는 파드가 생성되면 각각의 파드 IP 별로 veth 인터페이스를 라우팅 테이블에 등록한다. 칼리코가 L2 스위치처럼 파드의 맥정보를 받아서 가상 라우팅을 만들어서 등록하는 듯. 

root@ubun20-84:~# ip -c route
(...)
10.233.110.24 dev calice0906292e2 scope link
10.233.110.25 dev calibd2348b4f67 scope link

참고로 컨테이너는 네트워크 네임스페이스로 네트워크를 분리하니 2개의 파드가 서로 다른 네트워크 네임스페이스(4, 5)를 가지는 것을 확인할 수 있다.

root@ubun20-84:~# lsns -t net
        NS TYPE NPROCS     PID USER      NETNSID NSFS                                                COMMAND
4026531992 net     177       1 root   unassigned                                                     /lib/systemd/s
4026532559 net       3   72992 root            4 /run/netns/cni-61f99ebe-c314-d555-fa1d-0239ed361782 /pause
4026532629 net       2   73055 root            5 /run/netns/cni-0fd3099e-6229-4f97-6f82-f241a4432d99 /pause

이제 pod1에 접속하여 pod2로 ping 보내면 아래와 같이 정상적으로 응답을 받는다. 

pod1  ~  ping -c 10  10.233.110.25
PING 10.233.110.25 (10.233.110.25) 56(84) bytes of data.
64 bytes from 10.233.110.25: icmp_seq=1 ttl=63 time=0.150 ms
64 bytes from 10.233.110.25: icmp_seq=2 ttl=63 time=0.080 ms
64 bytes from 10.233.110.25: icmp_seq=3 ttl=63 time=0.083 ms
64 bytes from 10.233.110.25: icmp_seq=4 ttl=63 time=0.114 ms

패킷 덤프로 확인하면 칼리코가 생성한 인터페이스에서 패킷을 확인할 수 있다. veth가 해당 요청을 처리하였다.

root@ubun20-84:~# VETH1=calice0906292e2
root@ubun20-84:~# tcpdump -i $VETH1 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on calice0906292e2, link-type EN10MB (Ethernet), capture size 262144 bytes

16:59:21.970615 IP 10.233.110.24 > 10.233.110.25: ICMP echo request, id 43246, seq 1, length 64
16:59:21.970746 IP 10.233.110.25 > 10.233.110.24: ICMP echo reply, id 43246, seq 1, length 64

8 packets captured
8 packets received by filter
0 packets dropped by kernel

호스트 네트워크 네임스페이스의 ‘FORWARD’를 확인하면 pkts 카운트가 증가한 걸 확인할 수 있다. 같은 노드 내 파드가 서로 다른 노드로 접속을 하면 ‘FORWARD’  필터 통해서 처리된 걸 확인할 수 있다. 

root@ubun20-84:~# watch -d -n 1 "iptables -v --numeric --table filter --list FORWARD | egrep '(cali-FORWARD|pkts)'"

Every 1.0s: iptables -v --numeric --table filter --list FORWARD | egrep '(c...  ubun20-84: Sat Jan 22 17:03:22 2022

 pkts bytes target     prot opt in     out     source               destination
1412K  773M cali-FORWARD  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* cali:wUHhoiAYhphO9Ms
o */

같은 노드 내 파드가 생성되면 칼리코가 각 파드 별로 veth를 생성하고 호스트 라우팅 테이블에 등록한다. 라우팅 테이블에 각 파드 IP에 가상 인터페이스(veth)가 할당되었다. 따라서 같은 노드 내 파드에서 다른 파드로 통신하면 정상적으로 잘 통신된다.

 

이상 같은 노드 내 2개의 파드가 서로 통신을 하는 구조를 알아보았다. (어렵다)

 

2. 파드에서 외부 인터넷 통신

 

이제 파드가 외부 인터넷으로 어떻게 통신하는지 알아본다. 역시 결론부터 알아보자. 파드에서 외부로 통신하면 칼리코가 생성한 iptables nat 룰에 의하여 파드 ip가 MASQUERADE(마스크 처리, IP 변경 정도)되어 정상적으로 외부 통신할 수 있다.

 

 

먼저 칼리코의 NAT 정책을 마스터 노드에서 확인하자.

root@ubun20-81:~# calicoctl get ippool -o wide
NAME           CIDR             NAT    IPIPMODE   VXLANMODE   DISABLED   DISABLEBGPEXPORT   SELECTOR
default-pool   10.233.64.0/18   true   Always     Never       false      false              all()

NAT가  true이다. 

root@ubun20-84:~# iptables -n -t nat --list cali-nat-outgoing
Chain cali-nat-outgoing (1 references)
target     prot opt source               destination
MASQUERADE  all  --  0.0.0.0/0            0.0.0.0/0            /* cali:flqWnvo8yq4ULQLa */ match-set cali40masq-ipam-pools src ! match-set cali40all-ipam-pools dst random-fully

워커 노드에서 iptable nat를 확인하면 칼리코가 cali-nat-outgoing 정책으로 외부로 나가는 패킷은 MASQUERADE해서 전부 보내고 있다. 즉 외부로 보내는 트래픽은 MASQURADE 정책 통해서 나갈 수 있게 되어있다. 

 

파드를 생성해서 확인해 보자. 워커 노드에 pod1을 생성하였다.

[spkr@erdia22 01.Pod (ubun81:default)]$ k get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          20s   10.233.110.27   ubun20-84   <none>           <none>

해당 파드에서 외부로 ping 보내면 아래와 같이 veth 인터페이스(calice0906292e2 )와 노드의 포트(ens3)에서 패킷을 확인할 수 있다. 

 pod1  ~  ping -c 10 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=47.9 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=38.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=115 time=38.2 ms
spkr@ubun20-84:~$ sudo tcpdump -i calice0906292e2 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on calice0906292e2, link-type EN10MB (Ethernet), capture size 262144 bytes
17:31:40.088368 IP 10.233.110.27 > 8.8.8.8: ICMP echo request, id 33144, seq 1, length 64
17:31:40.136210 IP 8.8.8.8 > 10.233.110.27: ICMP echo reply, id 33144, seq 1, length 64

ens3는 노드 인터페이스인데, 파드의 IP가 아니라 노드의 IP로 출발지 IP가 변경되어서 외부로 나간걸 확인할 수 있다. 

spkr@ubun20-84:~$ sudo tcpdump -i ens3 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens3, link-type EN10MB (Ethernet), capture size 262144 bytes
17:31:40.088453 IP 172.17.29.84 > 8.8.8.8: ICMP echo request, id 48056, seq 1, length 64
17:31:40.136148 IP 8.8.8.8 > 172.17.29.84: ICMP echo reply, id 48056, seq 1, length 64

반면에 터널 인터페이스는 외부 통신에 관여를 하지 않는다.

spkr@ubun20-84:~$ sudo tcpdump -i tunl0 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tunl0, link-type RAW (Raw IP), capture size 262144 bytes
(패킷이 없다)

iptable cali-nat-outgoing 패킷을 확인하면 패킷이 증가하였다. 즉 해당 룰을 통하여 외부로 나간 걸 알 수 있다. 

root@ubun20-84:~# watch -d 'iptables -n -v -t nat --list cali-nat-outgoing'

Every 2.0s: iptables -n -v -t nat --list cali-nat-outgoing                      ubun20-84: Sat Jan 22 17:36:58 2022

Chain cali-nat-outgoing (1 references)
 pkts bytes target     prot opt in     out     source               destination
    8   624 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* cali:flqWnvo8yq4ULQLa
*/ match-set cali40masq-ipam-pools src ! match-set cali40all-ipam-pools dst random-fully

NAT 통해서 외부로 나가는 걸 확인하였다. 여기까지 외부로 나가는 통신을 확인하였다.

 

3. 다른 노드의 파드와 파드 통신 확인

 

결론을 먼저 말하면 아래와 같이 다른 노드의 파드와 통신하기 위해서 이전과 다른 것은 터널 인터페이스(tunl0) 인터페이스 통해서 패킷이 전달된다는 것이다. 라우팅 정보를 확인하면 터널 인터페이스에 다른 노드의 라우팅 정보가 등록되었다. 그리고 기본 설정으로 칼리코는 IPIP 터널 오버레이를 사용한다.

 

 

각 노드에서 라우팅 정보를 확인하면 상대 노드의 라우팅 정보가 등록되었다. 상대 노드의 인터페이스 IP(172.17.29.81/82/83/84)를 게이트웨이로 터널 인터페이스가 등록되었다. 상대 노드의 파드와 통신하려면(10.233.64.0/18) tunl0 인터페이스 통해서 다른 노드의 인터페이스로 전달된다. 

root@ubun20-81:~# route | head -2 ; route -n | grep tunl0
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.233.90.0     172.17.29.83    255.255.255.0   UG    0      0        0 tunl0
10.233.110.0    172.17.29.84    255.255.255.0   UG    0      0        0 tunl0
10.233.120.0    172.17.29.82    255.255.255.0   UG    0      0        0 tunl0

root@ubun20-84:~#  route | head -2 ; route -n | grep tunl0
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.233.90.0     172.17.29.83    255.255.255.0   UG    0      0        0 tunl0
10.233.93.0     172.17.29.81    255.255.255.0   UG    0      0        0 tunl0
10.233.120.0    172.17.29.82    255.255.255.0   UG    0      0        0 tunl0

10.233.x.x 대역은 칼리코가 할당하는 네트웍 대역이다. 

root@ubun20-81:~# calicoctl get ippool -o wide
NAME           CIDR             NAT    IPIPMODE   VXLANMODE   DISABLED   DISABLEBGPEXPORT   SELECTOR
default-pool   10.233.64.0/18   true   Always     Never       false      false              all()

2개의 노드에 각각 파드를 할당한다. 노드 이름은 각자 노드의 이름으로 변경한다. pod1, pod2가 각각 ubun20-83, 84에 배포되었다. 

curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/4/node2-pod2.yaml
kubectl apply -f node2-pod2.yaml

[spkr@erdia22 01.Pod (ubun81:default)]$ k get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          67s   10.233.110.28   ubun20-84   <none>           <none>
pod2   1/1     Running   0          67s   10.233.90.10    ubun20-83   <none>           <none>

파드에 접속해서 ping 보내고 tunl0 과 ens3 노드 인터페이스에서 패킷 캡쳐를 하면 IPIP 터널을 통해서 전달되는 Outer IP 헤더와 Inner IP 헤더를 확인할 수 있다.

[spkr@erdia22 ~ (ubun81:default)]$ k exec pod1 -it -- zsh
pod1  ~  ping -c 10 10.233.90.10
PING 10.233.90.10 (10.233.90.10) 56(84) bytes of data.
64 bytes from 10.233.90.10: icmp_seq=1 ttl=62 time=0.624 ms
64 bytes from 10.233.90.10: icmp_seq=2 ttl=62 time=0.436 ms
64 bytes from 10.233.90.10: icmp_seq=3 ttl=62 time=0.396 ms
64 bytes from 10.233.90.10: icmp_seq=4 ttl=62 time=0.439 ms

노드 인터페이스 ens3에 확인한 패킷을 보면 2개의 IP 헤더가 보인다. 

 

IPIP 모드 그림

https://en.wikipedia.org/wiki/IP_in_IP

 

tunl0 인터페이스에 아래와 같이 패킷이 보인다. 같은 노드 내 서로 다른 파드 통신에 사용하지 않던 tunl0 인터페이스인데 이제 다른 노도와 통신하려면 tunl0 통해서 패킷이 전달되는 걸 확인할 수 있다.

이렇게 다른 노드와 통신하려면 tunl0 인터페이스를 사용하고 오버레이 네트워크로 IPIP 터널을 사용하는 걸 확인할 수 있다.

 

조금씩 쿠버네티스 네트워크 배워가고 있다. 

 

참조

https://learnk8s.io/kubernetes-network-packets 

 

Tracing the path of network traffic in Kubernetes

Learn how packets flow inside and outside a Kubernetes cluster. Starting from the initial web request and down to the container hosting the application

learnk8s.io

 

 

반응형