IPIP 모드
IPIP는 위 그림과 같이 통신이 이뤄지며, 노드가 서로 다른 L3 네트워크에 있을 때 유용하고,
원본 IP 패킷을 다른 IP 패킷 내에 캡슐화하여 사용합니다.
또 거쳐가는 구간이 있어 약간의 오버헤드가 발생합니다. (tunl0, nic-nic)
IPIP에 대한 실습은 이전 게시글에서 진행했으므로 아래 링크를 참고하시기 바랍니다.
Direct 모드
Direct mode는 calico가 노드 간에 직접 ip 라우팅을 사용합니다, 통신 플로우가 매우 단순하고 캡슐화가 없기 때문에 오버헤드가 거의 없기 때문에 IPIP에 비하면 빠른 편에 속합니다.
캡슐화가 없기 때문에 목적지 pod에서 출발지 pod의 ip가 확인됩니다.
AWS에서 다이렉트 모드를 사용할 경우 NIC 에 매칭되지 않는 IP 패킷은 차단되니, NIC에 Source/Destination Check 기능을 Disable 해야 합니다. - 링크
$ aws ec2 modify-instance-attribute --instance-id <INSTANCE_ID> --source-dest-check "{\"Value\": false}"
현재 상태를 확인해보면 ipip 설정이 always로 설정되어 있고, 각 노드의 인터페이스 역시 tunl0 으로 설정되어 있습니다.
이 상태에서 ipip 설정을 never로 바꾸면 각 노드의 route 인터페이스가 변경되는 것을 확인할 수 있습니다.
$ calicoctl get ippool default-ipv4-ippool -o yaml | sed -e "s/ipipMode: Always/ipipMode: Never/" | calicoctl apply -f -
파드 생성
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
---
apiVersion: v1
kind: Pod
metadata:
name: pod3
spec:
nodeName: k8s-w0
containers:
- name: pod3
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
생성 후 정보 확인
$ k get pod | grep pod; calicoctl get wep | grep pod
파드 간 ping 통신 실행 및 패킷 캡쳐 확인
## control plane ##
$ kubectl exec -it pod1 -- zsh
## pod1 내부 ##
ping -c 10 {pod2 ip}
## 각 노드에서 tcpdump 수행 ##
$ tcpdump -i ens5 -nn icmp
위 그림과 같이 pod to pod ping 통신 시 노드에서 packet을 잡아보면 node ip로 통신이 잡히는 것이 아니라,
각 pod의 ip가 잡히는 것을 확인해 볼 수 있습니다.
이전 게시글에서 worker node를 총 3개 생성했고, 1개 node는 다른 subnet에 위치하고 있습니다.
이 경우에 direct mode에서 동일하게 통신이 가능한지 테스트해보겠습니다.
## pod1 내부에서 0번 노드의 pod로 icmp 통신 시도 ##
$ ping -c 10 172.16.34.7
## woker1 에서 eth0에 대해 tcpdump ##
$ tcpdump -i ens5 -nn icmp
## woekr0 에서도 동일하게 tcpdump ##
$ tcpdump -i ens5 -nn icmp
통신이 안되는 것은 아래와 같은 이유입니다.
AWS와 같은 CSP는 자체적으로 내제되어있는 route table을 통해 route를 관리합니다.
같은 서브넷에 있을 경우라면 별도의 route 설정이 필요 없기 때문에 pod to pod 통신이 가능했지만,
subnet이 다를 경우에는 각 서브넷 간에 routing이 필요한데 pod IP에 대한 라우팅이 route table에서는 불가능하기 때문입니다.
정말 통신이 절대 불가능해!?
"서로 다른 subnet에서 Direct mode를 사용할 수 있는 방법이 없는가?"라는 질문에는 "방법은 있습니다"라고 대답할 수 있습니다.
Self VPC를 사용하는 팀에게는 아주 익숙한 route table에 정책을 추가하면 Direct mode를 사용할 수 있습니다.
설정 절차는 아래와 같습니다.
AWS Route table에 pod ip를 ENI(인스턴스)에 할당하는 라우팅을 추가하면 쉽게 통신이 되는 것을 확인할 수 있습니다.
이렇게 설정하면 Calico는 노드 간 BGP를 사용하여 pod ip 정보를 교환합니다.
캡슐화 역시 진행되지 않기 때문에 pod ip를 그대로 확인할 수 있습니다.
이 설정의 단점은 AWS Route table을 수동으로 관리해야한다는 단점이 있고,
이 단점으로 당장의 노드 끼리의 통신 이외의 추가 설정과 제약이 발생할 가능성을 배제할 수 없다는 것입니다.
CrossSubnet 모드
개요 : 노드 간의 네트워크 대역이 같다면 Direct 모드로 동작하고, 노드 간의 네트워크 대역이 다르다면 IPIP 모드로 동작합니다.
## control plane ##
$ calicoctl patch ippool default-ipv4-ippool -p '{"spec":{"ipipMode":"CrossSubnet"}}'
## mode status ##
$ calicoctl get ippool -o wide
## pod 1 에서 통신 테스트 ##
$ ping -c 10 172.16.184.5 ## worker2 node pod
$ ping -c 10 172.16.34.7 ## worker0 node pod
이 상태에서 다시 한번 통신해보면 같은 대역에 있는 pod2에는 ens5 로 패킷이 잘 들어오는 것을 확인할 수 있고,
다른 대역에 있는 노드 쪽으로는 tunl0의 터널 인터페이스를 통해 IPIP 모드로 패킷이 들어오는 것을 확인해 볼 수 있습니다.
VXLAN 모드
VXLAN mode에서 calico는 Virtual Extensible LAN을 사용하여 노드 간 통신을 수행합니다.
VXLAN은 복잡한 네트워크 환경에서 유용하게 쓰이며, IPIP보다 큰 오버헤드가 발생하지만, 더 유연하게 네트워크를 구성할 수 있습니다.
다른 노드 간의 파드 통신은 vxlan 인터페이스를 통해 L2 프레임이 L4 UDP 패킷 내에 캡슐화되어 목적지 노드로 도달하고, vxlan 인터페이스가 outer 헤더를 제거하고 내부의 파드와 통신하게 됩니다.
실습 과정은 Flannel 게시글에서 진행한 실습과 비슷하기 때문에 아래 게시글을 참고하시면 됩니다.
Pod 패킷 암호화(with wireguard)
wireguard라는 오픈 소스를 통해 패킷을 암호화합니다.
cilium에서도 wireguard를 사용하고 있습니다.
wireguard에 대한 설명은 링크에서 자세히 설명하고 있습니다. 링크
WireGuard는 보시다시피 경쟁자들인 IPsec, SoftEther VPN, OpenVPN 등과 비교할 때, 코드 size(Line of Codes)가 현격하게 작습니다.
그말은 코드량이 많지 않으니 그만큼 철저히 검증될 가능성이 높고, 예상치 못한 곳에서의 버그로 인한 취약점이 발생할 가능성이 상대적으로 적다는 얘기가 됩니다.
wireGuard 설정
## 설치
$ apt install wireguard -y
## WireGuard 버전 확인
$ wg version
## Calico 설정
$ calicoctl get felixconfiguration -o yaml
$ calicoctl patch felixconfiguration default --type='merge' -p '{"spec":{"wireguardEnabled":true}}'
## 설정 확인
$ calicoctl get felixconfiguration default -o yaml | grep wireguardEnabled
wireguardEnabled: true
## KEY 확인
$ calicoctl get node -o yaml | grep wireguardPublicKey
wireguardPublicKey: FT8Hgc5mRb2ubI6MAiL95OnZCrP+50m3lhRS5V+sNlA=
wireguardPublicKey: RtOkNI/mJYRrksoWRyOZR0SdS1/uQok6t+r+DH48vm4=
wireguardPublicKey: Ai8sEMBBUdMczLGI7JBKlxTH8LHIUoxocWLxre/Q/RE=
wireguardPublicKey: 3XIvfdIgJ0eB6/Ym9NkHUHaVrVBVr6WsqzTtZX1IoT0=
## 인터페이스 확인
$ ip -c -d addr show wireguard.cali
12: wireguard.cali: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 8941 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.116.2/32 scope global wireguard.cali
valid_lft forever preferred_lft forever
## config 확인
$ wg showconf wireguard.cli
[Interface]
ListenPort = 51820
FwMark = 0x100000
PrivateKey = +MksHtoQJzZELFwZ+4nlRXxEtPKz7J41yFKHFamuCE4=
[Peer]
PublicKey = RtOkNI/mJYRrksoWRyOZR0SdS1/uQok6t+r+DH48vm4=
AllowedIPs = 172.16.34.8/32, 172.16.34.0/24, 172.16.34.9/32
Endpoint = 192.168.20.100:51820
[Peer]
PublicKey = Ai8sEMBBUdMczLGI7JBKlxTH8LHIUoxocWLxre/Q/RE=
AllowedIPs = 172.16.158.8/32, 172.16.158.0/24, 172.16.158.9/32
Endpoint = 192.168.10.101:51820
[Peer]
PublicKey = 3XIvfdIgJ0eB6/Ym9NkHUHaVrVBVr6WsqzTtZX1IoT0=
AllowedIPs = 172.16.184.0/24, 172.16.184.6/32, 172.16.184.7/32
Endpoint = 192.168.10.102:51820
파드 생성 및 확인
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
---
apiVersion: v1
kind: Pod
metadata:
name: pod3
spec:
nodeName: k8s-w0
containers:
- name: pod3
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
통신 확인
## worker node에서 수행 ##
$ ss -unlp | grep 51820
tcpdump 캡쳐
## worker node 에서 수행 ##
$ tcpdump -i ens5 -nn udp port 51820
패킷을 캡쳐해서 wireshark에서 확인하면 아래와 같이 UDP 51820 포트를 사용하고, wireguard 프로토콜을 통해 패킷이 암호화된 것을 확인해 볼 수 있습니다.
암/복호화가 발생하고 여러 프로세스 리소스가 발생할 수 있기 때문에 추가적인 오버헤드가 발생할 수 있습니다.
모니터링
## 기존 설정 확인
$ calicoctl get felixconfiguration -o yaml
## 프로메테우스 설치를 위한 enable patch
$ calicoctl patch felixconfiguration default --patch '{"spec":{"prometheusMetricsEnabled": true}}'
메트릭 수집을 위한 felix, calico kube controllers 서비스 생성
## Felix metrics 서비스 생성
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
## calico kube-controllers 서비스 생성 ##
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
클러스터 내부 리소스 생성
## 네임스페이스 생성 ##
apiVersion: v1
kind: Namespace
metadata:
name: calico-monitoring
labels:
app: ns-calico-monitoring
role: monitoring
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
[프로메테우스 설치]
## prometheus configmap 생성 ##
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
## 프로메테우스 pod 생성 ##
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
## 프로메테우스 접근을 위한 서비스 생성 ##
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
## 프로메테우스 대시보드 접근 URL 확인 ##
$ echo -e "Prometheus URL = http://$(curl -s ipinfo.io/ip):30001"
[그라파나 설치]
## grafana 에서 사용할 configmap 설정 ##
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
calico grafana 대시보드 생성
$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/grafana-dashboards.yaml
grafana pod 생성
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
외부 접근을 위해 nodePort type 서비스 생성
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
그라파나 웹 콘솔 접속하여 이전에 함께 생성한 Felix 대시보드 수집 확인
'Cloud > Kubernetes' 카테고리의 다른 글
[KANS] LoadBalancer (2) | 2024.10.05 |
---|---|
[KANS] Service : ClusterIP, NodePort (1) | 2024.09.28 |
[KANS] Calico CNI 기본 통신 (0) | 2024.09.14 |
[KANS] Flannel CNI (2) | 2024.09.08 |
[KANS] Kind를 이용한 Pause container (0) | 2024.09.06 |