본문 바로가기

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

[KANS] 쿠버네티스 네트워크 (7) Calico CNI

목표:

1. Calico architecture 및 구성요소에 대해서 먼저 학습합니다.

2. 실제 구성 환경과 그림을 통해 브릿지 역할(cni0)과 터널 인터페이스(flannel.1)를 확인하며 통신과정을 분석합니다.

3. 실제 구성 환경에서 Pod 통신과정을 확인합니다.

지난 시간 복습 

  • Flannel은 아래와 같은 방법을 이용하여 트래픽을 라우팅을 합니다.
  • etcd에 라우팅 정보를 저장해두고 ARP 테이블을 지속적으로 업데이트 합니다.
  • 역사적으로 VXLAN 기능을 사용하고 Calico의 네트워크 정책 기능을 사용하기 위해 Calico와 Flannel을 동시에 사용하였습니다. 이것을 주로 Canal이라고 불렀습니다. VXLAN IP-in-IP는 네트워크 구조에 따라 선택하였습니다. VXLAN은 가상 layer 2 네트워크를 구성하기 위해 사용됩니다. 그래서 일반적인 패킷보다 조금 더 큰 헤더 사이즈를 가지게 되고 약간의 성능 저하가 발생합니다. 
  • VXLAN은 Azure와 같이 IP-in-IP 기능이 지원되지 않는 환경이나 BGP를 지원하지 않는 환경에서 사용하기 용이합니다.

Calico 동작방식

 

A. calico datastore

a. 칼리코 동작을 위한 데이터 저장하는 곳
b. 저장소는 쿠버네티스 API 저장소 (기본설정값) 혹은 ETCD 선택 가능 

 

B. Bird

a. 오픈소스 소프트웨어 라우팅 데몬 프로그램
b. BGP 라우팅 프로토콜 이용 -> 광고
c. 각 노드는 다른 모든 노드의 파드 대역과 통신할수 있음.

 

C.  Felix

a. 버드로 학습한 상대방 노드의 파드 네트워크 대역을 호스트의 라우팅 테이블에 최종적으로 업데이트 하는 역할
b. IPtables 규칙 설정 관리 

 

D. confd

a. 가벼운 오픈소스 설정 변경 관리 툴
b. BGP 설정 등으로 칼리코 데이터 저장소에 변경이 발생하면 버드의 변경된 설정 파일을 만들고, 변경된 설정 파일을 반영

 

E. IPAM

a. 칼리코 자체의 IPAM 플러그인을 제공하며 플라넬은 기본 IPAM인 host-local IPAM을 사용함

 

실습 

# 버전 확인 - 링크
## kdd 의미는 쿠버네티스 API 를 데이터저장소로 사용 : k8s API datastore(kdd)
calicoctl version

Client Version:    v3.28.1
Git commit:        601856343
Cluster Version:   v3.28.1
Cluster Type:      k8s,bgp,kubeadm,kdd

# calico 관련 정보 확인
kubectl get daemonset -n kube-system
kubectl get pod -n kube-system -l k8s-app=calico-node -owide

kubectl get deploy -n kube-system calico-kube-controllers
kubectl get pod -n kube-system -l k8s-app=calico-kube-controllers -owide

# 칼리코 IPAM 정보 확인 : 칼리코 CNI 를 사용한 파드가 생성된 노드에 podCIDR 네트워크 대역 확인 
calicoctl ipam show

# Block 는 각 노드에 할당된 podCIDR 정보
calicoctl ipam show --show-blocks
calicoctl ipam show --show-borrowed
calicoctl ipam show --show-configuration


Quiz. 바로 위 Calico IPAM 과 아래 출력되는 IPAM 의 차이는 무엇일까요? 우선순위 확인 - 링크 , Flannel 과 차이
- 별도의 IPAM 이 있다면 무엇을 할 수 있을까요? (예. 특정 파드/네임스페이스에 파드의 네트워크 대역을 추가 및 삭제)
1. Kubernetes annotations
2. CNI configuration (Calico IPAM)
3. IP pool node selectors

# host-local IPAM 정보 확인 : k8s-m 노드의 podCIDR 은 host-local 대신 칼리코 IPAM 를 사용함
## 워커 노드마다 할당된 dedicated subnet (podCIDR) 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo
kubectl get node k8s-m -o json | jq '.spec.podCIDR'

# CNI Plugin 정보 확인 - 링크
tree /etc/cni/net.d/
cat /etc/cni/net.d/10-calico.conflist | jq
...
			"datastore_type": "kubernetes", # 칼리코 데이터저장소는 쿠버네티스 API 를 사용
      "ipam": { 
          "type": "calico-ipam" # IPAM 은 칼리코 자체 IPAM 을 사용
      },
...

# calicoctl node 정보 확인 : Bird 데몬(BGP)을 통한 BGP 네이버 연결 정보(bgp peer 는 노드의 IP로 연결) - 링크
calicoctl node status
calicoctl node checksystem

# ippool 정보 확인 : 클러스터가 사용하는 IP 대역 정보와 칼리코 모드 정보 확인
calicoctl get ippool -o wide

# 파드와 서비스 사용 네트워크 대역 정보 확인 
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
        "--service-cluster-ip-range=10.200.1.0/24",
        "--cluster-cidr=172.16.0.0/16",

kubectl get cm -n kube-system kubeadm-config -oyaml | grep -i subnet
 podSubnet: 172.16.0.0/16
 serviceSubnet: 10.96.0.0/12

# calico endpoint (파드)의 정보 확인 : WORKLOAD 는 파드 이름이며, 어떤 노드에 배포되었고 IP 와 cali 인터페이스와 연결됨을 확인
calicoctl get workloadEndpoint
calicoctl get workloadEndpoint -A
calicoctl get workloadEndpoint -o wide -A

# 노드에서 컨테이너(프로세스) 확인 : pstree
ps axf
   4405 ?        Sl     0:09 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id dd532e5efaad436ebe7d10cdd3bb2ffe5a2873a0604ce3b
   4425 ?        Ss     0:00  \_ /pause
   4740 ?        Ss     0:00  \_ /usr/local/bin/runsvdir -P /etc/service/enabled
   4811 ?        Ss     0:00      \_ runsv allocate-tunnel-addrs
   4819 ?        Sl     0:00      |   \_ calico-node -allocate-tunnel-addrs
   4812 ?        Ss     0:00      \_ runsv bird
   4994 ?        S      0:00      |   \_ bird -R -s /var/run/calico/bird.ctl -d -c /etc/calico/confd/config/bird.cfg
   4813 ?        Ss     0:00      \_ runsv cni
   4822 ?        Sl     0:00      |   \_ calico-node -monitor-token
   4814 ?        Ss     0:00      \_ runsv bird6
   4993 ?        S      0:00      |   \_ bird6 -R -s /var/run/calico/bird6.ctl -d -c /etc/calico/confd/config/bird6.cfg
   4815 ?        Ss     0:00      \_ runsv confd
   4820 ?        Sl     0:00      |   \_ calico-node -confd
   4816 ?        Ss     0:00      \_ runsv felix
   4824 ?        Sl     0:54      |   \_ calico-node -felix
   4817 ?        Ss     0:00      \_ runsv node-status-reporter
   4823 ?        Sl     0:00      |   \_ calico-node -status-reporter
   4818 ?        Ss     0:00      \_ runsv monitor-addresses
   4825 ?        Sl     0:00          \_ calico-node -monitor-addresses
  • calico-node 팟 (데몬셋)
  • 데몬셋으로 각 노드에 calico-node 파드가 동작하여, 해당 파드에 bird, felix, confd 등이 동작
  • Calico 컨트롤러 파드디플로이먼트로 생성

  • IPAM 
    • IP를 할당해주고 관리해주는 시스템 기본적으로 OS에는 local-IPAM이 존재함
    • host-local IPAM 정보 확인 : k8s-m 노드의 podCIDR 은 host-local 대신 칼리코 IPAM 를 사용함
    •  워커 노드마다 할당된 dedicated subnet (podCIDR) 확인

 

 

목표:
  • 동일 노드 내의 파드간 통신 
  • 다른 노드 내의 파드간 통신
  • 외부 통신 

 라우팅 ? 경로를 탐색하는 것 pod에서 통신 시 어떤 인터페이스를 통해 목적지에 갈 것인지 경로를 탐색하는 과정이 필요하다 

 

동일 노드 내의 파드간 통신

동일 노드 내의 파드간 통신   동일 노드 내의 파드 간 통신은 내부에서 직접 통신

 

# 네트워크 인터페이스 정보 확인 : 터널(ipip) 인터페이스가 존재!
ip -c -d addr show tunl0

# 네트워크 네임스페이스 확인
lsns -t net

# 네트워크 라우팅 경로 정보 확인
# 이중 bird 는 bird 데몬이 BGP 라우팅 프로토콜에 의해 파드 네트워크 대역을 전달받거나 전달하는 경로 → 각각 노드의 파드 대역입니다
Quiz. blackhole 라우팅은 왜 있을까요? 
ip -c route | grep bird

# 아래 tunl0 Iface 에 목적지 네트워크 대역은 ipip 인캡슐레이션에 의해서 각 노드에 전달됩니다 → 각각 노드의 파드 대역입니다
route -n
root@k8s-w1:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.10.1    0.0.0.0         UG    100    0        0 eth0
172.16.34.0     192.168.20.100  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.116.1    0.0.0.0         255.255.255.255 UH    0      0        0 cali14154d1369c
172.16.116.2    0.0.0.0         255.255.255.255 UH    0      0        0 calid3300767cec
172.16.116.3    0.0.0.0         255.255.255.255 UH    0      0        0 cali630f6961fbb
172.16.158.0    192.168.10.101  255.255.255.0   UG    0      0        0 tunl0
172.16.184.0    192.168.10.102  255.255.255.0   UG    0      0        0 tunl0
192.168.0.2     192.168.10.1    255.255.255.255 UGH   100    0        0 eth0
192.168.10.0    0.0.0.0         255.255.255.0   U     100    0        0 eth0
192.168.10.1    0.0.0.0         255.255.255.255 UH    100    0        0 eth0

# (옵션) iptables rule 갯수 확인 >> iptables rule 이 모든 노드에서 똑같나요? 다른가요?
iptables -t filter -S | grep cali | wc -l
iptables -t nat -S | grep cali | wc -l

 

Pod IP 범위로의 잘못된 트래픽 차단
  • Kubernetes와 같은 시스템에서 tunl0와 같은 터널링 인터페이스는 클러스터 노드 간의 통신을 위한 오버레이 네트워크를 구성하는 데 사용됩니다. 일반적으로 Flannel, Calico 등의 CNI 플러그인은 노드에 할당된 Pod IP 주소 범위를 관리하고, 네트워크 트래픽을 올바른 대상 노드로 라우팅합니다.
  • 노드에 할당된 podCIDR(32bit 다수 광고 방지!) 을 BGP 로 광고하고, 루핑 방지 를 위해서 blackhole(null) 라우팅을 추가한다.
  • blackhole 라우팅을 통해 이러한 유효하지 않은 IP 대역으로 들어오는 트래픽을 네트워크 스택에서 조기에 차단하여 불필요한 라우팅과 네트워크 자원 낭비를 방지할 수 있습니다. 이는 클러스터의 네트워크 안정성을 높이고 성능을 최적화하는 데 도움이 됩니다.

 

iptables rule 이 모든 노드에서 전부 다르다
 iptables rule 갯수 확인 : 필터(filter)에 기본 39 에서 91개로 룰(rule)이 많이 추가 되었다
>>
파드가 많아지면 rule 갯수는 늘어남

 

실습

 

  • node 1번에 팟 배포
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

# 마스터 노드에서 아래 실행
kubectl exec pod1 -it -- zsh
...
# 아래 처럼 호스트 네트워크 인터페이스 9번인 caliceY와 veth 연결되어 있다
# IP는 32bit 서브넷을 가진다
pod1> ip -c addr
...
4: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether ee:2f:83:31:9b:d0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.16.158.2/32 brd 172.16.158.2 scope global eth0
       valid_lft forever preferred_lft forever

# 라우팅 정보에 169.254.1.1 를 디폴트 게이트웨이 주소로 사용
pod1> route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         169.254.1.1     0.0.0.0         UG    0      0        0 eth0
169.254.1.1     0.0.0.0         255.255.255.255 UH    0      0        0 eth0

# ARP 정보는 현재 아무것도 없다
ip -c neigh
169.254.1.1 뭘까?
# 라우팅 정보에 169.254.1.1 를 디폴트 게이트웨이 주소로 사용
# calice 인터페이스(파드에 연결된 가상 인터페이스)에 proxy_arp 파일  (활성화)


ARP 정보를 어떻게 가져올까? - proxy_arp 기능을 이용 
# 파드1에서 게이트웨이의 IP인 169.254.1.1 의 MAC 주소를 알기 위해서 ARP Request 를 보낸다
# 이때 veth 연결된 calice#~ 에 proxy arp 설정이 되어 있고, 자신의 mac 주소(ee:ee:ee:ee:ee:ee)를 알려주고, 이후 정상 통신됨

최초 통신 이후에는 arp table에 veth 인터페이스의 맥주소를 학습함.



 

 


 

다른 노드 내의 파드간 통신

다른 노드 내의 파드간 통신  다른 노드 내의 통신 - 기본값이 IPIP 터널 통신을 통해서 다른 노드로의 통신이 이루어집니다.

  • 각 노드에 파드 네트워크 대역은 Bird 에 의해서 BGP 로 광고 전파/전달 되며, Felix 에 의해서 호스트의 라우팅 테이블에 자동으로 추가 및 삭제 됩니다
  • 다른 노드 간의 파드 통신은 tunl0 인터페이스를 통해 IP 헤더에 감싸져서 상대측 노드로 도달 후 tunl0 인터페이스에서 Outer 헤더를 제거하고 내부의 파드와 통신됩니다

  • 실제 패킷 확인 시 Outer IP 헤더와 Inner IP 헤더가 보인다 ( wireshark )

실습

  • 기본 상태 : 각 노드들의 파드 대역을 라우팅 테이블에 가지고 있고, 해당 경로에 대해서는 tunl0 인터페이스로 보냄
# 아래 명령어로 확인 시 나머지 노드들의 파드 대역을 자신의 호스트 라우팅 테이블에 가지고 있고, 해당 경로는 tunl0 인터페이스로 보내게 된다
route | head -2 ; route -n | grep tunl0

# 마스터노드, 노드1~
root@k8s-m:~# route | head -2 ; route -n | grep tunl0
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.46.0     192.168.100.102 255.255.255.192 UG    0      0        0 tunl0
172.16.197.0    192.168.100.103 255.255.255.192 UG    0      0        0 tunl0
172.16.228.64   192.168.100.101 255.255.255.192 UG    0      0        0 tunl0

# 노드1
root@k8s-w1:~# route | head -2 ; route -n | grep tunl0
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.29.0     192.168.100.10  255.255.255.192 UG    0      0        0 tunl0
172.16.46.0     192.168.100.102 255.255.255.192 UG    0      0        0 tunl0
172.16.197.0    192.168.100.103 255.255.255.192 UG    0      0        0 tunl0
...

# 터널 인터페이스에 IP가 할당되어 있고, MTU는 1480(IP헤더 20바이트 추가를 고려)을 사용하며, 현재 TX/RX 카운트는 0 이다
# Calico 사용 시 파드의 인터페이스도 기본 MTU 1480 을 사용한다
ifconfig tunl0

# 노드1
root@k8s-w1:~# ifconfig tunl0
tunl0: flags=193<UP,RUNNING,NOARP>  mtu 1480
        inet 172.16.228.76  netmask 255.255.255.255
        tunnel   txqueuelen 1000  (IPIP Tunnel)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# 노드2
root@k8s-w2:~# ifconfig tunl0
tunl0: flags=193<UP,RUNNING,NOARP>  mtu 1480
        inet 172.16.46.10  netmask 255.255.255.255
        tunnel   txqueuelen 1000  (IPIP Tunnel)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 

  • 노드 1, 2번에 파드 배포

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
    • pod가 생성되면 각 노드에 route table에 추가가 됨 

  • 이부분에 dump를 떠서 보겠다 (tunl0인터페이스로 통하는 패킷을 확인하기 위해)

  • 노드 1 기준 패킷 카운트 모니터링 
# tunl0 인터페이스 TX/RX 패킷 카운트 모니터링 실행
watch -d 'ifconfig tunl0 | head -2 ; ifconfig tunl0 | grep bytes'
root@k8s-w1:~# watch -d 'ifconfig tunl0 | head -2 ; ifconfig tunl0 | grep bytes'
tunl0: flags=193<UP,RUNNING,NOARP>  mtu 1480
        inet 172.16.228.76  netmask 255.255.255.255
        RX packets 0  bytes 0 (0.0 B)
        TX packets 0  bytes 0 (0.0 B)

# (옵션) 패킷 덤프 : calice#
tcpdump -i -nn <calice#>

# 패킷 덤프 : tunl0
tcpdump -i tunl0 -nn
혹은 패킷을 파일로 추출시
tcpdump -i tunl0 -nn -w /tmp/calico-tunl0.pcap

# 패킷 덤프 : IP 헤더에 상위 프로토콜을 IPIP(4)인 패킷만 필터
#tcpdump -i <각자 자신의 노드의 eth0> -nn proto 4
tcpdump -i ens5 -nn proto 4    # [실습환경 A Type]
tcpdump -i enp0s8 -nn proto 4  # [실습환경 B Type]
혹은 패킷을 파일로 추출시
tcpdump -i ens5 -nn proto 4 -w /tmp/calico-ipip.pcap   # [실습환경 A Type]
tcpdump -i enp0s8 -nn proto 4 -w /tmp/calico-ipip.pcap # [실습환경 B Type]


# [실습환경 A Type] 자신의 PC로 패킷 복사
scp ubuntu@<node 유동공인 IP>:/tmp/calico-ipip.pcap .
scp ubuntu@3.35.229.92:/tmp/calico-ipip.pcap .


# [실습환경 B Type] 자신의 PC로 패킷 복사 : https://github.com/invernizzi/vagrant-scp
## vagrant scp 플러그인 설치
vagrant plugin install vagrant-scp
vagrant plugin list

## k8s-w1 VM 내부의 파일을 자신의 PC(호스트)에 복사
vagrant scp k8s-w1:/tmp/calico-ipip.pcap ./
node1의 tunl0 패킷을 확인해보면 tunl0인터페이스의 IP만 보이는것을 알수 있다.

node1의 eth0 패킷을 확인해보면 IPIP 프로토콜을 확인 할수 있다.

목표
외부 통신

외부 통신  파드에서 외부(인터넷) 통신 시에는 해당 노드의 네트워크 인터페이스 IP 주소로 MASQUERADE(출발지 IP가 변경) 되어서 외부에 연결됨

 

  • pod와 외부 통신에는 터널 인터페이스 미관여 

실습

  • calico 설정 정보 확인 & 노드에 iptables 확인
# 마스터 노드에서 확인 : natOutgoing 의 기본값은 true 이다
calicoctl get ippool -o wide
NAME                  CIDR            NAT    IPIPMODE   VXLANMODE   DISABLED   SELECTOR
default-ipv4-ippool   172.16.0.0/16   true   Always     Never       false      all()

# 노드에서 확인 : 노드에서 외부로 통신 시 MASQUERADE 동작 Rule 확인
iptables -n -t nat --list cali-nat-outgoing
root@k8s-w1:~# 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

ipset list
ipset list cali40masq-ipam-pools
  • 파드배포
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
  • 통신확인
# 노드에서 실행
# iptables NAT MASQUERADE 모니터링 : pkts 증가 확인
watch -d 'iptables -n -v -t nat --list cali-nat-outgoing'

# (컨트롤플레인 노드) 파드 연결된 veth 를 변수를 확인
VETH1=$(calicoctl get workloadEndpoint | grep pod1 | awk '{print $4}')
echo $VETH1

# (워커노드1) 위에서 확인한 파드 연결된 veth 를 변수에 지정
VETH1=<각자 실습 환경에 따라 다름>
VETH1=calice0906292e2

# 패킷 덤프 실행 >> 어떤 인터페이스에서 패킷 캡쳐 확인이 되나요?
tcpdump -i any -nn icmp
tcpdump -i $VETH1 -nn icmp
tcpdump -i tunl0 -nn icmp
tcpdump -i ens5 -nn icmp    # [실습환경 A Type]
tcpdump -i enp0s8 -nn icmp  # [실습환경 B Type]
tcpdump -i enp0s3 -nn icmp  # [실습환경 B Type]

1) tcpdump -i $VETH1 -nn icmp

2) tcpdump -i tunl0 -nn icmp

  • 패킷없음 - 결론 : 외부로 나가는 통신에는 터널인터페이스를 통하지 않는다.

2) tcpdump -i eth0 -nn icmp