Post

쿠버네티스 서비스 네트워크 이해하기

삽질을 통해 서비스 네트워크 이해하기

Services

파드에서 장애가 발생하면, Deployment는 해당 파드를 제거하고 새로운 파드를 생성한다. 이처럼 파드는 쉽게 대체될 수 있는 존재이다. Pod to Pod 네트워크만으로는 쿠버네티스 시스템이 내구성이 있다고 말할 수 없고 한다. 가장 큰 문제는 파드의 IP로 네트워크를 구성할 경우, 파드가 재생성되면 IP가 달라질 수 있기에 문제가 발생한다. 그렇기에 Service라는 프록시서버를 두어, 현재 동작 중인 파드에 접속할 수 있도록 한다.

프록시 서버는 즉, 서비스는 다음과 같은 요구사항을 만족해야 한다.

  1. 스스로 내구성이 있어야 하며, 장애에 대응할 수 있어야 한다.(가용성)
  2. 트래픽을 전달할 서버 리스트를 가지고 있어야 한다.(EndPoint)
  3. 서버 리스트 내 서버들이 정상적인지 확인할 수 있는 방법을 알아야 한다.(Health Check)

현재의 서비스는 위의 요구사항을 만족한다. 어떤 분은 아래와 같은 말을 한다.

쿠버네티스 개발자들은 Service라는 리소스를 통해 이 문제를 우아하게 해결했다.

그렇다면 서비스는 어떻게 동작할까?

서비스 리소스를 설명하면, 서비스 유형은 ClusterIP, NodePort, LB가 있으며 모두 트래픽을 파드에게 전달해준다. 서비스는 파드 네트워크와는 다르게 veth과 같은 실질적인 인터페이스가 없다. 대신 리눅스 커널 기능인 netfilter를 통해 해결한다.

우선 netfilter에 대해 알아본다.

netfilter

쿠버네티스는 리눅스 커널 기능 중 하나인 netfilter와 user space에 존재하는 인터페이스인 iptables라는 소프트웨어를 이용하여 패킷 흐름을 제어한다. netfilter란 규칙기반 패킷 처리 엔진이며, kernel space에 위치하여 모든 오고 가는 패킷을 관찰한다. 그리고 규칙에 매칭되는 패킷을 발견하면, SRC/DST등을 바꾸는 등 미리 정의된 행동을 수행한다.

1_Pz-NixxBDbSnuPGyp8UWHQ.webp

[출처: https://en.m.wikipedia.org/wiki/File:Netfilter-packet-flow.svg]

이제 앞으로 분석할 내용을 제대로 파악하려면, Iptables을 자세히 알아야 한다.

iptables

iptables는 리눅스 시스템에서 네트워크 트래픽을 제어하는 방화벽 도구이다. 참고한 자료와 GPT-4를 통해 얻은 내용은 다음과 같다.

💡 관련 옵션 및 명령어

  • S: 현재 설정된 iptables 규칙을 출력합니다.

  • t nat: ‘nat’ 테이블을 대상으로 작업을 수행합니다. NAT(Network Address Translation)은 IP 패킷의 TCP/UDP 포트 번호와 소스 및 목적지 IP 주소를 재작성하는 방법입니다.

  • N: 새로운 체인을 생성합니다. 예를 들어, N KUBE-SVC-ERIFXISQEP7F7OF4는 ‘KUBE-SVC-ERIFXISQEP7F7OF4’라는 이름의 새 체인을 생성합니다.

  • A: 규칙을 체인의 끝에 추가합니다. 예를 들어, A KUBE-EXT-CVG3OEGEH7H5P3HQ -j KUBE-SVC-CVG3OEGEH7H5P3HQ는 ‘KUBE-EXT-CVG3OEGEH7H5P3HQ’ 체인의 끝에 규칙을 추가하며, 이 규칙은 패킷을 ‘KUBE-SVC-CVG3OEGEH7H5P3HQ’ 체인으로 점프시킵니다.

  • d: 목적지 주소를 지정합니다. 예를 들어, d 10.43.239.29/32는 목적지 IP 주소가 ‘10.43.239.29/32’인 패킷에 규칙을 적용하라는 의미입니다.

  • -s 옵션은 소스 IP 주소를 지정하는 데 사용됩니다. 예를 들어, -s 10.42.0.0/16은 ‘10.42.0.0/16’ 네트워크에서 시작하는 패킷에 규칙이 적용된다는 것을 의미합니다.

  • p: 프로토콜을 지정합니다. p tcp는 TCP 프로토콜을 사용하는 패킷에 규칙을 적용하라는 의미입니다.

  • m comment: comment 모듈을 사용하라는 의미입니다. 이 모듈은 iptables 규칙에 주석을 추가하는 데 사용됩니다.

  • -comment: comment 모듈에 대한 옵션으로, 주석을 지정합니다.

  • -dport: 대상 포트를 지정합니다. 예를 들어, -dport 80는 대상 포트가 80인 패킷에 규칙을 적용하라는 의미입니다.

  • j: 패킷이 규칙에 일치할 경우 수행할 작업을 지정합니다. 예를 들어, j KUBE-SVC-UQMCRMJZLI3FTLDP는 패킷을 ‘KUBE-SVC-UQMCRMJZLI3FTLDP’ 체인으로 점프시키라는 의미입니다.

  • ! 기호는 그 뒤에 오는 조건을 부정하는 데 사용됩니다. 예를 들어, ! -s 10.42.0.0/16은 ‘10.42.0.0/16’ 네트워크에서 시작하지 않는 패킷에 규칙이 적용된다는 것을 의미합니다.

1
2
3
4
5
6
7
8
9
10
11
12
- **`S`**: 현재 설정된 iptables 규칙을 출력합니다.
- **`t nat`**: 'nat' 테이블을 대상으로 작업을 수행합니다. NAT(Network Address Translation)은 IP 패킷의 TCP/UDP 포트 번호와 소스 및 목적지 IP 주소를 재작성하는 방법입니다.
- **`N`**: 새로운 체인을 생성합니다. 예를 들어, **`N KUBE-SVC-ERIFXISQEP7F7OF4`**는 'KUBE-SVC-ERIFXISQEP7F7OF4'라는 이름의 새 체인을 생성합니다.
- **`A`**: 규칙을 체인의 끝에 추가합니다. 예를 들어, **`A KUBE-EXT-CVG3OEGEH7H5P3HQ -j KUBE-SVC-CVG3OEGEH7H5P3HQ`**는 'KUBE-EXT-CVG3OEGEH7H5P3HQ' 체인의 끝에 규칙을 추가하며, 이 규칙은 패킷을 'KUBE-SVC-CVG3OEGEH7H5P3HQ' 체인으로 점프시킵니다.
- **`d`**: 목적지 주소를 지정합니다. 예를 들어, **`d 10.43.239.29/32`**는 목적지 IP 주소가 '10.43.239.29/32'인 패킷에 규칙을 적용하라는 의미입니다.
- **`-s`** 옵션은 소스 IP 주소를 지정하는 데 사용됩니다. 예를 들어, **`-s 10.42.0.0/16`**은 '10.42.0.0/16' 네트워크에서 시작하는 패킷에 규칙이 적용된다는 것을 의미합니다.
- **`p`**: 프로토콜을 지정합니다. **`p tcp`**는 TCP 프로토콜을 사용하는 패킷에 규칙을 적용하라는 의미입니다.
- **`m comment`**: comment 모듈을 사용하라는 의미입니다. 이 모듈은 iptables 규칙에 주석을 추가하는 데 사용됩니다.
- **`-comment`**: comment 모듈에 대한 옵션으로, 주석을 지정합니다.
- **`-dport`**: 대상 포트를 지정합니다. 예를 들어, **`-dport 80`**는 대상 포트가 80인 패킷에 규칙을 적용하라는 의미입니다.
- **`j`**: 패킷이 규칙에 일치할 경우 수행할 작업을 지정합니다. 예를 들어, **`j KUBE-SVC-UQMCRMJZLI3FTLDP`**는 패킷을 'KUBE-SVC-UQMCRMJZLI3FTLDP' 체인으로 점프시키라는 의미입니다.
- **`!`** 기호는 그 뒤에 오는 조건을 부정하는 데 사용됩니다. 예를 들어,  **`! -s 10.42.0.0/16`**은 '10.42.0.0/16' 네트워크에서 시작하지 않는 패킷에 규칙이 적용된다는 것을 의미합니다.

ClusterIP

ClusterIP는 클러스터 안에서 사용되는 고정 IP이다. ClusterIP와 파드를 연결하면, 클러스터 어디서든 해당 IP로 알맞는 파드에 접속할 수 있다.

실습 환경은 다음과 같다. 파드는 디플로이먼트로 배포가 된 4개의 파드이며, 서비스는 4개의 파드 목록을 가지고 있다. (사진으로 하려다 실습을 다시 하는 경우를 고려하여, CodeBlock으로 기록합니다.)

Untitled.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ k get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE     IP           NODE                                             NOMINATED NODE   READINESS GATES
service-test-cd79b78bd-wtc49   1/1     Running   0          4m49s   10.42.0.44   ip-10-32-11-62.ap-northeast-2.compute.internal   <none>           <none>
service-test-cd79b78bd-pq8bz   1/1     Running   0          4m49s   10.42.0.43   ip-10-32-11-62.ap-northeast-2.compute.internal   <none>           <none>
service-test-cd79b78bd-bkr52   1/1     Running   0          4m49s   10.42.1.42   ip-10-32-7-224.ap-northeast-2.compute.internal   <none>           <none>
service-test-cd79b78bd-82rpx   1/1     Running   0          4m49s   10.42.1.41   ip-10-32-7-224.ap-northeast-2.compute.internal   <none>           <none>
$ k get svc
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes     ClusterIP   10.43.0.1      <none>        443/TCP   41h
service-test   ClusterIP   10.43.62.130   <none>        80/TCP    4m51s
$ k get nodes -o wide
NAME                                             STATUS   ROLES                  AGE    VERSION        INTERNAL-IP   EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
ip-10-32-7-224.ap-northeast-2.compute.internal   Ready    <none>                 2d     v1.27.3+k3s1   10.32.7.224   <none>        Amazon Linux 2   5.10.184-175.731.amzn2.x86_64   containerd://1.7.1-k3s1
ip-10-32-11-62.ap-northeast-2.compute.internal   Ready    control-plane,master   2d2h   v1.27.3+k3s1   10.32.11.62   <none>        Amazon Linux 2   5.10.184-175.731.amzn2.x86_64   containerd://1.7.1-k3s1

이제 iptables의 규칙을 확인한다. 아래의 명령어 결과를 보면, 해당 IP로 들어오는 패킷은 모두 KUBE-SVC-GBTMB5ZTD56ECL5F으로 80port로 보내진다.

1
2
3
$ iptables -S -t nat | grep 10.43.62.130
-A KUBE-SERVICES -d 10.43.62.130/32 -p tcp -m comment --comment "default/service-test cluster IP" -m tcp --dport 80 -j KUBE-SVC-GBTMB5ZTD56ECL5F
-A KUBE-SVC-GBTMB5ZTD56ECL5F ! -s 10.42.0.0/16 -d 10.43.62.130/32 -p tcp -m comment --comment "default/service-test cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ

이제 “KUBE-SVC-GBTMB5ZTD56ECL5F”에 대한 정보를 얻으면 다음과 같다. 약 1/4 확률로 4개의 경로로 나눠진다.

1
2
3
4
5
6
7
8
$ iptables -S -t nat | grep KUBE-SVC-GBTMB5ZTD56ECL5F
-N KUBE-SVC-GBTMB5ZTD56ECL5F
-A KUBE-SERVICES -d 10.43.62.130/32 -p tcp -m comment --comment "default/service-test cluster IP" -m tcp --dport 80 -j KUBE-SVC-GBTMB5ZTD56ECL5F
-A KUBE-SVC-GBTMB5ZTD56ECL5F ! -s 10.42.0.0/16 -d 10.43.62.130/32 -p tcp -m comment --comment "default/service-test cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.0.43:8080" -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-CLPYGNZNNY3PRNQC
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.0.44:8080" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-54OS7XC6LLUCYZJF
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.1.41:8080" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SU625FHCSXBPEYVL
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.1.42:8080" -j KUBE-SEP-KYPXHCLNNXAPFHUW

그 중 하나에 접근하면 다음과 같은 정보를 얻을 수 있다. DNAT --to-destination 10.42.1.42:8080 트래픽을 파드로 전달하는 규칙이다. service-test-cd79b78bd-bkr52 이름의 파드로 전달한다.

1
2
3
4
5
iptables -S -t nat | grep KUBE-SEP-KYPXHCLNNXAPFHUW
-N KUBE-SEP-KYPXHCLNNXAPFHUW
-A KUBE-SEP-KYPXHCLNNXAPFHUW -s 10.42.1.42/32 -m comment --comment "default/service-test" -j KUBE-MARK-MASQ
-A KUBE-SEP-KYPXHCLNNXAPFHUW -p tcp -m comment --comment "default/service-test" -m tcp -j DNAT --to-destination 10.42.1.42:8080
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.1.42:8080" -j KUBE-SEP-KYPXHCLNNXAPFHUW

나머지 체인에 대해서 검색해보면 위와 같이 현재 동작중인 각 파드로 전달하는 내용을 확인할 수 있다. medium에 있는 포스팅에서 보기 좋게 표현한 그림도 확인할 수 있다.

NodePort

NodePort는 클러스터에 접근한 트래픽 중, 설정한 “포트”에 맞는 트래픽을 모두 서비스의 ClusterIP로 넘기는 방식이다. NodePort 방식 또한 똑같이 Netfilter를 이용하며, 위의 ClusterIP를 이용한다. 노드 단위에서 특정 포트로 들어오는 트래픽을 받아 ClusterIP로 넘겨준다. ClusterIP로 넘겨지면 알맞은 파드로 전달된다.

포트의 범위는 기본적으로 30000-32767 사이로 할당할 수 있다.

“externalTrafficPolicy” 옵션을 Local로 설정하면 해당 노드에 배치된 파드로만 접속된다고 한다.
이러면 SNAT이 필요하지 않아, 클라이언트 IP가 보존된다.

이제 실습을 진행해 보며, 내용을 확인해본다.

  1. 파일을 생성

    1
    
     vi nodeport.yml
    
  2. 아래의 내용을 복사 붙여 넣은 후 저장

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
     kind: Deployment
     apiVersion: apps/v1
     metadata:
       name: deployment-test-nodeport
     spec:
       replicas: 4
       selector:
         matchLabels:
           app: service_test_pod_nodeport
       template:
         metadata:
           labels:
             app: service_test_pod_nodeport
         spec:
           containers:
           - name: simple-http
             image: python:2.7
             imagePullPolicy: IfNotPresent
             command: ["/bin/bash"]
             args: ["-c", "echo 'Pod IP:' $POD_IP > index.html; python -m SimpleHTTPServer 8080"]
             env:
             - name: POD_IP
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
             ports:
             - name: http
               containerPort: 8080
     ---
     apiVersion: v1
     kind: Service
     metadata:
       name: service-test-nodeport
     spec:
       type: NodePort
       selector:
         app: service_test_pod_nodeport
       ports:
         - protocol: TCP
           port: 80
           targetPort: http
           nodePort: 30080
    
  3. 아래의 명령어를 실행하여, 리소스를 배포

    1
    2
    3
    
     $ kubectl apply -f nodeport.yml
     deployment.apps/deployment-test-nodeport created
     service/service-test-nodeport created
    
  4. 정상적으로 배포되었는지 확인한다. 앞으로 서비스의 IP주소는 자주 사용하니 복사하면 편하다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     $ kubectl get pod,svc
     NAME                                           READY   STATUS    RESTARTS   AGE
     pod/deployment-test-nodeport-8b69cf8bd-djx5c   1/1     Running   0          3m39s
     pod/deployment-test-nodeport-8b69cf8bd-qhvjt   1/1     Running   0          3m39s
     pod/deployment-test-nodeport-8b69cf8bd-4g4rj   1/1     Running   0          3m39s
     pod/deployment-test-nodeport-8b69cf8bd-jqxj6   1/1     Running   0          3m39s
    	
     NAME                            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
     service/kubernetes              ClusterIP   10.43.0.1       <none>        443/TCP        9d
     service/service-test-nodeport   NodePort    10.43.250.102   <none>        80:30080/TCP   3m39s
    

이제, 실제 트래픽이 해당 노드로 접속했을 때 어떤 식으로 변경하는지 알아본다. iptables에서 서비스의 노드포트를 검색하면 아래의 체인을 확인할 수 있다. 내용을 간단히 해석하면, “KUBE-NODEPORTS 체인의 규칙 중 하나는 트래픽이 30080 포트로 통신한다면 KUBE-EXT-5N7WIXIG6FFV5MSL 체인의 작업을 수행하라” 이다.

1
2
$ iptables -S -t nat | grep 30080
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/service-test-nodeport" -m tcp --dport 30080 -j KUBE-EXT-5N7WIXIG6FFV5MSL

이제, KUBE-EXT-5N7WIXIG6FFV5MSL 체인의 규칙을 확인해보면 아래와 같다.

1
2
3
4
5
$ iptables -S -t nat | grep KUBE-EXT-5N7WIXIG6FFV5MSL
-N KUBE-EXT-5N7WIXIG6FFV5MSL
-A KUBE-EXT-5N7WIXIG6FFV5MSL -m comment --comment "masquerade traffic for default/service-test-nodeport external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-5N7WIXIG6FFV5MSL -j KUBE-SVC-5N7WIXIG6FFV5MSL
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/service-test-nodeport" -m tcp --dport 30080 -j KUBE-EXT-5N7WIXIG6FFV5MSL

세번째로 출력된 -A KUBE-EXT-5N7WIXIG6FFV5MSL -j KUBE-SVC-5N7WIXIG6FFV5MSL 내용을 파악하면, KUBE-SVC-5N7WIXIG6FFV5MSL 로 다시 트래픽을 전달하며 KUBE-SVC-5N7WIXIG6FFV5MSL 이 체인의 이름은 위에서 ClusterIP에 대한 이름 규칙과 매우 유사하다.

한번 체인을 확인해보자

1
2
3
4
5
6
7
8
9
$ iptables -S -t nat | grep KUBE-SVC-5N7WIXIG6FFV5MSL
-N KUBE-SVC-5N7WIXIG6FFV5MSL
-A KUBE-EXT-5N7WIXIG6FFV5MSL -j KUBE-SVC-5N7WIXIG6FFV5MSL
-A KUBE-SERVICES -d 10.43.250.102/32 -p tcp -m comment --comment "default/service-test-nodeport cluster IP" -m tcp --dport 80 -j KUBE-SVC-5N7WIXIG6FFV5MSL
-A KUBE-SVC-5N7WIXIG6FFV5MSL ! -s 10.42.0.0/16 -d 10.43.250.102/32 -p tcp -m comment --comment "default/service-test-nodeport cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-5N7WIXIG6FFV5MSL -m comment --comment "default/service-test-nodeport -> 10.42.0.69:8080" -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-DNUSDASCNPIACNTF
-A KUBE-SVC-5N7WIXIG6FFV5MSL -m comment --comment "default/service-test-nodeport -> 10.42.0.70:8080" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-SLL6CN4RH3ALGW6H
-A KUBE-SVC-5N7WIXIG6FFV5MSL -m comment --comment "default/service-test-nodeport -> 10.42.1.65:8080" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-GGAEDKHILSR44UAR
-A KUBE-SVC-5N7WIXIG6FFV5MSL -m comment --comment "default/service-test-nodeport -> 10.42.1.66:8080" -j KUBE-SEP-ST3ZKQ7TPT3XY23S

하단의 4줄을 확인해 보면 위의 ClusterIP에서 실습했던 것과 같이, 트래픽을 각 파드로 전달하는 내용이 담겨 있다.

정리해 보면, iptables 규칙에 의해 각 노드로 들어오는 트래픽 중 30080포트를 이용하면 NodePort 체인의 규칙이 적용되며, NodePort 체인은 ClusterIP 체인으로 전달하며 결국 파드에게 트래픽이 전송된다.

Load Balancer

NodePort로 서비스를 외부로 노출하면 클러스터에 존재하는 모든 노드에서 특정 포트(NodePort)를 열어둔다. 하나의 노드의 주소만 노출하면, 노드에 장애가 생겼을 때 문제가 발생한다. 그렇기에 노드 앞단에 로드밸런서를 두어 Health 상태인 노드로만 트래픽을 전달해야 한다. 이를 위해서 쿠버네티스에서는 LoadBalancer Type을 지원한다. (NodePort 타입은 Load Balancer 타입을 위한 요소같다는 생각이 든다.)

로드밸런서 타입은 쿠버네티스 자체로 구현할 수 없고, 외부 로드밸런서와 연동되어 작동한다. 로드밸런스 타입은 별도의 외부 IP가 존재한다. 클라이언트가 외부 IP를 통해 접근하면, 적절한 규칙으로 노드를 배정해준다. (로드밸런서이니, 노드는 모두 공평한 부하를 받는다.) 그러면 NodePort에서 진행했던 과정이 일어난다.(로드밸런서 타입은 자동으로 노드 포트를 하나 점유한다.)

[vs NodePort]

NodePort와는 다르게 Load Balancer 타입은 Port 할당을 비활성화 할 수 있다. 노드 포트가 아닌 트래픽을 파드로 직접 라우팅하는(ex. AWS EKS) 로드밸런서의 경우에만 가능하다.이때는 spec.allocateLoadBalancerNodePorts = false로 설정하면 된다.

또한, 외부 로드밸런서를 통해 TLS, Draining 등이 가능하다.

요약

실습했던 내용을 다시 한번 요약한다.

서비스의 Cluster IP에 대해서는 다음과 같은 방식으로 동작한다.

서비스의 동작은 ‘kube-proxy’가 담당하며, 별다른 리소스를 소모하지 않고, iptables(netfilter)를 이용하여 커널영역에서 특정 패킷이 오면 정해둔 행동을 취한다. 여기서는 주로, Network Address Translation “NAT” 기능을 수행한다. 이를 통해, 실질적인 인터페이스 없이 reverse-proxy 역할을 서비스가 할 수 있게 된다. 그렇다면, kube-proxy는 어떤 역할을 수행하는가? kube-proxy는 API 서버로 파드의 Health check, 혹은 파드의 부하상태를 통해 엔드포인트를 관리한다. 또, 현재의 엔드포인트에 맞게 Netfilter의 규칙을 수정한다. 즉, kube-proxy는 로드밸런서에서의 manager server의 역할을 담당한다.

Q. 서비스를 생성하고, 이후 Selector 조건에 맞는 파드를 생성하면, 해당 파드도 엔드포인트에 추가되는가?

Yes, 쿠버네티스는 지속적으로 클러스터의 상태를 모니터링하고, 반응한다. kube-proxy가 이를 감지하고 추가한다.

Q. 서비스가 파드 목록 중 파드를 선택하는 기준은? 공평하게 선택하려고 노력한다.

아래와 같이 확률적으로 공평함을 유지하려 한다. “random –probability” 부분을 주목하면 알 수 있다. 다만, 이는 완벽하게 공평한 확률은 아니다. (0.333 와 같은 무한소수는 표현못함)

1
2
3
4
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.0.43:8080" -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-CLPYGNZNNY3PRNQC
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.0.44:8080" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-54OS7XC6LLUCYZJF
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.1.41:8080" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SU625FHCSXBPEYVL
-A KUBE-SVC-GBTMB5ZTD56ECL5F -m comment --comment "default/service-test -> 10.42.1.42:8080" -j KUBE-SEP-KYPXHCLNNXAPFHUW

서비스는 자체적으로 파드의 리스트들에 의해 로드밸런싱이 이뤄진다. 아래는 관련 실험 결과

1
2
3
4
5
$ for i in {1..100}; do curl 10.43.232.207; done | tee results.txt
$ echo "Pod1 responses: $(grep -c '<p>Hello from pod1</p>' results.txt)"
Pod1 responses: 52
[root@ip-10-32-11-62 kubernetes]# echo "Pod2 responses: $(grep -c '<p>Hello from pod2</p>' results.txt)"
Pod2 responses: 48

외부와의 통신 방식

ClusterIP는 클러스터 내부에서만 통신할 수 있다. 외부로 노출하려면, NodePort 혹은 Load Blancer 타입의 서비스를 사용한다.

  • NodePort

NodePort는 노드로 들어오는 트래픽 중 특정 포트를 열어 외부의 접근을 허용한다. ClusterIP 상위 Layer라고 생각하면 된다. 특정 포트로 들어온 트래픽은 ClusterIP와 같이 작동한다. 즉, ClusterIP로 넘겨지면 위에서 설명한 서비스의 방식대로 알맞은 파드로 전달된다.

  • Load Balancer

외부에는 하나의 엔드포인트를 주로 제공하니 NodePort 방식으로 트래픽을 받으면 1개의 노드로 모든 트래픽을 받게된다. 노드로 트래픽이 들어와서 ClusterIP Type과 같이 다시 로드밸런싱이 되어 여러 엔드포인트로 분산되겠지만, 하나의 노드에 모든 트래픽이 충분히 부담된다. 그렇기에 LoadBlancer 타입은 부하분산 자체를 노드 scope에서 진행한다. 외부 로드밸런서와 연결되어, 트래픽이 여러 노드로 분산된다. 여기에 externalTrafficPolicy=local로 설정한다면 네트워크 부하를 줄일 수 있다.

참고자료

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

https://www.eksworkshop.com/docs/networking/

https://coffeewhale.com/packet-network3

https://gasidaseo.notion.site/K8S-Service-1-f095388c48a84841b09a13b582f374c8

https://coffeewhale.com/k8s/network/2019/04/19/k8s-network-01/

https://coffeewhale.com/k8s/network/2019/05/11/k8s-network-02/

https://medium.com/finda-tech/kubernetes-네트워크-정리-fccd4fd0ae6

https://en.m.wikipedia.org/wiki/File:Netfilter-packet-flow.svg

This post is licensed under CC BY 4.0 by the author.