[Retry, k8s] 15. 서비스 API - 세션 어피니티
카테고리: KUBERNETES
태그: kubernetes
[Retry, k8s] 15. 서비스 API - 세션 어피니티
🔔 예제에서 공통적으로 사용할 Deployment, Pod
cat <<EOF > sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
EOF
cat <<EOF > sample-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: sample-pod
spec:
containers:
- name: tools-container
image: amsy810/tools:v2.0
EOF
🔔 세션 어피니티
쿠버네티스에서 서비스를 사용할 때, 클라이언트가 요청을 보낼 때마다 요청을 처리하는 파드를 지정하는 기능
- 이 기능은 서비스의 spec.sessionAffinity와 spec.sessionAffinityConfig를 사용하여 설정할 수 있다.

-
세션 어피니티의 기능은 각 쿠버네티스 노드에 iptables로 구현되어 있으며, 최대 세션 고정 시간(sessionAffinityConfig.clientIP.timeoutSeconds)을 설정할 수 있다.
# 마지막 요청 후 10초 이내의 요청은 같은 파드로 전송한다. cat <<EOF > sample-session-affinity.yaml apiVersion: v1 kind: Service metadata: name: sample-session-affinity spec: type: LoadBalancer selector: app: sample-app ports: - name: http-port portocol: TCP port: 8080 targetPort: 80 nodePort: 30084 sessionAffinity: ClusterIP sessionAffinityConfig: clientIP: timeoutSeconds: 10 EOF# 첫 번째 요청 전송 $ kubectl exec -it sample-pod -- curl http://sample-session-affinity.default.svc.cluster.local:8080 Host=sample-session-affinity.default.svc.cluster.local Path=/ From=sample-deployment-7f47966499-459f6 ClientIP=10.40.0.0 XFF= # 마지막 요청 후 10초 이내에 요청을 전송 $ kubectl exec -it sample-pod -- curl http://sample-session-affinity.default.svc.cluster.local:8080 Host=sample-session-affinity.default.svc.cluster.local Path=/ From=sample-deployment-7f47966499-459f6 ClientIP=10.40.0.0 XFF= # 마지막 요청 후 10초 이상 간격을 두고 요청 전송 $ kubectl exec -it sample-pod -- curl http://sample-session-affinity.default.svc.cluster.local:8080 Host=sample-session-affinity.default.svc.cluster.local Path=/ From=sample-deployment-7f47966499-7pz8b ClientIP=10.40.0.0 XFF= -
세션 어피니티를 비활성화하고 같은 요청을 전송해보면, 10초 이내에 보낸 요청이 다른 파드에 전송되는 것을 확인할 수 있다.
# 세션 어피니티를 비활성화 $ kubectl patch service sample-session-affinity -p '{"spec": {"sessionAffinity": "None"}}' service/sample-session-affinity patched # 첫 번째 요청 전송 $ kubectl exec -it sample-pod -- curl http://sample-session-affinity.default.svc.cluster.local:8080 Host=sample-session-affinity.default.svc.cluster.local Path=/ From=sample-deployment-7f47966499-jc25k ClientIP=10.46.0.0 XFF= # 마지막 요청 후 10초 이내에 요청을 전송 $ kubectl exec -it sample-pod -- curl http://sample-session-affinity.default.svc.cluster.local:8080 Host=sample-session-affinity.default.svc.cluster.local Path=/ From=sample-deployment-7f47966499-459f6 ClientIP=10.46.0.0 XFF=
NodePort/LoadBalancer에서도 세션 어피니티를 활성화 할 수 있다.
-
그러나 어느 쿠버네티스 노드에 전송하는지에 따라 같은 클라이언트 IP라도 같은 파드에 전송된다고 단정할 수 없으므로 NodePort/LoadBalancer에서는 세션 어피니티 기능을 사용하는 경우가 제한적이다.
-
세션 어피니티를 사용하면 클라이언트가 동일한 Pod로 연결되도록 보장할 수 있지만, 이는 로드 밸런서의 부하 분산 능력을 제한할 수 있다.

🔔 노드 간 통신 제외와 발신 측 IP 주소 유지
NodePort/LoadBalancer 서비스에서 쿠버네티스 노드에 도착한 요청은 노드를 통해 파드에도 로드밸런싱하게 되어 있어 불필요한 2단계 로드 밸런싱이 이루어진다.
📜 LoadBalancer/NodePort 서비스에 2단계 로드 밸런싱


이 2단계 로드 밸런싱은 균일하게 요청을 분산하기 쉽지만, 불필요한 레이턴시 오버헤드가 발생하거나 밸런싱을 수행할 때 NAT가 이루어져 발신 측 IP 주소가 유실되는 특징도 있다.
-
반면 데몬셋은 하나의 노드에 하나의 파드가 배치되기 때문에 굳이 다른 노드의 파드에 전송하지 않고 다른 노드에만 통신하고 싶은 경우가 있는데, 이때 spec.externalTrafficPolicy를 사용할 수 있다.
설정 개요 Cluster 노드 트래픽이 도착한 후 다른 노드에 있는 파드를 포함하여 다시 로드 밸런싱함으로써 파드 부하를 균등하게 분산한다. Local 노드 트래픽이 도착한 후 노드 간 로드 밸런싱을 하지 않는다.
(1) NodePort의 spec.externalTrafficPolicy:Cluster 동작
externalTrafficPolicy: Cluster인 경우
-
1) 외부로부터 요청이 들어오면 NodePort 서비스에 의해 하나의 노드 선택
-
2) 해당 노드에서 다시 각 노드에 있는 파드들 중 요청을 전달할 파드를 선택
-
3) 파드 네트워크를 통해 요청을 해당 파드를 전달
(2) NodePort의 spec.externalTrafficPolicy:Local 동작
externalTrafficPolicy: Local인 경우
-
1) 외부로부터 요청이 들어오면 NodePort 서비스에 의해 하나의 노드 선택
-
2) 해당 노드에 존재하는 파드에게만 요청을 전달함 (해당 노드에 파드가 여러 개면 균등하게 전달함)
(3) NodePort의 spec.externalTrafficPolicy:Local 매니페스트
apiVersion: v1
kind: Service
metadata:
name: sample-nodeport-local
spec:
type: NodePort
externalTrafficPolicy: Local
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 30081
selector:
app: sample-app
(4) LoadBalancer의 spec.externalTrafficPolicy:Local 매니페스트
LoadBalancer 서비스의 경우에는 별도로 헬스 체크용 NodePort가 할당되기 때문에 파드가 존재하지 않는 노드에는 로드 밸런서에서 요청이 전송되지 않는다.
-
헬스 체크용 NodePort의 포트 번호는 spec.healthCheckNodePort에서 설정할 수 있고, 지정하지 않으면 NodePort 서비스처럼 임의의 포트 번호가 할당된다.
-
healthCheckNodePort는 LoadBalancer 서비스와 externalTrafficPolicy가 Local인 경우에만 설정할 수 있는 항목이다.
(5) LoadBalancer의 spec.externalTrafficPolicy:Local 매니페스트
apiVersion: v1
kind: Service
metadata:
name: sample-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
$ kubectl get pod,service -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/sample-deployment-7f47966499-6p9f9 1/1 Running 0 12m 10.40.0.1 k8s-node03 <none> <none>
pod/sample-deployment-7f47966499-cs9f8 1/1 Running 0 12m 10.38.0.1 k8s-node01 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 49m <none>
service/sample-lb-local LoadBalancer 10.108.194.132 192.168.219.190 8080:30085/TCP 7m19s app=sample-app
# 노드 IP 주소와 헬스 체크용 NodePort에 접속
$ curl -s 192.168.219.101:30086
{
"service": {
"namespace": "default",
"name": "sample-lb-local"
},
"localEndpoints": 1
}
# 파드가 존재하지 않는(localEndpoints=0) 노드
$ curl -s 192.168.219.102:30086
{
"service": {
"namespace": "default",
"name": "sample-lb-local"
},
"localEndpoints": 0
}
# 파드가 존재하지 않는(localEndpoints=0) 노드의 경우는 503 HTTP Status가 반환됨
$ curl -sI 192.168.219.102:30086
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
X-Content-Type-Options: nosniff
Date: Sat, 01 Apr 2023 12:29:58 GMT
Content-Length: 96
댓글 남기기