CloudNet@ 가시다님이 진행하는 쿠버네티스 네트워크 스터디 KANS 3기 내용을 정리한 글입니다.
파드는 고유한 IP를 가지지만 재시작할 때마다 IP가 변경되기 때문에 파드에 안정적으로 접근하는 것이 쉽지 않습니다. 이를 해결하기 위해 쿠버네티스는 여러 파드를 하나의 로드밸런서로 묶어주는 "서비스"라는 객체를 제공합니다.
서비스는 세 가지 타입이 있으며, 클러스터 내부에서만 접근 가능한 Cluster IP, 노드의 IP와 포트를 통해 외부에서 접근할 수 있는 NodePort, 그리고 외부 로드밸런서와 함께 사용되는 LoadBalancer가 있습니다. 이 중에서 Cluster IP는 클러스터 내부에서 파드 간의 통신을 원활하게 하기 위한 가장 기본적인 형태의 서비스 타입입니다.
파드가 가상 인터페이스를 통해 통신하는 것과 달리, 서비스는 kube-proxy를 통해 각 노드에 네트워크 룰을 설정하여 트래픽을 관리합니다. kube-proxy는 클러스터 생성 시 설정된 방법(iptables, ipvs, nftables 중 하나)에 따라 각 노드에 서비스에 대한 룰을 생성하고 업데이트합니다.
이제 이러한 네트워크 룰이 실제로 어떻게 생성되고 작동하는지, 그리고 Cluster IP를 통해 트래픽이 어떻게 전달되는지 자세히 살펴보겠습니다.
클러스터 정보
쿠버네티스 버전: 1.31
pod cidr: 10.10.0.0/16
service. cidr: 10.200.1.0/24
kube-proxy: iptables 모드
ClusterIP
위의 그림처럼 구성을 해두고 테스트를 해보도록 하겠습니다.
traefik/whoami 이미지를 사용하여 각 워커노드에 파드를 한개씩 생성합니다.
nicolaka/netshoot 이미지를 이제 client용으로 컨트롤플레인에 파드를 한개 생성합니다. 그리고 호출해보도록 하겠습니다.
파드의 아이피를 통해 호출했을 때 입니다.
서비스를 통해 호출했을 때 입니다.
달라진 점이 Host가 서비스의 아이피로 바뀌었습니다. 호출을 파드아이피로 하던것을 서비스 아이피로 바꿨다는 것을 의미합니다.
그리고 또 달라진 점이 같은 서비스로 호출해도 응답을 주는 곳이 매번 달라지게 됩니다.
1000번을 호출해보면 아주 잘 분산되어 호출이 되는것을 확인해볼 수 있습니다.
🌐 iptables를 통한 Pod 간 통신 흐름
conntrack 테이블을 보면 client 파드의 host에서 iptables에 의해 DNAT(목적지의 아이피가 바뀜)이 일어난 것을 알 수 있습니다.
앞에서 3번째줄의 TTL을 확인해보면 120초간 conntrack 테이블을 유지하게 됩니다.
🔍 ngrep으로 흐름 분석해보기
ngrep은 텍스트 패턴을 기반으로 네트워크 패킷을 필터링 할 수 있는 도구입니다.
예)
ngrep -d <인터페이스 이름> '정규 표현식' '패킷 캡처 규칙'
직접 한번 명령어를 사용해서 통신흐름을 분석해보도록 하겠습니다.
ngrep -t -W byline -d eth0 '' 'tcp port 80'
- -t: 패킷의 타임스탬프를 출력에 포함합니다.
- -W byline: 각 패킷을 줄 단위로 표시합니다.
- -d eth0: eth0 인터페이스에서 패킷을 캡처합니다.
- '': 모든 패킷을 캡처하도록 설정한 것입니다. (특정 문자열을 지정하지 않았기 때문에 모든 페이로드가 캡처됩니다.)
- 'tcp port 80': TCP 80번 포트를 사용하는 패킷만을 필터링합니다.
명령어를 실행한 결과입니다.
🚦 Traffic policies - internalTrafficPolicy
쿠버네티스에서 서비스를 생성할 때 다양한 설정을 통해 트래픽을 제어하고 관리할 수 있습니다. 그 중 하나가 internalTrafficPolicy와 externalTrafficPolicy인데, 이는 서비스의 트래픽이 클러스터 내부에서 어떻게 분배될지를 결정하는 중요한 설정입니다. externalTrafficPolicy이는 clusterIP모드일때에는 설정하지 않기 때문에 다른 모드에서 설명하도록 하겠습니다.
이 설정은 두 가지 모드로 작동합니다.
- Cluster (기본값):
- 모든 노드에 있는 파드로 트래픽을 분산시킵니다. 모든 파드가 균등하게 트래픽을 처리할 수 있도록 합니다. 예를 들어, 클러스터 내에 여러 노드가 있고 서비스에 10개의 파드가 배포되어 있다면, 트래픽은 각 노드에 고르게 분산됩니다.
- Local:
- 해당 노드에 있는 파드로만 트래픽을 전달합니다. 이렇게 하면 외부 네트워크를 경유하지 않고도 동일한 노드 내에서 통신이 이루어지므로 레이턴시를 최소화할 수 있습니다. 그러나 노드에 해당 서비스의 파드가 존재하지 않을 경우 해당 노드에서의 트래픽은 서비스되지 않으므로 주의가 필요합니다.
테스트 할때 사용했던 서비스와 동일한 설정의 서비스를 하나 더 만들어서 Local로 설정해 iptables를 비교해보겠습니다.
Cluster (기본 값)
-A KUBE-SERVICES -d 10.200.1.162/32 -p tcp -m comment --comment "default/svc-clusterip:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-SVC-KBDEBIL6IU6WL7RF
-A KUBE-SVC-KBDEBIL6IU6WL7RF ! -s 10.10.0.0/16 -d 10.200.1.162/32 -p tcp -m comment --comment "default/svc-clusterip:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-MARK-MASQ
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.1.2:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-TBW2IYJKUCAC7GB3
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.2.2:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DOIEFYKPESCDTYCH
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.3.2:80" -j KUBE-SEP-2TLZC6QOUTI37HEJ
cluster 로 설정된 경우에는 랜덤하게 다른 노드에 있는 파드로 호출될 수 있도록 iptables설정 된 반면
Local
# controlplane
-A KUBE-SERVICES -d 10.200.1.59/32 -p tcp -m comment --comment "default/svc-clusterip-local:svc-webport has no local endpoints" -m tcp --dport 9000 -j DROP
# worker
-A KUBE-SERVICES -d 10.200.1.59/32 -p tcp -m comment --comment "default/svc-clusterip-local:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-SVL-EJNJNKHLVPWOB7IX
-A KUBE-SVL-EJNJNKHLVPWOB7IX ! -s 10.10.0.0/16 -d 10.200.1.59/32 -p tcp -m comment --comment "default/svc-clusterip-local:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-MARK-MASQ
-A KUBE-SVL-EJNJNKHLVPWOB7IX -m comment --comment "default/svc-clusterip-local:svc-webport -> 10.10.2.2:80" -j KUBE-SEP-B2VU2QEVXC6WCRSG
local로 설정 했을 때에는 webpod가 떠있지 않은 controlplane에서는 drop 룰이 설정되고 파드가 떠있는 다른 노드에서는 해당 노드에 떠있는 파드로만 통신이 가도록 iptables가 설정됩니다.
실제로 테스트 했을 때에도 controlplane 노드의 파드에서는 통신이 되지 않는 것을 확인했습니다.
🔗 세션 어피티니
쿠버네티스에서 서비스를 생성할 때 세션 어피니티는 클라이언트의 요청이 항상 같은 파드로 전달되도록 설정하는 기능입니다. 이를 통해 상태 정보를 유지해야 하는 서비스나 애플리케이션에서 클라이언트의 세션을 특정 파드에 고정할 수 있습니다.
이 설정은 두 가지 모드로 작동합니다.
- None (기본값): 세션 어피니티가 비활성화된 상태입니다. 클라이언트의 요청은 클러스터 내의 모든 파드로 라운드 로빈 방식 등으로 분산되어 처리됩니다. 이 모드에서는 세션 고정 없이 부하 분산이 이루어지므로 상태 정보를 별도로 관리하지 않는 애플리케이션에 적합합니다.
- ClientIP: 클라이언트의 IP 주소를 기반으로 세션 어피니티가 적용됩니다. 특정 클라이언트의 요청이 처음 연결된 파드에 지속적으로 전달되도록 설정하는 방식으로, 쿠키를 사용하는 세션 고정과 비슷한 원리입니다. service.spec.sessionAffinityConfig.clientIP.timeoutSeconds를 통해 세션 고정 시간(기본값 10800초, 최대 86400초)을 설정할 수 있습니다. 이 모드에서는 클라이언트의 요청이 항상 동일한 파드로 전달되기 때문에 세션 상태를 유지해야 하는 애플리케이션에 유용합니다.
테스트 할때 사용했던 서비스와 동일한 설정의 서비스를 하나 더 만들어서 SessionAffinity를 ClientIP로 설정해 iptables를 비교해보겠습니다.
None (기본 값)
-A KUBE-SERVICES -d 10.200.1.162/32 -p tcp -m comment --comment "default/svc-clusterip:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-SVC-KBDEBIL6IU6WL7RF
-A KUBE-SVC-KBDEBIL6IU6WL7RF ! -s 10.10.0.0/16 -d 10.200.1.162/32 -p tcp -m comment --comment "default/svc-clusterip:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-MARK-MASQ
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.1.2:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-TBW2IYJKUCAC7GB3
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.2.2:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-DOIEFYKPESCDTYCH
-A KUBE-SVC-KBDEBIL6IU6WL7RF -m comment --comment "default/svc-clusterip:svc-webport -> 10.10.3.2:80" -j KUBE-SEP-2TLZC6QOUTI37HEJ
ClientIP
-A KUBE-SERVICES -d 10.200.1.230/32 -p tcp -m comment --comment "default/svc-clusterip-session:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-SVC-QPXAKLHZRVLKUXEM
-A KUBE-SVC-QPXAKLHZRVLKUXEM ! -s 10.10.0.0/16 -d 10.200.1.230/32 -p tcp -m comment --comment "default/svc-clusterip-session:svc-webport cluster IP" -m tcp --dport 9000 -j KUBE-MARK-MASQ
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.1.2:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-NHYX47EIEFEIB2F4 --mask 255.255.255.255 --rsource -j KUBE-SEP-NHYX47EIEFEIB2F4
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.2.2:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-HB6QZH6WL34PGYGE --mask 255.255.255.255 --rsource -j KUBE-SEP-HB6QZH6WL34PGYGE
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.3.2:80" -m recent --rcheck --seconds 10800 --reap --name KUBE-SEP-FBVUONYERWMMZAJX --mask 255.255.255.255 --rsource -j KUBE-SEP-FBVUONYERWMMZAJX
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.1.2:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-NHYX47EIEFEIB2F4
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.2.2:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-HB6QZH6WL34PGYGE
-A KUBE-SVC-QPXAKLHZRVLKUXEM -m comment --comment "default/svc-clusterip-session:svc-webport -> 10.10.3.2:80" -j KUBE-SEP-FBVUONYERWMMZAJX
-A KUBE-SEP-FBVUONYERWMMZAJX -s 10.10.3.2/32 -m comment --comment "default/svc-clusterip-session:svc-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-FBVUONYERWMMZAJX -p tcp -m comment --comment "default/svc-clusterip-session:svc-webport" -m recent --set --name KUBE-SEP-FBVUONYERWMMZAJX --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.3.2:80
-A KUBE-SEP-HB6QZH6WL34PGYGE -s 10.10.2.2/32 -m comment --comment "default/svc-clusterip-session:svc-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-HB6QZH6WL34PGYGE -p tcp -m comment --comment "default/svc-clusterip-session:svc-webport" -m recent --set --name KUBE-SEP-HB6QZH6WL34PGYGE --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.2.2:80
-A KUBE-SEP-NHYX47EIEFEIB2F4 -s 10.10.1.2/32 -m comment --comment "default/svc-clusterip-session:svc-webport" -j KUBE-MARK-MASQ
-A KUBE-SEP-NHYX47EIEFEIB2F4 -p tcp -m comment --comment "default/svc-clusterip-session:svc-webport" -m recent --set --name KUBE-SEP-NHYX47EIEFEIB2F4 --mask 255.255.255.255 --rsource -m tcp -j DNAT --to-destination 10.10.1.2:80
이 규칙들은 최근 트래픽이 특정 백엔드 엔드포인트로 전달되었다면, 동일한 소스 IP가 3시간(10800초) 동안 동일한 백엔드 엔드포인트로 연결될 수 있도록 해줍니다
-m recent --rcheck --seconds 10800 --reap
- -m recent: 최근 트래픽의 기록을 추적
- --rcheck: 이 엔드포인트를 사용한 기록이 있는지 확인하는 역할
- --reap: 설정된 시간(--seconds)이 만료된 엔트리를 자동으로 정리
conntrack 명령어
conntrack -L
현재 추적 중인 연결 상태 조회할 수 있습니다. 한마디로 현재 저장되어있는 테이블을 모두 출력합니다.
-p 옵션을 통해 tcp, udp 프로토콜을 나누어서 조회할 수 있습니다.
conntrack -L -p tcp
conntrack -D
특정 연결정보를 삭제합니다. 별로 쓸일은 없을 것 같으니 생략! 전체를 날리는건 -F 옵션이 있지만 이것도 생략!
conntrack -C
현재 테이블의 수를 출력합니다.
conntrack -E
실시간으로 테이블을 모니터링할 수 있습니다. 새로운 연결정보가 생기거나 변경 및 삭제되었을 때 로그가 생성됩니다.
conntrack -S
현재 커널에서 추적 중인 연결에 대한 다양한 통계 정보를 확인할 수 있다. 시스템이 많은 연결을 처리할 때 drop, insert_failed, early_drop 등의 수치를 보고 테이블 크기를 조정할 때 참고하면 유용하다.
특히, 커널 5.15 버전 이상에서는 테이블에 여유 공간이 있어도 커널 체인의 길이 때문에 연결 삽입이 제한되는 경우가 보고된 적이 있다고 한다. 이럴 땐 conntrack -S 명령어로 chaintoolong 값이 0이 아닌지 확인하면 된다. (googld cloud 알려진문제 참고)
'K8S > 🔥 network study🔥' 카테고리의 다른 글
[네떡스터디🔥kans] 쿠버네티스 서비스 이해하기 - LoadBalancer(metallb) (0) | 2024.09.22 |
---|---|
[네떡스터디🔥kans] 쿠버네티스 서비스 이해하기 - NodePort (0) | 2024.09.22 |
[네떡스터디🔥kans] Calico CNI 알아보기 - BGP (1) | 2024.09.17 |
[네떡스터디🔥kans] Calico CNI 알아보기 - Direct 모드 (0) | 2024.09.09 |
[네떡스터디🔥kans] Calico CNI 알아보기 - VxLAN 모드 (0) | 2024.09.09 |