
본 문서는 FlannelCNI 기반으로 구축된 온프레미스 클러스터에서
파드 - 파드 || 서비스 || 헤드리스 서비스 간의 통신 경로에 대한 내용을 다룹니다.
FlannelCNI에 대한 딥다이브 내용은 아래 문서에서 다루고 있습니다.
쿠버네티스 네트워크 흐름에 대한 이해 (2/2) (Flannel CNI) [1]
A. 단일 호스트
단일 호스트에서 직접 통신, 서비스 통신, 헤드리스 서비스 통신 등을 실습하면서
가상 이더넷 페어, 브릿지, CoreDNS, Kube-proxy 등을 이해하고자 합니다.
단일 호스트 내 파드(Pods) 간 네트워크 통신
단일 호스트 내 파드(Pod)에서 서비스, 디플로이먼트(Service, Deploy)로 네트워크 통신
단일 호스트 내 파드(Pod)에서 헤드리스 서비스(StatefulSet)로 네트워크 통신
A.1. 파드 간 통신
단일 호스트 내 파드(Pods) 간 네트워크 통신
단일 호스트에서 한 파드(b)에서 다른 파드(a)로 요청을 보내면
가상 이더넷 페어(eth0 — veth0)과 브릿지(cni0)를 거쳐서 도착하게 됩니다.
요청에 대한 가상 이더넷 페어, 브릿지를 확인하고자 [실습 1]를 진행하였으나
실험 환경에서 TTL Hop을 줄이는 구간이 없어 traceroute에서 감지되지 않았습니다.
[실습 1] traceroute를 사용한 통신 흐름
1. 테스트 파드 생성하기
kubectl run a-pod --image=busybox --restart=Never -- sleep 3600
kubectl run b-pod --image=busybox --restart=Never -- sleep 3600
2. 테스트 파드 확인하기
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
a-pod 10.244.0.53
b-pod 10.244.0.54
3. 테스트 파드 접속하고 네트워크 테스트하기
kubectl exec -it b-pod -- /bin/sh
# traceroute 10.244.0.26
4. 테스트 파드 정리하기
kubectl delete pod/a-pod --force
kubectl delete pod/b-pod --force
💡
[traceroute 작동방식*] TTL(Time-To-Live)을 증가시키면서 패킷을 전송하고 TTL이 0이 되는 시점에서 ICMP Time Exceeded 메세지를 수집 따라서 TTL을 줄이는 네트워크 홉이 없는 경우에는 감지되지 않음 * 운영 체제에 따라서 ICMP가 아닌UDP를 사용할 수도 있음
세부적인 가상 이더넷 페어, 브릿지를 확인하기 위해 [실습 2]를 진행하였고
tcpdump를 통해서 파드 B에서 파드 A로 가는 흐름을 확인할 수 있었습니다.
Pod A netns (eth0)
→ Root netnes (veth75a7f7d6 → cni0 → vethded7ef9f)
→ Pod B netns (eth0)
[실습 2] tcpdump를 사용한 이더넷, 브릿지 확인
1. 테스트 파드 생성하기
kubectl run a-pod --image=busybox --restart=Never -- sleep 3600
kubectl run b-pod --image=busybox --restart=Never -- sleep 3600
2. 테스트 파드 IP 확인하기
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP | grep -E "(NAME|app)"
NAME IP
a-pod 10.244.0.53
b-pod 10.244.0.54
3. 파드 A의 패킷 캡쳐 시작하기
tcpdump -i any host 10.244.0.53
4. 파드 B에서 파드 A로 ping 전송하기
(4번은 별도의 터미널을 열어서 진행해주세요.)
kubectl exec -it b-pod -- /bin/sh
/ # ping -c 1 10.244.0.53
PING 10.244.0.53 (10.244.0.53): 56 data bytes
64 bytes from 10.244.0.53: seq=0 ttl=64 time=0.297 ms
--- 10.244.0.53 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.297/0.297/0.297 ms
5. 파드 A의 패킷 캡처 확인하기(3번을 실행한 터미널)
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
21:18:36.504305 vethded7ef9f P IP 10.244.0.54 > 10.244.0.53: ICMP echo request, id 39, seq 0, length 64
21:18:36.504380 veth75a7f7d6 Out IP 10.244.0.54 > 10.244.0.53: ICMP echo request, id 39, seq 0, length 64
21:18:36.504440 veth75a7f7d6 P IP 10.244.0.53 > 10.244.0.54: ICMP echo reply, id 39, seq 0, length 64
21:18:36.504459 vethded7ef9f Out IP 10.244.0.53 > 10.244.0.54: ICMP echo reply, id 39, seq 0, length 64
21:18:41.740639 veth75a7f7d6 P ARP, Request who-has 10.244.0.54 tell 10.244.0.53, length 28
21:18:41.740646 vethded7ef9f Out ARP, Request who-has 10.244.0.54 tell 10.244.0.53, length 28
21:18:41.740649 vethded7ef9f P ARP, Request who-has 10.244.0.53 tell 10.244.0.54, length 28
21:18:41.740653 veth75a7f7d6 Out ARP, Request who-has 10.244.0.53 tell 10.244.0.54, length 28
21:18:41.740699 vethded7ef9f P ARP, Reply 10.244.0.54 is-at ae:cb:f3:eb:3a:b9 (oui Unknown), length 28
21:18:41.740703 veth75a7f7d6 Out ARP, Reply 10.244.0.54 is-at ae:cb:f3:eb:3a:b9 (oui Unknown), length 28
21:18:41.740707 veth75a7f7d6 P ARP, Reply 10.244.0.53 is-at 7e:45:1f:49:82:91 (oui Unknown), length 28
21:18:41.740709 vethded7ef9f Out ARP, Reply 10.244.0.53 is-at 7e:45:1f:49:82:91 (oui Unknown), length 28
6. 파드 A, B의 브릿지 확인하기
아래 구문을 vethded7ef9f, veth75a7f7d6으로 실행해볼 것
# Host
ip link | grep vethded7ef9f
4178: vethded7ef9f@if2: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
7. 테스트 파드 정리하기
kubectl delete pod/a-pod --force
kubectl delete pod/b-pod --force
이를 통해서
일반적으로 알려진 파드 간 네트워크 통신을 증명할 수 있었습니다.
A.2. 파드에서 서비스로 통신
단일 호스트 내 파드(Pod)에서 서비스, 디플로이먼트(Service, Deploy)로 네트워크 통신
단일 호스트에서 한 파드(b)에서 다른 서비스(a)로 요청을 보내면
가상 이더넷 페어, 브릿지 외에 coredns, kube-proxy를 경유한다 알고 있습니다.
(가설의 편의를 위해서 kube-proxy는 iptables 방식을 사용하며 외부 변수는 통제)
이를 확인하기 위해 [실습 3]을 진행하였으나
TCP(HTTP/S)를 사용하는 wget은 정상적으로 NGINX을 응답 받았으나,
UDP/ICMP를 사용하는 traceroute는 요청이 외부로 나가는 현상이 보였습니다.
즉, Private IP를 직접 명시하지 않고 도메인 이름을 사용하는 경우에는 traceroute가 기능하지 않음을 알 수 있었습니다.
[실습 3] wget, traceroute를 이용한 통신 흐름 확인
1. 테스트 앱 배포하기
kubectl create deploy a-app --image=nginx --replicas=2
kubectl run b-app --image=busybox -- sleep 3600
kubectl expose deployment a-app --port=80 --target-port=80 --type=ClusterIP
2. 테스트 앱 확인하기
kubectl get svc,endpoints,deploy,pod -o=custom-columns=TYPE:.kind,NAME:.metadata.name | grep -E "(TYPE|app)"
TYPE NAME
Service a-app
Endpoints a-app
Deployment a-app
Pod a-app-57974fdb4-mcz5n
Pod a-app-57974fdb4-mv7qn
Pod b-app
3. 테스트 앱 요청 전송하기
kubectl exec b-app -- wget -qO- http://a-app.default.svc.cluster.local
...
Welcome to nginx!
4. 테스트 앱 요청 경로 확인하기
kubectl exec -it b-app -- traceroute -m 10 a-app.default.svc.cluster.local
traceroute to a-app.default.svc.cluster.local (10.103.129.86), 10 hops max, 46 byte packets
1 10.244.0.1 (10.244.0.1) 0.015 ms 0.014 ms 0.013 ms
2 172.30.1.254 (172.30.1.254) 1.416 ms 1.498 ms 1.327 ms
3 61.77.108.1 (61.77.108.1) 2.935 ms * 19.481 ms
4 125.141.249.140 (125.141.249.140) 2.617 ms 3.073 ms 3.046 ms
5 * * *
6 * * *
7 * * *
8 * * *
9 * * *
10 * * *
5. 테스트 앱 정리하기
kubectl delete deploy/a-app --force
kubectl delete pod/b-app --force
kubectl delete svc/a-app
결국 세부적인 흐름을 확인하기 위해 [실습 4]를 진행하면서
4-10 과정을 통해 B Pod에서 cluster.local 질의 흐름을 보고
11-15 과정을 통해 B Pod에서 a-app.default.svc.cluster.local 질의 흐름을 보고
16-20 과정을 통해 B Pod에서 A Pod에 도달하기까지 가상 이더넷 페어, 브릿지 등을 볼 수 있었습니다.
풀어서 설명하면 대략적으로 아래와 같은 흐름인 것 같습니다.
네임서버 위치를 찾는 과정 (nameserver + iptables)
특정한 앤드포인트를 찾는 과정 (nameserver + iptables)
특정한 엔드포인트로 요청이 가는 과정
Pod B netns (eth0)
→ Root netnes (vetha999267c → cni0 → vethedfea565)
→ Pod A netns (eth0)
[실습 4] wget 시 도메인 질의와 네트워크 흐름
1. 테스트 앱 배포하기
kubectl create deploy a-app --image=nginx --replicas=2
kubectl run b-app --image=busybox -- sleep 3600
kubectl expose deployment a-app --port=80 --target-port=80 --type=ClusterIP
2. 테스트 앱 확인하기
kubectl get svc a-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
a-app ClusterIP 10.103.129.8680/TCP 52m
3. 테스트 앱 IP 확인하기
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP | grep -E "(NAME|app)"
NAME IP
a-app-57974fdb4-mcz5n 10.244.0.143
a-app-57974fdb4-mv7qn 10.244.0.144
b-app 10.244.0.147
4. b-pod의 /etc/hosts 확인하기
IPv4, IPv6와 도메인 간의 연결고리를 최초로 확인할 수 있습니다.
kubectl exec b-app -- cat /etc/hosts /etc/resolv.conf
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.0.147 b-app
5. b-pod의 /etc/resolv.conf 확인하기
/etc/hosts에 선언되지 않은 경우 네임서버로 질의하도록 설정이 가능합니다.
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
6. b-pod에서 cluster.local에 대한 nslookup 실행해보기
실제로 cluster.local을 호출하면 네임서버 IP가 반환되는지 확인합니다.
kubectl exec -it b-app -- nslookup cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
7. 네임서버(CoreDNS) 위치 확인하기
쿠버네티스의 기본 네임서버는 CoreDNS Service의 ClusterIP를 향합니다.
kubectl get svc -A -o=custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,CLUSTER-IP:.spec.clusterIP | grep -E "(NAME|10.96.0.1)"
NAMESPACE NAME CLUSTER-IP
default kubernetes 10.96.0.1
kube-system kube-dns 10.96.0.10
8. 네임서버의 패킷 포워딩 경로 확인하기
CoreDNS ClusterIP에 도메인 질의를 하면 패킷 포워딩 체인을 볼 수 있습니다.
sudo iptables -t nat -L KUBE-SERVICES -n | grep 10.96.0.1
KUBE-SVC-JD5MR3NA4I4DYORP tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:metrics cluster IP */
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.96.0.1 /* default/kubernetes:https cluster IP */
KUBE-SVC-TCOU7JCQXEZGVUNU udp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns cluster IP */
KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */
9. 네임서버의 패킷 포워딩 체인 확인하기
패킷 포워딩 체인에는 두 아이피(10.244.0.105, 10.244.0.107)가 보입니다.
sudo iptables -t nat -L KUBE-SVC-JD5MR3NA4I4DYORP -n
echo "=================================================="
sudo iptables -t nat -L KUBE-SVC-NPX46M4PTMTKRN6Y -n
echo "=================================================="
sudo iptables -t nat -L KUBE-SVC-TCOU7JCQXEZGVUNU -n
echo "=================================================="
sudo iptables -t nat -L KUBE-SVC-ERIFXISQEP7F7OF4 -n
echo "=================================================="
Chain KUBE-SVC-JD5MR3NA4I4DYORP (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.96.0.10 /* kube-system/kube-dns:metrics cluster IP */
KUBE-SEP-3FUHYLOKDTXSLTUI all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:metrics -> 10.244.0.105:9153 */ statistic mode random probability 0.50000000000
KUBE-SEP-LINGYYJVFJ44OFRC all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:metrics -> 10.244.0.107:9153 */
==================================================
Chain KUBE-SVC-NPX46M4PTMTKRN6Y (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.96.0.1 /* default/kubernetes:https cluster IP */
KUBE-SEP-XJNE7KJ7C5OK5DHV all -- 0.0.0.0/0 0.0.0.0/0 /* default/kubernetes:https -> 172.30.1.48:6443 */
==================================================
Chain KUBE-SVC-TCOU7JCQXEZGVUNU (1 references)
target prot opt source destination
KUBE-MARK-MASQ udp -- !10.244.0.0/16 10.96.0.10 /* kube-system/kube-dns:dns cluster IP */
KUBE-SEP-SMSEVC7UC3TMMLEX all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:dns -> 10.244.0.105:53 */ statistic mode random probability 0.50000000000
KUBE-SEP-D2LNDAFJVFQ3SYLN all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:dns -> 10.244.0.107:53 */
==================================================
Chain KUBE-SVC-ERIFXISQEP7F7OF4 (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.96.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */
KUBE-SEP-RJ76AAHLMPF4DXXJ all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:dns-tcp -> 10.244.0.105:53 */ statistic mode random probability 0.50000000000
KUBE-SEP-I6P6J5WVH6DMN5I3 all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kube-dns:dns-tcp -> 10.244.0.107:53 */
==================================================
10. 네임서버(CoreDNS) 파드 확인하기
두 아이피는 CoreDNS Pod에 할당된 IP와 동일합니다.
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP -A | grep -E "(NAME|coredns)"
NAME IP
coredns-7c65d6cfc9-g2qxv 10.244.0.105
coredns-7c65d6cfc9-sscfz 10.244.0.107
11. b-pod에서 a-app.default.svc.cluster.local에 대한 nslookup 실행해보기
네임서버(CoreDNS, 6번 참고)에 질의해 가상IP(10.103.129.86)를 봤습니다.
kubectl exec -it b-app -- nslookup a-app.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: a-app.default.svc.cluster.local
Address: 10.103.129.86
12. a-app.default.svc.cluster.local에 대한 패킷 포워딩 경로 확인하기
실제로 iptables에서 포워딩 체인을 향하도록 하는 것을 볼 수 있습니다.
sudo iptables -t nat -L KUBE-SERVICES -n | grep 10.103.129.86
KUBE-SVC-CHTNYTLOWMWCGZWR tcp -- 0.0.0.0/0 10.103.129.86 /* default/a-app cluster IP */
13. a-app.default.svc.cluster.local에 대한 패킷 포워딩 체인 확인하기해당 포워딩 체인에서는 두 아이피(10.244.0.143, 10.244.0.144)가 보입니다.
sudo iptables -t nat -L KUBE-SVC-CHTNYTLOWMWCGZWR -n
Chain KUBE-SVC-CHTNYTLOWMWCGZWR (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.103.129.86 /* default/a-app cluster IP */
KUBE-SEP-ZZRQGRW2BIJ6VNXP all -- 0.0.0.0/0 0.0.0.0/0 /* default/a-app -> 10.244.0.143:80 */ statistic mode random probability 0.50000000000
KUBE-SEP-BJVKNWLDIWYBU2WJ all -- 0.0.0.0/0 0.0.0.0/0 /* default/a-app -> 10.244.0.144:80 *
14. a-app 파드 확인하기두 아이피는 a-app Pod에 할당된 IP와 동일합니다.
kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP -A | grep -E "(NAME|a-app)"
NAME IP
a-app-57974fdb4-mcz5n 10.244.0.143
a-app-57974fdb4-mv7qn 10.244.0.144
15. a-app-57974fdb4-mcz5n Pod (10.244.0.143)의 tcpdump 확인하기
sudo tcpdump -i any host 10.244.0.143 -n
16. a-app-57974fdb4-mcz5 Pod (10.244.0.144)의 tcpdump 확인하기
sudo tcpdump -i any host 10.244.0.144 -n
17. a-app Service (10.103.129.86)의 tcpdump 확인하기
sudo tcpdump -i any host 10.103.129.86 -n
18. b-app Pod에서 a-app Service의 도메인으로 질의하기
kubectl exec b-app -- wget -qO- http://a-app.default.svc.cluster.local
...
Welcome to nginx!
...
19. a-app-* Pod tcpdump 둘 중 하나(15번, 16번)에 패킷 덤프가 기록될 것
b-pod netns eth — root netns vetha999267c
a-pod netns eth — root netns vethedfea565
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
14:44:36.756039 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756048 vetha999267c Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756051 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756054 veth53932873 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756056 vethf290baee Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756059 veth6bcce521 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756062 veth8a8d0f26 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756064 vethf201ff42 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756067 veth8aa03fe0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756070 veth396efc3f Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756072 vethd435ec95 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756075 vethb68ca92d Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756078 vethbd42ba02 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756080 vetheeff4d57 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756083 vethab0e2c30 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756086 veth9d176d07 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756089 vethb41c5c9a Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756091 veth86736a4f Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756094 veth725de8a5 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756096 veth672167ea Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756099 veth6b029ff9 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756101 veth17423715 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756104 veth902ab610 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756107 veth73448725 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756109 vethd1954510 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756111 vetha204b856 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756114 vethf751aa00 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756116 vethe6bf6e5d Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756118 veth8385c227 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756121 vethfb36eb94 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756153 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [S.], seq 3569082682, ack 4003348581, win 64308, options [mss 1410,sackOK,TS val 3133631796 ecr 2321197533,nop,wscale 7], length 0
14:44:36.756174 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 0
14:44:36.756175 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 0
14:44:36.756216 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [P.], seq 1:95, ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 94: HTTP: GET / HTTP/1.1
14:44:36.756218 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [P.], seq 1:95, ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 94: HTTP: GET / HTTP/1.1
14:44:36.756222 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [.], ack 95, win 502, options [nop,nop,TS val 3133631796 ecr 2321197533], length 0
14:44:36.756322 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [P.], seq 1:234, ack 95, win 502, options [nop,nop,TS val 3133631796 ecr 2321197533], length 233: HTTP: HTTP/1.1 200 OK
14:44:36.756345 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 234, win 506, options [nop,nop,TS val 2321197534 ecr 3133631796], length 0
14:44:36.756347 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 234, win 506, options [nop,nop,TS val 2321197534 ecr 3133631796], length 0
14:44:36.756363 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [P.], seq 234:849, ack 95, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 615: HTTP
14:44:36.756371 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 849, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756372 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [.], ack 849, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756398 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [F.], seq 849, ack 95, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 0
14:44:36.756443 cni0 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [F.], seq 95, ack 850, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756447 vethedfea565 Out IP 10.244.0.147.55042 > 10.244.0.144.80: Flags [F.], seq 95, ack 850, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756457 vethedfea565 P IP 10.244.0.144.80 > 10.244.0.147.55042: Flags [.], ack 96, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 0
14:44:42.105516 cni0 Out ARP, Request who-has 10.244.0.144 tell 10.244.0.1, length 28
14:44:42.105533 vethedfea565 Out ARP, Request who-has 10.244.0.144 tell 10.244.0.1, length 28
14:44:42.105644 vethedfea565 P ARP, Request who-has 10.244.0.147 tell 10.244.0.144, length 28
14:44:42.105658 vetha999267c Out ARP, Request who-has 10.244.0.147 tell 10.244.0.144, length 28
14:44:42.105696 vethedfea565 P ARP, Reply 10.244.0.144 is-at 52:13:49:ee:00:09, length 28
14:44:42.105701 cni0 In ARP, Reply 10.244.0.144 is-at 52:13:49:ee:00:09, length 28
14:44:42.105717 vetha999267c P ARP, Reply 10.244.0.147 is-at 56:c4:29:ee:b8:b5, length 28
14:44:42.105720 vethedfea565 Out ARP, Reply 10.244.0.147 is-at 56:c4:29:ee:b8:b5, length 28
20. a-app Service로 향하는 tcpdump 확인하기
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
14:44:36.756026 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [S], seq 4003348580, win 64860, options [mss 1410,sackOK,TS val 2321197533 ecr 0,nop,wscale 7], length 0
14:44:36.756161 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [S.], seq 3569082682, ack 4003348581, win 64308, options [mss 1410,sackOK,TS val 3133631796 ecr 2321197533,nop,wscale 7], length 0
14:44:36.756171 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [.], ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 0
14:44:36.756213 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [P.], seq 1:95, ack 1, win 507, options [nop,nop,TS val 2321197533 ecr 3133631796], length 94: HTTP: GET / HTTP/1.1
14:44:36.756224 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [.], ack 95, win 502, options [nop,nop,TS val 3133631796 ecr 2321197533], length 0
14:44:36.756334 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [P.], seq 1:234, ack 95, win 502, options [nop,nop,TS val 3133631796 ecr 2321197533], length 233: HTTP: HTTP/1.1 200 OK
14:44:36.756342 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [.], ack 234, win 506, options [nop,nop,TS val 2321197534 ecr 3133631796], length 0
14:44:36.756366 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [P.], seq 234:849, ack 95, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 615: HTTP
14:44:36.756369 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [.], ack 849, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756401 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [F.], seq 849, ack 95, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 0
14:44:36.756438 vetha999267c P IP 10.244.0.147.55042 > 10.103.129.86.80: Flags [F.], seq 95, ack 850, win 502, options [nop,nop,TS val 2321197534 ecr 3133631797], length 0
14:44:36.756462 vetha999267c Out IP 10.103.129.86.80 > 10.244.0.147.55042: Flags [.], ack 96, win 502, options [nop,nop,TS val 3133631797 ecr 2321197534], length 0
21. 테스트 앱 정리하기
kubectl delete deploy/a-app --force
kubectl delete pod/b-app --force
kubectl delete svc/a-app
위의 1, 2번을 주황색으로 표현하였고 3번은 붉은 색으로 표현하였습니다.
(물론 실제로 네임서버 질의 또한 아래와 같은 순서대로 진행 될 것입니다.)
(순서 : Pod netns → Root netns veth, cni, veth → Pod netns)
A.3. 파드에서 헤드리스 서비스로 통신
단일 호스트 내 파드(Pod)에서 헤드리스 서비스(StatefulSet)로 네트워크 통신
데이터베이스 등의 특징을 가진 앱의 경우,
헤드리스 서비스*를 사용하여 파드 별 도메인을 사용할 수 있습니다.
{name}-{ordinal}.{service}.{namespace}.svc.cluster.local
a-app-0.a-app.default.svc.cluster.local
⚠️
[헤드리스 서비스 생성 방법*] StatefulSet의 .spec.serviceName을 명시 Service의 .spec.type을 ClusterIP로 .spec.clusterIP는 None으로 명시
공식문서[A.3-1]에서는 헤드리스 서비스는 로드 밸런싱을 하지 않는다고 설명합니다.
⚠️
헤드리스 서비스의 경우, 클러스터 IP가 할당되지 않고, kube-proxy가 이러한 서비스를 처리하지 않으며, 플랫폼에 의해 로드 밸런싱 또는 프록시를 하지 않는다. DNS가 자동으로 구성되는 방법은 서비스에 셀렉터가 정의되어 있는지 여부에 달려있다.
하지만 헤드리스 서비스도 로드밸런싱을 지원함을 [실습 5]에서 볼 수 있습니다.
이것이 특정 버전, 기능에서 발생하는 문제인지는 다루지 않고 있습니다.
[실습 5] 헤드리스 서비스의 로드 밸런싱 검증
1. 테스트 앱 배포하기
mkdir -p ~/network-test
cd ~/network-test
cat <statefulset.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: a-app
labels:
app: a-app
spec:
selector:
matchLabels:
app: a-app
serviceName: "a-app"
replicas: 3
template:
metadata:
labels:
app: a-app
spec:
containers:
- name: my-container
image: nginx:1.20
ports:
- containerPort: 80
name: web
---
apiVersion: v1
kind: Service
metadata:
name: a-app
labels:
app: a-app
spec:
clusterIP: None # 헤드리스 서비스로 만들기 위한 설정
selector:
app: a-app
ports:
- port: 80
targetPort: web
name: http
EOF
kubectl apply -f statefulset.yaml
kubectl run b-app --image=busybox -- sleep 3600
2. 테스트 앱 확인하기
kubectl get svc,statefulset,pod | grep -E "(NAME|-app)"
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/a-app ClusterIP None none 80/TCP 2d7h
NAME READY AGE
statefulset.apps/a-app 3/3 2d7h
NAME READY STATUS RESTARTS AGE
pod/a-app-0 1/1 Running 0 2d7h
pod/a-app-1 1/1 Running 0 2d7h
pod/a-app-2 1/1 Running 0 2d7h
pod/b-app 1/1 Running 55 (22m ago) 2d7h
3. a-app-0 로그 감시하기(tail)
kubectl logs a-app-0 -f
4. a-app-1 로그 감시하기(tail)
kubectl logs a-app-1 -f
5. a-app-2 로그 감시하기(tail)
kubectl logs a-app-2 -f
6. a-app 앤드포인트로 요청 전송하기
kubectl exec b-app -- wget -qO- a-app.default.svc.cluster.local
kubectl exec b-app -- wget -qO- a-app.default.svc.cluster.local
kubectl exec b-app -- wget -qO- a-app.default.svc.cluster.local
7. 3,4,5번 터미널의 로그를 확인하면 로드 밸런싱이 기능함을 알 수 있습니다.
8. 테스트 앱 삭제하기
kubectl delete -f statefulset.yaml
kubectl delete pod/b-app
헤드리스 서비스가 파드 별 하위 도메인을 제공함을 [실습 6]에서 확인했습니다.
동시에 a-app.default.svc.cluster.local에서 차이점을 발견할 수 있었습니다.
Service : nslookup의 결과 ClusterIP를 반환
Headless Service : nslookup의 결과 PodIP를 반환
[실습 6] 헤드리스 서비스의 하위 도메인
1. 테스트 앱 배포하기
mkdir -p ~/network-test
cd ~/network-test
cat <statefulset.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: a-app
labels:
app: a-app
spec:
selector:
matchLabels:
app: a-app
serviceName: "a-app"
replicas: 3
template:
metadata:
labels:
app: a-app
spec:
containers:
- name: my-container
image: nginx:1.20
ports:
- containerPort: 80
name: web
---
apiVersion: v1
kind: Service
metadata:
name: a-app
labels:
app: a-app
spec:
clusterIP: None # 헤드리스 서비스로 만들기 위한 설정
selector:
app: a-app
ports:
- port: 80
targetPort: web
name: http
EOF
kubectl apply -f statefulset.yaml
2. 테스트 앱 확인하기
kubectl get svc,statefulset,pod | grep -E "(NAME|-app)"
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/a-app ClusterIP None none 80/TCP 2d7h
NAME READY AGE
statefulset.apps/a-app 3/3 2d7h
NAME READY STATUS RESTARTS AGE
pod/a-app-0 1/1 Running 0 2d7h
pod/a-app-1 1/1 Running 0 2d7h
pod/a-app-2 1/1 Running 0 2d7h
3. nslookup 확인하기
kubectl run nslookup-pod-0 --image=busybox -it --rm -- nslookup a-app-0.a-app.default.svc.cluster.local
kubectl run nslookup-pod-1 --image=busybox -it --rm -- nslookup a-app-1.a-app.default.svc.cluster.local
kubectl run nslookup-pod-2 --image=busybox -it --rm -- nslookup a-app-2.a-app.default.svc.cluster.local
kubectl run nslookup-pod --image=busybox -it --rm -- nslookup a-app.default.svc.cluster.local
kubectl logs nslookup-pod-0
kubectl logs nslookup-pod-1
kubectl logs nslookup-pod-2
kubectl logs nslookup-pod
# nslookup-pod-0
# nslookup a-app-0.a-app.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: a-app-0.a-app.default.svc.cluster.local
# nslookup-pod-1
# nslookup a-app-1.a-app.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: a-app-1.a-app.default.svc.cluster.local
Address: 10.244.0.221
# nslookup-pod-2
# nslookup a-app-2.a-app.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: a-app-2.a-app.default.svc.cluster.local
Address: 10.244.0.222
# nslookup-pod
# nslookup a-app.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: a-app.default.svc.cluster.local
Address: 10.244.0.221
Name: a-app.default.svc.cluster.local
Address: 10.244.0.222
Name: a-app.default.svc.cluster.local
Address: 10.244.0.219
4. dig 확인하기
kubectl run dig-pod-0 --image=tutum/dnsutils -it --rm -- dig a-app-0.a-app.default.svc.cluster.local
kubectl run dig-pod-1 --image=tutum/dnsutils -it --rm -- dig a-app-1.a-app.default.svc.cluster.local
kubectl run dig-pod-2 --image=tutum/dnsutils -it --rm -- dig a-app-2.a-app.default.svc.cluster.local
kubectl run dig-pod --image=tutum/dnsutils -it --rm -- dig a-app.default.svc.cluster.local
kubectl logs dig-pod-0
kubectl logs dig-pod-1
kubectl logs dig-pod-2
kubectl logs dig-pod
# dig-pod-0 # dig a-app-0.a-app.default.svc.cluster.local ; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> a-app-0.a-app.default.svc.cluster.local ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56600 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;a-app-0.a-app.default.svc.cluster.local. IN A ;; ANSWER SECTION: a-app-0.a-app.default.svc.cluster.local. 26 IN A 10.244.0.219 ;; Query time: 0 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Sun Mar 30 13:33:56 UTC 2025 ;; MSG SIZE rcvd: 123 # dig-pod-1 # dig a-app-1.a-app.default.svc.cluster.local ; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> a-app-1.a-app.default.svc.cluster.local ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15398 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;a-app-1.a-app.default.svc.cluster.local. IN A ;; ANSWER SECTION: a-app-1.a-app.default.svc.cluster.local. 30 IN A 10.244.0.221 ;; Query time: 0 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Sun Mar 30 13:34:28 UTC 2025 ;; MSG SIZE rcvd: 123 # dig-pod-2 # dig a-app-2.a-app.default.svc.cluster.local ; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> a-app-2.a-app.default.svc.cluster.local ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29238 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;a-app-2.a-app.default.svc.cluster.local. IN A ;; ANSWER SECTION: a-app-2.a-app.default.svc.cluster.local. 25 IN A 10.244.0.222 ;; Query time: 0 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Sun Mar 30 13:35:29 UTC 2025 ;; MSG SIZE rcvd: 123 # dig-pod # dig a-app.default.svc.cluster.local ; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> a-app.default.svc.cluster.local ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29717 ;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;a-app.default.svc.cluster.local. IN A ;; ANSWER SECTION: a-app.default.svc.cluster.local. 30 IN A 10.244.0.222 a-app.default.svc.cluster.local. 30 IN A 10.244.0.219 a-app.default.svc.cluster.local. 30 IN A 10.244.0.221 ;; Query time: 0 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Sun Mar 30 13:35:55 UTC 2025 ;; MSG SIZE rcvd: 201
5. 테스트 앱 정리하기
kubectl delete -f statefulset.yaml --force
ClusterIP는 논리적 NAT으로서 kube-proxy가 생성함을 [실습 4]에서 배웠습니다.
따라서 a-app.default.svc.cluster.local의 네트워크 흐름 또한 차이가 발생합니다.
Service : CoreDNS → Kube-Proxy(iptables) → Pod
Headless Service : CoreDNS → Pod