들어가며
쿠버네티스의 Service 리소스는 여러 워커 노드에 흩어져있는 Pod와 통신할 수 있도록 (Pod가 ephemeral resource라는 이유도 있음) 안정적인 endpoint를 제공하는 역할을 합니다.
위 내용을 기본적으로 알고 있다는 가정 하에, 2개의 Pod가 서로 많은 통신을 하며 아주 긴밀히 연결되어 동작하는 경우를 생각해보겠습니다.
서로 각자의 Pod를 연결해주는 Service를 통해 아무런 문제 없이 통신할 수 있으며, Service를 통해 여러 워커 노드에 흩어진 Pod에 고르게 트래픽이 전달될 것입니다.
EKS 같은 쿠버네티스 환경을 사용하는 경우 서로 다른 워커 노드 간에 트래픽을 전달하는 것 역시 비용을 발생시키므로, 가능하다면 동일한 워커 노드에 있는 Pod에 트래픽이 전달되도록 한다면 비용도 절감할 수 있고 성능도 개선할 수 있을 것입니다.
Service Internal Traffic Policy 는 v1.26 버전에서 stable로 들어온 feature로, 이 설정을 통해 어떤 Pod에서 시작된 트래픽이 동일한 노드 내의 엔드포인트로만 트래픽을 라우팅하도록 할 수 있습니다.
관련 공식 문서는 다음을 참고해주세요. (https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/)
내부 트래픽? 외부 트래픽?
사실 Service 리소스에는 비슷한 기능을 하지만 조금 다른 유사한 External Traffic Policy 가 있습니다.
둘의 차이점은 내부 트래픽에 적용되는지, 외부 트래픽에 적용되는지에 따라 다릅니다.
쿠버네티스에서 말하는 내부 트래픽과 외부 트래픽의 차이는 다음과 같습니다.
•
내부 트래픽
◦
쿠버네티스 클러스터 내부에서 발생하는 트래픽
◦
Service 간 통신 또는 Pod 간 통신
◦
쿠버네티스 클러스터 내의 리소스 간에 발생하는 통신이기에 일반적으로 latency가 낮음
•
외부 트래픽
◦
쿠버네티스 클러스터 외부에서 발생하는 트래픽
◦
클러스터 외부에서 Service에 접근하는 경우 등 (NodePort, LoadBalancer 타입)
◦
클러스터 외부와의 통신이기에 latency가 상대적으로 높음
서비스 내부 트래픽 정책 사용
동일한 노드에서만 트래픽이 전달되도록 하는 것은 아주 간단하게 설정 가능합니다.
Service 리소스의 .spec.internalTrafficPolicy 를 Local 로 설정하면 내부 전용 트래픽 정책을 활성화 할 수 있습니다.
이 설정을 통해 kube-proxy 는 클러스터 내부 트래픽을 노드 내부 엔드포인트로만 사용하도록 합니다.
API Spec을 살펴보면 다음과 같습니다. (https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#service-v1-core)
외부 트래픽에 대한 동일한 설정인 .spec.externalTrafficPolicy 도 함께 확인할 수 있습니다.
Field | Description |
internalTrafficPolicy string | InternalTrafficPolicy describes how nodes distribute service traffic they receive on the ClusterIP. If set to "Local", the proxy will assume that pods only want to talk to endpoints of the service on the same node as the pod, dropping the traffic if there are no local endpoints. The default value, "Cluster", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features). |
externalTrafficPolicy string | externalTrafficPolicy describes how nodes distribute service traffic they receive on one of the Service's "externally-facing" addresses (NodePorts, ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure the service in a way that assumes that external load balancers will take care of balancing the service traffic between nodes, and so each node will deliver traffic only to the node-local endpoints of the service, without masquerading the client source IP. (Traffic mistakenly sent to a node with no endpoints will be dropped.) The default value, "Cluster", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features). Note that traffic sent to an External IP or LoadBalancer IP from within the cluster will always get "Cluster" semantics, but clients sending to a NodePort from within the cluster may need to take traffic policy into account when picking a node. |
kube-proxy는 .spec.internalTrafficPolicy 의 설정에 따라서 라우팅되는 엔드포인트를 필터링하며, 2가지 값을 사용합니다.
•
Local : 노드 내부 엔드포인트만 라우팅
•
Cluster : (default) 모든 엔드포인트로 라우팅, .spec.internalTrafficPolicy 를 설정하지 않으면 해당 값으로 적용됩니다.
아래 예제는 내부 트래픽 정책을 활성화하는 Service 리소스 예제입니다.
apiVersion: v1
kind: Service
metadata:
name: echo-server-internal
namespace: ns-common
spec:
internalTrafficPolicy: Local
ports:
- port: 8080
protocol: TCP
type: ClusterIP
selector:
app: echoserver
YAML
복사
내부 트래픽 정책 테스트
.spec.internalTrafficPolicy: Cluster 설정
먼저 echo-server 라는 백엔드 서버를 replica 2개로 생성하고, Service의 .spec.internalTrafficPolicy 를 Cluster 또는 설정하지 않은 상태로 생성합니다. (설정하지 않아도 생성 후 서비스 리소스를 살펴보면 .spec.internalTrafficPolicy: Cluster 로 설정되어 있음을 확인할 수 있습니다.)
그리고 curl 명령을 수행할 수 있는 internal-traffic-test Pod를 생성합니다.
$ kubectl get pods,svc -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/echo-server-7c689c58b9-564j8 1/1 Running 0 26s 100.64.185.208 ip-100-64-185-121.ap-northeast-2.compute.internal <none> <none>
pod/echo-server-7c689c58b9-rzkq6 1/1 Running 0 26s 100.64.181.87 ip-100-64-176-10.ap-northeast-2.compute.internal <none> <none>
pod/internal-traffic-test 1/1 Running 0 18s 100.64.178.138 ip-100-64-176-10.ap-northeast-2.compute.internal <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/echo-server-non-internal ClusterIP 172.20.59.208 <none> 8080/TCP 26s app=echoserver
Bash
복사
현재는 내부 트래픽을 제한하고 있지 않기 때문에, 양쪽의 echo-server Pod에 모두 트래픽이 전달되어야 합니다.
curl 요청을 수행해보면, 아래와 같이 양쪽의 Pod로 요청이 분산되어 전달되는 것을 확인할 수 있습니다.
.spec.internalTrafficPolicy: Local 설정
위와 동일한 테스트 환경에서, Service만 .spec.internalTrafficPolicy: Local 로 설정하여 변경합니다.
$ kubectl get pods,svc -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/echo-server-7c689c58b9-lfmfg 1/1 Running 0 15s 100.64.182.3 ip-100-64-176-10.ap-northeast-2.compute.internal <none> <none>
pod/echo-server-7c689c58b9-qvb8n 1/1 Running 0 15s 100.64.191.164 ip-100-64-185-121.ap-northeast-2.compute.internal <none> <none>
pod/internal-traffic-test 1/1 Running 0 4m27s 100.64.178.138 ip-100-64-176-10.ap-northeast-2.compute.internal <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/echo-server-internal ClusterIP 172.20.180.20 <none> 8080/TCP 15s app=echoserver
Bash
복사
echo-server-7c689c58b9-lfmfg 는 ip-100-64-176-10.ap-northeast-2.compute.internal 노드에 위치해 있고, echo-server-7c689c58b9-qvb8n 는 ip-100-64-185-121.ap-northeast-2.compute.internal 노드에 위치해 있습니다.
curl 명령을 수행하는 Pod는 ip-100-64-176-10.ap-northeast-2.compute.internal 에 있기 때문에, curl 요청은 echo-server-7c689c58b9-lfmfg Pod에만 요청이 전달되어야 합니다.
아래 결과를 살펴보면, 실제로 curl 요청을 수행하는 Pod와 동일한 노드에 있는 echo-server-7c689c58b9-lfmfg Pod에만 트래픽이 전달되는 것을 확인할 수 있습니다.
.spec.internalTrafficPolicy: Local을 설정했지만, 동일한 노드에 대상 Pod가 없는 경우
쿠버네티스 문서를 참고하면, .spec.internalTrafficPolicy: Local 을 설정하고 동일한 노드에 대상 Pod가 없으면 다른 노드에 있는 대상 Pod가 있더라도 엔드포인트가 없는 것처럼 동작한다고 설명하고 있습니다. 이것도 한 번 테스트 해보겠습니다.
서비스는 동일하게 internalTrafficPolicy: Local 로 설정하고, 아래와 같이 echo-server 와 internal-traffic-test 를 서로 다른 노드에 스케줄되도록 Pod를 배치시킵니다.
$ kubectl get pods,svc -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/echo-server-7c689c58b9-qvb8n 1/1 Running 0 3m24s 100.64.191.164 ip-100-64-185-121.ap-northeast-2.compute.internal <none> <none>
pod/internal-traffic-test 1/1 Running 0 7m36s 100.64.178.138 ip-100-64-176-10.ap-northeast-2.compute.internal <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/echo-server-internal ClusterIP 172.20.180.20 <none> 8080/TCP 3m24s app=echoserver$ kuubectl get endpointslices.discovery.k8s.io -n ns-common
Bash
복사
이제 curl http://echo-server-internal:8080 으로 요청을 실행해보면 이전과 다르게 timeout이 발생하는 것을 확인할 수 있습니다.
~ $ curl http://echo-server-internal:8080
curl: (28) Failed to connect to echo-server-internal port 8080 after 132346 ms: Couldn't connect to server
~ $ curl http://echo-server-internal:8080 -v
* processing: http://echo-server-internal:8080
* Trying 172.20.180.20:8080...
* connect to 172.20.180.20 port 8080 failed: Operation timed out
* Failed to connect to echo-server-internal port 8080 after 133390 ms: Couldn't connect to server
* Closing connection
curl: (28) Failed to connect to echo-server-internal port 8080 after 133390 ms: Couldn't connect to server
Plain Text
복사
마치며
내부 트래픽 정책은 트래픽이 시작된 Pod와 동일한 노드에 endpoint가 있을 경우에만 트래픽을 전달하는 것으로, 통신 비용을 절감하고 성능을 개선할 수 있는 역할을 합니다.
그러나 트래픽 분산이 불균형하게 일어날 잠재적인 위험성을 갖고 있습니다.
또한 마지막 예시에서 확인했듯이, 동일한 노드에 타겟으로 하는 Pod가 없다면 다른 노드에 Pod가 있더라도 트래픽이 전달되지 않고 Timeout이 발생하는 단점이 있습니다.
따라서 내부 트래픽 정책을 문제 없이 사용하려면 Pod 들을 노드에 적절히 스케줄링하는 것이 매우 중요합니다.
이는 affinity 나 이전 포스트였던 topologySpreadConstraints 를 조합하여 스케줄링할 수도 있고, 여차하면 DaemonSet 을 통해서도 적절하게 구성 가능할 수 있을 것입니다.