오늘의 주제는 "Pod 와 서비스 간 통신" 이라는 쿠버네티스가 해결한 4가지 주제중 한 가지 입니다. 서비스 오브젝트를 생성하면 내부적으로 어떻게 동작하는지 살펴볼 예정입니다.

쿠버네티스는 배포한 파드에 대하여 내/외부 통신을 도와줍니다. 파드는 쿠버네티스에서 동적인 생명 주기를 갖기 때문에 이와 관련하여 안정적인 네트워크를 지원할 수 있어야 하고, 이를 Kube Proxy가 해결합니다.


이전 글 목차

https://dev-whoan.xyz/111, 네트워크로 시작하는 쿠버네티스 — 내가 데이터를 보낸다면
https://dev-whoan.xyz/112, 네트워크로 시작하는 쿠버네티스 - Network Namespace
https://dev-whoan.xyz/113, 네트워크로 시작하는 쿠버네티스 - Docker Network
https://dev-whoan.xyz/114, 네트워크로 시작하는 쿠버네티스 - iptables, ipvs, ipip, vxlan


사전 준비물

앞으로 쿠버네티스에서 실습을 하기 위한 클러스터를 구성합니다.

  • network-study 네임스페이스 생성
  • CNI를 설치합니다.
    • 저의 경우 Calico를 선택했습니다.
  • Load Balancer 설치
    • 저의 경우 베어메탈 서버를 이용하고 있으며, MetalLB를 설치해 홈 네트워크의 아이피 대역을 이용할 수 있도록 했습니다.

Pod IP의 비일관성

현재 시스템에서 Kube Proxy는 iptables 모드로 동작하고 있습니다.

$ kubectl get pod -n kube-system -o wide | grep "kube-proxy"
kube-proxy-fc578                          1/1     Running   0               48s     10.108.10.102     worker2   <none>           <none>
kube-proxy-lqrjn                          1/1     Running   0               58s     10.108.10.103     worker3   <none>           <none>
kube-proxy-nrks7                          1/1     Running   0               50s     10.108.10.101     worker1   <none>           <none>
kube-proxy-vg52m                          1/1     Running   0               54s     <none>            master1   <none>           <none>

$ kubectl logs -n kube-system kube-proxy-nrks7 | grep "Using"
I1215 12:44:47.895588       1 server_linux.go:66] "Using iptables proxy"
I1215 12:44:49.285601       1 server_linux.go:169] "Using iptables Proxier"
I1215 12:44:49.357815       1 proxier.go:278] "Using iptables mark for masquerade" ipFamily="IPv4" mark="0x00004000"
I1215 12:44:49.362095       1 proxier.go:278] "Using iptables mark for masquerade" ipFamily="IPv6" mark="0x00004000"

쿠버네티스는 클러스터 구성시 설정한 --pod-network-cidr, 그리고 kube proxy에 설정된 --cluster-cidr을 통해 파드의 IP CIDR를 구성할 수 있습니다.

이 옵션을 통해 설정된 값을 바탕으로 kube proxy는 Cluster IP를 제공하며, 여기에 iptables 혹은 ipvs를 사용하여 트래픽을 처리하게 됩니다. Pod IP의 비일관성을 해결하기 위해 서비스 오브젝트에 대한 Cluster IP를 할당하고, 여기에 대상의 파드를 할당함으로써 일관된 Cluster IP를 제공합니다.

--- # nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-study
  namespace: network-study
spec:
  replicas: 1
  selector:
    matchLabels:
      type: nginx-study
  template:
    metadata:
      name: nginx-study-app
      labels:
        type: nginx-study
    spec:
      containers:
        - image: nginx
          name: nginx-container
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
      nodeSelector:
        kubernetes.io/hostname: worker1

## HostOS 혹은 쿠버네티스 구성이 가능한 시스템에서 수행합니다.
# 생성한 쿠버네티스의 Service CIDR, Pod Network CIDR은 다음과 같습니다.
$ kubectl get cm -n kube-system kubeadm-config -o yaml
apiVersion: v1
data:
  ClusterConfiguration: |
    ...
    networking:
      podSubnet: 192.168.0.0/16
      serviceSubnet: 172.16.0.0/12
      ...
    ...

$ kubectl apply -f nginx.yaml
deployment.apps/nginx-study created
$ kubectl get pod -n network-study
NAME                          READY   STATUS    RESTARTS   AGE
nginx-study-b5996c7b9-nfvqd   1/1     Running   0          92s

$ kubectl expose deploy nginx-study -n network-study --type=ClusterIP --port=80 --target-port=80
service/nginx-study exposed

$ kubectl describe svc -n network-study nginx-study
Name:              nginx-study
Namespace:         network-study
Labels:            <none>
Annotations:       <none>
Selector:          type=nginx-study
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                172.30.103.10
IPs:               172.30.103.10
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.235.152:80
Session Affinity:  None
Events:            <none>
## worker1에서 iptables을 통해 확인해 봅시다.
# 서비스를 expose 하기 전에는 nginx-study와 관련된 iptables 규칙이 없습니다.
root@worker1:~# iptables -t nat -L -n -v | grep "nginx-study"
root@worker1:~#

# nginx-study를 서비스로 노출시키면, 아래와 같이 관련된 규칙이 나타나게 됩니다.
# 이 정보는 worker2 등 다른 시스템에서 확인하더라도 동일하게 확인할 수 있습니다.
root@worker1:~# iptables -t nat -L -n -v | grep "nginx-study"
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.235.152      0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.235.152:80
    0     0 KUBE-SVC-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            172.30.103.10        /* network-study/nginx-study cluster IP */
    0     0 KUBE-MARK-MASQ  tcp  --  *      *      !192.168.0.0/16       172.30.103.10        /* network-study/nginx-study cluster IP */
    0     0 KUBE-SEP-LJ5QOTZF6XC46AXQ  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.235.152:80 */

## worker2 에서 확인한 값
root@worker2:~# iptables -t nat -L -n -v | grep "nginx-study"
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.235.152      0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.235.152:80
    0     0 KUBE-SVC-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            172.30.103.10        /* network-study/nginx-study cluster IP */
    0     0 KUBE-MARK-MASQ  tcp  --  *      *      !192.168.0.0/16       172.30.103.10        /* network-study/nginx-study cluster IP */
    0     0 KUBE-SEP-LJ5QOTZF6XC46AXQ  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.235.152:80 */

Kube Proxy 파드의 로그를 확인해 보면 생성한 Service를 바탕으로 iptables 규칙이 생성된 것을 확인할 수 있습니다.

$ kubectl logs -n kube-system kube-proxy-nrks7 | grep "nginx-study"
I1215 12:44:49.823887       1 servicechangetracker.go:106] "Service updated ports" service="network-study/nginx-study" portCount=1
I1215 12:44:50.073028       1 config.go:124] "Calling handler.OnEndpointSliceAdd" endpoints="network-study/nginx-study-9rjfb"
I1215 12:44:50.140306       1 servicechangetracker.go:211] "Adding new service port" portName="network-study/nginx-study" servicePort="172.18.189.139:80/TCP"
I1215 12:44:50.143013       1 endpointslicecache.go:303] "Setting endpoints for service port name" portName="network-study/nginx-study" endpoints=["192.168.235.152:80"]

tcpdump를 이용하여 worker1 노드에서 해당 서비스로 트래픽을 흘려보냈을 때 어떻게 동작하는지 확인해 보겠습니다.

## 쿠버네티스 노드 중 아무곳에서나 요청을 보내봅니다.
root@master1:~# curl 172.18.189.139
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.

For online documentation and support please refer to "nginx.org".
Commercial support is available at "nginx.com".

Thank you for using nginx.

## worker1 노드에서 tcpdump로 확인해 봅니다.
root@worker1:~# tcpdump -i any port 80
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
12:48:58.161637 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [S], seq 2298058529, win 64240, options [mss 1460,sackOK,TS val 1768291727 ecr 0,nop,wscale 7], length 0
12:48:58.162053 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [S], seq 2298058529, win 64240, options [mss 1460,sackOK,TS val 1768291727 ecr 0,nop,wscale 7], length 0
12:48:58.162217 cali675956f83bb In  IP 192.168.235.152.http > 192.168.137.64.61145: Flags [S.], seq 3030732140, ack 2298058530, win 64260, options [mss 1440,sackOK,TS val 147421508 ecr 1768291727,nop,wscale 7], length 0
12:48:58.162284 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [S.], seq 3030732140, ack 2298058530, win 64260, options [mss 1440,sackOK,TS val 147421508 ecr 1768291727,nop,wscale 7], length 0
12:48:58.163329 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1768291729 ecr 147421508], length 0
12:48:58.163421 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1768291729 ecr 147421508], length 0
12:48:58.165280 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [P.], seq 1:79, ack 1, win 502, options [nop,nop,TS val 1768291731 ecr 147421508], length 78: HTTP: GET / HTTP/1.1
12:48:58.165439 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [P.], seq 1:79, ack 1, win 502, options [nop,nop,TS val 1768291731 ecr 147421508], length 78: HTTP: GET / HTTP/1.1
12:48:58.165564 cali675956f83bb In  IP 192.168.235.152.http > 192.168.137.64.61145: Flags [.], ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 0
12:48:58.165612 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [.], ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 0
12:48:58.166314 cali675956f83bb In  IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 1:239, ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 238: HTTP: HTTP/1.1 200 OK
12:48:58.166375 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 1:239, ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 238: HTTP: HTTP/1.1 200 OK
12:48:58.167083 cali675956f83bb In  IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 239:854, ack 79, win 502, options [nop,nop,TS val 147421513 ecr 1768291731], length 615: HTTP
12:48:58.167148 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 239:854, ack 79, win 502, options [nop,nop,TS val 147421513 ecr 1768291731], length 615: HTTP
12:48:58.167352 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 1768291733 ecr 147421512], length 0
12:48:58.167454 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 1768291733 ecr 147421512], length 0
12:48:58.168204 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 1768291734 ecr 147421513], length 0
12:48:58.168575 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 1768291734 ecr 147421513], length 0
12:48:58.174186 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [F.], seq 79, ack 854, win 501, options [nop,nop,TS val 1768291740 ecr 147421513], length 0
12:48:58.174398 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [F.], seq 79, ack 854, win 501, options [nop,nop,TS val 1768291740 ecr 147421513], length 0
12:48:58.175491 cali675956f83bb In  IP 192.168.235.152.http > 192.168.137.64.61145: Flags [F.], seq 854, ack 80, win 502, options [nop,nop,TS val 147421521 ecr 1768291740], length 0
12:48:58.175622 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [F.], seq 854, ack 80, win 502, options [nop,nop,TS val 147421521 ecr 1768291740], length 0
12:48:58.176467 tunl0 In  IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 1768291742 ecr 147421521], length 0
12:48:58.176610 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 1768291742 ecr 147421521], length 0

그런데 이상합니다. 분명 172.18.189.139의 서비스 오브젝트의 Cluster IP로 요청을 보냈는데 tcpdump를 확인해 보니 알 수 없는 대역 192.168.137.64로부터 요청이 들어옵니다. 왜 그런지 한번 직접 확인해 보시기 바랍니다.

  • 이때까지의 글을 모두 읽으셨다면, 확인하실 수 있습니다.

Kube Proxy는 기본적으로 iptables 모드를 이용하고 있고, 이에 따라 우리가 살펴봤던 IPVS의 로드 밸런싱을 비교적 똑똑하게 처리하지 못합니다. 이와 관련하여 IPVS에 대한 설정은 직접 수행해 보시기 바랍니다.


트래픽 분산 및 로드밸런싱

  • 시작하기에 앞서, 앞에서 배포한 nginx 디플로이먼트의 레플리카 수를 3개로 늘려주세요.
  • nginx-study 서비스 또한 다시 배포해 주세요.

Kube Proxy는 iptables, ipvs를 이용하여 레플리카로 구성된 서비스에 대해 트래픽 분산을 수행합니다. 우리가 디플로이먼트 혹은 레플리카셋 등을 통해 하나의 애플리케이션을 둘 이상의 파드를 갖도록 구성하고, 서비스를 할당할 경우 트래픽 분산을 처리하게 되는 것이죠.

다만 iptables와 ipvs의 모드에 따라 서비스 Endpoints 연결에 대한 차이가 발생합니다. iptables는 임의의 라우팅 규칙을 확률에 따라 선택하게 됩니다.

root@worker1$:~# iptables -t nat -L -v -n | grep "nginx-study"
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.182.4        0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.182.4:80
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.189.69       0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.189.69:80
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.235.135      0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.235.135:80
# 172.27.83.52, Cluster IP로 들어오는 요청은 KUBE-SEP-...으로 처리됩니다.
# 주의할 점은 라우팅은 순서대로 처리된다는점 입니다.
    0     0 KUBE-SVC-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            172.27.83.52         /* network-study/nginx-study cluster IP */    
# 192.168.0.0/16이 아니라는 표시는 출발지가 쿠버네티스의 Pod CIDR이 아닌경우 입니다.    
    0     0 KUBE-MARK-MASQ  tcp  --  *      *      !192.168.0.0/16       172.27.83.52         /* network-study/nginx-study cluster IP */
    0     0 KUBE-SEP-3GSDTNPYCMTOPFUF  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.182.4:80 */ statistic mode random probability 0.33333333349
    0     0 KUBE-SEP-ILIVPKL7LONIRUSF  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.189.69:80 */ statistic mode random probability 0.50000000000
    0     0 KUBE-SEP-SIBCELYMSJMZUCJP  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.235.135:80 */
   
# 첫 번째 엔드포인트 확률에 걸릴 경우
root@worker1:~# iptables -t nat -L KUBE-SEP-3GSDTNPYCMTOPFUF -n -v
Chain KUBE-SEP-3GSDTNPYCMTOPFUF (1 references)
 pkts bytes target     prot opt in     out     source               destination
# KUBE-SEP-3GSDTNPYCMTOPFUF로 들어오는 요청에 대해 SNAT, DNAT 처리하는 것을 확인할 수 있습니다.
# 목적지: TCP 192.168.182.4:80
    0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.182.4        0.0.0.0/0            /* network-study/nginx-study */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.182.4:80

각 레플리카의 Pod IP (엔드포인트)에 대해 라우팅 규칙이 설정되어 있는 것을 확인할 수 있습니다. 그런데, 제일 마지막 엔드포인트에 대해서는 확률이 지정되어 있지 않은 것을 확인할 수 있습니다. 즉, 앞의 두 규칙에 라우팅 되지 않은 트래픽은 모두 192.168.235.135로 라우팅 됩니다.

// probability 계산 코드.
// <https://github.com/kubernetes/kubernetes/blob/cb93d6ee69b8d4ca8701336e4f7cb278751f34e4/pkg/proxy/iptables/proxier.go#L503>
// n번째 엔드포인트에 대해 1/N을 수행합니다.
// 우리의 경우 총 3개 이므로, 1/3, 1/2, 1이 됩니다.
// 그렇기 때문에 제일 마지막 iptables 규칙은 random probability가 지정되지 않는것을 알 수 있습니다.
func computeProbability(n int) string {
	return fmt.Sprintf("%0.10f", 1.0/float64(n))
}

// This assumes proxier.mu is held
func (proxier *Proxier) precomputeProbabilities(numberOfPrecomputed int) {
	if len(proxier.precomputedProbabilities) == 0 {
		proxier.precomputedProbabilities = append(proxier.precomputedProbabilities, "")
	}
	for i := len(proxier.precomputedProbabilities); i <= numberOfPrecomputed; i++ {
		proxier.precomputedProbabilities = append(proxier.precomputedProbabilities, computeProbability(i))
	}
}

// This assumes proxier.mu is held
func (proxier *Proxier) probability(n int) string {
	if n >= len(proxier.precomputedProbabilities) {
		proxier.precomputeProbabilities(n)
	}
	return proxier.precomputedProbabilities[n]
}

iptables 모드로 kube proxy를 구성하고, 레플리카 오브젝트에 대해 서비스 오브젝트로 연결한다면, 확률에 따라 선택한 엔드포인트의 파드가 죽어있는 상태라면, 해당 요청은 Fail 하게 됩니다.

파드를 사용 불가 상태로 만든 뒤, 직접 서비스로 요청을 보내 Fail을 확인해 보겠습니다. 저의 경우 worker3가 33% 확률로 라우팅 규칙이 선택되기 때문에, 192.168.182.4에 대해 패킷 로스 100%로 설정하겠습니다.

$ kubectl get pods -n network-study -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP                NODE      NOMINATED NODE   READINESS GATES
nginx-study-6cbc845ff5-86bnt   1/1     Running   0          27m   192.168.235.135   worker1   <none>           <none>
nginx-study-6cbc845ff5-fbxxv   1/1     Running   0          28m   192.168.189.69    worker2   <none>           <none>
nginx-study-6cbc845ff5-lffzb   1/1     Running   0          28m   192.168.182.4     worker3   <none>           <none>
  • 아이피를 보고 눈치채신 분이 계실 수 있습니다. 각 노드에서 운영하는 NIC의 대역에 따라 파드 아이피를 할당하며 이를 바탕으로 배포되어 있는 노드를 유추할 수 있습니다.
## worker3에서 tc 패키지를 이용해 임의로 패킷 로스 100%로 설wㅓㅇ합니다.
root@worker3:~# sudo tc qdisc add dev tunl0 root netem loss 100%
root@worker3:~#

## 쿠버네티스를 구성하는 다른 노드에서 192.168.182.4로 요청을 보내봅니다.
# 패킷 로스 설정 전
root@master1:~# curl 192.168.182.4
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.

For online documentation and support please refer to "nginx.org".
Commercial support is available at "nginx.com".

Thank you for using nginx.

# 설정 후
root@master1:~# curl 192.168.182.4
curl: (28) Failed to connect to 192.168.182.4 port 80 after 130379 ms: Connection timed out

root@master1:~# tcpdump -i any port 80
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
# 192.168.182.4로 가는 요청은 처리되지 않는 것을 확인할 수 있습니다.
14:16:23.213340 tunl0 Out IP master1.26045 > 192.168.182.4.http: Flags [S], seq 944653463, win 64240, options [mss 1460,sackOK,TS val 2004030469 ecr 0,nop,wscale 7], length 0
14:16:24.239302 tunl0 Out IP master1.26045 > 192.168.182.4.http: Flags [S], seq 944653463, win 64240, options [mss 1460,sackOK,TS val 2004031495 ecr 0,nop,wscale 7], length 0
14:16:34.823831 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [S], seq 1458160175, win 64240, options [mss 1460,sackOK,TS val 2004042080 ecr 0,nop,wscale 7], length 0
14:16:34.825215 tunl0 In  IP 192.168.189.69.http > master1.25757: Flags [S.], seq 3313784763, ack 1458160176, win 64260, options [mss 1440,sackOK,TS val 521483265 ecr 2004042080,nop,wscale 7], length 0
14:16:34.825729 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2004042082 ecr 521483265], length 0
14:16:34.827183 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [P.], seq 1:77, ack 1, win 502, options [nop,nop,TS val 2004042083 ecr 521483265], length 76: HTTP: GET / HTTP/1.1
14:16:34.828059 tunl0 In  IP 192.168.189.69.http > master1.25757: Flags [.], ack 77, win 502, options [nop,nop,TS val 521483268 ecr 2004042083], length 0
14:16:34.828761 tunl0 In  IP 192.168.189.69.http > master1.25757: Flags [P.], seq 1:239, ack 77, win 502, options [nop,nop,TS val 521483268 ecr 2004042083], length 238: HTTP: HTTP/1.1 200 OK
14:16:34.828902 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 2004042085 ecr 521483268], length 0
14:16:34.829632 tunl0 In  IP 192.168.189.69.http > master1.25757: Flags [P.], seq 239:854, ack 77, win 502, options [nop,nop,TS val 521483269 ecr 2004042085], length 615: HTTP
14:16:34.829742 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 2004042086 ecr 521483269], length 0
14:16:34.834683 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [F.], seq 77, ack 854, win 501, options [nop,nop,TS val 2004042091 ecr 521483269], length 0
14:16:34.835959 tunl0 In  IP 192.168.189.69.http > master1.25757: Flags [F.], seq 854, ack 78, win 502, options [nop,nop,TS val 521483276 ecr 2004042091], length 0
14:16:34.836167 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 2004042092 ecr 521483276], length 0

분명 쿠버네티스는 Fault Tolerance 한 시스템을 제공하는 것으로 알고 있는데, 이 경우 장애가 발생하면 정상적인 처리가 불가능할 것으로 예상됩니다. 어떻게 해야 이를 예방할 수 있을까요?

마찬가지로 IPVS에 대한 구성을 직접 수행해 보시기 바랍니다.


서비스 디스커버리

Kube Proxy는 쿠버네티스에 배포된 서비스에 대해 트래픽을 라우팅 합니다. 예시로 다른 파드에서 우리가 배포한 nginx-study 서비스에 ping을 보내보겠습니다.

# 혹은 nginx-study.network-study.svc.cluster.local
/app # ping nginx-study.network-study.svc
PING nginx-study.network-study.svc (172.27.83.52): 56 data bytes
...

위 로그를 확인해 보면, 172.27.83.52에 대한 Service IP를 정상적으로 받아오는 것을 알 수 있습니다. 이는 쿠버네티스에 배포되어 있는 CoreDNS (버전, 혹은 설정에 따라 그 종류가 다를 수 있습니다.)가 정상적으로 수행하고 있음을 알 수 있습니다.

Core DNS는 쿠버네티스 API와의 통신을 통해 서비스에 대한 DNS 레코드를 관리하게 됩니다. 이를 통해 서비스 오브젝트에 저장되어 있는 아이피를 획득할 수 있고, 우리가 위에서 살펴본 바와 같이 iptables 혹은 ipvs의 규칙을 바탕으로 FQDN → Service IP → Target Pod로의 서비스 디스커버리가 동작합니다.

12:53:45.462231 tunl0 In  IP 192.168.235.139.47399 > 192.168.189.73.domain: 36509+ A? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.462741 cali881bb8ca45a Out IP 192.168.235.139.47399 > 192.168.189.73.domain: 36509+ A? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.462806 tunl0 In  IP 192.168.235.139.47399 > 192.168.189.73.domain: 39023+ AAAA? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.463022 cali881bb8ca45a Out IP 192.168.235.139.47399 > 192.168.189.73.domain: 39023+ AAAA? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.464267 cali881bb8ca45a In  IP 192.168.189.73.domain > 192.168.235.139.47399: 39023 NXDomain*- 0/1/0 (158)
12:53:45.464421 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.47399: 39023 NXDomain*- 0/1/0 (158)
12:53:45.465107 cali881bb8ca45a In  IP 192.168.189.73.domain > 192.168.235.139.47399: 36509 NXDomain*- 0/1/0 (158)
12:53:45.465270 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.47399: 36509 NXDomain*- 0/1/0 (158)
12:53:45.466494 tunl0 In  IP 192.168.235.139.58856 > 192.168.189.73.domain: 4431+ A? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.466788 cali881bb8ca45a Out IP 192.168.235.139.58856 > 192.168.189.73.domain: 4431+ A? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.466876 tunl0 In  IP 192.168.235.139.58856 > 192.168.189.73.domain: 8064+ AAAA? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.467002 cali881bb8ca45a Out IP 192.168.235.139.58856 > 192.168.189.73.domain: 8064+ AAAA? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.467974 cali881bb8ca45a In  IP 192.168.189.73.domain > 192.168.235.139.58856: 8064*- 0/1/0 (154)
12:53:45.468059 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.58856: 8064*- 0/1/0 (154)
12:53:45.468910 cali881bb8ca45a In  IP 192.168.189.73.domain > 192.168.235.139.58856: 4431*- 1/0/0 A 172.27.83.52 (120)
12:53:45.468998 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.58856: 4431*- 1/0/0 A 172.27.83.52 (120)

클러스터 외부 통신

Kbue Proxy는 서비스 오브젝트의 Node Port, LoadBalancer 타입을 지원합니다. 이를 통해 클러스터에 배포된 애플리케이션에 대해 외부 접근을 처리합니다.

NodePort

nginx-study를 지우고 NodePort를 아래와 설정하여 재배포 한 뒤, iptables 규칙의 변경 사항을 확인해 보겠습니다.

apiVersion: v1
kind: Service
metadata:
  name: nginx-study
  namespace: network-study
spec:
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30002
  selector:
    type: nginx-study
  type: NodePort
---
Name:                     nginx-study
Namespace:                network-study
Labels:                   <none>
Annotations:              <none>
Selector:                 type=nginx-study
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       172.26.210.120
IPs:                      172.26.210.120
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30002/TCP
Endpoints:                192.168.182.7:80,192.168.189.71:80,192.168.235.138:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
## kube-proxy log입니다
I1218 13:32:14.265314       1 config.go:218] "Calling handler.OnServiceAdd"
I1218 13:32:14.267558       1 servicechangetracker.go:106] "Service updated ports" service="network-study/nginx-study" portCount=1
I1218 13:32:14.272867       1 servicechangetracker.go:211] "Adding new service port" portName="network-study/nginx-study" servicePort="172.26.210.120:80/TCP"
I1218 13:32:14.277897       1 proxier.go:828] "Syncing iptables rules" ipFamily="IPv4"
I1218 13:32:14.284552       1 iptables.go:361] "Running" command="iptables-save" arguments=["-t","nat"]
I1218 13:32:14.394584       1 proxier.go:1547] "Reloading service iptables data" ipFamily="IPv4" numServices=10 numEndpoints=12 numFilterChains=6 numFilterRules=12 numNATChains=4 numNATRules=20
I1218 13:32:14.394850       1 iptables.go:426] "Running" command="iptables-restore" arguments=["-w","5","-W","100000","--noflush","--counters"]
I1218 13:32:14.514618       1 service_health.go:124] "Existing healthcheck" service="nginx-gateway/ngf-nginx-gateway-fabric" port=31653
I1218 13:32:14.514949       1 cleanup.go:67] "Deleting conntrack stale entries for services" IPs=[]
I1218 13:32:14.515048       1 cleanup.go:73] "Deleting conntrack stale entries for services" nodePorts=[]
I1218 13:32:14.515113       1 proxier.go:822] "SyncProxyRules complete" ipFamily="IPv4" elapsed="243.417367ms"
I1218 13:32:14.515236       1 bounded_frequency_runner.go:296] sync-runner: ran, next possible in 1s, periodic in 1h0m0s
I1218 13:32:14.553788       1 config.go:124] "Calling handler.OnEndpointSliceAdd" endpoints="network-study/nginx-study-kslhj"
I1218 13:32:14.555003       1 endpointslicecache.go:303] "Setting endpoints for service port name" portName="network-study/nginx-study" endpoints=["192.168.182.7:80","192.168.189.71:80","192.168.235.138:80"]
I1218 13:32:14.555301       1 proxier.go:828] "Syncing iptables rules" ipFamily="IPv4"
I1218 13:32:14.557159       1 iptables.go:361] "Running" command="iptables-save" arguments=["-t","nat"]
I1218 13:32:14.670538       1 proxier.go:1547] "Reloading service iptables data" ipFamily="IPv4" numServices=10 numEndpoints=15 numFilterChains=6 numFilterRules=10 numNATChains=9 numNATRules=35
I1218 13:32:14.670977       1 iptables.go:426] "Running" command="iptables-restore" arguments=["-w","5","-W","100000","--noflush","--counters"]
I1218 13:32:14.809056       1 proxier.go:1576] "Network programming" ipFamily="IPv4" endpoint="network-study/nginx-study" elapsed=0.808519384
I1218 13:32:14.810226       1 service_health.go:124] "Existing healthcheck" service="nginx-gateway/ngf-nginx-gateway-fabric" port=31653
I1218 13:32:14.810415       1 cleanup.go:67] "Deleting conntrack stale entries for services" IPs=[]
I1218 13:32:14.810493       1 cleanup.go:73] "Deleting conntrack stale entries for services" nodePorts=[]
I1218 13:32:14.810549       1 proxier.go:822] "SyncProxyRules complete" ipFamily="IPv4" elapsed="255.851106ms"
I1218 13:32:14.810644       1 bounded_frequency_runner.go:296] sync-runner: ran, next possible in 1s, periodic in 1h0m0s

그럼 노드에서 iptables 규칙을 확인해 봅시다.

## 어느 노드에서 수행해도 상관없습니다.
root@worker1:~# iptables -t nat -L KUBE-NODEPORTS -vn
Chain KUBE-NODEPORTS (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-EXT-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            127.0.0.0/8          /* network-study/nginx-study */ nfacct-name  localhost_nps_accepted_pkts
    0     0 KUBE-EXT-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */
    0     0 KUBE-EXT-UZSA3IJO67ISGJBK  tcp  --  *      *       0.0.0.0/0            127.0.0.0/8          /* nginx-gateway/ngf-nginx-gateway-fabric:http */ nfacct-name  localhost_nps_accepted_pkts
    0     0 KUBE-EXT-UZSA3IJO67ISGJBK  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* nginx-gateway/ngf-nginx-gateway-fabric:http */
    0     0 KUBE-EXT-RAQOCCT45E5XYVU5  tcp  --  *      *       0.0.0.0/0            127.0.0.0/8          /* nginx-gateway/ngf-nginx-gateway-fabric:https */ nfacct-name  localhost_nps_accepted_pkts
    0     0 KUBE-EXT-RAQOCCT45E5XYVU5  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* nginx-gateway/ngf-nginx-gateway-fabric:https */
    
## kube proxy의 로그를 확인해보아도 마찬가지입니다.
root@master1:~# kubectl logs -n kube-system kube-proxy-nrks7 | grep "30002"
root@master1:~#

이상합니다. 분명 iptables에 노드포트를 바탕으로 :30002 혹은 이에 매칭되는 포트 번호와 관련된 규칙이 있을 것 같은데, 보이지 않습니다. 그렇다면 Node Port의 30002는 어떻게 처리되는 걸까요? 한번 nginx가 배포된 노드에서 삽질을 시작해 봅시다.

root@worker1:~# crictl ps
CONTAINER           IMAGE                                                                                             CREATED             STATE               NAME                       ATTEMPT             POD ID              POD
8c9654ead5fb4       docker.io/library/nginx@sha256:3d696e8357051647b844d8c7cf4a0aa71e84379999a4f6af9b8ca1f7919ade42   About an hour ago   Running             nginx-container            1                   54b9c701fc362       nginx-study-6cbc845ff5-86bnt
...

root@worker1:~# crictl inspect 8c9654 | jq -r '.info.runtimeSpec.linux.namespaces[] |select(.type=="network") | .path'
/var/run/netns/1984a9ac-c383-48fd-8b8a-b4028b416c0f

root@worker1:~# ip netns list
...
1984a9ac-c383-48fd-8b8a-b4028b416c0f (id: 2)
...

root@worker1:~# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1973/nginx: master
tcp6       0      0 :::80                   :::*                    LISTEN      1973/nginx: master

## iptables 중 192.168.235.138에 해당하는 정보를 확인했습니다.
root@worker1:/etc/cni/net.d# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000
    link/ether 2a:e2:1d:8f:be:3a brd ff:ff:ff:ff:ff:ff link-netns 77503e0a-7e63-47e8-b1a6-a67aade60f3d
    inet 192.168.235.138/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::28e2:1dff:fe8f:be3a/64 scope link
       valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0

root@worker1:~# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f iptables -L -nv
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

이는 쿠버네티스 버전에 따라 동작이 다르게 나타날 수 있습니다. 과거 버전의 경우 기존의 iptables 혹은 ipvs를 통해 구성이 되어 아래와 같이 **tcp dpt:{{ NODE_PORT }}**가 명시되어 있을 수 있습니다.

KUBE-SVC-XXX  tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:30002

그러나 쿠버네티스는 성능 등의 이유로 iptables를 nftables로 점진적 전환하는 상태이고, 1.31 버전에서는 NodePort 규칙의 경우 nftables를 통해 구성되어 있습니다.

root@worker1: ~# nft list table ip nat | grep "30002"
		meta l4proto tcp ip daddr 127.0.0.0/8  tcp dport 30002 # nfacct-name  localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
		meta l4proto tcp  tcp dport 30002 counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX

마찬가지로 nftables를 통해 iptables에 존재하는 동일한 체인을 확인할 수 있습니다.

root@worker1:~# nft list table ip nat | grep "KUBE-EXT-ZTGPJZYBPOKDS2TX" --before 5 --after 5
  ...
		 counter packets 45020 bytes 2739046 jump KUBE-POSTROUTING
		 counter packets 44874 bytes 2729543 jump cali-POSTROUTING
	}

	chain KUBE-NODEPORTS {
		meta l4proto tcp ip daddr 127.0.0.0/8  tcp dport 30002 # nfacct-name  localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
		meta l4proto tcp  tcp dport 30002 counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
		meta l4proto tcp ip daddr 127.0.0.0/8  tcp dport 30927 # nfacct-name  localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-UZSA3IJO67ISGJBK
		meta l4proto tcp  tcp dport 30927 counter packets 0 bytes 0 jump KUBE-EXT-UZSA3IJO67ISGJBK
		meta l4proto tcp ip daddr 127.0.0.0/8  tcp dport 32288 # nfacct-name  localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-RAQOCCT45E5XYVU5
		meta l4proto tcp  tcp dport 32288 counter packets 0 bytes 0 jump KUBE-EXT-RAQOCCT45E5XYVU5
	}
--
	chain KUBE-SEP-FSYDGJCKWB55G5AI {
		ip saddr 192.168.189.72  counter packets 0 bytes 0 jump KUBE-MARK-MASQ
		meta l4proto tcp   counter packets 0 bytes 0 dnat to 192.168.189.72:9443
	}

	chain KUBE-EXT-ZTGPJZYBPOKDS2TX {
		 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
		counter packets 0 bytes 0 jump KUBE-SVC-ZTGPJZYBPOKDS2TX
	}

	chain KUBE-SVC-ZTGPJZYBPOKDS2TX {
	...

LoadBalancer

이제 최종장입니다. 노드포트는 노드의 포트를 노출한다는 점으로 인해 보안성이 떨어지고 포트의 범위가 한정적이며 로드 밸런싱의 성능 등을 쿠버네티스에 의존한다는 단점이 존재합니다. 이를 해결하기 위해 실질적으로 우리는 서비스 오브젝트의 클라우드에서 지원하는 로드밸런서 혹은 MetalLB 등을 이용하여 온프레미스 서버의 로드밸런싱을 처리하곤 합니다.

그러면 기존의 nginx-study 서비스 오브젝트를 제거하고, LoadBalancer 타입의 서비스 오브젝트를 배포해 보겠습니다.

apiVersion: v1
kind: Service
metadata:
  name: nginx-study
  namespace: network-study
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    type: nginx-study
  type: LoadBalancer
---
Name:                     nginx-study
Namespace:                network-study
Labels:                   <none>
Annotations:              <none>
Selector:                 type=nginx-study
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       172.27.177.151
IPs:                      172.27.177.151
LoadBalancer Ingress:     10.108.200.1
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  31943/TCP
Endpoints:                192.168.182.7:80,192.168.189.71:80,192.168.189.74:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason        Age   From                Message
  ----    ------        ----  ----                -------
  Normal  IPAllocated   29s   metallb-controller  Assigned IP ["10.108.200.1"]
  Normal  nodeAssigned  29s   metallb-speaker     announcing from node "worker2" with protocol "layer2"
0     0 KUBE-MARK-MASQ  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* masquerade traffic for network-study/nginx-study external destinations */
0     0 KUBE-EXT-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            127.0.0.0/8          /* network-study/nginx-study */ nfacct-name  localhost_nps_accepted_pkts
0     0 KUBE-EXT-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */
0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.189.71       0.0.0.0/0            /* network-study/nginx-study */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.189.71:80
0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.189.74       0.0.0.0/0            /* network-study/nginx-study */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.189.74:80
0     0 KUBE-MARK-MASQ  all  --  *      *       192.168.182.7        0.0.0.0/0            /* network-study/nginx-study */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study */ tcp to:192.168.182.7:80
0     0 KUBE-SVC-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            172.27.177.151       /* network-study/nginx-study cluster IP */
0     0 KUBE-EXT-ZTGPJZYBPOKDS2TX  tcp  --  *      *       0.0.0.0/0            10.108.200.1         /* network-study/nginx-study loadbalancer IP */
0     0 KUBE-MARK-MASQ  tcp  --  *      *      !192.168.0.0/16       172.27.177.151       /* network-study/nginx-study cluster IP */
0     0 KUBE-SEP-SWBCFBCVJ4JWY6IC  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.182.7:80 */ statistic mode random probability 0.33333333349
0     0 KUBE-SEP-OGDKAREGM7CZ5I7K  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.189.71:80 */ statistic mode random probability 0.50000000000
0     0 KUBE-SEP-QFKIBWA6SISBZBHX  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* network-study/nginx-study -> 192.168.189.74:80 */

iptables 규칙을 확인해 보니, Cluster IP에 추가로 loadbalancer IP에 대해서도 KUBE-EXT-ZTPGJ…를 통해 라우팅 규칙을 추가한 것을 확인할 수 있습니다.

한번 tcpdump 등을 활용해서 External IP에서 발생하는 요청을 확인해 보세요.


그렇다면 이것도…

  • Kubernetes를 설치한 뒤 CNI를 배포해야 하는 이유는 무엇인가요?
  • CNI 없이 kube proxy만 이용할 때, 쿠버네티스 클러스터 노드 혹은 시스템에서 서비스 오브젝트의 Cluster IP로 접근할 수 있나요? 이유는 무엇인가요?
  • Kube Proxy의 iptables 모드를 이용한다면 레플리카 중 하나만 뻗더라도 시스템 장애가 발생할 것으로 예상됩니다. 이를 예방하기 위해 어떤 대책이 있을까요?
  • iptables와 nftables의 차이는 무엇인가요?
  • 쿠버네티스를 구성중인 노드에서도 FQDN 질의를 통해 서비스에 접근하고 싶습니다. 어떻게 해야 할까요?

쿠버네티스는 컨테이너, 파드, 서비스, 노드 사이에서 발생하는 네트워크를 처리하기 위해 ip 할당, 라우팅 정책, 로드 밸런싱, 그리고 스위칭을 적극적으로 활용합니다. 특히 우리가 앞에서 살펴보았던 하나의 호스트 내에서 다중화되었거나 격리된 환경에서의 네트워크 처리는 반드시 필요합니다.

오늘은 쿠버네티스의 실질적인 네트워크에 들어가기에 앞서 쿠버네티스 네트워크를 찾아보면 만날 수 있는 개념 혹은 패키지들에 대해 알아보고자 합니다.


이전 글 목차
https://dev-whoan.xyz/111, 네트워크로 시작하는 쿠버네티스 — 내가 데이터를 보낸다면
https://dev-whoan.xyz/112, 네트워크로 시작하는 쿠버네티스 - Network Namespace
https://dev-whoan.xyz/113, 네트워크로 시작하는 쿠버네티스 - Docker Network


사전 준비물

ubuntu 22.04 * 2이 필요합니다. 저는 “내가 데이터를 보낸다면”에서 사용한 network ubuntu 2대를 사용하겠습니다.
- 네트워크를 Host Only에서 bridged로 변경해 주세요.


iptables

iptables는 kube-proxy에서 사용할 수 있는 프록시 모드 중 하나입니다. 이를 통해 쿠버네티스에서 컨트롤 플레인의 서비스, 엔드포인트 슬라이스 오브젝트의 추가와 제거를 감시합니다. 또한 각 서비스의 Cluster IP와 Port에 대한 트래픽을 캡처하고, 서비스의 백엔드 세트 중 하나로 redirect 합니다.

iptables는 리눅스의 Netfliter 프레임워크를 활용하여 패킷을 제어하는 패키지입니다. iptables는 방화벽, NAT, 라우팅 등을 주로 수행합니다. iptables는 이러한 동작을 수행하기 위해 Chain이라 불리는 Rule Group을 설정합니다.

  • input: 시스템으로 들어오는 패킷을 처리합니다.
  • forward: 다른 네트워크 인터페이스로 전달되는 패킷을 처리합니다.
  • output: 시스템에서 나가는 패킷을 처리합니다.

이러한 체인은 테이블에 속하게 되는데, 주요 테이블은 다음과 같습니다.

  • filter: drop, accept 등 패킷 필터링을 수행합니다.
  • nat: network address translation에 대한 처리를 수행합니다.
  • mangle: 헤더 변경 등 패킷의 수정을 처리합니다.
  • raw: 패킷 추적을 비활성화하거나 설정합니다.

그러면 ubuntu 22.04 시스템을 바탕으로, iptables를 확인해 보겠습니다.

network@network01:~$ sudo iptables --list
Chain INPUT (policy DROP)
target     prot opt source               destination
ufw-before-logging-input  all  --  anywhere             anywhere
...

Chain FORWARD (policy DROP)
target     prot opt source               destination
ufw-before-logging-forward  all  --  anywhere             anywhere
...

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ufw-before-logging-output  all  --  anywhere             anywhere
...

기본적인 input, forward, output 체인을 확인할 수 있으며 추가적으로 ufw(방화벽) 관련 chain도 확인하실 수 있습니다.

실제로 iptables의 동작을 확인해보기 위해 network01 시스템에 nginx를 설치하고 확인해 보겠습니다.

network@network01:~$ sudo apt-get update && sudo apt-get install nginx

## network02
network@network02:~$ curl 192.168.1.83

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

이제 network01 시스템에서 80 포트로 들어오는 모든 패킷을 drop 하는 설정을 한 뒤, 다시 확인해 보겠습니다.

network@network01:~$ sudo iptables -A INPUT -p tcp --dport 80 -j DROP
network@network02:~$ curl 192.168.1.83
^C # 정상적인 연결이 안됨

## 연결 요청이 들어왔나 보기 위해 tcpdump를 활용해 봅시다.
## request는 있지만, reply가 없는것을 확인할 수 있습니다.
network@network01:~$ sudo tcpdump -i enp0s1 tcp port 80
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:42:20.647819 IP 192.168.1.84.52202 > network01.http: Flags [S], seq 2687585736, win 64240, options [mss 1460,sackOK,TS val 4045880976 ecr 0,nop,wscale 7], length 0
00:42:21.660618 IP 192.168.1.84.52202 > network01.http: Flags [S], seq 2687585736, win 64240, options [mss 1460,sackOK,TS val 4045881985 ecr 0,nop,wscale 7], length 0
00:42:23.672601 IP 192.168.1.84.52202 > network01.http: Flags [S], seq 2687585736, win 64240, options [mss 1460,sackOK,TS val 4045884000 ecr 0,nop,wscale 7], length 0

network02의 ip만을 허용한다면 어떻게 될까요?

network@network01:~$ sudo iptables -A INPUT -s 192.168.1.84 -j ACCEPT
network@network01:~$ sudo iptables --list
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       tcp  --  anywhere             anywhere             tcp dpt:http
ACCEPT     all  --  192.168.1.84         anywhere
network@network02:~$ curl 192.168.1.83
^C

그래도 연결이 되지 않습니다. 왜 연결이 안 되는 걸까요?

## 다른 테스트를 위해 INPUT 설정을 초기화 합니다.
network@network01:~$ sudo iptables --flush INPUT
network@network01:~$ sudo iptables --list INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
network@network01:~$ sudo iptables -A INPUT -s 192.168.1.84 -j ACCEPT
network@network01:~$ sudo iptables -A INPUT -p tcp --dport 80 -j DROP
network@network01:~$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  192.168.1.84         anywhere
DROP       tcp  --  anywhere             anywhere             tcp dpt:http

위와 같이 network02 시스템에서 nginx에 접근을 요청하면 정상적으로 html이 출력되는 것을 확인할 수 있습니다. 그 이유는 iptables의 규칙은 설정된 순서대로 우선순위를 갖기 때문입니다.

Forward 체인을 확인해 보기 위해 Host OS에서 network 02로 요청을 보낸다면, network 01 시스템의 nginx로 연결해 보도록 하겠습니다.

## ip 패킷에 대한 forwarding을 허용합니다.
network@network02:~$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
network@network02:~$ sudo sysctl -p

## 80포트로 들어오는 패킷을 network 01로 라우팅 합니다.
network@network02:~$ sudo iptables --table nat --append PREROUTING \\
  --proto tcp --dport 80 \\
  --jump DNAT --to-destination 192.168.1.83:80

PREROUTING을 추가했습니다. 이는 들어오는 패킷을 현재 시스템에서 처리하지 않고, 다음 destination으로 jump 함을 의미합니다. network 02 시스템에 대해 curl요청을 보내면, 아무 반응이 없습니다. 잠시 멈추시어 그 이유를 생각해 보시기 바랍니다.

## network 01로 패킷을 forwarding 합니다.
network@network02:~$ sudo iptables --append FORWARD --proto tcp \\
  --destination 192.168.1.83 --dport 80 --jump ACCEPT
  
## 패킷 중 목적지 IP가 network 01의 80포트인 패킷의 출발지 IP를 192.168.1.84로 변경합니다.
network@network02:~$ sudo iptables --table nat --append POSTROUTING \\
  --proto tcp --destination 192.168.1.83 --dport 80 \\
  --jump SNAT --to-source 192.168.1.84

iptables에는 LOG를 통해 규칙에 대한 패킷을 기록할 수 있습니다. 한번 위 동작에 대해 로그를 확인해 봅시다.

## iptables는 순서를 갖기 때문에, 기존의 FORWARD 규칙을 삭제하고 다시 설정합니다.
network@network02:~$ sudo iptables --append FORWARD \\
  --destination 192.168.1.83 --proto tcp \\
  --dport 80 --jump LOG \\
  --log-prefix "Forward to ->Network 01 Nginx: " --log-level 4
network@network02:~$ sudo iptables --append FORWARD --proto tcp \\
  --destination 192.168.1.83 --dport 80 \\
  --jump ACCEPT

## 기본적으로 /var/log/syslog를 통해 iptables의 로그를 확인할 수 있습니다.
network@network02:~$ tail -f /var/log/syslog
...
Dec  3 04:15:48 network02 kernel: [13070.401666] Forward to ->Network 01 NginxIN=enp0s1 OUT=enp0s1 MAC=2a:14:0f:0a:0d:2e:... SRC=192.168.1.2 DST=192.168.1.83 LEN=64 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=51098 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0
...
  • 우리가 설정한 다른 규칙들에도 LOG를 남겨보세요.

output 체인의 경우 다음과 같이 설정할 수 있습니다. 한번 직접 실습해 보시길 바랍니다.

sudo iptables --append OUTPUT --proto tcp|udp --dport PORT --jump DROP|ACCEPT|LOG

IPVS

IPVS는 쿠버네티스에서 사용할 수 있는 프록시 모드 중 하나입니다. 이를 통해 쿠버네티스 서비스와 엔드포인트 슬라이스를 감시하고, netlink 인터페이스를 호출해 IPVS 규칙을 생성하고, 이를 쿠버네티스와 주기적으로 동기화합니다. 이 제어 루프를 통해 IPVS를 원하는 상태로 일치하도록 보장합니다.

특히 IPVS의 경우 SNAT, DNAT에 대한 지원이 없기 때문에 필요에 따라 iptables와 함께 사용해야 합니다.

쿠버네티스에서 IPVS 모드를 이용할 경우 다음의 기능을 사용할 수 있습니다.

  • 라운드 로빈, Least Connection 기반의 로드 밸런싱
    • iptables의 경우 백엔드를 임의로 선택합니다. 즉, iptables의 경우 선택된 파드가 응답하지 않으면 연결이 실패하지만, IPVS는 다른 백엔드 파드로 재시도합니다.
  • Cluster IP와 NodePort에 대한 라우팅

IPVS 실습을 위해 ipvs admin 패키지를 설치하고, 라운드 로빈을 활성화해 줍니다.

## network 01
network@network01:~$ sudo sysctl -w net.ipv4.ip_forward=1
network@network01:~$ sudo apt-get install ipvsadm -y
network@network01:~$ lsmod | grep "ip_vs"
network@network01:~$ sudo modprobe ip_vs
network@network01:~$ sudo modprobe ip_vs_rr
network@network01:~$ lsmod | grep "ip_vs"
ip_vs_rr               20480  0
ip_vs                 196608  2 ip_vs_rr
nf_conntrack          196608  6 xt_conntrack,nf_nat,xt_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs
nf_defrag_ipv6         24576  2 nf_conntrack,ip_vs
libcrc32c              16384  6 nf_conntrack,nf_nat,btrfs,nf_tables,raid456,ip_vs

## network 01에서는 nginx 컨테이너를 두 개 띄워줍니다.
network@network01:~$ sudo docker run -d --name nginx1 -p 8080:80 nginx
network@network01:~$ sudo docker run -d --name nginx2 -p 8081:80 nginx
## 이후 각 컨테이너에 요청을 보내면 아래의 응답이 오도록 컨테이너를 수정합니다.
network@network01:~$ curl localhost:8080
nginx 8080 container
network@network01:~$ curl localhost:8081
nginx 8081 container

## network 02
network@network02:~$ sudo sysctl -w net.ipv4.ip_forward=1
network@network02:~$ sudo apt-get install ipvsadm -y
network@network02:~$ lsmod | grep "ip_vs"
network@network02:~$ sudo modprobe ip_vs
network@network02:~$ sudo modprobe ip_vs_rr
network@network02:~$ lsmod | grep "ip_vs"
ip_vs_rr               20480  0
ip_vs                 196608  2 ip_vs_rr
nf_conntrack          196608  6 xt_conntrack,nf_nat,xt_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs
nf_defrag_ipv6         24576  2 nf_conntrack,ip_vs
libcrc32c              16384  6 nf_conntrack,nf_nat,btrfs,nf_tables,raid456,ip_vs

이후 network 02 시스템에서 다음의 작업을 수행해야 합니다.

## 패킷 라우팅을 허용합니다.
network@network02:~$ sudo sysctl -w net.ipv4.ip_forward=1
## 커널의 Connection을 Track하는 기능을 활성화 합니다.
## IPVS의 경우 커널 수준의 로드 밸런싱이기 때문에, 연결이 추적 불가할 때 NAT가 불가합니다.
## 이를 통해 클라이언트의 요청으로 생성된 연결을 추적하고
## 추적된 연결의 응답 패킷을 적절히 라우팅 합니다.
network@network02:~$ sudo sysctl -w net.ipv4.vs.conntrack=1

network@network02:~$ sudo iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.83 --dport 8080 -j MASQUERADE
network@network02:~$ sudo iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.83 --dport 8081 -j MASQUERADE

network@network02:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
network@network02:~$ sudo iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  tcp  --  anywhere             192.168.1.83         tcp dpt:http-alt
MASQUERADE  tcp  --  anywhere             192.168.1.83         tcp dpt:tproxy

network@network02:~$ sudo ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.84:80 rr
  -> 192.168.1.83:8080            Masq    1      1          1
  -> 192.168.1.83:8081            Masq    1      1          0

한번 각 설정을 off 한 다음, tcpdump로 직접 상태를 보며 무엇이 문제인지 확인해 보세요.
Host OS에서 Network 02로 요청을 보낼 때, 로드 밸런싱이 정상적으로 동작하는 것을 확인할 수 있습니다.

$ curl 192.168.1.84
nginx 8080 container

$ curl 192.168.1.83:8080
nginx 8080 container

$ curl 192.168.1.83:8081
nginx 8081 container

$ curl 192.168.1.84
nginx 8081 container

$ curl 192.168.1.84
nginx 8080 container

ipvs 실습의 경우 round robin만 활용했습니다. 이외의 방식 (Least Connection)도 한번 직접 수행해 보시면 좋을 것 같습니다.


Overlay 네트워크

Overlay 네트워크는 기존의 네트워크 상위에 논리 네트워크를 만들고, IP 주소와 트래픽 제어를 수행하는 기술입니다. 쿠버네티스에서는 Overlay 네트워크를 통해 Pod의 연결을 지원합니다.

IPIP (IP in IP)

IPIP는 IP 패킷을 다른 IP 패킷 안에 캡슐화하는 기술입니다.

좌측이 일반적인 IP 패킷이라면, 우측은 IPIP에 대한 패킷입니다.

좌측 그림이 일반적인 IP 패킷이라면, 오른쪽 그림은 IPIP를 나타냅니다. Outer IP Header를 추가하여 IP를 캡슐화하는 기술인데, 이때 Outer IP Header는 일반적인 IP Header와 동일합니다.

바꿔 말하면, 새로운 IP Header를 추가하여 출발지와 목적지 등을 새롭게 설정할 수 있습니다.

IPIP를 적용한 Overlay 네트워크에서는 컨테이너의 값을 Inner IP Header로 설정하여 원 출발지/목적지로 설정하고, Outer IP Header에는 컨테이너를 운영하는 시스템(노드)을 Outer IP Header로 설정하여 캡슐화를 진행합니다.

그러나 IP를 다른 IP 패킷 안에 캡슐화하기 때문에, 패킷의 최대 크기 Maximum Transmission Unit (MTU)를 조절해야 하거나, 네트워크 오버헤드로 인해 성능이 줄어들 수 있습니다. 또한 다중 테넌트를 지원하지 않기 때문에 대규모 환경에서는 VXLAN 등을 이용해야 합니다.

IPIP를 통해 Network 시스템에 가상의 대역을 갖는 ip를 생성하고, 통신을 수행해 보겠습니다.

## Network 01
network@network01:~$ sudo ip tunnel add ipip0 mode ipip local 192.168.1.83 remote 192.168.1.84
network@network01:~$ sudo ip link set ipip0 up
network@network01:~$ sudo ip addr add 10.0.0.1/24 dev ipip0
network@network01:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 1a:df:51:84:a5:4a brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.83/24 metric 100 brd 192.168.1.255 scope global dynamic enp0s1
       valid_lft 7034sec preferred_lft 7034sec
    inet6 fe80::18df:51ff:fe84:a54a/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:d6:7e:70:ed brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
5: ipip0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 192.168.1.83 peer 192.168.1.84
    inet 10.0.0.1/24 scope global ipip0
       valid_lft forever preferred_lft forever
    inet6 fe80::5efe:c0a8:153/64 scope link
       valid_lft forever preferred_lft forever

network@network01:~$ ping 10.0.0.2 -c 3
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=18.6 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=6.06 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=12.4 ms

--- 10.0.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2011ms
rtt min/avg/max/mdev = 6.064/12.346/18.572/5.106 ms
    
## Network 02
network@network02:~$ sudo ip tunnel add ipip0 mode ipip local 192.168.1.84 remote 192.168.1.83
network@network02:~$ sudo ip link set ipip0 up
network@network02:~$ sudo ip addr add 10.0.0.2/24 dev ipip0
network@network02:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 2a:14:0f:0a:0d:2e brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.84/24 metric 100 brd 192.168.1.255 scope global dynamic enp0s1
       valid_lft 5621sec preferred_lft 5621sec
    inet6 fe80::2814:fff:fe0a:d2e/64 scope link
       valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: ipip0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 192.168.1.84 peer 192.168.1.83
    inet 10.0.0.2/24 scope global ipip0
       valid_lft forever preferred_lft forever
    inet6 fe80::5efe:c0a8:154/64 scope link
       valid_lft forever preferred_lft forever
       
## Network 01이 관리중인 ipip로 요청을 보내보면, 정상적으로 동작하는걸 확인할 수 있습니다.
network@network02:~$ curl 10.0.0.1:8080
nginx 8080 container

VXLAN

Virtual eXtensible LAN의 약자로, 네트워크 가상화의 표준(standard)으로, UDP를 활용하여 L2의 이더넷 프레임을 캡슐화합니다. UDP를 활용하기 때문에 L3를 기반으로 하며, 캡슐화를 통해 L2 네트워크를 생성합니다. 쉽게 말하면 물리 네트워크 위에 가상의 L2 네트워크를 만드는 기술입니다.

우측이 VXLAN이 설정된 패킷입니다.

  • Inner L2 Frame: VXLAN 내에서 통신하는 원래의 트래픽입니다.
  • VXLAN Header: VXLAN 네트워크 정보를 포함합니다.
  • Outer UDP Header: 캡슐화된 VXLAN 트래픽을 전송하기 위해 사용하며, 포트 4789번을 가집니다. 캡슐화를 효율적으로 처리하는 데 사용됩니다.
  • Outer IP Header: 물리 네트워크의 L3 헤더로, 캡슐화된 트래픽을 목적지로 전송하기 위한 헤더입니다. 실제 라우팅 가능한 IP 네트워크에서 트래픽을 전달하는 역할을 수행합니다.
  • Outer L2 Header: 물리 네트워크의 L2 헤더입니다. 캡슐화된 트래픽이 물리 네트워크의 스위치 혹은 라우터에 전달될 때 사용됩니다.

VXLAN은 Multicast 혹은 Unicast 방식으로 가상 네트워크에서 통신을 수행합니다.

Multicast는 224.0.0.0 ~ 239.255.255.255 대역에 대해 그룹을 생성하고, 같은 그룹의 장치들끼리 트래픽을 주고받을 수 있도록 합니다. 즉, VXLAN이 사용하는 VTEP인 VXLAN을 관리하는 시스템인 네트워크 장치 간 통신이 가능하게 합니다. Multicast의 경우, 위 주소가 Outer IP Header의 목적지 주소가 되며, Unicast의 경우 다른 VTEP의 물리적 IP 주소가 Outer IP Header의 목적지 주소가 됩니다. VXLAN의 가상 네트워크는 Inner IP Header에 담겨 실제 통신에 사용됩니다. VTEP는 Underlay 네트워크인 물리 네트워크와 Overlay 네트워크인 VXLAN 인터페이스가 될 수 있습니다.

그럼 이제 VXLAN을 통해 Overlay 네트워크를 구성하고, 마찬가지로 docker 컨테이너로 접근해 보겠습니다.

Multicast의 경우, vxlan의 endpoint group을 지정해 주면 IGMP (Internet Group Management Protocol)를 통해 자동으로 구성이 수행됩니다.

  • 멀티캐스트 트래픽은 사용 중인 스위치/라우터가 멀티캐스트 라우팅을 지원하고, IGMP 메시지를 처리할 수 있을 때 전달됩니다.
## Network 01
network@network01:~$ sudo ip link add vxlan0 type vxlan id 241203 \\
  dev enp0s1 group 239.1.1.1 \\
  dstport 4789
network@network01:~$ sudo ip addr add 10.1.1.1/24 dev vxlan0
network@network01:~$ sudo ip link set vxlan0 up
network@network01:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 1a:df:51:84:a5:4a brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.83/24 metric 100 brd 192.168.1.255 scope global dynamic enp0s1
       valid_lft 4498sec preferred_lft 4498sec
    inet6 fe80::18df:51ff:fe84:a54a/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:d6:7e:70:ed brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:d6ff:fe7e:70ed/64 scope link
       valid_lft forever preferred_lft forever
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
7: veth3d8b49d@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether ea:4e:ad:f8:e2:ae brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::e84e:adff:fef8:e2ae/64 scope link
       valid_lft forever preferred_lft forever
10: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 96:70:45:03:fc:fb brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.1/24 scope global vxlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::9470:45ff:fe03:fcfb/64 scope link
       valid_lft forever preferred_lft forever
network@network01:~$ ping 10.1.1.2 -c 4
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=13.5 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=1.74 ms
64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=1.23 ms
64 bytes from 10.1.1.2: icmp_seq=4 ttl=64 time=1.12 ms

--- 10.1.1.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3015ms
rtt min/avg/max/mdev = 1.124/4.396/13.496/5.258 ms

## Network 02
network@network02:~$ sudo ip link add vxlan0 type vxlan id 241203 \\
  dev enp0s1 group 239.1.1.1
network@network02:~$ sudo ip addr add 10.1.1.2/24 dev vxlan0
network@network02:~$ sudo ip link set vxlan0 up
network@network02:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 2a:14:0f:0a:0d:2e brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.84/24 metric 100 brd 192.168.1.255 scope global dynamic enp0s1
       valid_lft 6691sec preferred_lft 6691sec
    inet6 fe80::2814:fff:fe0a:d2e/64 scope link
       valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
9: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 0a:ec:bb:3d:3d:f8 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.2/24 scope global vxlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::8ec:bbff:fe3d:3df8/64 scope link
       valid_lft forever preferred_lft forever
network@network02:~$ ping 10.1.1.1 -c 4
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=2.41 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=2.50 ms
64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=6.22 ms
64 bytes from 10.1.1.1: icmp_seq=4 ttl=64 time=7.82 ms

--- 10.1.1.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3017ms
rtt min/avg/max/mdev = 2.409/4.738/7.822/2.352 ms

## 아래 명령은 두 개의 shell을 접속하여 수행해 보세요.
network@network02:~$ curl 10.1.1.1:8080
nginx 8080 container
# 나머지 하나의 Shell 에서는 tcpdump를 수행하여 확인해봅시다.
network@network02:~$ sudo tcpdump -i enp0s1 udp port 4789 -n
[sudo] password for network:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
11:44:56.662220 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [S], seq 156139928, win 64860, options [mss 1410,sackOK,TS val 318379903 ecr 0,nop,wscale 7], length 0
11:44:56.663992 IP 192.168.1.83.39249 > 192.168.1.84.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.1.8080 > 10.1.1.2.39200: Flags [S.], seq 169487905, ack 156139929, win 65160, options [mss 1460,sackOK,TS val 1355529330 ecr 318379903,nop,wscale 7], length 0
11:44:56.664049 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [.], ack 1, win 507, options [nop,nop,TS val 318379905 ecr 1355529330], length 0
11:44:56.664133 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [P.], seq 1:78, ack 1, win 507, options [nop,nop,TS val 318379905 ecr 1355529330], length 77: HTTP: GET / HTTP/1.1
11:44:56.665779 IP 192.168.1.83.39249 > 192.168.1.84.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.1.8080 > 10.1.1.2.39200: Flags [.], ack 78, win 509, options [nop,nop,TS val 1355529331 ecr 318379905], length 0
11:44:56.665998 IP 192.168.1.83.39249 > 192.168.1.84.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.1.8080 > 10.1.1.2.39200: Flags [P.], seq 1:237, ack 78, win 509, options [nop,nop,TS val 1355529331 ecr 318379905], length 236: HTTP: HTTP/1.1 200 OK
11:44:56.665998 IP 192.168.1.83.39249 > 192.168.1.84.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.1.8080 > 10.1.1.2.39200: Flags [P.], seq 237:258, ack 78, win 509, options [nop,nop,TS val 1355529331 ecr 318379905], length 21: HTTP
11:44:56.666018 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [.], ack 237, win 506, options [nop,nop,TS val 318379907 ecr 1355529331], length 0
11:44:56.666040 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [.], ack 258, win 506, options [nop,nop,TS val 318379907 ecr 1355529331], length 0
11:44:56.666452 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [F.], seq 78, ack 258, win 506, options [nop,nop,TS val 318379907 ecr 1355529331], length 0
11:44:56.674543 IP 192.168.1.83.39249 > 192.168.1.84.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.1.8080 > 10.1.1.2.39200: Flags [F.], seq 258, ack 79, win 509, options [nop,nop,TS val 1355529340 ecr 318379907], length 0
11:44:56.674574 IP 192.168.1.84.56103 > 192.168.1.83.4789: VXLAN, flags [I] (0x08), vni 241203
IP 10.1.1.2.39200 > 10.1.1.1.8080: Flags [.], ack 259, win 506, options [nop,nop,TS val 318379915 ecr 1355529340], length 0

Unicast를 활용한 내용과 VXLAN의 아이피 CIDR을 서로 다르게 설정한 통신도 한번 수행해 보시기 바랍니다.


마치며

오늘은 다음 주제인 Pod와 서비스 간 통신을 시작하기에 앞서 쿠버네티스에서 네트워크를 구성하는데 기본적인 내용을 다루어보았습니다.

느리다면 느린, 길다면 긴 3주 동안 쿠버네티스의 네트워크를 바라보기 위한 필수적인 내용을 다루었습니다. 읽어주신 모든 분께 도움이 되었으면 합니다.

감사합니다.


참고

+ Recent posts