Pod를 expose할 때 Kubernetes의 Service를 통해 다른 Pod혹은 외부에서 접근할 수 있도록 설정할 수 있다. 더 정확히는 Service를 생성하면 해당하는 Pod들에 대한 endpoint가 자동으로 생성되어, connection이 가능해진다. 물론 없어도 연결이 아예 불가능한 것은 아니지만 쿠버네티스적 관점에서는 Service가 필요하다.
노드 외부에서의 연결이 들어온 상태이고 Service를 NodePort나 LB타입으로 설정한 경우는 어떨까?
기본 설정에서는 Service에 여러 개의 Pod가 연결되어 있다면 쿠버네티스의 kube-proxy에 의해 iptables가 설정될 것이고, 패킷은 iptables에 따라 랜덤하게 전달될 것이다.
하지만 만약 패킷이 특정 노드에 전달된 경우 해당 노드 외에도 다른 노드에 파드가 있다면, 그 파드에도 패킷이 전달될 여지가 있다.
이런 경우 도착한 노드에 있는 파드로만 패킷을 전달하고 싶을 때에는 어떻게 해야할까?
이를 위해서 externalTrafficPolicy 라는 항목이 있다.
테스트
사전 준비
아래와 같이 replica 2개 짜리 deployment를 하나 생성하고, 서로 다른 노드에 올라가는 것을 확인한다.
(외부에서 curl 응답되는 것을 보기 위해 luksa 성님의 kubia 이미지를 사용했다)
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: test
name: test
spec:
replicas: 2
selector:
matchLabels:
run: test
template:
metadata:
labels:
run: test
spec:
nodeSelector:
app: test
containers:
- image: luksa/kubia
name: test
ports:
- containerPort: 8080
두 개의 노드(worker0, worker1)에 각각 pod가 배포되었다.
[host ~]# kubectl get po -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-86d7ff54f9-5kbwl 1/1 Running 0 5m5s 10.128.4.194 worker0 <none> <none>
test-86d7ff54f9-dt8ng 1/1 Running 0 15m 10.131.2.22 worker1 <none> <none>
worker0, worker1의 정보는 아래와 같다.
worker0가 ip 172.20.22.238을 가진다. 이 테스트에서는 이 ip로 curl을 보낼 예정이다.
[host ~]# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP
worker0 Ready upf 152d v1.18.3+2cf11e2 172.20.22.238 <none>
worker1 Ready upf 153d v1.18.3+2cf11e2 172.20.22.239 <none>
이제 외부에서 expose할 수 있도록 nodePort를 가진 서비스를 생성한다.
동일 라벨을 사용하면 차후 라벨 제거 시 replica가 추가 생성되기 때문에, 라벨 셀렉터를 다른 것으로 변경하고 수동으로 replica에 라벨을 넣어줄 것이다.
[host ~]# kubectl expose deploy/test -n default --type NodePort --port 8080 --dry-run -o yaml > service.yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
expose: test # Changed
name: test
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
expose: test # Changed
type: NodePort
status:
loadBalancer: {}
Service 배포 전 아직 아무런 endpoint가 없는 상태이다.
[host ~]# kubectl get svc,po,ep -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 172.30.0.1 <none> 443/TCP 160d <none>
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/test-86d7ff54f9-5kbwl 1/1 Running 0 5m32s 10.128.4.194 worker0 <none> <none>
pod/test-86d7ff54f9-dt8ng 1/1 Running 0 15m 10.131.2.22 worker1 <none> <none>
NAME ENDPOINTS AGE
endpoints/kubernetes ... 160d
아래와 같이 라벨을 추가하고 Service를 Deploy하면 endpoint가 생성된 것을 볼 수 있다.
[host ~]# kubectl label po test-86d7ff54f9-5kbwl expose=test
pod/test-86d7ff54f9-5kbwl labeled
[host ~]# kubectl label po test-86d7ff54f9-dt8ng expose=test
pod/test-86d7ff54f9-dt8ng labeled
[host ~]# kubectl get po --show-labels
NAME READY STATUS RESTARTS AGE LABELS
test-86d7ff54f9-5kbwl 1/1 Running 0 11m expose=test,pod-template-hash=86d7ff54f9,run=test
test-86d7ff54f9-dt8ng 1/1 Running 0 21m expose=test,pod-template-hash=86d7ff54f9,run=test
딱히 NodePort값을 특정하지 않았기 때문에 랜덤하게 포트값 35542를 받았다. 이제 worker0 ip:35542로 전달되는 패킷들은 test pod로 전달될 것이다.
curl 로 메시지 보내기
앞에서 말한 것처럼 worker0(172.20.22.238)로 curl을 보낸다.
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-5kbwl
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-5kbwl
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
보는 것과 같이 두 pod 모드에 패킷이 도착하고 있다.
처음 Service에 대해 배울 때 나오는 내용처럼, iptables에 의해 패킷이 공평하게 분배되는 것을 확인할 수 있다.
만약 worker0에 있는 Pod가 죽으면 어떻게 될까?
유사 파드 Missing 상태를 만들어내보자. 이번엔 아래와 같이 worker0위에 있는 Pod에 아까 붙였던 라벨을 제거한다.
[host ~]# kubectl label pod test-86d7ff54f9-5kbwl expose= --overwrite
pod/test-86d7ff54f9-5kbwl labeled
아래와 같이 endpoint가 삭제된다.
[host ~]# kubectl get svc,po,ep -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 172.30.0.1 <none> 443/TCP 160d <none>
service/test NodePort 172.30.69.12 <none> 8080:35542/TCP 2m24s expose=test
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/test-86d7ff54f9-5kbwl 1/1 Running 0 16m 10.128.4.194 worker0 <none> <none>
pod/test-86d7ff54f9-dt8ng 1/1 Running 0 26m 10.131.2.22 worker1 <none> <none>
NAME ENDPOINTS AGE
endpoints/test 10.131.2.22:8080 2m24s
동일하게 worker0로 curl을 보내면, 아래와 같이 worker1에 있는 Pod로 패킷이 전달되는 것을 확인할 수 있다.
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
[host ~]# curl -k 172.20.22.238:35542
You've hit test-86d7ff54f9-dt8ng
endpoint가 제거될 경우, kube-proxy가 api-server를 통해 이를 전달받아 iptables를 업데이트한다. iptables에는 worker0 상의 Pod에 대한 정보가 없어졌기 때문에 worker1의 Pod 라우팅만 남는다.
따라서 자연스럽게 worker1의 Pod에 패킷이 전달된다.
이런 설정들은 Service를 생성하면 별도로 기재해놓지 않아도 externalTrafficPolicy가 Cluster로 설정되기 때문에 가능한 것이다.
externalTrafficPolicy가 Cluster로 설정되면, Cluster 에 있는 모든 Pod에 대해 Routing 하도록 iptables를 수정한다.
[host ~]# kubectl get svc test -o yaml
apiVersion: v1
kind: Service
metadata:
...
labels:
expose: test
...
name: test
namespace: default
spec:
...
externalTrafficPolicy: Cluster
이 externalTrafficPolicy: Cluster를 통한 분배는 좋으면서 좋지 않다. 공평한 분배와 본래 Service 특성을 생각해봤을 때는 당연하고도 좋은 일이지만, 두 Node간 물리 거리가 멀리 떨어져 있는 경우(많은 Hop을 거쳐야 하는 경우) 굳이 다른 노드로 보내서 불필요한 라우팅을 초래한다.
Hop에 따른 서비스 지연을 고려하지 않아도 되는 서비스라면 상관없겠지만(그런 서비스가 많은지는 잘 모르겠지만), 그렇지 않다면 가급적 물리적으로 가까운 노드에 배포되도록 한다던가하는 노력이 필요하다.
이런 Hop들에 대한 영향을 없애기 위해서, externalTrafficPolicy: Local 설정이 있다.
externalTrafficPolicy: local 인 경우
externalTrafficPolicy가 Local로 설정될 경우, 같은 노드 내에 있는 Pod로만 패킷이 전달된다. 따라서 불필요하게 다른 Pod로 패킷이 가는 상황을 막을 수 있다. 그러면 만약 externalTrafficPolicy: Local일 때 해당 노드에 있는 Pod가 죽으면 어떻게 될까?
Policy를 Local로 변경 후, 동일하게 worker0로 패킷을 전달한다.
...
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
externalTrafficPolicy: Local
selector:
...
아래의 결과처럼 timed out이 발생하는 것을 볼 수 있다.
[host ~]# curl -k 172.20.22.238:35542
curl: (7) Failed connect to 172.20.22.238:35542; Connection timed out
이는 externalTrafficPolicy: Local특성 상 노드 상에 있는 Pod에만 패킷을 전달하고, 노드 바깥에 있는 Pod에 대해서는 고려하지 않기 때문이다.
아쉽게도 평소에는 노드 상에 있는 Pod에게 패킷을 보내는데 유사시에는 다른 Node로 패킷을 보내주는 완벽한 설정은 없다.
요약
-
Service는 externalTrafficPolicy: Cluster가 Default다. 패킷이 골고루 분배되지만 불필요한 Hop이 생길 수 있다.
-
Hop을 줄이려면 externalTrafficPolicy: local을 사용하자
-
그러나 패킷이 안갈 수도 있다. 주의하자.
'Cloud > Kubernetes' 카테고리의 다른 글
Admission Control (0) | 2021.07.27 |
---|---|
Kubernetes NetworkPolicy 란? (0) | 2020.12.26 |
Istio를 알아보자 - 1. 기본 항목 구성 (0) | 2020.12.13 |