Kubernetes

Calico CNI의 구조를 파악하고 통신 테스트하기

mokpolar 2024. 9. 17. 21:30
반응형

들어가며

안녕하세요?

Calico CNI의 구조를 파악하고 Pod들간의 통신을 테스트한 글을 써보겠습니다.
이 글은 CloudNeta팀 가시다님의 KANS(Kubernetes Advanced Network Study) 3주차 과제로 작성되었습니다.

 

Calico CNI에 대하여

Calico의 구조

Calico는 쿠버네티스에서서 동작하는 CNI (Container Network Interface) 입니다.

Calico 를 설명하는 그림으로 배웠지만 스스로 이해를 쉽게 하기 위해서 구조를 그려봤습니다.


Calico를 CNI로서 설치하면 이런 구조로 통신이 가능해지는 것 같습니다.

Calico CNI를 설치하면 Calico-node Pod가 배포됩니다.
여기에서 실행되는 Felix, BIRD, Confd라는 3가지 프로세스를 통해 Calico가 동작합니다.

  • Felix : 인터페이스 관리, 라우팅 정보 관리, ACL 관리, 상태 체크
  • BIRD: BGP Peer 에 라우팅 정보 전파 및 수신, BGP RR(Route Reflector, 자신의 라우팅 정보를 또 다시 다른 라우터에게 전달)
  • Confd : calico global 설정과 BGP 설정 변경 시(트리거) BIRD 에 적용
    그외,
  • Calico IPAM plugin : 클러스터 내에서 파드에 IP 대역 할당
  • Calico-kube-controllers : calico 동작 관련 감시(watch)

 

Calico에서 노드간 라우팅 정보 공유는 어떤 통신을 통해 일어나나요

위 그림에 나왔듯이, 각 노드간 통신을 하려면 각각의 라우팅 정보를 공유해야 되고,

Calico는 이를 위해서 BGP 프로토콜을 사용합니다.

BGP, Border Gateway Protocol

 

BGP 프로토콜을 통해서 각 노드의 Calico-node Pod는 BGP Peering을 맺습니다.

 

Calico 구성 및 설정

Calico CNI 설치

이미 Kubernetes Cluster는 생성되어 있으며,
CNI가 없는 상황에서부터 시작하겠습니다.

컨트롤 플레인 노드 하나, 워커 노드 3개로 구성된 클러스터입니다.

처음에는 통신이 되지 않아, 노드가 Ready 상태가 아닙니다.

kubectl get node
NAME     STATUS     ROLES           AGE     VERSION
k8s-m    NotReady   control-plane   9m44s   v1.30.5
k8s-w0   NotReady   <none>          7m7s    v1.30.5
...

Calico CNI를 설치해줍니다.

kubectl apply -f https://raw.githubusercontent.com/gasida/KANS/main/kans3/calico-kans.yaml
# kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml

 

노드가 연결 됩니다.

kubectl get node 

NAME     STATUS   ROLES           AGE   VERSION
k8s-m    Ready    control-plane   39m   v1.30.5
k8s-w0   Ready    <none>          36m   v1.30.5
k8s-w1   Ready    <none>          76s   v1.30.5
k8s-w2   Ready    <none>          85s   v1.30.5

 

이제 Calico ctl 도 깔아보겠습니다.

curl -L https://github.com/projectcalico/calico/releases/download/v3.28.1/calicoctl-linux-amd64 -o calicoctl
chmod +x calicoctl && mv calicoctl /usr/bin
calicoctl version
Client Version:    v3.28.1
...

 

Calico pod들까지 잘 배포되었는지 확인합니다.
calico-kube-controller라는 pod 1개와 calico-node pod가 4개 보입니다.

kubectl get pod -A
NAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-77d59654f4-xhrjz   1/1     Running   0          107m
kube-system   calico-node-59k9c                          1/1     Running   0          117m
kube-system   calico-node-7zlcn                          1/1     Running   0          94m
kube-system   calico-node-84857                          1/1     Running   0          117m
kube-system   calico-node-f8wrj                          1/1     Running   0          94m
kube-system   coredns-55cb58b774-8nchw                   1/1     Running   0          107m
kube-system   coredns-55cb58b774-9tskp                   1/1     Running   0          107m
kube-system   etcd-k8s-m                                 1/1     Running   0          132m
kube-system   kube-apiserver-k8s-m                       1/1     Running   0          132m
...

 

calico-kube-controller는 deployment로,

kubectl get deploy -n kube-system
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
calico-kube-controllers   1/1     1            1           119m

 

calico-node 는 daemonset으로 배포되어 있다는 사실을 알 수 있습니다.

kubectl get daemonset -n kube-system
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
calico-node   4         4         4       4            4           kubernetes.io/os=linux   119m
kube-proxy    4         4         4       4            4           kubernetes.io/os=linux   134m

Calico의 설정들

설치가 완료되었으니 Calico에 어떤 설정들이 적용되어있는지 확인해보겠습니다.

먼저 IPAM입니다.

 

위의 그림에서 나오는 IPAM 플러그인은 Calico CNI를 사용하는 노드에서 pod의 ip를 할당한다고 설명했습니다.

Calicoctl을 통해 이 클러스터가 어떤 pod cidr을 갖고 있는지 확인해보겠습니다.

 

전체 IP Pool은 아래와 같습니다.

calicoctl ipam show
+----------+---------------+-----------+------------+--------------+
| GROUPING |     CIDR      | IPS TOTAL | IPS IN USE |   IPS FREE   |
+----------+---------------+-----------+------------+--------------+
| IP Pool  | 172.16.0.0/16 |     65536 | 7 (0%)     | 65529 (100%) |
+----------+---------------+-----------+------------+--------------+

 

그리고 각 노드에는 Pod가 어떤 범위로 할당이 될까요?
Block이라는 단위로 각 노드에 할당된 PodCIDR 정보를 확인할 수 있습니다.

calicoctl ipam show --show-blocks
+----------+-----------------+-----------+------------+--------------+
| GROUPING |      CIDR       | IPS TOTAL | IPS IN USE |   IPS FREE   |
+----------+-----------------+-----------+------------+--------------+
| IP Pool  | 172.16.0.0/16   |     65536 | 7 (0%)     | 65529 (100%) |
| Block    | 172.16.116.0/24 |       256 | 4 (2%)     | 252 (98%)    |
| Block    | 172.16.158.0/24 |       256 | 1 (0%)     | 255 (100%)   |
| Block    | 172.16.184.0/24 |       256 | 1 (0%)     | 255 (100%)   |
| Block    | 172.16.34.0/24  |       256 | 1 (0%)     | 255 (100%)   |
+----------+-----------------+-----------+------------+--------------+

 

그럼 이 정보는 어디에 저장되어 있을까요?
컨트롤 플레인에서 /etc/cni/net.d/10-calico.conflist 를 확인해보겠습니다.

cat /etc/cni/net.d/10-calico.conflist | jq
{
  "name": "k8s-pod-network",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "calico",
      "log_level": "info",
      "log_file_path": "/var/log/calico/cni/cni.log",
      "datastore_type": "kubernetes",
      "nodename": "k8s-m",
      "mtu": 0,
      "ipam": {
        "type": "calico-ipam"
      },
      "policy": {
        "type": "k8s"
      },
      "kubernetes": {
        "kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
      }
    },
    {
      "type": "portmap",
      "snat": true,
      "capabilities": {
        "portMappings": true
      }
    },
    {
      "type": "bandwidth",
      "capabilities": {
        "bandwidth": true
      }
    }
  ]
}

 

 

데이터 저장소는 Kubernetes API이며 IPAM은 Calico 자체 IPAM을 사용한다는 사실을 알 수 있습니다.

      "datastore_type": "kubernetes",
      "ipam": {
        "type": "calico-ipam"
      },

 

 

또한 Calico 구조에서 BGP Peering을 통해서 노드간 연결이 이루어지는걸 확인했습니다.
Bird가 상대 노드의 파드 네트워크 대역을 학습하고 host route table, iptable 에 rule을 추가합니다.

아래를 보면 각 노드의 IP를 node-to-node mesh 타입으로서 Peer Address를 갖고 있다는 사실을 알 수 있습니다.

calicoctl node status
Calico process is running.

IPv4 BGP status
+---------------+-------------------+-------+----------+-------------+
| PEER ADDRESS  |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+---------------+-------------------+-------+----------+-------------+
| 172.31.10.103 | node-to-node mesh | up    | 09:00:38 | Established |
| 172.31.10.101 | node-to-node mesh | up    | 09:00:55 | Established |
| 172.31.10.102 | node-to-node mesh | up    | 09:01:17 | Established |
+---------------+-------------------+-------+----------+-------------+

 

 

컨트롤 플레인에서 프로세스를 확인해보면
아래와 같이 bird, confd, felix 프로레스가 동작하고 있다는 사실도 알 수 있습니다.

ps axf
...
   4029 ?        Sl     0:14 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 859dc3762a4ff26edd02b59c4ad90ceb4a68dbd6a9c83833732
   4048 ?        Ss     0:00  \_ /pause
   4292 ?        Ss     0:00  \_ /usr/local/bin/runsvdir -P /etc/service/enabled
   4370 ?        Ss     0:00      \_ runsv node-status-reporter
   4380 ?        Sl     0:00      |   \_ calico-node -status-reporter
   4371 ?        Ss     0:00      \_ runsv bird6
   4574 ?        S      0:00      |   \_ bird6 -R -s /var/run/calico/bird6.ctl -d -c /etc/calico/confd/config/bird6.cfg
   4372 ?        Ss     0:00      \_ runsv bird
   4575 ?        S      0:01      |   \_ bird -R -s /var/run/calico/bird.ctl -d -c /etc/calico/confd/config/bird.cfg
   4373 ?        Ss     0:00      \_ runsv felix
   4383 ?        Sl     2:04      |   \_ calico-node -felix
   4374 ?        Ss     0:00      \_ runsv cni
   4382 ?        Sl     0:00      |   \_ calico-node -monitor-token
   4375 ?        Ss     0:00      \_ runsv confd
   4385 ?        Sl     0:01      |   \_ calico-node -confd
   4376 ?        Ss     0:00      \_ runsv monitor-addresses
   4381 ?        Sl     0:00      |   \_ calico-node -monitor-addresses
   4377 ?        Ss     0:00      \_ runsv allocate-tunnel-addrs
   4384 ?        Sl     0:00          \_ calico-node -allocate-tunnel-addrs

 

 

Calico 통신 테스트

같은 노드에 있는 파드간 통신

 

아래는 Calico CNI 상에서 같은 노드 내 파드 간 통신을 배운대로 이해를 위해 똑같이 그려본 구조입니다.

아래 구조대로 Pod2개를 생성하고 통신이 어떻게 이루어지는 지를 확인해보겠습니다.

 


위와 같이 Pod를 배포하기 전의 상태를 먼저 확인하겠습니다

네트워크 인터페이스 정보를 확인하면, 터널 (ipip) 인터페이스가 존재합니다.

ip -c -d addr show tunl0
3: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 8981 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0 minmtu 0 maxmtu 0
    ipip any remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 172.16.34.0/32 scope global tunl0
       valid_lft forever preferred_lft forever

 

네트워크 라우팅 정보를 확인해보면,
위에서 소개했던 bird 정보가 있습니다.

 

bird daemon 이 BGP Routing Protocol에 의해 파드 네트워크 대역을 전달받는 경로를 볼 수 있습니다.
172.31.10.101, 2, 3를 보면 현재 존재하는 각각의 노드의 해당 대역을 확인할 수 있습니다.

ip -c route | grep bird
172.16.34.0/24 via 172.31.10.101 dev tunl0 proto bird onlink
blackhole 172.16.116.0/24 proto bird
172.16.158.0/24 via 172.31.10.102 dev tunl0 proto bird onlink
172.16.184.0/24 via 172.31.10.103 dev tunl0 proto bird onlink

 

IPAM block을 통해 일부 살펴봤던 내용과 일치합니다.

| Block    | 172.16.158.0/24 |       256 | 1 (0%)     | 255 (100%)   |
| Block    | 172.16.184.0/24 |       256 | 1 (0%)     | 255 (100%)   |
| Block    | 172.16.34.0/24  |       256 | 1 (0%)     | 255 (100%)   |

 

이제 테스트용 Pod를 배포해보겠습니다.
nodeName을 통해 같은 노드에 배포합니다.

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  nodeName: k8s-w1
  containers:
  - name: pod1
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2
spec:
  nodeName: k8s-w1
  containers:
  - name: pod2
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0

 

이제 컨트롤플레인에서 아래 커맨드로 생성되는 pod를 지켜보겠습니다.

watch -d calicoctl get workloadEndpoint

 

다른 터미널을 열어 위 pod를 배포합니다.

kubectl apply -f podtopod.yaml

 

아래와 같이 생성됩니다.

WORKLOAD   NODE     NETWORKS          INTERFACE
pod1       k8s-w1   172.16.158.3/32   calice0906292e2
pod2       k8s-w1   172.16.158.4/32   calibd2348b4f67

 

이제 해당 k8s-w1 노드에서 확인해보겠습니다.

root@k8s-w1:~# ip -c route
172.16.158.3 dev calice0906292e2 scope link
172.16.158.4 dev calibd2348b4f67 scope link

root@k8s-w1:~# ip -c link
8: calice0906292e2@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8981 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-4e600ed4-9a3c-eb9d-ebee-97e34cc46d8f
9: calibd2348b4f67@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8981 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netns cni-bf783ca6-1197-f6f6-108a-c6f80be4e79e

 

그림에서 본 대로입니다.

calice0906292e2, calibd2348b4f67 라는 두 개의 가상 인터페이스가 존재하며,
네트워크 네임스페이스 cni-4e600ed4-9a3c-eb9d-ebee-97e34cc46d8f , cni-bf783ca6-1197-f6f6-108a-c6f80be4e79e 와 연결되어 있습니다.

 

172.16.158.3 IP 로 가는 트래픽은 calice0906292e2,
172.16.158.4 IP로 가는 트래픽은 calibd2348b4f67 가상 네트워크 인터페이스를 통해 전달됩니다.

 

이전 글에서 확인해 본 (쿠버네티스 아니고 리눅스) 네트워크 네임스페이스도 확인해보겠습니다.
2개의 pause container가 개별적으로 생성되어 있다는 사실도 알 수 있습니다.
NETNSID가 0, 1에 매칭되어 있습니다.

lsns -t net
        NS TYPE NPROCS   PID USER     NETNSID NSFS                                                COMMAND
4026532215 net       2 59887 65535          0 /run/netns/cni-4e600ed4-9a3c-eb9d-ebee-97e34cc46d8f /pause
4026532275 net       2 59924 65535          1 /run/netns/cni-bf783ca6-1197-f6f6-108a-c6f80be4e79e /pause

 

 

이제 통신 정보를 확인하기 위해 확인한 가상 네트워크 인터페이스로 tcpdump를 떠보겠습니다.
노드에서 아래 커맨드를 실행합니다.

tcpdump -i calice0906292e2 -nn
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on calice0906292e2, link-type EN10MB (Ethernet), snapshot length 262144 bytes

tcpdump -i calibd2348b4f67 -nn
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on calibd2348b4f67, link-type EN10MB (Ethernet), snapshot length 262144 bytes

 

컨트롤플레인에서 pod1로 접근해서 pod2로 ping을 날려보겠습니다.

kubectl exec pod1 -it -- zsh

ping -c 10 172.16.158.4
PING 172.16.158.4 (172.16.158.4) 56(84) bytes of data.
64 bytes from 172.16.158.4: icmp_seq=1 ttl=63 time=0.152 ms
64 bytes from 172.16.158.4: icmp_seq=2 ttl=63 time=0.078 ms
64 bytes from 172.16.158.4: icmp_seq=3 ttl=63 time=0.086 ms

 

calice0906292e2의 상황

tcpdump -i calice0906292e2 -nn
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on calice0906292e2, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:09:14.372494 IP6 fe80::2ccf:73ff:fe33:4406 > ff02::2: ICMP6, router solicitation, length 16
21:10:26.898792 ARP, Request who-has 169.254.1.1 tell 172.16.158.3, length 28
21:10:26.898817 ARP, Reply 169.254.1.1 is-at ee:ee:ee:ee:ee:ee, length 28
21:10:26.898820 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 1, length 64
21:10:26.898916 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 1, length 64
21:10:27.908528 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 2, length 64
21:10:27.908585 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 2, length 64
21:10:28.932556 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 3, length 64
21:10:28.932618 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 3, length 64
21:10:31.940494 ARP, Request who-has 172.16.158.3 tell 172.31.10.102, length 28
21:10:31.940544 ARP, Reply 172.16.158.3 is-at 2e:cf:73:33:44:06, length 28

c

alibd2348b4f67의 상황

tcpdump -i calibd2348b4f67 -nn
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on calibd2348b4f67, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:10:19.908506 IP6 fe80::8401:bbff:fe03:3b0f > ff02::2: ICMP6, router solicitation, length 16
21:10:26.898873 ARP, Request who-has 172.16.158.4 tell 172.31.10.102, length 28
21:10:26.898882 ARP, Reply 172.16.158.4 is-at 86:01:bb:03:3b:0f, length 28
21:10:26.898884 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 1, length 64
21:10:26.898896 ARP, Request who-has 169.254.1.1 tell 172.16.158.4, length 28
21:10:26.898899 ARP, Reply 169.254.1.1 is-at ee:ee:ee:ee:ee:ee, length 28
21:10:26.898901 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 1, length 64
21:10:27.908567 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 2, length 64
21:10:27.908578 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 2, length 64
21:10:28.932591 IP 172.16.158.3 > 172.16.158.4: ICMP echo request, id 69, seq 3, length 64
21:10:28.932609 IP 172.16.158.4 > 172.16.158.3: ICMP echo reply, id 69, seq 3, length 64

 

위와 같이 각 가상 네트워크 인터페이스에서 통신을 확인해볼 수 있습니다.

 

ARP 요청과 응답을 보면 172.16.158.3이 172.16.158.4의 의 MAC 주소를 요청하는 패킷이 보입니다.
172.16.158.4는 자신의 MAC 주소를 ee:ee:ee:ee:ee:ee 로 응답합니다.

 

각 IP 주소가 MAC 주소로 변환되고 있으며, 172.16.158.3과 172.16.158.4가 서로의 MAC 주소를 성공적으로 알아내어 통신하고 있다는 사실을 알 수 있습니다.

 

노드에서 외부로 나가는 통신

노드의 파드에서 외부로 나가는 통신의 구조는 아래와 같습니다.

위와 같이 생성되는 Cali.. 어쩌고 하는 가상 인터페이스에서 proxy arp 설정이 있습니다.
파드와 외부간 직접 통신에서는 tunnel 인터페이스는 관여하지 않습니다.

이때 iptables에서 NAT 테이블의 cali-nat-outgoing 체인을 출력해봅니다.

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

 

Calico는 이렇게 NAT(Network Address Translation) 규칙을 설정하여 Kubernetes 클러스터 내에서 Pod가 외부 네트워크로 트래픽을 보낼 때 NAT 처리를 수행합니다.

 

MASQUERADE 부분을 볼 수 있는데, 여기서 소스 IP 주소를 변경하여 패킷을 외부로 나갈 때 호스트의 IP 주소로 바꿉니다.

 

서로 다른 노드에 있는 파드 간 통신

서로 다른 노드에 있는 파드간 통신의 구조는 아래와 같습니다.

 

위에서 배웠듯이, Bird에 의해서 BGP로 파드 네트워크 대역이 광고 전파/전달 됩니다.
그리고 Felix에 의해서 호스트의 라우팅 테이블에 자동으로 추가되거나 삭제됩니다.

 

다른 경우와 달리 서로 다른 노드에 있는 파드간의 통신은 tunl0 인터페이스를 통해서 IP 헤더에 감싸져서 다른 노드에 도달 합니다.

그리고 상대 노드의 tunl0 인터페이스에서 Outer 헤더를 제거하고 내부의 파드와 통신되는 구조입니다.

 

컨트롤 플레인에서 route -n 을 통해서 보면, tunl0 인터페이스와 Destination을 볼 수 있습니다.

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.34.0     172.31.10.101   255.255.255.0   UG    0      0        0 tunl0
172.16.116.0    0.0.0.0         255.255.255.0   U     0      0        0 *
...
172.16.158.0    172.31.10.102   255.255.255.0   UG    0      0        0 tunl0
172.16.184.0    172.31.10.103   255.255.255.0   UG    0      0        0 tunl0
...

 

Calico의 다른 라우팅 모드

위에서 설명한 서로 노드 간 파드 통신은 Default 모드인 IPIP 모드의 경우입니다.
아래 그림은 IPIP 외 다른 모드들의 통신 방식은 어떻게 다른지 설명하고 있습니다.

 

 

반응형