Dev / App Infra

K8s Network - Calico Pod-To-Pod 통신(내부&외부) 검증 실습

K8s Network

11 min read
K8s Network - Calico Pod-To-Pod 통신(내부&외부) 검증 실습

Calico

Calico는 CNI 중 하나로, BGP 프로토콜을 사용하는 Non-overlay Network입니다.
이전에 알아봤던, Flannel이 Tunneling(vxlan, ip-in-ip)를 통한 Overlay Network인 점과는 다른 점이라고 할 수 있습니다. 다만, 네트워크 상황에 따라 Calico도 Overlay Network를 활용할 수 있어 범용성이 조금 더 넓습니다.

Overlay Network의 반대말은?

“Underlay Network” 또는 “Direct Routing (Native Routing)”입니다.

Calico daemonset

Calico는 Flannel과 마찬가지로, daemonset으로 배포되어 각 노드에 배포됩니다.
이때, Flannel과는 조금 다른 점이 있는데 Calico 구성 시, Control Plane에는 daemonset pod 뿐만 아니라, Calico controller가 추가로 생성됩니다.

추가로, Caclio는 라우팅 정보를 K8s 컴포넌트인 ETCD에 데이터를 저장합니다.

ETCD

Calico 컴포넌트

Calico CNI에는 여러 컴포넌트들이 존재합니다. IPAM, Felix, Bird 등이 이에 속하며, 컴포넌트들은 하나의 Pod로 노드에서 구동됩니다.

1. IPAM

IPAM은 IP Address Management로 Pod에게 IP주소를 할당하고 관리하는 역할을 담당합니다.
따로, 설정을 변경하지 않는 이상 default로 사용되며, 하나의 ip pool이 기본입니다.
하지만, 필요한 경우 Pod CIDR을 쪼개 여러개의 IP Pool을 구성해서 node, namespace, pod annotion에 따라 선택이 가능합니다.

IPAM은 구성도처럼 노드에 구성되며, 각 노드마다 1개 이상의 IP Block을 가질 수 있습니다.

calicoctl ipam show #calico가 관리하는 CIDR 대역을 확인 가능합니다.

calicoctl 명령 사용하려면?

curl -o calicoctl -L https://github.com/projectcalico/calico/releases/download/v3.27.2/calicoctl-linux-amd64
chmod +x calicoctl
sudo mv calicoctl /usr/local/bin/
calicoctl ipam show --show-blocks
IPAM 컴포넌트로 인하여, 각 노드별로 POD에 대한 CIDR 대역을 가집니다. 이때, 해당 대역을 IPAM Block으로 관리합니다.

2. Felix

felix는 Routing Table 및 iptables 설정을 관리합니다.
두 설정을 통하여, 자신의 노드 및 다른 노드에 대하여 Pod-to-Pod 통신을 관리합니다.

iptables -t filter -S | grep cali #전체 iptables 확인
iptables -t nat -S | grep cali #NAT 관련 iptables 확인
3. Bird

bird는 각 노드의 라우팅 정보를 BGP프로토콜을 통하여 공유합니다.

ip -c route #라우팅 정보 확인
ip -c route | grep bird #bird 관련 라우팅 정보 확인 시, tunl이라는 인터페이스를 확인할 수 있습니다.

사담으로 bird 구성 관련 이슈가 있었던 점을 공유합니다.

<구성 과정>
1. K8s Cluster 구성 후, Calico CNI 설치(Worker Node Join 전)
-> Control Plane에서 정상적으로 Calico Pod 동작 확인

  1. Worker Node Join 후
    -> 정상 동작 중이던 Calico Pod 및 Worker Node에 자동 생성된 Pod도 이슈 발생

<이슈 원인 및 해결>

--> 구성중 이슈가 있었지만, 위 내용을 통해 Calico가 BGP 프로토콜을 통해서 Node간 라우팅 정보를 공유한다는 사실을 더욱 명확하게 알 수 있었습니다.

Calico 네트워크 인터페이스

calico는 여러 네트워크 인터페이스를 통해 내부/외부 통신을 가능하게 합니다.

1. tunl NIC

Calico는 Pod-to-Pod 통신을 위하여, tunl NIC가 host 네트워크 네임스페이스(root)에 생성됩니다.

ip a | grep tunl
calicoctl get ippools -o wide # IPIPMODE = Always 인 경우 IPIP 터널링 사용

2. calice NIC

calice는 Pod와 vRouter를 연결하는 NIC입니다.

ip -c addr show | grep cali A -5 #calice NIC의 link-netnsid 확인

Pod 생성 후 calice 인터페이스 확인

calicoctl get workloadendpoints -o wide #Pod가 어떤 calice NIC를 사용하는지 확인
ip -a #(Pod가 생성된 Worker Node에서 실행) - 생성된 Pod 개수마다 calice NIC가 생성되어 매치됨
kubectl exec -it pod1 – ip link #Pod 내부의 NIC 확인

정말 Pod의 NIC인 eth@if17이 Host의 calice0906292e2 NIC와 pair 되어있을까?

Pod <> Host NIC Pair 검증

crictl ps --name pod1 #Pod의 Container ID 확인
crictl inspect <container ID> | jq '.info.pid'#Container의 PID 확인
nsenter -t 163962 -n ip link show eth0 #Pod에서 eth0에 연결된 Host NIC 확인
ip -o link | grep '^17:' #17 index인 NIC 확인

--> 확인 시, Worker1에서 실행 중인 Pod와, Worker1의 NIC가 매치됨을 확인

Pod-to-Pod 통신 실습

1. 동일 Node의 Pod 간 통신

하나의 Node에 두 개의 Pod 생성

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

Worker1 내부에 Pod1, 2를 생성 후 통신 시, 아래의 빨간 선과 같이 vRouter -> calice NIC -> Pod eth0 구조의 통신이 이뤄지게 됩니다.

Proxy_ARP

kubernetes 환경과 같이 가상의 네트워크 장비를 사용하면, 실제 MAC 주소를 사용하지 못합니다.
그렇기에, 목적 Pod의 MAC을 알 수 없을 때, Host 인터페이스가 ARP 응답을 대신해서 라우팅 가능하게 하는 기술을 Proxy_ARP라고 합니다.

Calico CNI를 활용하는 경우, Pod 간 통신 시 Proxy_ARP를 활용하게 됩니다.

왜 Pod > Pod2 통신 시, Calico의 default GW로 요청이 전달될까?

이유는 Calico가 존재하는 OSI 7Layer에 있습니다.

Calico는 L3 layer를 사용합니다.
Pod1 > Pod2로 Ping을 보낼 때, Pod1의 내부 Routing Table에는 /32 단위의 경로가 존재하지 않습니다.
Pod의 내부 routing table에는 default route만이 존재합니다. 이때, default route를 통해, Host Network Namespace로 트래픽을 모두 위임하게 됩니다.
즉, 기본 경로(Default route)는 Pod1 <> Host를 연결하는 veth pair의 반대쪽(host side)로 향하게 됩니다

--> Pod1 내부에는 ip route가 다음과 같음:

default via <veth peer IP> dev eth0 
#이 via 주소가 Calico가 만든 host veth endpoint (caliXXXX) 의 주소입니다. 즉, Calico의 "default gateway"

default via <veth peer IP> dev eth0 - 이 via 주소가 Calico가 만든 host veth endpoint (caliXXXX) 의 주소입니다. 즉, Calico의 "default gateway"

2. 타 Node 간 Pod 통신

각 Node에 Pod 생성

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

두 노드간 Pod 통신 시, 아래 빨간 선과 같이 tunl0 NIC를 활용하여 통신이 이뤄집니다.

tunl0 인터페이스를 tcpdump를 걸고 확인 시, Pod1 > Pod2로 통신 시에 해당 인터페이스를 거치는 것을 확인 가능합니다. 앞서 설명한 바와 같이, IPIP 모드(default)를 사용하여 Overlay로 tunneling 통신됩니다.

3. Pod의 외부 통신

Calico CNI 또한 Flannel CNI와 마찬가지로 MASQUERADE 기술을 통해 외부 통신이 됩니다. MASQUERADE는 iptables에서 사용하는 SNAT 방식으로, 내부 네트워크의 출발지 IP를 외부 인터페이스의 IP로 동적으로 변환해주는 기능입니다.

Calico 설치 시, 각 노드별로 MASQUERADE, iptables, route 설정을 합니다.

외부 통신 시의 흐름도는 아래와 같습니다.

iptables -n -v -t nat --list cali-nat-outgoing #iptables에 설정된 NAT 설정 조회

흐름도와 같이 외부 통신은 eth0을 통해서 나가게 되기에, tcpdump를 eth0에 걸어둡니다.

-> eth0의 IP가 10.0.220.7이다.
--> 즉, Pod의 IP(출발지 IP)가 eth0의 IP(외부 인터페이스 IP)로 변경된다는 것!

Share This Post

Check out these related posts

개발팀의 애자일 도입 이야기2

개발팀의 애자일 도입 이야기 1

Lambda@Edge 고급 로깅 제어 기능