개요
Flannel과 마찬가지로 Calico에서의 통신 흐름을 탐구
동일 노드에서의 Pod 간 통신
동일 노드에서의 Pod 간 통신에서는 내부에서 직접적으로 통신
IPTABLES의 FORWARD 정책을 이용하고, 동일 노드에서의 Pod 간 통신에서는 tunl0 인터페이스는 관여하지 않음
169.254.1.1을 호스트에 설정하지 않고도 Proxy ARP로 처리할 수 있다는 것이며,Pod와 Calico 인터페이스는 P2P로 통신하기 때문에 호스트 L2에 도달하지 않으므로 모든 Calico 인터페이스가 동일한 맥주소 (ee:ee:ee:ee:ee:ee)를 사용해도 문제가 없음
노드 초기 상태 확인
# 터널 인터페이스 존재 확인
ip -c -d addr show tunl0
# 호스트 네트워크 네임스페이스 확인
lsns -t net
# 라우팅 경로 확인
# 루핑 방지를 위해 Blackhole이 존재
ip -c route | grep bird
# tunl0 인터페이스를 사용하는 주소 대역은 IPIP 캡슐화 되어 각 노들 전달
# 즉, 각 노드의 podCIDR을 의미
route -n
Shell
복사
Pod 배포 (node1-pod2.yaml)
노드 이름을 직접 기재했고, 이미지는 네트워크 장애 처리에 용이한 netshoot을 이용
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
Shell
복사
# Pod 확인
kubectl get po -A -o wide | grep default
# endpoint 및 veth 확인
calicoctl get workloadendpoints
Shell
복사
변경된 노드 상태 확인
# Calico 인터페이스 2개가 추가됨을 확인
ip -c link
# Pod 별 Pause 컨테이너 생성으로 네트워크 네임스페이스 생성
lsns -t net
# Calico 내용이 라우팅 테이블에 반영
ip -c route
Shell
복사
Pod 통신 이전 상태
# Pod 1 접속
kubectl exec -it pod1 -- zsh
# 호스트의 12번 인터페이스와 veth 연결
ip -c addr
# 라우팅 정보로 169.254.1.1을 디폴트 GW로 이용
route -n
# ARP 정보 없음
ip -c neigh
Shell
복사
# Pod 2 접속
kubectl exec -it pod2 -- zsh
# 호스트의 13번 인터페이스와 veth 연결
ip -c addr
# 라우팅 정보로 169.254.1.1을 디폴트 GW로 이용
route -n
# ARP 정보 없음
ip -c neigh
Shell
복사
Pod 간 통신 준비
# 좌측은 마스터 노드
# IPTABLES 필터 테이블의 FORWARD 리스트에서 cali-FORWARD를 확인
watch -d -n 1 "iptables -v --numeric --table filter --list FORWARD | egrep '(cali-FORWARD|pkts)'"
# 중간은 마스터 노드
# Pod를 통해 veth를 확인
VETH1=$(calicoctl get workloadEndpoint | grep pod1 | awk '{print $4}')
VETH2=$(calicoctl get workloadEndpoint | grep pod2 | awk '{print $4}')
echo $VETH1
echo $VETH2
# 우측 상하단은 워커 노드
VETH1=...
VETH2=...
# Proxy ARP 설정을 확인
# cat /proc/sys/net/ipv4/conf/<veth>/proxy_arp
cat /proc/sys/net/ipv4/conf/$VETH1/proxy_arp
cat /proc/sys/net/ipv4/conf/$VETH2/proxy_arp
# 우측 상단
# veth1 tcpdump
tcpdump -i $VETH1 -nn
# 우측 하단
# veth2 tcpdump
tcpdump -i $VETH2 -nn
Shell
복사
Pod 간 통신 실행
# Pod 1 접속
kubectl exec -it pod1 -- zsh
# ping 10회 수행
ping -c 10 <pod2-ip>
# 디폴트 GW 169.254.1.1의 MAC 주소를 ARP에 의해 학습
ip -c -s neigh
Shell
복사
pod1에서 디폴트 GW인 169.254.1.1의 MAC 주소를 알기 위해 ARP Request 전송하고, veth인 Calico 인터페이스에서 Proxy ARP 설정이 되어 있기 때문에 자신의 MAC 주소인 ee:ee:ee:ee:ee:ee를 알려주면서 정상 통신 가능
** 다음 실습을 위해 pod1, pod2를 삭제
외부로의 통신
Pod가 위치한 노드의 네트워크 인터페이스 IP 주소로 MASQUERADE 되는 방식으로 Pod가 외부로 통신
Calico의 기본 설정은 natOutgoing: true이므로 MASQUERADE로 연결이 가능한 것이고, 해당 방식 역시 tunl0 인터페이스는 통신에 관여하지 않음
natOutgoing: false로 설정 시, NAT의 MASQUERADE 정책이 삭제되면서 tcpdump에 요청 패킷만 있고 응답 패킷은 없음
노드 초기 상태 확인
# natOutgoing 값이 true임을 확인
calicoctl get ippool -o wide
# 노드에서 외부로 통신을 위한 MASQUERADE 정책을 확인
iptables -n -t nat --list cali-nat-outgoing
# MASQUERADE 엔트리 관련 헤더 데이터를 나열
# https://linux.die.net/man/8/ipset
ipset list cali40masq-ipam-pools
Shell
복사
Pod 배포 (node1-pod1.yaml)
노드 이름을 직접 기재
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
Shell
복사
외부로의 통신 준비
# 좌측은 마스터 노드
# iptables NAT MASQUERADE 모니터링
watch -d 'iptables -n -v -t nat --list cali-nat-outgoing'
# 중간은 마스터 노드
# Pod 연결된 veth 변수를 확인
VETH1=$(calicoctl get workloadEndpoint | grep pod1 | awk '{print $4}')
echo $VETH1
# 우측 1,2,3,4 창
# 위에서 확인한 veth를 변수에 지정
VETH1=...
# 우측 1,2,3,4 창
tcpdump -i any -nn icmp
tcpdump -i $VETH1 -nn icmp
tcpdump -i tunl0 -nn icmp
tcpdump -i ens5 -nn icmp
Shell
복사
외부로의 통신 실행
# Pod 1 접속
kubectl exec -it pod1 -- zsh
# ping 10회 수행
ping -c 10 8.8.8.8
Shell
복사
192.186.10.101은 노드의 외부로 통신할 수 있는 네트워크 인터페이스로, 외부 통신 시 IP 주소 변경되어서 기록되는 것을 확인 가능
** 다음 실습을 위해 pod1을 삭제
다른 노드에서의 Pod 간 통신
IPIP 모드에서는 tunl0 인터페이스를 거치면서 IP 헤더를 한 번 더 감싸면서 전달되고, 목적지에서의 tunl0 인터페이스에서 벗겨지면서 Pod로 도달
각 노드의 네트워크 대역은 bird에 의해서 BGP로 광고 및 전파되고, felix에 의해서 해당 내용이 반영
노드 초기 상태 확인
# 마스터 및 워커 노드에서 실행
# 특정 노드에서 나머지 노드들의 podCIDR을 테이블로 보유하고 tunl0에서 이용
route | head -2 ; route -n | grep tunl0
# 노드 별 tunl0 정보 확인
# Calico를 이용하면 Pod의 인터페이스도 기본 MTU 1480을 사용
ifconfig tunl0
Shell
복사
Pod 배포 (node2-pod2.yaml)
노드 이름을 직접 기재
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-w2
containers:
- name: pod2
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
Shell
복사
# 마스터 노드에서 실행
# 워커 노드 1, 2에 생성된 endpoint 확인
calicoctl get workloadendpoints
# 워커 노드에서 실행
# Pod 생성 후에는 각 상대방 노드의 IP를 전달 받아 저장
route -n | head -2 ; route -n | grep 172.16.
Shell
복사
Pod 간 통신 준비
# 좌측은 워커 노드
# tunl0 인터페이스 TX/RX 패킷 수 확인
watch -d 'ifconfig tunl0 | head -2 ; ifconfig tunl0 | grep bytes'
# tunl0 인터페이스 패킷 덤프
# 파일 저장은 tcpdump -i tunl0 -nn -w <filename>
tcpdump -i tunl0 -nn
# 노드 인터페이스 패킷 덤프
# 파일 저장은 tcpdump -i ens5 -nn proto 4 -w <filename>
tcpdump -i ens5 -nn proto 4
Shell
복사
Pod 간 통신 실행
# pod1 접속
kubectl exec pod1 -it -- zsh
# ping 10회 수행
ping -c 10 <pod2-ip>
Shell
복사
tunl0 인터페이스의 TX/RX 패킷 수 증가된 것을 확인할 수 있고, tunl0 인터페이스 상에서는 Pod의 IP를 확인할 수 있으며 노드 인터페이스에서는 노드들 간의 전달을 위한 IP를 확인 가능
** 다음 실습을 위해 pod1, pod2를 삭제
참고
** Flannel에서 사용했던 VXLAN과 IPIP 방식의 차이가 궁금하여 정리
기능 | IPIP | VXLAN |
캡슐화 방식 | IP 패킷을 다른 IP 패킷에 캡슐화 | UDP 패킷을 VXLAN 헤더에 캡슐화 |
헤더 오버헤드 | 20바이트 | 8바이트 |
지원하는 네트워크 | 모든 IP 네트워크 | UDP를 지원하는 네트워크 |
보안 | 암호화를 지원하지 않음 | UDP를 통해 암호화를 지원 |
IPIP, VXLAN 모두 오버레이 네트워크에서 사용되는 터널링 프로토콜
단순 패킷 전송에 중점을 둔 IPIP, 더 많은 네트워킹 기능을 지원하는 VXLAN