들어가며
이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 KANS 스터디 3주 차 주제인 "Calico CNI"에 대해서 정리한 내용입니다.
(스터디 내용이 많아 "k8s Calico CNI(1)"와 "k8s Calico CNI mode & 운영(2)"으로 포스팅을 나누어 작성합니다.)
Calico Network Mode
Calico의 network mode는 다음과 같이 4가지 타입이 있습니다. 이전 실습에서 확인한 모드는 IPIP 모드이고, 이 외에도 3개의 모드가 더 있습니다.
IPIP 모드는 tunl과 IPIP 패킷 해석을 해야 하는 과정으로 인해 오버헤드가 발생하기 때문에 Direct 모드보다 퍼포먼스가 적습니다.
VXLAN 모드는 IPIP와 다르게 UDP 기반으로 터널링 통신을 한다는 점이 다릅니다.
Pod 패킷 암호화는 Pod내 보안이 필요한 정보가 통신될 경우, 암호화해야 하는 상황에서 와이어가드를 사용하여 암호화할 수 있습니다.
Direct Node
Direct 모드는 클라우드 사업자 네트워크의 경우 NIC에 매칭되지 않는 IP 패킷은 차단되기 때문에 NIC에 Source/Destination Check 기능을 Disable 해야 합니다. 이는 클라우드 사업자의 하이퍼바이저 레벨에서 드롭시키기 때문입니다.
AWS 기준으로 다음과 같이 Source/Destination Check 기능을 끌 수 있습니다.
# AWS CLI 로 특정 인스턴스의 Source/Destination Check 기능을 Disable 하기
aws ec2 modify-instance-attribute --instance-id <*INSTANCE_ID*> --source-dest-check "{\"Value\": false}"
IPIP 모드 사용시 정보 확인
# Calico 모드 정보 확인
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()
# (옵션) 모니터링
watch -d "route -n | egrep '(Destination|UG)'"
# 설정
calicoctl get ippool default-ipv4-ippool -o yaml
calicoctl get ippool default-ipv4-ippool -o yaml | sed -e "s/ipipMode: Always/ipipMode: Never/" | calicoctl apply -f -
# 모드 정보 확인 : IPIPMODE 가 Never 로 변경!
calicoctl get ippool -o wide
root@k8s-m:~/yaml# calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 172.16.0.0/16 true Never Never false all()
# BGP 로 전달 받은 파드 네트워크 대역이 호스트 라우팅 테이블에 적용되었는지 확인 : Iface 가 tunl0 에서 ens5 혹은 enp0s8 로 변경!
route -n | egrep '(Destination|UG)'
root@k8s-w1:~# route -n | egrep '(Destination|UG)'
tunl0에서 ens5로 인터페이스가 변경되어, 터널 인터페이스로 라우팅을 하지 않는 것을 확인했습니다.
동작 확인
# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/NDKS/main/5/node3-pod3.yaml
kubectl apply -f node3-pod3.yaml
# 파드 IP 정보 확인
kubectl get pod -o wide
calicoctl get wep
# 파드 Shell 접속(zsh)
kubectl exec -it pod1 -- zsh
## 파드 Shell 에서 아래 입력
ping <pod2 혹은 pod3 IP>
# 파드가 동작하는 노드의 eth0(예시)에서 패킷 덤프
tcpdump -i <eth0> -nn icmp
tcpdump -i ens5 -nn icmp # [실습환경 A Type]
tcpdump -i enp0s8 -nn icmp # [실습환경 B Type]
# 파일로 저장 후 해당 파일을 다운받아서 확인(wireshark 등 사용)
tcpdump -i <eth0> icmp -w /tmp/calico-direct.pcap
tcpdump -i ens5 icmp -w /tmp/calico-direct.pcap # [실습환경 A Type]
tcpdump -i enp0s8 icmp -w /tmp/calico-direct.pcap # [실습환경 B Type]
Pod1 에서 Pod2로 통신 시 TCP dump를 확인해 보면, 통신은 잘 되고 Pod들의 IP가 그대로 노출됩니다.
하지만 Pod1에서 Pod3로 통신을 시도하면, 나가는 패킷은 있지만, 들어오는 패킷은 없는 즉 통신이 안 되는 상황이 발생합니다.
이는 AWS Virtual Router에 Pod 간 라우팅 정보가 없기 때문에 발생합니다. AWS Virtual Router는 기본적으로 할당된 Subnet 간의 라우팅만 관리하기 때문에, 대역폭이 다른 두 서브넷 간의 Pod 간 다이렉트 통신이 안 되는 것입니다.
해당 문제를 해결할 수 있는 방법은 네트워크 팀에서 Pod to Pod 간 통신이 가능하도록 네트워크 대역을 추가해 주는 것입니다.
(하지만 현업에서는 네트워크 팀과 쿠버네티스 운영팀이 협업해야 하고, 라우팅 정보 수정 시 휴먼에러가 발생하지 않도록 사전 조치를 취해야 하기 때문에 쉽지 않을 것으로 보입니다.)
크로스 서브넷
노드 간 같은 네트워크 대역(Direct 모드로 동작) , 노드 간 다른 네트워크 대역(IPIP 모드로 동작) 시킬 수 있습니다.
calicoctl patch ippool default-ipv4-ippool -p '{"spec":{"ipipMode":"CrossSubnet"}}'
VXLAN 모드
Pod to Pod 간 통신이 노드와 노드 구간에서는 VXLAN 인캡슐레이션을 통해서 이루어집니다.
Pod 패킷 암호화 모드(네트워크 레벨)
Calico의 다양한 네트워크 모드 환경 위에서 WireGuard 터널을 자동 생성 및 파드 트래픽을 암호화하여 노드 간 전달합니다.
여기서 Pod 간의 통신 패킷을 암호화하기 위해 사용되는 WireGuard에 대해 살펴보겠습니다.
WireGuard
WireGuard는 성능과 보안을 중점으로 설계된 현대적인 VPN(Virtual Private Network) 프로토콜입니다. 기존의 VPN 솔루션들에 비해 가볍고 빠르며, 설정이 간단하면서도 강력한 암호화 기능을 제공합니다. WireGuard는 주로 리눅스 환경에서 개발되었으나, 모든 주요 운영체제(Windows, macOS, iOS, Android 등)에서 사용할 수 있습니다.
WireGuard의 주요 특징은 다음과 같습니다.
가벼움과 단순성
WireGuard는 약 4,000줄 정도의 코드로 이루어져 있어 매우 가볍고, 유지 보수 및 보안 검토가 용이합니다. 기존의 VPN 프로토콜인 OpenVPN이나 IPsec과 비교하면 훨씬 더 단순한 구조를 가지고 있습니다. 이는 더 적은 버그와 보안 취약점을 의미합니다.
강력한 보안
최신 암호화 알고리즘을 사용하여 VPN 트래픽을 보호합니다. WireGuard는 ChaCha20 암호화 알고리즘과 Poly1305 인증 알고리즘을 기본적으로 사용하여 빠르면서도 강력한 보안을 제공합니다. 또한, 보안상의 문제를 피하기 위해 구식 암호화 방법을 제거하고 간결하게 유지하고 있습니다.
빠른 성능
WireGuard는 높은 성능을 자랑하며, 특히 모바일 환경과 같이 연결이 자주 끊기는 환경에서 빠르고 안정적인 연결을 제공합니다. 지연 시간(Latency)이 적고, 높은 처리량(Throughput)을 지원합니다.
Always-On VPN
WireGuard는 클라이언트가 한번 연결을 설정하면, 연결이 끊어져도 자동으로 복구됩니다. 네트워크 변경(예: Wi-Fi에서 LTE로 전환)이 있어도 VPN 연결이 끊기지 않도록 설계되어 있어 모바일 사용자에게 특히 유용합니다. index 기반으로 peer hash table이 운용되기 때문입니다.
간편한 구성
복잡한 설정 파일과 긴 인증서가 필요하지 않습니다. WireGuard는 공개 키 기반의 간단한 설정을 사용하며, 각 피어(peer)는 고유의 공개 키로 서로 인증합니다. IPsec이나 OpenVPN보다 설정이 훨씬 간편합니다.
WireGuard 설치 및 확인
# 설치
apt install wireguard -y
# WireGuard 버전 확인
wg version
# 설정
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
각 Peer 들의 공개 키 정보가 자동으로 세팅됩니다.
TCP dump를 통해 패킷을 확인해 보면 암호화되어있는 것을 확인할 수 있습니다.
운영 - 모니터링
칼리코의 모니터링을 통해 운영을 할 수 있는 환경을 Prometheus와 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
2단계 : 클러스터 준비
# 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
3단계 : 프로메테우스 설치
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
4단계 : Prometheus 매트릭 조회
# 파드 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]
5단계 : Grafana 매트릭 조회
# 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]
실습 결과는 다음과 같습니다.
마치며
이번 스터디에서는 k8s에서 주로 사용되는 CNI Plug-in인 Calico에 대해 살펴보았습니다. 스터디를 진행하며 이전까지 기본 CNI를 사용하며 네트워크에는 관심을 가지고 있지 않다가, 여러 CNI들을 살펴보며 하나씩 이해해 나가는 것이 재미있네요. 알려주시는 가시다 님과 스터디에 도움을 주는 엔지니어분들의 내공을 매 회차마다 실감하고 있습니다.
스터디를 하면서 패킷 덤프를 통해 장애구간을 알아내고, 이에 대한 적절한 조치를 하는 과정에 대해 학습하게 된 것은 앞으로 실무에 있어서 큰 도움이 될 것이라는 생각이 드네요!
이상으로 3주 차 스터디 내용을 마무리하도록 하겠습니다.
감사합니다 :)
'클라우드 컴퓨팅 & NoSQL > [KANS] 쿠버네티스 네트워크 심화 스터디' 카테고리의 다른 글
[5주차 - Service : LoadBalancer] KANS 스터디 (24.09.29) (3) | 2024.10.05 |
---|---|
[4주차 - Service : ClusterIP, NodePort] KANS 스터디 (24.09.22) (2) | 2024.09.26 |
[3주차(1/2) - k8s Calico CNI] KANS 스터디 (24.09.08) (1) | 2024.09.18 |
[2주차(2/2) - Flannel CNI] KANS 스터디 (24.09.01) (4) | 2024.09.05 |
[2주차(1/2) - K8S Pause container] KANS 스터디 (24.09.01) (6) | 2024.09.04 |