kube-proxy


Kube-proxy의 동작 방식

1. iptables

  1. Table - iptables는 여러 테이블로 구성되어 있으며, 각 테이블은 특정 유형의 패킷 처리를 담당한다.
  1. Chains - 각 테이블은 여러 체인을 포함하고 있습니다. 주요 체인은 다음과 같다.

kube-proxy는 spec.externalTrafficPolicy의 설정에 따라서 라우팅되는 엔드포인트를 필터링 한다[1]. 서비스에는 spec.externalTrafficPolicy 필드가 존재하며 Cluster(Default 설정) 또는 Local 값을 갖으며 서비스의 externalTrafficPolicy 정책이 Local인 경우 트래픽이 랜덤으로 분산되지 않는다.




Scenario test

Case1. PREROUTING - When externalTrafficPolicy is Cluster.

1. nginx 샘플 배포
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: test
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: test
  labels:
    app: nginx
  annotations:
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80

해당 매니페스트를 배포 하면 서비스의 externalTrafficPolicy는 기본 설정인 Cluster 값을 갖는다.

2. 배포 확인
$ kubectl get po -o wide -n test
NAME                    READY   STATUS    RESTARTS   AGE   IP             NODE                                              NOMINATED NODE   READINESS GATES
nginx-55f598f8d-25sx9   1/1     Running   0          13m   10.20.11.179   ip-10-20-11-149.ap-northeast-2.compute.internal   <none>           <none>
nginx-55f598f8d-wcqt7   1/1     Running   0          13m   10.20.10.127   ip-10-20-10-207.ap-northeast-2.compute.internal   <none>           <none>

$ kubectl get svc -o wide -n test
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE   SELECTOR
nginx   ClusterIP   172.20.247.136   <none>        80/TCP    30m   app=nginx
3. 워커노드의 iptables 확인
$ iptables -L PREROUTING -v -n -t nat

Chain PREROUTING (policy ACCEPT 2703 packets, 245K bytes)
pkts bytes target     prot opt in     out     source               destination
4823K  502M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
$ iptables -L KUBE-SERVICES -v -n -t nat

Chain KUBE-SERVICES (2 references)
pkts bytes target                     prot opt in     out     source               destination
    0     0 KUBE-SVC-IU46S4VZFN77LK6S  tcp  --  *      *       0.0.0.0/0            172.20.247.136       /* test/nginx:http cluster IP */ tcp dpt:80
1946  117K KUBE-NODEPORTS             all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
$ iptables -L KUBE-SVC-IU46S4VZFN77LK6S -v -n -t nat

Chain KUBE-SVC-IU46S4VZFN77LK6S (1 references)
pkts bytes target                     prot opt in     out     source               destination
    0     0 KUBE-SEP-XAAQ4E3V5J6GEKUJ  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http -> 10.20.10.127:80 */ statistic mode random probability 0.50000000000
    0     0 KUBE-SEP-S7XCV467AW272WRX  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http -> 10.20.11.179:80 */
$ iptables -L KUBE-SEP-XAAQ4E3V5J6GEKUJ -v -n -t nat

Chain KUBE-SEP-XAAQ4E3V5J6GEKUJ (1 references)
pkts bytes target          prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  all  --  *      *       10.20.10.127         0.0.0.0/0            /* test/nginx:http */
    0     0 DNAT            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http */ tcp to:10.20.10.127:80


$ iptables -L KUBE-SEP-S7XCV467AW272WRX -v -n -t nat

Chain KUBE-SEP-S7XCV467AW272WRX (1 references)
pkts bytes target          prot opt in     out     source               destination
    0     0 KUBE-MARK-MASQ  all  --  *      *       10.20.11.179         0.0.0.0/0            /* test/nginx:http */
    0     0 DNAT            tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http */ tcp to:10.20.11.179:80
4. 파드의 수를 3개로 늘린 후 KUBE-SVC-IU46S4VZFN77LK6S 확인
$ iptables -L KUBE-SVC-IU46S4VZFN77LK6S -v -n -t nat
Chain KUBE-SVC-IU46S4VZFN77LK6S (1 references)
pkts bytes target                     prot opt in     out     source               destination
    0     0 KUBE-SEP-XAAQ4E3V5J6GEKUJ  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http -> 10.20.10.127:80 */ statistic mode random probability 0.33333333349
    0     0 KUBE-SEP-K34D3XS7VWEHKFPE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http -> 10.20.10.218:80 */ statistic mode random probability 0.50000000000
    0     0 KUBE-SEP-S7XCV467AW272WRX  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* test/nginx:http -> 10.20.11.179:80 */

Case2. PREROUTING - When externalTrafficPolicy is Local.


Case3. POSTROUTING - When AWS_VPC_K8S_CNI_EXTERNALSNAT is false

  1. POSTROUTING 모든 프로토콜의 source와 destination에 대해서 KUBE-POSTROUTING, AWS-SNAT-CHAIN-0 규칙이 순서대로 적용된다.
[root@ip-10-20-136-210 ~]# iptables -L POSTROUTING -v -n -t nat
Chain POSTROUTING (policy ACCEPT 1111 packets, 75960 bytes)
 pkts bytes target     prot opt in     out     source               destination
32314 2183K KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
32189 2174K AWS-SNAT-CHAIN-0  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* AWS SNAT CHAIN */
  1. KUBE-POSTROUTING MARK가 0x4000 아닌 패킷에 대해서 RETURN 동작을 수행 RETURN은 체인의 처리를 중단하고 호출한 체인으로 돌아가게 한다. 추가로, MARK는 패킷에 내부적인 마크를 설정하는 것을 의미한다. MASQUERADE는 패킷의 출발지 주소를 게이트웨이 장치에 부여된 공인 IP 주소로 변경 되도록 한다.
[root@ip-10-20-136-210 ~]# iptables -L KUBE-POSTROUTING -v -n -t nat
Chain KUBE-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination
 4946  330K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
    0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
    0     0 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */ random-fully

모든 패킷이 RETURN 되므로 MASQUERADE에 의해 SNAT 되지 않고, 상위 체인으로 돌아간다.

  1. AWS-SNAT-CHAIN-0 목적지 주소가 VPC대역(100.64.0.0./16, 10.20.0.0/16)이면 모두 RETURN되므로 SNAT를 수행하지 않는다. 그 외의 트래픽 중 VLAN 인터페이스로 나가지 않는 패킷에 대해, 목적지가 로컬이 아닌 경우 SNAT를 수행한다. SNAT 시 소스 IP를 10.20.136.210으로 변경한다. ‘random-fully’ 옵션은 SNAT 과정에서 사용되는 소스 포트를 완전히 무작위로 선택하도록 한다.
[root@ip-10-20-136-210 ~]# iptables -L AWS-SNAT-CHAIN-0 -v -n -t nat
Chain AWS-SNAT-CHAIN-0 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  *      *       0.0.0.0/0            100.64.0.0/16        /* AWS SNAT CHAIN */
19848 1422K RETURN     all  --  *      *       0.0.0.0/0            10.20.0.0/16         /* AWS SNAT CHAIN */
 6577  406K SNAT       all  --  *      !vlan+  0.0.0.0/0            0.0.0.0/0            /* AWS, SNAT */ ADDRTYPE match dst-type !LOCAL to:10.20.136.210 random-fully

AWS_VPC_K8S_CNI_EXTERNALSNAT 옵션이 false인 경우 VPC 내부 에서는 파드 IP로 통신하지만, VPC 외부 대역인 경우에는 노드의 IP로 SNAT 되어 통신한다.


Case4. POSTROUTING - When AWS_VPC_K8S_CNI_EXTERNALSNAT is true

  1. POSTROUTING 모든 프로토콜의 source와 destination에 대해서 KUBE-POSTROUTING 규칙이 적용된다. AWS_VPC_K8S_CNI_EXTERNALSNAT 옵션을 변경한 후, AWS-SNAT-CHAIN-0 체인이 사라졌다.
[root@ip-10-20-20-176 ~]# iptables -L POSTROUTING -v -n -t nat
Chain POSTROUTING (policy ACCEPT 186 packets, 12174 bytes)
 pkts bytes target     prot opt in     out     source               destination
1508K  101M KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
  1. KUBE-POSTROUTING MARK가 0x4000 아닌 패킷에 대해서 RETURN 동작을 수행 RETURN은 체인의 처리를 중단하고 호출한 체인으로 돌아가게 한다. 추가로, MARK는 패킷에 내부적인 마크를 설정하는 것을 의미한다. MASQUERADE는 패킷의 출발지 주소를 게이트웨이 장치에 부여된 공인 IP 주소로 변경 되도록 한다.
[root@ip-10-20-20-176 ~]# iptables -L KUBE-POSTROUTING -v -n -t nat
Chain KUBE-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination
 6050  408K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
    0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
    0     0 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */ random-fully

AWS_VPC_K8S_CNI_EXTERNALSNAT 옵션이 true인 경우 어떤 곳에서도 SNAT 를 수행하는 곳이 없기 때문에 파드 IP 그대로 통신을 하게 된다.


Case5. POSTROUTING - When AWS_VPC_K8S_CNI_RANDOMIZESNAT is none

AWS_VPC_K8S_CNI_RANDOMIZESNAT 옵션 설명에 따르면 none 이 아닌 다른 옵션(Default: prng)을 사용할 경우에는 OS level (/proc/sys/net/ipv4/ip_local_port_range)에 정의되지 않은 소스포트를 할당한다.

  1. POSTROUTING
[root@ip-10-20-20-176 ~]# iptables -L POSTROUTING -v -n -t nat
Chain POSTROUTING (policy ACCEPT 127 packets, 9109 bytes)
 pkts bytes target     prot opt in     out     source               destination
1547K  104M KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
37274 2518K AWS-SNAT-CHAIN-0  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* AWS SNAT CHAIN */
  1. AWS-SNAT-CHAIN-0 SNAT시 ‘random-fully’ 옵션이 제거 된 것을 볼 수 있다. 따라서, OS level (/proc/sys/net/ipv4/ip_local_port_range)에 정의된 소스포트를 이용하여 동작한다.
[root@ip-10-20-20-176 ~]# iptables -L AWS-SNAT-CHAIN-0 -v -n -t nat
Chain AWS-SNAT-CHAIN-0 (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  *      *       0.0.0.0/0            100.64.0.0/16        /* AWS SNAT CHAIN */
27512 1922K RETURN     all  --  *      *       0.0.0.0/0            10.20.0.0/16         /* AWS SNAT CHAIN */
   47  2820 SNAT       all  --  *      !vlan+  0.0.0.0/0            0.0.0.0/0            /* AWS, SNAT */ ADDRTYPE match dst-type !LOCAL to:10.20.20.176
[root@ip-10-20-20-176 ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999

서비스에는 spec.externalTrafficPolicy 필드가 존재하며 Cluster(Default 설정) 또는 Local 값을 갖으며 서비스의 externalTrafficPolicy 정책이 Local인 경우 트래픽이 랜덤으로 분산되지 않는다.



2. nftables

nftables 또한 netfilter 프레임워크 위에서 동작하지만 해시 기반 이라는 점 에서 속도면이 빠르다.

1. 전체 kube-proxy nftables 테이블 확인
# kube-proxy가 생성한 전체 nftables 룰셋 확인
$ nft list table ip kube-proxy

table ip kube-proxy {
        comment "rules for kube-proxy"
        set cluster-ips {
                type ipv4_addr
                comment "Active ClusterIPs"
                elements = { 172.20.0.1, 172.20.0.10,
                              172.20.154.213, 172.20.158.209,
                              172.20.166.27 }
        }

        set nodeport-ips {
                type ipv4_addr
                comment "IPs that accept NodePort traffic"
                elements = { 10.20.128.129 }
        }

        map no-endpoint-services {
                type ipv4_addr . inet_proto . inet_service : verdict
                comment "vmap to drop or reject packets to services with no endpoints"
                elements = { 172.20.154.213 . tcp . 443 comment "kube-system/aws-load-balancer-webhook-service:webhook-server" : goto reject-chain }
        }

        map no-endpoint-nodeports {
                type inet_proto . inet_service : verdict
                comment "vmap to drop or reject packets to service nodeports with no endpoints"
        }

        map firewall-ips {
                type ipv4_addr . inet_proto . inet_service : verdict
                comment "destinations that are subject to LoadBalancerSourceRanges"
        }

        map service-ips {
                type ipv4_addr . inet_proto . inet_service : verdict
                comment "ClusterIP, ExternalIP and LoadBalancer IP traffic"
                elements = { 172.20.0.10 . tcp . 53 : goto service-NWBZK7IH-kube-system/kube-dns/tcp/dns-tcp,
                              172.20.0.10 . udp . 53 : goto service-FY5PMXPG-kube-system/kube-dns/udp/dns,
                              172.20.166.27 . tcp . 8080 : goto service-2SONTFIS-sgp/nginx-service/tcp/,
                              172.20.0.1 . tcp . 443 : goto service-2QRHZV4L-default/kubernetes/tcp/https,
                              172.20.158.209 . tcp . 443 : goto service-QJWSRGPH-kube-system/eks-extension-metrics-api/tcp/metrics-api,
                              172.20.0.10 . tcp . 9153 : goto service-AS2KJYAD-kube-system/kube-dns/tcp/metrics }
        }

        map service-nodeports {
                type inet_proto . inet_service : verdict
                comment "NodePort traffic"
        }

        chain filter-prerouting-pre-dnat {
                type filter hook prerouting priority dstnat - 10; policy accept;
                ct state new jump firewall-check
        }

        chain filter-output-pre-dnat {
                type filter hook output priority -110; policy accept;
                ct state new jump firewall-check
        }

        chain filter-input {
                type filter hook input priority filter; policy accept;
                ct state new jump nodeport-endpoints-check
                ct state new jump service-endpoints-check
        }

        chain filter-forward {
                type filter hook forward priority filter; policy accept;
                ct state new jump service-endpoints-check
                ct state new jump cluster-ips-check
        }

        chain filter-output {
                type filter hook output priority filter; policy accept;
                ct state new jump service-endpoints-check
                ct state new jump cluster-ips-check
        }

        chain nat-prerouting {
                type nat hook prerouting priority dstnat; policy accept;
                jump services
        }

        chain nat-output {
                type nat hook output priority -100; policy accept;
                jump services
        }

        chain nat-postrouting {
                type nat hook postrouting priority srcnat; policy accept;
                jump masquerading
        }

        chain nodeport-endpoints-check {
                ip daddr @nodeport-ips meta l4proto . th dport vmap @no-endpoint-nodeports
        }

        chain service-endpoints-check {
                ip daddr . meta l4proto . th dport vmap @no-endpoint-services
        }

        chain firewall-check {
                ip daddr . meta l4proto . th dport vmap @firewall-ips
        }

        chain services {
                ip daddr . meta l4proto . th dport vmap @service-ips
                ip daddr @nodeport-ips meta l4proto . th dport vmap @service-nodeports
        }

        chain masquerading {
                meta mark & 0x00004000 == 0x00000000 return
                meta mark set meta mark ^ 0x00004000
                masquerade fully-random
        }

        chain cluster-ips-check {
                ip daddr @cluster-ips reject comment "Reject traffic to invalid ports of ClusterIPs"
                ip daddr 172.20.0.0/16 drop comment "Drop traffic to unallocated ClusterIPs"
        }

        chain mark-for-masquerade {
                meta mark set meta mark | 0x00004000
        }

        chain reject-chain {
                comment "helper for @no-endpoint-services / @no-endpoint-nodeports"
                reject
        }

......

        chain service-2QRHZV4L-default/kubernetes/tcp/https {
                numgen random mod 2 vmap { 0 : goto endpoint-WANRV762-default/kubernetes/tcp/https__10.20.131.112/443, 1 : goto endpoint-YWEXS4MY-default/kubernetes/tcp/https__10.20.136.125/443 }
        }

        chain endpoint-T5VHWRWL-sgp/nginx-service/tcp/__100.64.7.86/80 {
                ip saddr 100.64.7.86 jump mark-for-masquerade
                meta l4proto tcp dnat to 100.64.7.86:80
        }

        chain service-2SONTFIS-sgp/nginx-service/tcp/ {
                numgen random mod 2 vmap { 0 : goto endpoint-T5VHWRWL-sgp/nginx-service/tcp/__100.64.7.86/80, 1 : goto endpoint-2FSOWKR2-sgp/nginx-service/tcp/__100.64.8.163/80 }
        }

        chain endpoint-2FSOWKR2-sgp/nginx-service/tcp/__100.64.8.163/80 {
                ip saddr 100.64.8.163 jump mark-for-masquerade
                meta l4proto tcp dnat to 100.64.8.163:80
        }
}
2. 서비스 라우팅 맵 확인 (KUBE-SERVICES에 해당)
# service-ips 맵 확인 — ClusterIP 기반 라우팅 맵
$ nft list map ip kube-proxy service-ips

table ip kube-proxy {
        map service-ips {
                type ipv4_addr . inet_proto . inet_service : verdict
                comment "ClusterIP, ExternalIP and LoadBalancer IP traffic"
                elements = { 172.20.0.10 . tcp . 53 : goto service-NWBZK7IH-kube-system/kube-dns/tcp/dns-tcp,
                              172.20.0.10 . udp . 53 : goto service-FY5PMXPG-kube-system/kube-dns/udp/dns,
                              172.20.166.27 . tcp . 8080 : goto service-2SONTFIS-sgp/nginx-service/tcp/,
                              172.20.0.1 . tcp . 443 : goto service-2QRHZV4L-default/kubernetes/tcp/https,
                              172.20.158.209 . tcp . 443 : goto service-QJWSRGPH-kube-system/eks-extension-metrics-api/tcp/metrics-api,
                              172.20.0.10 . tcp . 9153 : goto service-AS2KJYAD-kube-system/kube-dns/tcp/metrics }
        }
}
3. 특정 서비스의 엔드포인트 분산 확인 (KUBE-SVC-xxx에 해당)

numgen random mod 2 는 0~1 사이의 랜덤 숫자 생성한다. 라우팅 비율은 50:50 균등 분산이 이루어진다.

# 특정 서비스 체인 확인
$ nft list chain ip kube-proxy service-2QRHZV4L-default/kubernetes/tcp/https

table ip kube-proxy {
        chain service-2QRHZV4L-default/kubernetes/tcp/https {
                numgen random mod 2 vmap { 0 : goto endpoint-WANRV762-default/kubernetes/tcp/https__10.20.131.112/443, 1 : goto endpoint-YWEXS4MY-default/kubernetes/tcp/https__10.20.136.125/443 }
        }
}
4. 엔드포인트 DNAT 확인 (KUBE-SEP-xxx에 해당)
  1. ip saddr 10.20.131.112 jump mark-for-masquerade: 출발지가 자기 자신(10.20.131.112)이면 MASQUERADE 마킹 → 헤어핀(hairpin) 트래픽 처리

MASQUERADE = 패킷의 출발지(src) IP를 나가는 인터페이스의 IP로 자동 변경하는 것 MASQUERADE 적용 시 (정상 동작). SNAT(파드 IP -> 노드 IP)

  1. meta l4proto tcp dnat to 10.20.131.112:443: 목적지 IP를 10.20.131.112:443으로 DNAT
# 특정 엔드포인트 체인 확인
$ nft list chain ip kube-proxy endpoint-WANRV762-default/kubernetes/tcp/https__10.20.131.112/443

table ip kube-proxy {
        chain endpoint-WANRV762-default/kubernetes/tcp/https__10.20.131.112/443 {
                ip saddr 10.20.131.112 jump mark-for-masquerade
                meta l4proto tcp dnat to 10.20.131.112:443
        }
}

📚 References