본문 바로가기
K8s/Advanced Network

[KANS3] 4. Service ClusterIP + NodePort

by okms1017 2024. 9. 29.
728x90

✍ Posted by Immersive Builder  Seong

 

1. Service 

서비스란 ? 

서비스는 쿠버네티스에서 동작하는 어플리케이션을 클러스터 내부/외부에서 유연하게 접속하기 위한 오브젝트입니다. 

특정한 상황에서 파드가 재실행되는 경우 파드의 IP가 변경되므로 이를 해결하기 위해 고정 VIP와 도메인 주소를 제공합니다. 서비스는 접속 요청을 파드로 전달할 뿐만 아니라 트래픽의 부하를 분산하는 역할을 합니다. 

 

kube-proxy 모드

모든 클러스터 노드에 kube-proxy 파드가 데몬셋으로 배포되어서 서비스 통신 동작에 대한 설정을 관리합니다. 

kube-proxy 모드에는 User Space proxy, iptables proxy, IPVS proxy, nftables proxy, eBPF 모드가 있습니다. 

 

  1. User Space proxy 모드 : Client > NIC1 > netfilter > kube-proxy > NIC2 > Pod. Kernel 과 User Space 간 Context-Switching이 발생하여 비효율적입니다. 현재는 사용하지 않습니다. 
  2. iptables proxy 모드 : Client > NIC1 > netfilter > NIC2 > Pod. Kernel 영역의 netfilter에서 트래픽을 바로 처리합니다. User Space를 거치지 않으므로 Context-Switching으로 인한 오버헤드가 발생하지 않습니다. (기본 모드)
  3. IPVS proxy 모드 : Client > NIC1 > netfilter + IPVS > NIC2 > Pod. 동일하게 netfilter를 사용하여 트래픽을 처리하되, 분산 처리는 IPVS가 담당합니다. iptables proxy 모드보다 성능이 좋고 규칙 수가 적습니다. (권장 모드)
  4. nftables proxy 모드 : Kubernetes v1.31 베타 버전의 기능입니다. iptables API가 아닌 nftables API를 사용하여 트래픽을 처리합니다. iptables proxy 모드보다 빠르고 성능이 좋습니다. 단, 리눅스 기반이고 Kernel v5.13 이상에서만 동작합니다. 
  5. eBPF 모드 : Client > NIC1 > eBPF/XDP > NIC2 > Pod. Kernel 영역의 netfilter를 Bypass하여 L2 구간에서 트래픽을 처리합니다. L3 구간의 오버헤드가 없어 가장 성능이 좋습니다. 

※ 최근에는 eBPF 기반의 Cillium에서 kube-proxy의 역할을 처리하여 kube-proxy는 선택 사항이 되고 있습니다. 

 

kube-proxy는 UDP, TCP, SCTP 통신을 프록시하며, HTTP 프로토콜은 처리하지 못합니다.

SCTP는 5G 통신을 위한 프로토콜이고, AWS의 ALB는 SCTP 프로토콜을 지원하지 않아 쿠버네티스의 Ingress로 사용할 수 없습니다. 대신, LoxiLB를 사용하여 통신환경을 구성할 수 있습니다. 

 


서비스 유형 

서비스의 종류에는 ClusterIP, NodePort, LB, ExternalName 4가지 유형이 있습니다. 

 

  • ClusterIP : 클러스터 내부에서 접속하기 위한 VIP가 생성됩니다. 

CloudNet@

 

  • NodePort : 클러스터 모든 노드의 지정된 포트에 대한 접속이 파드로 전달됩니다. 클러스터 외부에서 접속할 수 있으나 단일 엔드포인트를 제공하지는 않습니다. 클러스터 내부에서는 ClusterIP 방식으로 동작합니다. 

CloudNet@

 

  • LoadBalancer : 클라우드 서비스의 로드밸런서에 대한 접속이 파드로 전달됩니다. 클러스터 외부에서도 접속할 수 있으며 단일 엔드포인트를 제공합니다. 

CloudNet@

 

  • ExternalName : 클러스터 내부에서 서비스의 도메인에 대한 접속이 지정된 호스트로 전달됩니다. ExternalName은 대상 호스트가 로드밸런싱을 수행하도록 사용자가 직접 구성해 주어야 합니다. 

 


2. ClusterIP

구성 및 동작 확인 

[ 오브젝트 배포 ] 

 

각 워커노드에 백엔드 파드를 하나씩 배포합니다. 

 

컨트롤 플레인에 백엔드 파드로 통신을 테스트 하기 위한 파드를 하나 배포합니다. 

 

백엔드 파드로 트래픽을 전달할 ClusterIP 타입의 서비스를 배포합니다. 

 

Port는 서비스의 포트(9000), TargetPort는 파드의 포트(80), Endpoint는 서비스의 대상 파드를 각각 의미합니다. 

 

 

[ 서비스 접속 테스트 ]

 

클라이언트(net-pod)에서 백엔드 파드로 접속을 테스트합니다. 

 

서비스 배포 시, kube-proxy에 의해서 iptables 규칙이 모든 노드에 동일하게 추가됩니다. 

그리고 서비스(10.200.1.196)로 인입되는 트래픽은 분산룰에 의해 랜덤으로 파드에 전달됩니다. 

 

소켓 및 라우팅 정보에는 서비스 IP(10.200.1.196)와 포트(9000/TCP)에 해당하는 항목이 없습니다. 

즉, 트래픽은 유저 스페이스가 아닌 커널 영역의 분산룰에 의해 처리됨을 확인할 수 있습니다. 

 

서비스로 반복 접속을 시도하여 부하 분산을 테스트합니다. 

1000번의 접속을 시도하였을 때 3개의 파드로 약 33% 정도로 부하가 분산되고 있는 모습입니다. 

 

conntrack은 시스템 커넥션 정보를 저장하는 모듈입니다. iptables의 상태를 추적해보면 서비스로 요청한 패킷의 응답이 source_ip로 파드의 IP를 가지고 나옴을 알 수 있습니다. 

 

클러스터 외부에서는 ClusterIP 타입의 서비스로 접속이 불가능합니다. 클러스터에 조인되지 않은 내부 노드에서의 접속 시도 또한 마찬가지입니다. 

 

 

[ 패킷 분석 ]

 

워커노드로 유입되는 트래픽은 80 포트입니다. 당연히 9000 포트의 트래픽은 존재하지 않습니다. 

myk8s-worker-eth0 (80)
myk8s-worker-eth0 (9000)
myk8s-worker-veth (80)
myk8s-worker-veth (9000)

 

반면, 테스트 파드(net-pod)에서 캡처한 패킷은 서비스의 IP와 포트(9000)를 가지고 있습니다. 

net-pod-eth0 (9000)

 

** ngrep : 특정 URL 호출에 대한 네트워크 패킷 분석 도구 

 

 

[ IPTables 정책 확인 ]

 

아래 규칙을 ClusterIP 타입의 서비스를 인지하고 트래픽을 처리합니다. 

 

KUBE-SVC-KBDEBIL6IU6WL7RF 체인의 첫 번째 규칙과 일치할 확률은 약 33%이고, 해당 규칙과 매칭되지 않을 경우 두 번째 규칙과 일치할 확률은 50%가 됩니다. 둘 다 매칭되지 않을 경우 마지막 규칙의 일치 확률은 100%가 됩니다. 

 

9000 포트로 인입된 패킷을 DNAT하여 해당 파드의 80 포트로 전달합니다. 

 

서비스 배포 시 생성되는 iptables 규칙들은 kube-proxy에 의해 모든 노드에 추가됩니다. 

 

 

[ 파드 장애 시 서비스 동작 확인 ]

 

특정 파드(webpod3)를 삭제하면 해당 엔드포인트가 리스트에서 제거됩니다. 

파드 장애로 인한 제거 상황

파드가 복구되기까지 순단이 발생하므로 나머지 파드로 부하가 분산됩니다. 

부하 분산 결과

 

파드를 재생성하면 다시 엔드포인트가 추가되고, 서비스 디스커버리에 의해 통신이 가능한 상태가 됩니다. 

파드 재생성 이후 상황
부하 분산 결과

 

Session Affinity

클라이언트에서 최초로 접속한 파드에 대한 연결 상태를 저장하여 세션을 고정하는 기능입니다. 

특정 어플리케이션 파드로 동일한 요청을 보내야 할 경우 사용됩니다. 

 

기본 설정은 비활성화입니다. ({"sessionAffinity":"none"})

Session Affinity 기능을 활성화합니다. 

 

세션 유지시간은 3시간이며, 접속 시마다 갱신됩니다. 

 

iptables 규칙에 "-m recent --rcheck --seconds 10800 --reap" 옵션이 추가됩니다. 

'recent' 모듈(동적으로 IP 주소 목록을 생성하고 확인) , 'rcheck' (현재 ip list 에 해당 ip가 있는지 체크) , 'reap' (--seconds 와 함께 사용, 오래된 엔트리 삭제)

 

클라이언트에서 서비스 접속 시 하나의 파드로 고정됩니다. 

 

이번에는 세션 유지시간을 30초로 변경합니다. 

 

30초 경과 이후 다른 파드로 접속됩니다. 

 

** ClusterIP Service 단점 

- 클러스터 외부에서 접속이 불가능함

- IPTables는 헬스체크 기능을 제공하지 않음 => Readiness Probe 설정 

- 랜덤 분산 알고리즘, Session Affinity 외에 다른 분산 알고리즘 적용 불가 => IPVS는 다양한 분산 알고리즘 제공 

- 목적지 파드가 다수 있는 환경에서 출발지 파드와 목적지 파드가 동일한 노드에 배치되어 있어도 랜덤 분산 알고리즘에 의해 다른 노드의 목적지 파드로 연결되는 불합리한 문제 발생

 


3. NodePort

구성 및 동작 확인

[ 오브젝트 배포 ]

 

노드에 echo 디플로이먼트를 배포합니다. (replicas=3) 

 

외부의 트래픽을 전달하기 위한 NodePort 타입의 서비스를 배포합니다. 

 

NodePort는 30000-32767 범위 내에서 랜덤하게 할당되며, 현재 32962 포트를 사용하고 있습니다. 

Port는 서비스 포트, TargetPort는 목적지 파드의 포트를 각각 의미합니다. 

 

참고로 포트 리스닝 없이 iptables 분산룰에 의해 처리됩니다. 

 

 

[ 서비스 접속 테스트 ]

 

외부 클라이언트(mypc)에서 백엔드 파드로 접속을 테스트합니다. 

 

내부 파드에서 로그를 확인해보면 클라이언트의 IP 주소가 아닌 노드의 IP 주소로 S.IP가 변경되어 전달됩니다. 

 

모든 노드에서 동일한 NodePort로 파드에 접속이 가능합니다. 

 

그리고 파드가 배포되지 않는 컨트롤 플레인으로 접속을 시도해도 연결됩니다. 

 

클러스터 내부에서는 CLUSTER-IP와 서비스 포트를 통해 접속합니다. 

**

 

externalTrafficPolicy

NodePort로 접속할 때 해당 노드에 배포된 파드로만 접속하는 기능입니다. 

이 때, SNAT 하지 않으므로 외부 클라이언트의 IP 주소가 보존되는 특징이 있습니다. 

CloudNet@

 

그러나 해당 노드에 파드가 없다면 통신이 되지 않는 문제가 발생합니다. 

따라서 클라이언트는 접속하고자 하는 파드가 배포된 노드의 IP를 알고 있어야 합니다. 

CloudNet@

 

[ 서비스 접속 테스트 ]

 

기본 설정은 'Cluster'입니다. ({"externalTrafficPolicy":"Cluster"})

externalTrafficPolicy 기능을 활성화합니다. 

 

두 가지 케이스를 모두 테스트하기 위해 replica 수를 2개로 변경합니다. 

현재 2번 워커 노드에만 파드가 존재하지 않습니다. 

 

1번, 3번 워커노드로 접속 시 100%로 연결됩니다. 

 

그리고 외부 클라이언트의 IP 주소가 그대로 보존됩니다. 

 

하지만 목적지 파드가 배치되지 않은 노드로의 접속 시도는 실패합니다. 

 

 

[ IPTables 정책 확인 ]

 

파드가 배치된 1번 워커노드에 접속하여 IPtables 규칙을 확인합니다. 

KUBE-XLB가 삭제되고, KUBE-EXT와 KUBE-SVL 규칙이 추가되었습니다. KUBE-SVL 규칙은 파드가 배치된 노드에만 추가됩니다. 그리고 자신의 노드에 배치된 파드(10.10.1.3:8080)로만 DNAT 연결됩니다. 

myk8s-worker

 

모든 노드에서 KUBE-SVC 규칙이 존재합니다. 

 

** NodePort Service 단점 

- 외부에서 노드의 IP/Port로 직접 접속하므로 보안에 취약함 => LB 타입 서비스로 외부 공개 최소화

- 여전히 랜덤 분산 알고리즘으로 인해 iptables의 불필요한 규칙들이 추가됨 

- Client IP 보존을 위해 externalTrafficPolicy 기능 활성화 시, 파드가 없는 노드로의 접속 실패 현상 => LB 타입 서비스의 헬스체크 기능 사용 

 

Readiness Probe + Endpoints/EndpointSlice

Readiness Probe는 쿠버네티스 클러스터에서 파드의 어플리케이션이 정상적으로 실행 중이고, 외부의 요청을 처리할 준비가 되어 있는지를 주기적으로 검사하는 메커니즘입니다. 

 

Endpoints는 특정 서비스와 관련된 파드의 IP 주소와 포트 정보를 하나의 오브젝트에 저장하는 리소스입니다. 파드가 증가할수록 Endpoint 오브젝트가 커지므로 API 서버와 kube-proxy의 성능에 부담을 줄 수 있습니다. 

 

반면, EndpointSlice는 하나의 서비스에 대해 여러 개의 작은 Endpoint 그룹으로 나누어 구성하는 방식입니다. 파드 정보를 여러 개의 슬라이스로 나누어 관리하므로 API 서버와 kube-proxy의 성능을 최적화할 수 있습니다. 

 

 

 

[ 파드 배포 및 확인 ]

 

파드를 배포하고 healthcheck 파일을 출력할 수 있는지 여부를 검사합니다.  

# mario.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mario
  labels:
    app: mario
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mario
  template:
    metadata:
      labels:
        app: mario
    spec:
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        effect: "NoSchedule"
      nodeSelector:
        node-role.kubernetes.io/control-plane: ""
      containers:
      - name: mario
        image: pengbai/docker-supermario
        readinessProbe:
          exec:
            command:
            - cat
            - healthcheck
---
apiVersion: v1
kind: Service
metadata:
  name: mario
spec:
  ports:
    - name: mario-webport
      port: 80
      targetPort: 8080
      nodePort: 30001
  selector:
    app: mario
  type: NodePort
  externalTrafficPolicy: Local

 

EndpointSlice는 리스트에 있지만, Endpoint는 리스트에 없는 상태입니다. 

 

EndpointSlice가 Endpoint를 조회할 때보다 많은 정보를 제공합니다. Endpoints의 Conditions 항목에서 "Ready:false"임을 확인할 수 있습니다. 

 

파드의 이벤트를 통해 healthcheck 파일이 존재하지 않아서 Readiness probe가 실패하였음을 알 수 있습니다. 

 

healthcheck 파일을 생성합니다. 

 

파일을 생성함과 동시에 파드의 컨테이너 프로세스가 READY 상태가 되고, EndPoint의 목록에 해당 파드가 추가됩니다. 

 

잠시 머리 식힐겸. 게임 한 판 해줍니다. 

 

Topology Aware Routing

클라우드 환경에서 쿠버네티스 클러스터 노드들이 여러 가용 영역(AZ)에 분산 배치되어 있을 때, 동일한 가용 영역(AZ)의 목적지 파드로만 트래픽을 라우팅하는 기능입니다. 

 

Topology Aware Hint - AWS 기술 블로그

 

특히, 가용 영역 간에 네트워크 트래픽 비용을 절감할 수 있는 장점이 있습니다. 

그리고 externalTrafficPolicy와는 달리, 요청한 가용 영역에 파드가 없을 경우 다른 가용 영역으로 트래픽을 전달합니다. 

 

 


[출처] 

1) CloudNet@, KANS 실습 스터디

2) https://kubernetes.io/docs/reference/networking/virtual-ips/

 

Virtual IPs and Service Proxies

Every node in a Kubernetes cluster runs a kube-proxy (unless you have deployed your own alternative component in place of kube-proxy). The kube-proxy component is responsible for implementing a virtual IP mechanism for Services of type other than ExternalN

kubernetes.io

3) https://kubernetes.io/docs/concepts/cluster-administration/proxies/

 

Proxies in Kubernetes

This page explains proxies used with Kubernetes. Proxies There are several different proxies you may encounter when using Kubernetes: The kubectl proxy: runs on a user's desktop or in a pod proxies from a localhost address to the Kubernetes apiserver clien

kubernetes.io

4) https://kubernetes.io/docs/concepts/services-networking/service/

 

Service

Expose an application running in your cluster behind a single outward-facing endpoint, even when the workload is split across multiple backends.

kubernetes.io

5) https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/

 

EndpointSlices

The EndpointSlice API is the mechanism that Kubernetes uses to let your Service scale to handle large numbers of backends, and allows the cluster to update its list of healthy backends efficiently.

kubernetes.io

6) https://aws.amazon.com/ko/blogs/tech/amazon-eks-reduce-cross-az-traffic-costs-with-topology-aware-hints/

 

Amazon EKS에서 Topology Aware Hint 기능을 활용하여 Cross-AZ 통신 비용 절감하기 | Amazon Web Services

Amazon EKS로 클러스터 구성 시 일반적으로 고가용성을 위해서 모든 가용 영역(Availability Zone, AZ)에 워커 노드들을 배치합니다. 클러스터에서 실행되는 Pod들 또한 모든 AZ 에 배포되도록 설정하면 높

aws.amazon.com

728x90