Calico CNI
Calico는 쿠버네티스 뿐만 아니라 오픈시프트, 도커EE, 오픈 스택과 같은 서비스를 지원하는 Network 플러그인입니다.
주로 대규모 클러스터에서 효율적으로 작동하고, 네트워크 정책을 통한 마이크로 세그맨테이션을 지원합니다.
최근?에는 eBPF를 지원하므로 네트워킹 성능을 크게 향상 시켰습니다.
eBPF의 장점은 네트워크 경로가 단순화되어 지연 시간이 감소하고, 처리량이 증가됩니다.
더 세밀한 네트워크 정책 적용이 가능합니다.
실습 환경 구성
실습은 AWS 환경에서 EC2를 통해 진행했으며, 실습 구조는 아래와 같습니다.
AWS 환경에서 테스트를 진행하므로 route 전용 노드는 제외했습니다.
CNI 설치가 아직 되지 않아, node의 상태가 NotReady로 되어있습니다.
내부에서 ip 관련 정보들도 조회해 봅니다.
Calico CNI를 배포해 봅니다.
# 공식 yaml 파일은 아래와 같지만 실제 설치 시 CALICO_IPV4POOL_BLOCK_SIZE를 24로 변경해 설치했습니다.
$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml
설치 이전에는 /opt/cni/bin 하위에 calico 관련 프로그램이 없었으나, 설치 이후에 생성된 것을 확인할 수 있고,
iptables 역시 기존 filter : 50개, nat : 48개 였던 정책이 증가한 것을 확인할 수 있습니다.
## 설치 전 ##
(⎈|HomeLab:N/A) root@k8s-m:/opt/cni/bin# iptables -t filter -L | wc -l
50
(⎈|HomeLab:N/A) root@k8s-m:/opt/cni/bin# iptables -t nat -L | wc -l
48
## 설치 후 ##
(⎈|HomeLab:N/A) root@k8s-m:~# iptables -t filter -L | wc -l
108
(⎈|HomeLab:N/A) root@k8s-m:~# iptables -t nat -L | wc -l
126
조금 더 calico를 알아보고 사용해보기 위해 calicoctl을 설치해보겠습니다.
calicoctl은 calico의 리소스 관리(정책, ip풀, 노드 등)와 상태 점검을 하는데에 사용됩니다.
$ 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
위의 명령어들이 오류 없이 수행되면 아래와 같이 정상적으로 설치된 것을 확인할 수 있습니다.
calico 설치 이후에 not ready 상태였던 worker node들의 상태도 Ready 상태로 전환된 것을 확인할 수 있습니다.
리소스 생성 확인하기 이전 실습들에서 kube-ops-view는 여러번 사용하여 kubeview라는 툴로 확인해보겠습니다.
kubeview와 kube-ops-view의 차이점은 node 별로 생성 자원에 대해 보여주는 ops-view와 달리 kube-view는 control-plan 입장에서 자원의 상태를 보여주는 데에 차이가 있습니다.
최소 5초의 refresh 타임을 통해 동기화하고, 웹 페이지도 가볍게 반응합니다.
## kube view 설치 ##
$ helm repo add cowboysysop https://cowboysysop.github.io/charts/
$ helm install my-release cowboysysop/kubeview
앞서 배포한 calico node의 상태도 확인해 볼 수 있고, 자원을 클릭하면 아래와 같이 세부 정보도 확인할 수 있습니다.
메트릭 서버 역시 이후 사용을 위해 설치해 놓겟습니다.
# metrics-server
$ helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
$ helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
$ kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server
$ kubectl get apiservices |egrep '(AVAILABLE|metrics)'
# 확인
$ kubectl top node
$ kubectl top pod -A --sort-by='cpu'
$ kubectl top pod -A --sort-by='memory'
Calico 아키텍처
1. kubectl 또는 calicoctl을 사용하여 파드를 생성하거나, calico 설정을 변경하면 calico datastore에 정보가 반영됩니다.
2. calicoctl은 칼리코 오브젝트를 CRUD를 하 수 있습니다.
3. Calico datastore는 칼리코 동작을 위해 데이터를 저장하며, 저장소는 쿠버네티스 api(기본 설정) 또는 etcd를 선택할 수 있습니다.
etcd로 선택할 경우 flow 관련 설정에 대한 정의가 필요하므로 주로 api를 사용합니다.
4. bird는 opensource 라우팅 데몬 프로그램으로, 노드의 파드 네트워크 대역을 BGP 라우팅 프로토콜을 통해 광고합니다.
5. Felix는 버드로 학습한 상대방 노드의 파드 네트워크 대역을 호스트의 라우팅 테이블에 최종적으로 업데이트하며, iptables 규칙 설정을 관리합니다.
배포된 calico는 4개 node와 1개의 controller로 이루어져있음을 알 수 있습니다.
calicoctl로 확인하면 현재 calico cni에서 할당 가능한 cidr을 보여주며,
아래 정보들을 ipam show를 통해 확인할 수 있습니다.
항목 | 설명 |
IP 풀 (IP Pool) | 사용 가능한 IP 주소 범위를 보여줍니다. |
할당된 IP (Allocated) | 현재 노드나 파드에 할당된 IP 주소의 수를 표시합니다. |
미할당 IP (Unallocated) | 아직 할당되지 않은 사용 가능한 IP 주소의 수를 나타냅니다. |
블록 (Blocks) | IP 주소 블록의 수와 각 블록의 크기를 보여줍니다. |
노드별 할당 (Allocations by node) | 각 노드에 할당된 IP 주소의 수를 나열합니다. |
오프셋 (Offsets) | 특정 IP 주소가 할당된 위치에 대한 정보를 제공합니다. |
--show-blocks 옵션을 사용하면 각 worker node가 pod에 할당하기 위해 보유하고 있는 대역들을 확인 할 수 있습니다.
아래의 route 정보를 보면 터널 인터페이스의 정보를 BGP 라우팅 데몬을 통해 bird로 학습되어 자동으로 설정된 것을 확인할 수 있습니다.
root@k8s-m:~# ip -c route
default via 192.168.10.1 dev ens5 proto dhcp src 192.168.10.10 metric 100
172.16.34.0/24 via 192.168.20.100 dev tunl0 proto bird onlink
blackhole 172.16.116.0/24 proto bird
172.16.158.0/24 via 192.168.10.101 dev tunl0 proto bird onlink
172.16.184.0/24 via 192.168.10.102 dev tunl0 proto bird onlink
kubernetes에서는 host-local이라는 자체 ip pam을 보유하고 있지만, prod 환경에서 사용하는 CNI들은 자체적으로 ip pam을 보유하고 있다.
ip pam이 적용되는 우선순위는 annotation, cni configuration, ip pool node selector 입니다.
## host-local ##
$ kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo
172.16.0.0/24 172.16.2.0/24 172.16.1.0/24 172.16.3.0/24
calicoctl node status 명령어를 통해 각 노드의 peer ip를 확인할 수 있고,
checksystem을 통해 현재 calico 데몬의 상태 이상 여부를 확인 할 수 있습니다.
$ calicoctl node status
Calico process is running.
IPv4 BGP status
+----------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+----------------+-------------------+-------+----------+-------------+
| 192.168.20.100 | node-to-node mesh | up | 05:28:09 | Established |
| 192.168.10.101 | node-to-node mesh | up | 05:28:10 | Established |
| 192.168.10.102 | node-to-node mesh | up | 05:28:08 | Established |
+----------------+-------------------+-------+----------+-------------+
get ip pool을 통해 현재 할당되어 있는 ip pool을 확인할 수 있으며,
추가되는 ip pool 역시 확인 할 수 있습니다.
$ calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED DISABLEBGPEXPORT SELECTOR
default-ipv4-ippool 172.16.0.0/16 true Always Never false false all()
# calico endpoint (파드)의 정보 확인 : WORKLOAD 는 파드 이름이며, 어떤 노드에 배포되었고 IP 와 cali 인터페이스와 연결됨을 확인
$ calicoctl get workloadEndpoint
$ calicoctl get workloadEndpoint -A
$ calicoctl get workloadEndpoint -o wide -A
파드 <-> 파드간 통신
Step 1. 파드 생성 전 1번 노드의 기본 정보를 확인해 봅니다.
# 네트워크 인터페이스 정보 확인 : 터널(ipip) 인터페이스가 존재!
$ ip -c -d addr show tunl0
5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 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.158.0/32 scope global tunl0
valid_lft forever preferred_lft forever
$ lsns -t net
root@k8s-w1:~# lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 129 1 root unassigned /sbin/i
Step 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-w1
containers:
- name: pod2
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
kubeview에서도 잘 생성되었는지 확인해 봅니다.
파드 배포 후 확인
worker node에서 신규 추가된 라우팅은 칼리코에서 pod에 할당 할 ip가 표시되고 있습니다.
이 처리가 완료되면 pod에 동일한 ip가 할당되는 것을 확인할 수 있습니다.
calicoctl 명령어를 통해서 엔드포인트를 조회하여 veth 정보도 확인할 수 있습니다.
link 조회 시에도 신규 할 당된 인터페이스가 up으로 올라와 있는 것을 확인할 수 있습니다.
$ ip -c link
파드 내부에서 확인해 보면 eth0에 연결되어 있는 host의 네트워크 인터페이스는 if7으로 확인할 수 있습니다.
여기서 work node에서 네트워크 인터페이스를 확인해보면 7번 네트워크를 확인할 수 있습니다.
$ kubectl exec pod1 -it -- zsh ## pod 진입
$ ip -c a
3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8981 qdisc noqueue state UP group default qlen 1000
link/ether da:fb:80:6f:6c:b9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.158.3/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::d8fb:80ff:fe6f:6cb9/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
## work node 1번에서 ##
$ ip -c link
7: 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-d0913aef-1453-28cf-c9b4-eeff6d5f5163
pod2 번에서 동일한 명령어 수행 시에 host 네트워크 인터페이스를 각각 별도로 연결되어 있는 상태를 확인할 수 있습니다.
## pod2에서 수행 ##
$ ip -c a
3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8981 qdisc noqueue state UP group default qlen 1000
link/ether b6:5c:82:62:c9:cf brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.158.4/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::b45c:82ff:fe62:c9cf/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
파드간 통신 실행 및 확인
여기서 중요한 것 중 하나는 calice 인터페이스 인데 여기에 proxy_arp 기능이 꺼져있다면 통신을 받지 못하기 때문에
정상적인 통신이 불가능합니다.
# iptables 필터 테이블에 FORWARD 리스트 중 cali-FORWARD 룰 정보를 필터링해서 watch 로 확인
$ watch -d -n 1 "iptables -v --numeric --table filter --list FORWARD | egrep '(cali-FORWARD|pkts)'"
# 1번 pod에서 수행
$ ping -c 10 172.16.158.4
worker node에서 실행해 놓은 패킷 모리터링에서 패킷 수치가 상승하는 것을 확인해볼 수 있습니다.
아래 내용에서 확인할 수 있는 것과 같이 arp 프로토콜이 먼저 수행되었고, default gw가 먼저 응답으로 맥 주소를 전달하여 통신이 이루어 진 것을 확인할 수 있습니다.
파드 -> 외부(인터넷) 통신
Step 0. 파드 배포 전 기본 상태 확인
NAT가 True로 되어있으면 파드가 사용하는 cidr가 아닌 경우는 워커노드의 마스커레딩을 타고 외부로 통신하게 됩니다.
$ calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED DISABLEBGPEXPORT SELECTOR
default-ipv4-ippool 172.16.0.0/16 true Always Never false false all()
# worker node에서 실행
$ 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
Step 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
calice의 host network namespcae를 통해 외부로 통신하면 아래와 같이 ping이 정상적으로 수행되는 것을 확인할 수 있습니다.
반대로 tunnel interface를 대상으로 tcpdump를 수행하면 통신 자체가 tunnel을 타지 않기 때문에 패킷이 들어오지 않는 것도 확인할 수 있었습니다.
## control plan ##
$ VETH1=$(calicoctl get workloadEndpoint | grep pod1 | awk '{print $4}')
$ echo $VETH1
## 터미널 1 번 (pod) ##
$ ping -c 10 8.8.8.8
## 터미널 2번 (work node) ##
$ watch -d 'iptables -n -v -t nat --list cali-nat-outgoing'
VETH1=calice0906292e2
## 터미널 3번 (worker node) ##
$ tcpdump -i $VETH1 -nn icmp
$ tcpdump -i tunl0 -nn icmp
case1) veth1에서 tcpdump 수행
case2) tunnel interface 대상으로 tcpdump 수행
다른 노드에서 파드 <-> 파드 통신
아래 명령어를 통해 터널 인터페이스로 서로에게 통신할 수 있도록 route 설정이 되어 있는 것을 확인할 수 있습니다.
$ route | head -2 ; route -n | grep tunl0
Step 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-w2
containers:
- name: pod2
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
## 각 노드에서 수행 ##
$ route -n | head -2 ; route -n | grep 172.16.
blackhole 정책으로 인해 통신이 불가능하지만 세부 routing이 32비트로 생성되면서 통신이 가능한 상태가 되어있습니다.
pod1 > pod2 ping test 수행 및 각 노드에서 모니터링 수행
## pod 1 ##
$ ping -c 10 {pod2 ip}
## node 1 ##
$ watch -d 'ifconfig tunl0 | head -2 ; ifconfig tunl0 | grep bytes'
## node 2 ##
$ tcpdump -i tunl0 -nn
위의 통신 플로우를 그림으로 그리면 아래와 같습니다.
각 pod는 자체적으로 eth0 인터페이스를 갖고 이 eth0은 호스트 네트워크를 통하게 됩니다.
calice는 호스트 네트워크와 pod의 네트워크 인터페이스를 연결하는 VETH이며, virtual router 는 전달받은 트래픽을 라우팅하게 됩니다. tunl0를 통해 IPIP 터널링을 통해 캡슐화된 패킷이 실제 네트워크로 나가게 됩니다.
'Cloud > Kubernetes' 카테고리의 다른 글
[KANS] LoadBalancer (2) | 2024.10.05 |
---|---|
[KANS] Service : ClusterIP, NodePort (1) | 2024.09.28 |
[KANS] Calico 네트워크 모드 (1) | 2024.09.15 |
[KANS] Flannel CNI (2) | 2024.09.08 |
[KANS] Kind를 이용한 Pause container (0) | 2024.09.06 |