퍼블릭 클라우드를 사용하면, 실제 물리 서버를 어떤 Region 과 Zone(Availability Zone) 의 것을 사용할지 결정하게 된다. Region 은 Zone 을 묶는 상위 개념이며, Region 과 Region 사이에는 도시나 국가 만큼의 큰 거리 차이가 존재한다. Zone 은 하나의 Region 안에 여러개 존재할 수 있으며, 흔히 생각하는 데이터센터라고 생각하면 좋다.

  • Region: ap-northeast-2 (서울)
    • Zone: ap-northeast-2a (데이터센터 a)
    • Zone: ap-northeast-2b (데이터센터 b)
    • Zone: ap-northeast-2c (데이터센터 c)

아래는 Region 과 Zone 의 차이를 비교한 표이다.

규모 도시/국가 단위 데이터센터 단위
물리적 거리 수십~수백 km 수백 m ~ 수 km
네트워크 레이턴시 높음 (10~200ms) 매우 낮음 (1~2ms)
장애 범위 전체 Region 장애 가능 Zone 단위 장애
비용 Region 간 트래픽 비쌈 Zone 간 트래픽 저렴 또는 무료
목적 DR(재해복구), 글로벌 배포 고가용성, 고성능 서비스

 

레이턴시를 고려하였을 때, 최대한 같은 zone 에 존재하는 node 의 pod 로 요청을 보내는 것이 유리하다. 이러한 설정을 하는 것을 Topology Aware Hint 라고 한다.

 

1. Topology Aware Hint 의 기능

같은 zone/region 으로 요청을 보내도록 우선순위를 설정할 수 있다. 기본 설정은 같은 zone 우선이다. 같은 node 로 우선순위를 설정할 수는 없다. 같은 node 단위에서 라우팅 하도록 하려면 클러스터 외부 트래픽에 대해 노드에서의 로드 밸런싱 제외 을 참고하자.(단 외부 요청에 한함)

 

2. 설정방법

Topology Aware Hint 는 Service 에 직접 설정하는 것이 아니라, EndpointSlice 컨트롤러가 node 의 zone/region 라벨을 보고 EndpointSlice에 hint를 자동으로 생성해준다.(hints.forZones) 각 노드에 존재하는 kube-proxy 는이 힌트를 읽고, client 노드와 같은 zone 의 Endpoints 를 우선으로 라우팅해준다.

실제로 사람이 설정해주어야 하는 것은 node 별로 label 을 붙여주는 것이다. 즉, 라벨만 붙여주면 클라이언트 node 와 동일한 zone/region 의 파드가 우선적으로 선택되게 된다. 참고로, 퍼블릭 클라우드를 사용하면 기본적으로 zone/region 라벨이 붙게 된다.

  • topology.kubernetes.io/region
  • topology.kubernetes.io/zone
$ kubectl label nodes node1 topology.kubernetes.io/region=ap-northeast-2
$ kubectl label nodes node1 topology.kubernetes.io/zone=ap-northeast-2-a

topology.kubernetes.io/region: ap-northeast-2
topology.kubernetes.io/zone: ap-northeast-2-a

 

3. 확인방법

아래의 커맨드로 EndpointSlice 를 조회해보면 fotZones 힌트가 생성된 것을 볼 수 있다.

$ kubectl get endpointslice -l kubernetes.io/service-name={ClusterIP 서비스명} -o yaml
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: sample-svc-abc12
addressType: IPv4
endpoints:
  - addresses:
      - 10.244.1.15
    zone: zone-a
    hints:
      forZones:
        - name: zone-a
  - addresses:
      - 10.244.2.19
    zone: zone-b
    hints:
      forZones:
        - name: zone-b
ports:
  - port: 80

 

https://kubernetes.io/ko/docs/concepts/services-networking/topology-aware-hints/ 참고

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

NodePort 나 LoadBalancer 서비스에서는 외부 트래픽이 노드로 도착하면, 노드에서 쿠버네티스 클러스터 내의 적절한 파드로 로드밸런싱을 해준다. 즉, 트래픽을 받은 노드에 속한 파드로 전송되는 것이 보장되지 않는 것이다. 만약 LoadBalancer 서비스에서 외부 로드밸러서가 1차적으로 각 노드로 로드 밸런싱 중이라면, 노드에서 불필요한 2차 로드밸런싱이 이루어지는 형태가 된다.(NAT 가 발생해 발신측 IP 주소가 유실되는 문제도 있다.)

일반적인 파드의 경우는 각 노드에 1개 이상이 존재하는 것을 보장하지는 않기 때문에, 이러한 노드에서 파드로의 로드 밸런싱이 필요하다. 하지만, 데몬셋 같이 하나의 노드에 하나의 파드가 보장되는 경우에는 굳이 다른 노드의 파드로 로드 밸런싱 할 필요가 없어진다. 그런 경우에는 spec.externalTrafficPolicy 항목을 사용하여 외부 트래픽을 노드 내에서만 처리할지를 설정할 수 있다.

 

1. 노드 내에서만 로드 밸런싱 하도록 설정

아래의 매니페스트와 같이 spec.externalTrafficPolicy: Local 으로 생성한다. 외부로부터의 트래픽을 어떻게 로드밸런싱 할지에 대한 정책이기 때문에 당연히 NodePort, LoadBalancer 서비스에서 사용 가능하다.(ClusterIP 는 내부 트래픽이기 때문에 설정 불가능)

apiVersion: v1
kind: Service
metadata:
  name: nodeport-local
spec:
  type: NodePort
  externalTrafficPolicy: Local
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30081
  selector:
    app: sample-app
spec.externalTrafficPolicy 설정값 설명
Cluster 노드에 도착한 트래픽을 클러스터 내에 존재하는 다른 노드에 속한 파드까지 대상으로 하여 다시 로드밸런싱한다.(기본값)
Local 노드에 도착한 트래픽을 해당 노드 내의 파드 대상으로만 로드밸런싱한다.

 

이제 아래 커맨드로 파드가 1개 존재하는 desktop-worker 노드를 호출하면, 여러번 호출해도 동일한 파드로 요청이 가는 것을 볼 수 있다. client IP 주소도 유지된다.

# 서비스 IP, Port 확인
$ kubectl get svc nodeport-local
NAME                    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
sample-nodeport-local   NodePort   10.96.25.136   <none>        8080:30081/TCP   76m

# 파드 확인
$ kubectl get pods -o custom-columns="NAME:{metadata.name},Node:{spec.nodeName},NodeIP:{status.hostIP}"
NAME                                 Node              NodeIP
sample-deployment-75c768d5fb-2lkwp   desktop-worker2   172.18.0.3
sample-deployment-75c768d5fb-9qdjm   desktop-worker    172.18.0.4
sample-deployment-75c768d5fb-g5vkv   desktop-worker2   172.18.0.3

# 아래 커맨드를 여러번 실행해본다.
$ curl -s http://172.18.0.4:30081

 

2. LoadBalancer 에서 노드 health check 하기

NodePort 서비스에서 spec.externalTrafficPolicy: Local 로 지정한 경우, 노드 내에 대상 파드가 하나도 없는 경우에는 TCP 수준의 Connection refused 에러가 발생한다. 목적지 포트를 Listen 하는 프로세스(파드)가 아예 없기 때문이다. 반면에 LoadBalancer 서비스에서는 spec.externalTrafficPolicy: Local, spec.healthCheckNodePort 를 지정하여(미지정시 랜덤 포트가 자동 지정됨) 외부 로드 밸런서가 해당 노드가 타겟 파드를 가지고 있는지 확인할 수 있게 한다.(헬스 체크) 해당 노드에 파드가 존재하지 않으면 http status 503 이 반환되고, 로드 밸런싱 대상에서 제외된다.

아래와 같은 매니페스트로 LoadBalancer 에 헬스 체크용 노드포트를 지정한다.(30086)

apiVersion: v1
kind: Service
metadata:
  name: lb-local
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  healthCheckNodePort: 30086
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30085
  selector:
    app: sample-app

 

spec.healthCheckNodePort 는 LoadBalancer 서비스 이면서 spec.externalTrafficPolicy: Local 인 경우만 지정할 수 있는 항목이다. 아래는 헬스 체크 엔드포인트가 응답하는 예시이다.

$ curl -s http://172.18.0.4:30086
{
  "service": {
    "namespace": "default",
    "name": "lb-local"
  },
  "localEndpoints": 1
}

 

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,

세션 어피니티란, 한번 연결된 타겟 파드로 다음번에도 연결되도록 하는 것이다. 즉, ClusterIP 서비스에 세션 어피니티를 적용한 경우(즉, 내부 네트워크에 적용), 파드끼리 통신할 때 한번 연결된 파드에 다음번 통신때도 연결된다.

 

1. 세션 어피니티 설정

아래와 같이 spec.sessionAffinity 항목과 spec.sessionAffinityConfig 항목을 설정한다.

spec.sessionAffinity: ClientIP 는 요청을 보낸 측을 식별할 때 client IP 주소로 식별하겠다는 것이고, spec.sessionAffinityConfig 의 설정은 세션 고정 시간을 의미한다.

apiVersion: v1
kind: Service
metadata:
  name: session-affinity
spec:
  type: LoadBalancer
  selector:
    app: sample-app
  ports:
  - name: http-port
    protocol: TCP
    port: 8080
    targetPort: 80
    nodePort: 30084
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10

 

sessionAffinityConfig.clientIP.timeoutSeconds: 10 을 설정했으므로, 요청 후 10초 이내의 요청은 같은 파드로 전송된다.

$ kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
sample-deployment-75c768d5fb-2lkwp   1/1     Running   0          33d
sample-deployment-75c768d5fb-9qdjm   1/1     Running   0          33d
sample-deployment-75c768d5fb-g5vkv   1/1     Running   0          33d

# 첫 요청
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://session-affinity.default.svc.cluster.local:8080
Host=session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-g5vkv  ClientIP=10.244.2.118  XFF=

# 첫 요청으로부터 10초 이내에 요청
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://session-affinity.default.svc.cluster.local:8080
Host=session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-g5vkv  ClientIP=10.244.2.118  XFF=

# 두번째 요청으로부터 10초 이내에 요청
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://session-affinity.default.svc.cluster.local:8080
Host=session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-g5vkv  ClientIP=10.244.2.118  XFF=

# 세번째 요청으로부터 10초 초과한 후 요청
# 다른 파드로 연결됨
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://session-affinity.default.svc.cluster.local:8080
Host=session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-9qdjm  ClientIP=10.244.2.118  XFF=

 

spec.sessionAffinity: None 으로 수정한 후 동일한 테스트를 진행해보면, 10초 이내에 재요청 하더라도 다른 파드에 전송되는 것을 볼 수 있다.

# 첫번째 요청
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://no-session-affinity.default.svc.cluster.local:8080
Host=no-session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-g5vkv  ClientIP=10.244.2.118  XFF=

# 첫번째 요청 후 10초 이내 두번째 요청 => 다른 파드로 전송됨
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://no-session-affinity.default.svc.cluster.local:8080
Host=no-session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-9qdjm  ClientIP=10.244.2.118  XFF=

# 두번째 요청 후 10초 이내 세번째 요청 => 다른 파드로 전송됨
$ kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://no-session-affinity.default.svc.cluster.local:8080
Host=no-session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-g5vkv  ClientIP=10.244.2.118  XFF=

# 세번째 요청 후 10초 이내 네번째 요청 => 다른 파드로 전송됨
kubectl exec -it sample-deployment-75c768d5fb-2lkwp -- curl http://no-session-affinity.default.svc.cluster.local:8080
Host=no-session-affinity.default.svc.cluster.local  Path=/  From=sample-deployment-75c768d5fb-2lkwp  ClientIP=10.244.2.1  XFF=

 

위의 예제에서는 LoadBalancer 서비스를 생성할 때 생성된 ClusterIP 를 통해 1개의 노드 내에서 세션 어피니티가 적용되는 모습을 보았다. 그런데, 세션 어피니티는 실제로는 각 쿠버네티스 노드에 iptables 로 구현되어 있다. 따라서 NodePort 서비스에 세션 어피니티를 지정하면 동일한 client 의 재요청이라 하더라도 어느 노드에서 수신하느냐에 따라 다른 파드로 연결될 수 있음에 유의하자.(즉, 세션 어피니티는 노드 단위에서 작동한다.)

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,