KANS 3기 3주차 - Calico CNI & Mode

Oasis·2024년 9월 21일

KANS

목록 보기
3/9

가시다님의 KANS [3기] 스터디 내용을 정리한 포스트 입니다.

1.실습 환경

  • AWS 기반으로 Calico 실습을 위한 K8S를 배포합니다.
  • 실습 환경: K8S v1.30.X, 노드 OS(Ubuntu 22.04 LTS) , CNI(Calico v3.28.1, IPIP, NAT enable) , IPTABLES proxy mode
  • 2개의 네트워크 대역이 존재 : AWS 환경에서 k8s-rtr 은 없고, AWS 내부 라우터가 대신 라우팅 처리합니다.
  • 실습 환경 구성
  • 기본 실습 환경 배포
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-3w.yaml

# CloudFormation 스택 배포
aws cloudformation deploy --template-file kans-3w.yaml --stack-name mylab --parameter-overrides MyInstanceType=t2.micro KeyName=2024-03-11_eks  SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 k8s-m EC2 IP 출력
aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2

# [모니터링] CloudFormation 스택 상태 : 생성 완료 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

# k8s-m EC2 SSH 접속
ssh -i ~/.ssh/2024-03-11_eks.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)

CNI가 설치되지 않아 node의 상태가 NotReady로 되어있습니다.

  • Calico CNI v3.28.1 설치
# 모니터링
watch -d 'kubectl get pod -A -owide'

# calico cni install
## kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml - 서브넷 24bit 추가
# 기본 yaml 에 4946줄 이동 후 아래 내용 추가 해둠
vi calico.yaml
...
            # Block size to use for the IPv4 POOL created at startup. Block size for IPv4 should be in the range 20-32. default 24
            - name: CALICO_IPV4POOL_BLOCK_SIZE
              value: "24"
kubectl apply -f https://raw.githubusercontent.com/gasida/KANS/main/kans3/calico-kans.yaml

# calicoctl install
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

# CNI 설치 후 파드 상태 확인
kubectl get pod -A -o wide
  • CNI 설치 후 calico controller, node 파드가 생성됨
  • k8s node의 상태도 Ready로 확인됨
  • kube-ops-view 설치
# helm show values geek-cookbook/kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 --set env.TZ="Asia/Seoul" --namespace kube-system

# 설치 확인
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율)
echo -e "KUBE-OPS-VIEW URL = http://$(curl -s ipinfo.io/ip):30000/#scale=1.5"

# (참고) 삭제
helm uninstall -n kube-system kube-ops-view
  • metrics-server 설치
# 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'

# (참고) 삭제
helm uninstall -n kube-system metrics-server

2.Calico 기본 통신 이해

Calico 개요

Calico CNI (Container Network Interface)는 Kubernetes 클러스터에서 네트워크 연결을 관리하고 보안을 강화하는 데 사용되는 CNI 플러그인 중 하나입니다. 주로 네트워크 정책 및 보안을 제공하는데 강점이 있으며, BGP(Border Gateway Protocol)를 사용해 데이터 패킷을 효율적으로 라우팅합니다.

주요 기능:

1.	네트워크 정책: Pod 간의 트래픽을 세밀하게 제어할 수 있으며, 이를 통해 보안 수준을 높일 수 있습니다.
2.	IPIP 및 VXLAN 터널링: 클러스터 내 네트워크를 더욱 유연하게 구성할 수 있습니다.
3.	확장성: 대규모 클러스터에서도 안정적으로 동작하며, 고성능을 제공합니다.

Calico는 순수한 L3 네트워크 솔루션이기 때문에, L2 네트워크를 사용하지 않고 IP 기반으로 통신을 처리합니다. 클러스터 내부 및 외부로의 연결을 효율적으로 관리할 수 있어, 특히 네트워크 보안과 성능을 중요시하는 환경에서 많이 사용됩니다.

  • Calico 구성 요소
  • Felix (필릭스) : 인터페이스 관리, 라우팅 정보 관리, ACL 관리, 상태 체크
    • Programs routes and ACLs, and anything else required on the host to provide desired connectivity for the endpoints on that host. Runs on each machine that hosts endpoints. Runs as an agent daemon.
  • BIRD (버드): BGP Peer 에 라우팅 정보 전파 및 수신, BGP RR(Route Reflector)
    • Gets routes from Felix and distributes to BGP peers on the network for inter-host routing. Runs on each node that hosts a Felix agent. Open source, internet routing daemon.
  • Confd : calico global 설정과 BGP 설정 변경 시(트리거) BIRD 에 적용해줌
    • Monitors Calico datastore for changes to BGP configuration and global defaults such as AS number, logging levels, and IPAM information. Open source, lightweight configuration management tool.
  • Dikasted : Enforces network policy for Istio service mesh. Runs on a cluster as a sidecar proxy to Istio Envoy.
  • Calico CNI Plugin : k8s 에 calico 네트워킹 환경을 제공
    • Provides Calico networking for Kubernetes clusters
  • Datastore plugin : calico 설정 정보를 저장하는 곳 - k8s API datastore(kdd) 혹은 etcd 중 선택
    • Increases scale by reducing each node’s impact on the datastore
  • Calico IPAM plugin : 클러스터 내에서 파드에 할당할 IP 대역
    • Uses Calico’s IP pool resource to control how IP addresses are allocated to pods within the cluster
  • calico-kube-controllers : calico 동작 관련 감시(watch)
  • Typha : Datastore 와 felix, confd 다수간 연결하지 않고 Datastore 는 단일 Typha(중계자 역할) 로 연결, Typha 는 상태 캐시 및 이벤트 중복 제거 등으로 대규모 환경에서 부하를 감소 할 수 있음, 기본적으로 설치는 되지만 설정 구성되어 있지 않음
    • Increases scale by reducing each node’s impact on the datastore. Runs as a daemon between the datastore and instances of Felix. Installed by default, but not configured.
  • calicoctl : calico 오브젝트를 CRUD 할 수 있다, 즉 datastore 접근 가능
    • Command line interface to create, read, update, and delete Calico objects. calicoctl command line is available on any host with network access to the Calico datastore as either a binary or a container
  • Calico 기본 정보 확인
# 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 정보 확인
calicoctl ipam show

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

# host-local IPAM 정보 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' ;echo

# CNI Plugin 정보 확인 - 링크
tree /etc/cni/net.d/
cat /etc/cni/net.d/10-calico.conflist | jq

# 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 (파드)의 정보 확인
calicoctl get workloadEndpoint -A

2.1 파드 <-> 파드 통신

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

  • iptables FORWARD Rule 에 정책(허용) 사용
  • caliX# 인터페이스에 proxy arp 설정으로 파드에서 바라보는 게이트웨이의 MAC 정보를 파드가 전달 받음
  • 동일 노드 내에 파드 간 통신에서는 tunnel 인터페이스는 미 관여

  • 파드 생성 후 확인
# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/4/node1-pod2.yaml
kubectl apply -f node1-pod2.yaml

# 생성된 파드 정보 확인
kubectl get pod -o wide
root@k8s-m:~# kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          34s   172.16.228.78   k8s-w1   <none>           <none>
pod2   1/1     Running   0          34s   172.16.228.79   k8s-w1   <none>           <none>

# calicoctl 이용한 endpoint 확인 : veth 정보도 출력!
calicoctl get workloadendpoints
WORKLOAD   NODE     NETWORKS          INTERFACE
pod1       k8s-w1   172.16.158.2/32   calice0906292e2
pod2       k8s-w1   172.16.158.1/32   calibd2348b4f67

  • 파드 생성 후 워커노드에서 정보 확인
    • k8s-w1 노드에 신규로 생성된 pod 2대가 Running 상태임
    • 3개의 calice#~와 라우팅 대역이 테이블에 추가된 것으로 확인 할 수 있다.
  • 파드에 shell 접속 후 확인
    [파드1]
# 마스터 노드에서 아래 실행
kubectl exec pod1 -it -- zsh
...
# IP는 32bit 서브넷을 가진다
pod1> ip -c addr

# 라우팅 정보에 169.254.1.1 를 디폴트 게이트웨이 주소로 사용
pod1> route -n

# ARP 정보는 현재 아무것도 없다
ip -c neigh

[파드2]

# 마스터 노드에서 아래 실행
kubectl exec pod2 -it -- zsh
...
# IP는 32bit 서브넷을 가진다
pod2> ip -c addr

# 라우팅 정보에 169.254.1.1 를 디폴트 게이트웨이 주소로 사용
pod2> route -n

# ARP 정보는 현재 아무것도 없다
ip neighbor show

  • 파드간 통신 확인
노드에서 실행)
# iptables 필터 테이블에 FORWARD 리스트 중 cali-FORWARD 룰 정보를 필터링해서 watch 로 확인
watch -d -n 1 "iptables -v --numeric --table filter --list FORWARD | egrep '(cali-FORWARD|pkts)'" 

# (마스터노드) 파드 연결된 veth 를 변수를 확인
VETH1=$(calicoctl get workloadEndpoint | grep pod1 | awk '{print $4}')
echo $VETH1
VETH2=$(calicoctl get workloadEndpoint | grep pod2 | awk '{print $4}')
echo $VETH2

# (워커노드1) 위에서 확인한 파드 연결된 veth 를 변수에 지정
VETH1=calice0906292e2
VETH2=calibd2348b4f67

# 노드1 calice# 인터페이스의 proxy arp 설정 확인
# cat /proc/sys/net/ipv4/conf/<자신의 pod1에 연결된 calice# 이름>/proxy_arp
cat /proc/sys/net/ipv4/conf/$VETH1/proxy_arp
cat /proc/sys/net/ipv4/conf/$VETH2/proxy_arp

# 파드1 혹은 파드2에 veth 로 연결된 호스트 네트워크 인터페이스 calice# 중 1개 선택해서 tcpdump
tcpdump -i $VETH1 -nn

# 파드1 Shell 에서 실행 : 정상 통신!
kubectl exec pod1 -it -- zsh
--------------------
ping -c 10 <파드2 IP>
  • 파드1에서 게이트웨이의 IP인 169.254.1.1 의 MAC 주소를 알기 위해서 ARP Request 를 보내고 이때 veth 연결된 calice#~ 에 proxy arp 설정이 되어 있고, 자신의 mac 주소(ee:ee:ee:ee:ee:ee)를 알려주고, 이후 정상 통신됨

2.2 파드 -> 외부(인터넷) 통신

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

  • calico 기본 설정은 natOutgoing: true 이다. 즉, iptables 에 MASQUERADE Rule(룰) 에 의해서 외부에 연결됨
  • calice# 인터페이스에 proxy arp 설정
  • 파드와 외부간 직접 통신에서는 tunnel 인터페이스는 미 관여
1. 파드 배포 전 기본 상태 확인

# 마스터 노드에서 확인 : natOutgoing 의 기본값은 true 이다
$ 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()

# 워커 노드에서 확인 : 노드에서 외부로 통신 시 MASQUERADE 동작 Rule 확인
$ 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
  1. 파드 배포
# 파드 생성
curl -O https://raw.githubusercontent.com/gasida/NDKS/main/4/node1-pod1.yaml
kubectl apply -f node1-pod1.yaml

# 생성된 파드 정보 확인
kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          5s    172.16.158.6   k8s-w1   <none>           <none>

  1. 외부 통신 확인
[터미널1] (파드)
# 파드에서 외부 정상 통신 확인
kubectl exec pod1 -it -- zsh

# 통신 확인 
pod1> ping -c 10 8.8.8.8

[터미널2] (W1)
watch -d 'iptables -n -v -t nat --list cali-nat-outgoing'

[터미널3] (W1)
tcpdump -i calice0906292e2 -nn icmp

2.3 다른 노드에서 파드 <-> 파드 통신

다른 노드 환경에서 파드 간 통신 시에는 IPIP 터널(기본값) 모드를 통해서 이루어 집니다

  • 각 노드에 파드 네트워크 대역은 Bird 에 의해서 BGP 로 광고 전파/전달 되며, Felix 에 의해서 호스트의 라우팅 테이블에 자동으로 추가 및 삭제 됩니다
  • 다른 노드 간의 파드 통신은 tunl0 인터페이스를 통해 IP 헤더에 감싸져서 상대측 노드로 도달 후 tunl0 인터페이스에서 Outer 헤더를 제거하고 내부의 파드와 통신됩니다
  • 실제 패킷 확인 시 Outer IP 헤더와 Inner IP 헤더가 보인다!
  1. 파드 배포 전 기본 상태 확인
route | head -2 ; route -n | grep tunl0

  1. 파드 배포

    노드1과 노드2에 각각 파드 1개씩 생성

# 파드 생성
$curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/4/node2-pod2.yaml
$kubectl apply -f node2-pod2.yaml

# calicoctl 이용한 endpoint 확인
$calicoctl get workloadendpoints
WORKLOAD   NODE     NETWORKS          INTERFACE
pod1       k8s-w1   172.16.158.7/32   calice0906292e2
pod2       k8s-w2   172.16.184.4/32   calibd2348b4f67
  • 라우팅 정보 확인
route -n | head -2 ; route -n | grep 172.16.

# 노드1
root@k8s-w1:~# route -n | head -2 ; route -n | grep 172.16.
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.34.0     192.168.20.100  255.255.255.0   UG    0      0        0 tunl0
172.16.116.0    192.168.10.10   255.255.255.0   UG    0      0        0 tunl0
172.16.158.0    0.0.0.0         255.255.255.0   U     0      0        0 *
172.16.158.1    0.0.0.0         255.255.255.255 UH    0      0        0 calie862d12b00b
172.16.158.7    0.0.0.0         255.255.255.255 UH    0      0        0 calice0906292e2
172.16.184.0    192.168.10.102  255.255.255.0   UG    0      0        0 tunl0

# 노드2
root@k8s-w2:~# route -n | head -2 ; route -n | grep 172.16.
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.34.0     192.168.20.100  255.255.255.0   UG    0      0        0 tunl0
172.16.116.0    192.168.10.10   255.255.255.0   UG    0      0        0 tunl0
172.16.158.0    192.168.10.101  255.255.255.0   UG    0      0        0 tunl0
172.16.184.0    0.0.0.0         255.255.255.0   U     0      0        0 *
172.16.184.1    0.0.0.0         255.255.255.255 UH    0      0        0 cali05f414d4d59
172.16.184.2    0.0.0.0         255.255.255.255 UH    0      0        0 cali35120ff9265
172.16.184.3    0.0.0.0         255.255.255.255 UH    0      0        0 cali5b1cbf5ab56
172.16.184.4    0.0.0.0         255.255.255.255 UH    0      0        0 calibd2348b4f67
  1. 통신 확인
[터미널1] (파드)
## pod 1 ##
$ ping -c 10 {pod2 ip}

[터미널2] (w1)
## node 1 ##
$ watch -d 'ifconfig tunl0 | head -2 ; ifconfig tunl0 | grep bytes'

[터미널1] (w2)
## node 2 ##
$ tcpdump -i tunl0 -nn

tunl0를 통해 IPIP 터널링을 통해 캡슐화된 패킷이 실제 네트워크로 나가는 것을 알수 있습니다.

3.Calico 네트워크 모드

3.1 IPIP 모드

파드 간 통신이 노드와 노드 구간에서는 IPIP 인캡슐레이션을 통해서 이루어 집니다.

  • 다른 노드 간의 파드 통신은 tunl0 인터페이스를 통해 IP 헤더에 감싸져서 상대측 노드로 도달 후 tunl0 인터페이스에서 Outer 헤더를 제거하고 내부의 파드와 통신
  • 다른 노드의 파드 대역은 BGP로 전달 받아 호스트 라우팅 테이블에 업데이트됨
  • Azure 네트워크에서는 IPIP 통신이 불가능하여 IPIP 모드 대신 VXLAN 모드 사용
    • 멀티 캐스트, 브로드캐스트, IP-IP 캡슐화 패킷 및 GRE(일반 라우팅 캡슐화) 패킷은 Azure VNet 내에서 차단됩니다

"2.3 다른 노드에서 파드 <-> 파드 통신" 과 동일한 내용으로 3.1 IPIP 모드 실습은 생략합니다.

3.2 Direct 모드

  • 파드 통신 패킷이 출발지 노드의 라우팅 정보를 보고 목적지 노드로 원본 패킷 그대로 전달합니다
  • 오버헤드가 거의 없기 때문에 IPIP에 비하면 빠른 편에 속합니다
  • 클라우드 사업자 네트워크의 경우 NIC 에 매칭되지 않는 IP 패킷은 차단되니, NIC에 Source/Destination Check 기능을 Disable 해야 합니다
$ aws ec2 modify-instance-attribute --instance-id <INSTANCE_ID> --source-dest-check "{\"Value\": false}"

[ipip 설정 변경전 - Always]

현재 상태를 보면 ipip 설정이 always로 설정되어 있으며 각 노드의 인터페이스가 'tunl0'으로 설정되어 있습니다.
ipip 설정을 never로 바꾸면 각 노드의 인터페이스가 'ens0'으로 변경된것을 확인할 수 있습니다. 즉 tunl0에서 ens5로 인터페이스가 변경되어 터널 인터페이스로 라우팅을 하지 않는 것을 확인했습니다.

[ipip 설정 변경후 - Never]

  • 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/5/node3-pod3.yaml
kubectl apply -f node3-pod3.yaml

  • 파드간 ping 통신 실행 및 패킷 캡쳐 확인
# 파드 Shell 접속
kubectl exec -it pod1 -- zsh
## 파드 Shell 에서 아래 입력
ping <pod2 혹은 pod3 IP>

# 파드가 동작하는 노드의 eth0에서 패킷 덤프
tcpdump -i ens5 -nn icmp

# 파일로 저장 후 해당 파일을 다운받아서 확인(wireshark 등 사용)
tcpdump -i ens5 icmp -w /tmp/calico-direct.pcap

[Pod1 -> Pod2로 ping test]

Pod1 에서 Pod2로 통신 시 TCP dump를 확인해 보면, 통신은 잘 되고 Pod들의 IP가 그대로 노출됨

[Pod1 -> Pod3로 ping test]

Pod1에서 Pod3로 통신을 시도하면, 나가는 패킷은 있지만, 들어오는 패킷은 없이 통신이 안됨

AWS Virtual Router에 Pod 간 라우팅 정보가 없기 때문에 발생합니다. AWS Virtual Router는 기본적으로 할당된 Subnet 간의 라우팅만 관리하기 때문에 대역폭이 다른 두 서브넷 간의 Pod 간 다이렉트 통신이 안된다.
이를 해결하기 위해서는 VPC route table 에 노드에 파드 대역 라우팅 추가로 통신 가능하다.

  • CrossSubnet 모드

    노드 간 같은 네트워크 대역(Direct 모드로 동작) , 노드 간 다른 네트워크 대역(IPIP 모드로 동작) 시킬 수 있습니다.

# CrossSubnet 모드 설정
calicoctl patch ippool default-ipv4-ippool -p '{"spec":{"ipipMode":"CrossSubnet"}}'

# 모드 확인
calicoctl get ippool -o wide
NAME                  CIDR            NAT    IPIPMODE      VXLANMODE   DISABLED   SELECTOR
default-ipv4-ippool   172.16.0.0/16   true   CrossSubnet   Never       false      all()

# 파드 생성
kubectl apply -f node3-pod3.yaml
calicoctl get wep

# 호스트 라우팅 정보 확인
route -n | grep UG
172.16.34.0     192.168.20.100  255.255.255.0   UG    0      0        0 tunl0 # 노드간 다른 네트워크 대역 - IPIP 모드
172.16.158.0    192.168.10.101  255.255.255.0   UG    0      0        0 ens5 # 노드간 같은 네트워크 대역 - Direct 모드
172.16.184.0    192.168.10.102  255.255.255.0   UG    0      0        0 ens5 # 노드간 같은 네트워크 대역 - Direct 모드

(⎈|HomeLab:default) root@k8s-m:~# k get pods -owide
NAME   READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          3m36s   172.16.158.10   k8s-w1   <none>           <none>
pod2   1/1     Running   0          3m36s   172.16.184.7    k8s-w2   <none>           <none>
pod3   1/1     Running   0          3m36s   172.16.34.4     k8s-w0   <none>           <none>

# 파드 Shell 접속(zsh)
kubectl exec -it pod1 -- zsh
## 파드 Shell 에서 아래 입력
ping <pod2 혹은 pod3 IP>

[Pod1 -> Pod2로 ping test]

Pod1 에서 Pod2로 통신 시 TCP dump를 확인해 보면, 통신은 잘 되고 Pod들의 IP가 그대로 노출됨

[Pod1 -> Pod3로 ping test]

Pod1 에서 Pod3로 통신 시 TCP dump를 확인해 보면, 통신은 잘 되고 Pod들의 IP가 그대로 노출됨

3.3 VXLAN 모드

파드 간 통신이 노드와 노드 구간에서는 VXLAN 인캡슐레이션을 통해서 이루어 집니다

3.3 Pod 패킷 암호화

Calico 의 다양한 네크워크 모드 환경 위에서 WireGuard 터널을 자동 생성 및 파드 트래픽을 암호화하여 노드간 전달합니다

  • WireGuard
    Pod 간의 통신 패킷을 암호화하기 위해 사용되는 WireGuard에 대해 간략하게 살펴보겠습니다.

    • WireGuard는 구닥다리 IPsec 및 OpenVPN의 대항마로 등장한 open source VPN project 이며 작년, Linux 5.6 커널에 WireGuard 1.0.0 기본 패키지로 탑재되었다.
    • 정말 간결한 코드 구조빠른 성능 (모든 것이 kernel에서 동작하고, 주요 암호 알고리즘에 대해서 병렬처리하므로써 빠른 속도를 자랑함)
    • WireGuard는 보시다시피 경쟁자들인 IPsec, SoftEther VPN, OpenVPN 등과 비교할 때, 코드 size(Line of Codes)가 현격하게 작다.
      그말은 코드량이 많지 않으니 그만큼 철저히 검증될 가능성이 높고, 예상치 못한 곳에서의 버그로 인한 취약점이 발생할 가능성이 상대적으로 적다는 얘기가 된다.
    • 달리는 기차 위에서 안정적으로 사내망에 접속하고 싶을 때 : Public key를 기반으로 peer를 인식하기 때문에 LTE 기지국이 바뀌더라도 tunnel이 끊기지 않고 안정적으로 유지
  • WireGuard 설치

# 설치
apt install wireguard -y

# WireGuard 버전 확인
wg version

  • WireGuard 설정
# 설정
calicoctl get felixconfiguration -o yaml
calicoctl patch felixconfiguration default --type='merge' -p '{"spec":{"wireguardEnabled":true}}'

# 확인
calicoctl get felixconfiguration default -o yaml | grep wireguardEnabled
root@k8s-m:~/yaml# calicoctl get felixconfiguration default -o yaml | grep wireguardEnabled
  wireguardEnabled: true

calicoctl get node -o yaml | grep wireguardPublicKey
calicoctl get node <노드 Name> -o yaml | grep wireguardPublicKey
root@k8s-m:~/yaml# calicoctl get node k8s-w1 -o yaml | grep wireguardPublicKey
  wireguardPublicKey: BToK9bLEhMaPUJsuKy3KdrxVOpklyo0qlGRdMN6lHWc=

# wireguard.cali 인터페이스 확인
ip -c -d addr show wireguard.cali
root@k8s-w1:~# ip -c -d addr show wireguard.cali
12: wireguard.cali: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none  promiscuity 0 minmtu 0 maxmtu 2147483552
    wireguard numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 172.16.228.74/32 scope global wireguard.cali
       valid_lft forever preferred_lft forever

ifconfig wireguard.cali
root@k8s-w1:~# ifconfig wireguard.cali
wireguard.cali: flags=209<UP,POINTOPOINT,RUNNING,NOARP>  mtu 1440
        inet 172.16.228.69  netmask 255.255.255.255  destination 172.16.228.69
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)

# wireguard.cali 설정 확인 : 통신 포트, Peer/Endpoint 정보, 패킷 암호화를 위한 공개키/사설키 정보
wg showconf wireguard.cali
root@k8s-w1:~# wg showconf wireguard.cali
[Interface]
ListenPort = 51820
FwMark = 0x100000
PrivateKey = AIgTihI2p4icwVMR4sIvuVaSqwKlkxMImQp4A/Gm+Gg=

[Peer]
PublicKey = BToK9bLEhMaPUJsuKy3KdrxVOpklyo0qlGRdMN6lHWc=
AllowedIPs = 172.16.228.64/26, 172.16.228.69/32, 172.16.228.67/32
Endpoint = 192.168.100.101:51820

[Peer]
PublicKey = 9TCD8hG6SLutZSOZSzQeqj6O0icJAxA3RPIipcBKBxs=
AllowedIPs = 172.16.197.0/26, 172.16.197.3/32, 172.16.197.5/32
Endpoint = 192.168.100.103:51820

[Peer]
PublicKey = Ercb/0pNZ+I1ELOkiXlWbZA9J0Fjt7XqsstDH4GhNmI=
AllowedIPs = 172.16.46.3/32, 172.16.46.0/26, 172.16.46.5/32
Endpoint = 192.168.100.102:51820

# Peer 의 정보(고유한 index)
wg show
interface: wireguard.cali
  public key: 8TNaYyzzc1N4SHfqE+Y5b4rMBKX/lqPe0tWO/h8sOB4=
  private key: (hidden)
  listening port: 51820
  fwmark: 0x100000

peer: 6WtZqEKSmoSKiFp20fhk/jyTcrTqf9qshyZI1HvE9Qk=
  endpoint: 192.168.10.102:51820
  allowed ips: 172.16.184.0/32, 172.16.184.0/24, 172.16.184.1/32

peer: +fOEOJgFxueIbrp709iB4F4gFRb2ny4lWKbxCNNfczM=
  endpoint: 192.168.20.100:51820
  allowed ips: 172.16.34.0/32, 172.16.34.0/24, 172.16.34.1/32

peer: d2LgXvRo4DwsyhiLXUn9TEt6D3l4pFIVlCD7KESR/m0=
  endpoint: 192.168.10.101:51820
  allowed ips: 172.16.158.0/32, 172.16.158.0/24, 172.16.158.1/32

# 키 정보 확인
wg show all public-key
wireguard.cali	8TNaYyzzc1N4SHfqE+Y5b4rMBKX/lqPe0tWO/h8sOB4=

wg show all private-key
wireguard.cali	kJbrfATGFP2v4sl+Wqg1Gv8zwFpIXshYFFD3udMDd3k=

wg show all preshared-keys
wireguard.cali	6WtZqEKSmoSKiFp20fhk/jyTcrTqf9qshyZI1HvE9Qk=	(none)
wireguard.cali	+fOEOJgFxueIbrp709iB4F4gFRb2ny4lWKbxCNNfczM=	(none)
wireguard.cali	d2LgXvRo4DwsyhiLXUn9TEt6D3l4pFIVlCD7KESR/m0=	(none)

# 그외 키타 정보
wg show all dump
wg show all endpoints
wg show all peers
wg show all transfer
wg show all persistent-keepalive
  • 동작 확인
# 파드 생성
$kubectl apply -f node3-pod3.yaml

(⎈|HomeLab:default) root@k8s-m:~# k get pods -owide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE   READINESS GATES
pod1   1/1     Running   0          67s   172.16.158.12   k8s-w1   <none>           <none>
pod2   1/1     Running   0          67s   172.16.184.9    k8s-w2   <none>           <none>
pod3   1/1     Running   0          67s   172.16.34.6     k8s-w0   <none>           <none>

# 파드 Shell 접속(zsh)
kubectl exec -it pod1 -- zsh
## 파드 Shell 에서 아래 입력
ping <pod2 혹은 pod3 IP>

# 파드가 동작하는 노드의 eth0(예시)에서 패킷 덤프
ss -unlp
tcpdump -i <eth0> -nn udp port 51820
tcpdump -i ens5 -nn udp port 51820
혹은 아래 처럼 파일로 저장 후 해당 파일을 다운받아서 확인(wireshark 등 사용)
tcpdump -i <eth0> udp port 51820 -w /tmp/calico-wireguard.pcap
tcpdump -i ens5 udp port 51820 -w /tmp/calico-wireguard.pcap

# 현재 모든 워커 노드에 tcpdump 후 ping curl 테스트 시 모든 노드에서 트래픽이 발생한다 >> 이유는 VirtualBox 에 nic2 에 "무작위 모드 : 모두 허용" 상태여서, 모든 노드로 패킷이 전달됨
# VirtualBox 에 nic2 에 "무작위 모드 : 거부"로 설정 후 테스트 하게 되면 정확하게, 출발지와 목적지의 VM에서만 패킷이 확인된다!
모드워커노드) tcpdump -i ens5 -nn udp port 51820

TCP dump를 통해 패킷이 암호화 되어 있는 것을 확인 할 수 있다.

3.4 운영 모니터링

프로메테우스와 그라파나를 통해서 calico 운영 상태를 모니터링 할 수 있습니다.
다음 설정을 참고해서 grafana 운영 데쉬보드를 구성해 보겠습니다.

  1. 메트릭 리포트 활성화를 위한 calico 설정
# Felix configuration
calicoctl get felixconfiguration -o yaml
calicoctl patch felixconfiguration default  --patch '{"spec":{"prometheusMetricsEnabled": true}}'

# Creating a service to expose Felix metrics : Felix by default uses port 9091 TCP to publish its metrics.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: felix-metrics-svc
  namespace: kube-system
spec:
  clusterIP: None
  selector:
    k8s-app: calico-node
  ports:
  - port: 9091
    targetPort: 9091
EOF
kubectl get svc,ep -n kube-system felix-metrics-svc

# kube-controllers configuration : Prometheus metrics are enabled by default on TCP port 9094 for calico-kube-controllers
## Creating a service to expose kube-controllers metrics
calicoctl get kubecontrollersconfiguration default -o yaml
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: kube-controllers-metrics-svc
  namespace: kube-system
spec:
  clusterIP: None
  selector:
    k8s-app: calico-kube-controllers
  ports:
  - port: 9094
    targetPort: 9094
EOF
kubectl get svc,ep -n kube-system kube-controllers-metrics-svc
  1. 클러스터 준비
# Namespace creation
kubectl create -f -<<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: calico-monitoring
  labels:
    app:  ns-calico-monitoring
    role: monitoring
EOF
kubectl get ns

# Service account creation
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: calico-prometheus-user
rules:
- apiGroups: [""]
  resources:
  - endpoints
  - services
  - pods
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: calico-prometheus-user
  namespace: calico-monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: calico-prometheus-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: calico-prometheus-user
subjects:
- kind: ServiceAccount
  name: calico-prometheus-user
  namespace: calico-monitoring
EOF
kubectl get sa -n calico-monitoring
  1. 프로메테우스 설치
#
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: calico-monitoring
data:
  prometheus.yml: |-
    global:
      scrape_interval:   15s
      external_labels:
        monitor: 'tutorial-monitor'
    scrape_configs:
    - job_name: 'prometheus'
      scrape_interval: 5s
      static_configs:
      - targets: ['localhost:9090']
    - job_name: 'felix_metrics'
      scrape_interval: 5s
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_name]
        regex: felix-metrics-svc
        replacement: $1
        action: keep
    - job_name: 'felix_windows_metrics'
      scrape_interval: 5s
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_name]
        regex: felix-windows-metrics-svc
        replacement: $1
        action: keep
    - job_name: 'typha_metrics'
      scrape_interval: 5s
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_name]
        regex: typha-metrics-svc
        replacement: $1
        action: keep
    - job_name: 'kube_controllers_metrics'
      scrape_interval: 5s
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_name]
        regex: kube-controllers-metrics-svc
        replacement: $1
        action: keep
EOF
kubectl get cm -n calico-monitoring prometheus-config

# Create Prometheus pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: prometheus-pod
  namespace: calico-monitoring
  labels:
    app: prometheus-pod
    role: monitoring
spec:
  nodeSelector:
    kubernetes.io/os: linux
  serviceAccountName: calico-prometheus-user
  containers:
  - name: prometheus-pod
    image: prom/prometheus
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    volumeMounts:
    - name: config-volume
      mountPath: /etc/prometheus/prometheus.yml
      subPath: prometheus.yml
    ports:
    - containerPort: 9090
  volumes:
  - name: config-volume
    configMap:
      name: prometheus-config
EOF
kubectl get pods prometheus-pod -n calico-monitoring -owide
  1. view metrics
# 파드 IP 확인
kubectl get pods prometheus-pod -n calico-monitoring -owide

# 파드 IP metrics 엔드포인트 curl 접속 확인
curl <파드 IP>:9090/metrics
curl 172.16.34.7:9090/metrics

#
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: prometheus-dashboard-svc
  namespace: calico-monitoring
spec:
  type: NodePort
  selector:
    app: prometheus-pod
    role: monitoring
  ports:
    - protocol: TCP
      port: 9090
      targetPort: 9090
      nodePort: 30001 
EOF
kubectl get svc,ep -n calico-monitoring

# 프로메테우스 접속 주소
echo -e "Prometheus URL = http://$(curl -s ipinfo.io/ip):30001"  # [실습환경 A Type]

  • 그라파나로 메트릭 확인
# Provisioning datasource
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-config
  namespace: calico-monitoring
data:
  prometheus.yaml: |-
    {
        "apiVersion": 1,
        "datasources": [
            {
               "access":"proxy",
                "editable": true,
                "name": "calico-demo-prometheus",
                "orgId": 1,
                "type": "prometheus",
                "url": "http://prometheus-dashboard-svc.calico-monitoring.svc:9090",
                "version": 1
            }
        ]
    }
EOF
kubectl get cm -n calico-monitoring

# Provisioning Calico dashboards : Here you will create a configmap with Felix and Typha dashboards.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/grafana-dashboards.yaml

# Creating Grafana pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: grafana-pod
  namespace: calico-monitoring
  labels:
    app:  grafana-pod
    role: monitoring
spec:
  nodeSelector:
    kubernetes.io/os: linux
  containers:
  - name: grafana-pod
    image: grafana/grafana:latest
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    volumeMounts:
    - name: grafana-config-volume
      mountPath: /etc/grafana/provisioning/datasources
    - name: grafana-dashboards-volume
      mountPath: /etc/grafana/provisioning/dashboards
    - name: grafana-storage-volume
      mountPath: /var/lib/grafana
    ports:
    - containerPort: 3000
  volumes:
  - name: grafana-storage-volume
    emptyDir: {}
  - name: grafana-config-volume
    configMap:
      name: grafana-config
  - name: grafana-dashboards-volume
    configMap:
      name: grafana-dashboards-config
EOF

#
kubectl get pod -n calico-monitoring

#
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: calico-monitoring
spec:
  type: NodePort
  selector:
    app:  grafana-pod
    role: monitoring
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
      nodePort: 30002 
EOF
kubectl get svc,ep -n calico-monitoring

# 그라파나 접속 주소 : 초기 계정 ( admin , admin )
echo -e "Grafana URL = http://$(curl -s ipinfo.io/ip):30002"  # [실습환경 A Type]
echo -e "Grafana URL = http://192.168.10.10:30002"            # [실습환경 B Type]

그라파나 웹 접속 후 Felix 대시보드를 확인해 봅니다.

0개의 댓글