1. Cluster IP
개요
- 클러스터 내부 접근 전용: ClusterIP는 클러스터 내부에서만 접근 가능하며, 도메인(DNS) 기반으로도 접근할 수 있습니다.
- DNAT 기반 통신: 클라이언트가 ClusterIP로 접근 시 해당 노드의 iptables 규칙에 따라 DNAT 처리가 되어, 백엔드 Pod로 트래픽이 분산됩니다.
- iptables 규칙 자동 생성: ClusterIP 서비스 생성 시
kube-proxy
가 각 노드의iptables
에 관련 규칙을 자동으로 설정하여, 모든 노드에서 동일한 방식으로 트래픽이 처리됩니다.
실습 환경 구성
ClusterIP 통신 원리를 이해하기 위해, 실습 리소스를 배포합니다.
- 클라이언트 파드 :
net-pod
- 목적지 파드 :
webpod1,2,3
목적지(backend) 파드(Pod) 생성 : 3 pod.yaml
cat <<EOT> 3pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: webpod1
labels:
app: webpod
spec:
nodeName: myk8s-worker
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: webpod2
labels:
app: webpod
spec:
nodeName: myk8s-worker2
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: webpod3
labels:
app: webpod
spec:
nodeName: myk8s-worker3
containers:
- name: container
image: traefik/whoami
terminationGracePeriodSeconds: 0
EOT
클라이언트(TestPod) 생성 : netpod.yaml
cat <<EOT> netpod.yaml
apiVersion: v1
kind: Pod
metadata:
name: net-pod
spec:
nodeName: myk8s-control-plane
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOT
서비스(ClusterIP) 생성 : svc-clusterip.yaml
cat <<EOT> svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 9000 # 서비스 IP 에 접속 시 사용하는 포트 port 를 의미
targetPort: 80 # 타킷 targetPort 는 서비스를 통해서 목적지 파드로 접속 시 해당 파드로 접속하는 포트를 의미
selector:
app: webpod # 셀렉터 아래 app:webpod 레이블이 설정되어 있는 파드들은 해당 서비스에 연동됨
type: ClusterIP # 서비스 타입
EOT
리소스 생성 및 확인
#생성
kubectl apply -f 3pod.yaml,netpod.yaml,svc-clusterip.yaml
# 파드와 서비스 사용 네트워크 대역 정보 확인
# 파드와 서비스 사용 네트워크 대역 정보 확인
kubectl cluster-info dump | grep -m 2 -E "cluster-cidr|service-cluster-ip-range"
"--service-cluster-ip-range=10.200.1.0/24",
"--cluster-cidr=10.10.0.0/16",
# 파드 확인
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
net-pod 1/1 Running 0 30m 10.10.0.8 myk8s-control-plane <none> <none>
webpod1 1/1 Running 0 30m 10.10.2.3 myk8s-worker <none> <none>
webpod2 1/1 Running 0 30m 10.10.1.4 myk8s-worker2 <none> <none>
webpod3 1/1 Running 0 30m 10.10.3.3 myk8s-worker3 <none> <none>
# 서비스 확인
kubectl get svc svc-clusterip
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-clusterip ClusterIP 10.200.1.221 <none> 9000/TCP 25m
서비스(ClusterIP) 접속 확인
변수정의
용이한 실습을 위해 파드와 서비스 IP를 변수로 지정합니다.
# webpod 파드의 IP를 변수에 지정
WEBPOD1=$(kubectl get pod webpod1 -o jsonpath={.status.podIP})
WEBPOD2=$(kubectl get pod webpod2 -o jsonpath={.status.podIP})
WEBPOD3=$(kubectl get pod webpod3 -o jsonpath={.status.podIP})
echo $WEBPOD1 $WEBPOD2 $WEBPOD3
# 서비스 IP 변수 지정 : svc-clusterip 의 ClusterIP주소
SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})
echo $SVC1
net-pod -> web-pod IP로 직접 통신
반복문을 통해 webpod1,2,3 파드의 IP로 직접 통신합니다.
for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod; done
for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | grep Hostname; done
for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | grep Host; done
for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod | egrep 'Host|RemoteAddr'; done
목적지 파드(web-pod)의 IP로 직접 접속 시, 응답헤더의 RemoteAddr
는 출발지 파드(net-pod)의 IP가 출력됨을 확인할 수 있습니다.
net-pod -> 서비스(svc-clusterip)로 통신
생성한 svc-clusterip
서비스의 ClusterIP로 접속합니다.
# TCP 80,9000 포트별 접속 확인 : 출력 정보 의미 확인
root@myk8s-control-plane:/# curl -s --connect-timeout 1 $SVC1
root@myk8s-control-plane:/# curl -s --connect-timeout 1 $SVC1:9000
마찬가지로, 정상적으로 통신됨을 확인할 수 있습니다.
서비스(ClusterIP) 부하분산 확인
반복해서 서비스로 요청을 날려보면, 서비스에 연결된 엔드포인트로 랜덤하게 요청이 가는 것을 확인할 수 있습니다.
100번 접속을 시도 후 결과를 확인하면 대략 33% 정도로 부하분산 접속됨을 확인할 수 있습니다.
서비스(ClusterIP) 통신 흐름 분석
어떻게 서비스(ClusterIP)로 통신되는 가능한 것일까요? 각 노드의 TCP Listen 정보를 보면 서비스로 요청한 9000번 포트도 존재하지 않습니다.
각 워커 노드에서 패킷 덤프 확인(tcpdump)
각 노드에서 패킷을 캡처하여 네트워크를 분석합니다.
pcap
파일을 만들어, wireshark 툴을 이용하여 확인.
tcpdump -i eth0 tcp port 80 -nnq
tcpdump -i eth0 tcp port 80 -w /root/svc1-1.pcap
tcpdump -i eth0 tcp port 9000 -nnq
ngrep -tW byline -d eth0 '' 'tcp port 80'
# tcpdump/ngrep : vethX
VETH1=<각자 자신의 veth 이름>
tcpdump -i $VETH1 tcp port 80 -nn
tcpdump -i $VETH1 tcp port 80 -w /root/svc1-2.pcap
tcpdump -i $VETH1 tcp port 9000 -nn
TCP 계층에서는 9000번 포트는 트래픽이 없는 것을 확인할 수 있습니다. 이것은 Iptables이 사용하는 Netfilter의 동작원리를 살펴 보아야합니다.
결론적으로, kube-proxy
를 통해 IP와 포트가 리다이렉션 되고 iptables 룰에 의해 트래픽을 제어하기 때문에 노드레벨에서 캡처되는 패킷애는 보이지 않습니다.
서비스(ClusterIP) Iptables 분석
그렇다면 iptables에는 ClusterIP가 어떻게 룰이 적용되어 통신 할 수 있는지 살펴봅니다.
서비스(ClusterIP) - iptables 통신흐름
쿠버네티스 ClusterIP
서비스의 트래픽이 클러스터 내에서 어떻게 라우팅되는지 자세하게 살펴봅니다.
- PREROUTING
PREROUTING
체인은 모든 들어오는 패킷이 라우팅되기 전에 가장 먼저 실행되는 체인입니다.- 목적지가 0.0.0.0/0인 모든 트래픽에 대해
KUBE-SERVICES
체인으로 전달됩니다.
- 목적지가 0.0.0.0/0인 모든 트래픽에 대해
- KUBE-SERVICES
KUBE-SERVICES
체인은ClusterIP
및NodePort
와 같은 서비스 관련 트래픽을 처리합니다.ClusterIP
가9000
포트로 들어오는 패킷이KUBE-SVC-KBDBEBILI6IU6WL7RF
라는 서비스 관련 체인으로 전달됩니다.
- KUBE-SVC-YYY
이 체인은 개별 서비스에 대해KUBE-SERVICES
에서 라우팅된 트래픽을 처리합니다. 각 서비스에 대한 고유한 엔드포인트 목록과 라운드로빈, 확률 기반의 로드밸런싱을 처리할 수 있습니다.KUBE-SVC-YYY
체인은 세 개의 백엔드 Pod 또는 Node를 나타내는KUBE-SEP-...
체인으로 트래픽을 분산하고 있습니다.- 로드밸런싱은
statistic mode random probability
방식으로 처리되고 있으며, 각KUBE-SEP-
규칙의 확률이 달라질 수 있습니다.
- KUBE-SEP-ZZ1 ~
KUBE-SEP
체인은 서비스의 개별 백엔드 엔드포인트로 트래픽을 전달합니다. 이 체인은 실제로 Pod IP와 포트로 트래픽을 전달하며, DNAT를 사용하여 최종적으로 목적지와 IP 포트를 변경합니다.KUBE-SEP-T7YVH2JOMUTQFUDU
체인은 목적지 IP와 포트를10.10.1.4:80
으로 변경하는DNAT
규칙을 사용합니다.- 이 체인을 통해
KUBE-SVC-YYY
체인에서 전달된 트래픽이 최종 백엔드 엔드포인트로 라우팅됩니다.
클러스터에서 외부로 나가는 트래픽 처리
KUBE-POSTROUTING 체인은 외부로 나가는 트래픽을 처리하며, SNAT
을 통해 클러스터 외부에서 트래픽 출발지 IP주소가 올바르게 보이도록 설정합니다.
이러한 설정이 없으면, 클러스터 외부의 다른 네트워크가 클러스터 내의 출발지 IP주소를 모르게 되어 응답이 실패할 수 있습니다.
KUBE-POSTROUTING 에서
0x4000 마킹에 따라 SNAT 여부를 결정하고 패킷의 트래픽을 처리합니다.
0x4000
마크가 없는 경우,- 패킷에
0x4000
마크가 없는 경우, 더 이상KUBE-POSTROUTING
체인을 처리하지 않고 다음 규칙으로 전달 (RETURN
).
- 패킷에
0x4000
마크가 있는 경우,KUBE-POSTROUTING
체인 내에서 모든 패킷에0x4000
마크를 설정MASQUERADE
규칙을 통해 쿠버네티스 서비스 트래픽의 출발지 IP를 변경하여 올바른 SNAT 처리를 적용.
iptables -t nat -S | grep KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
모든 노드에서 iptables 규칙 확인
서비스를 생성하면 kube-proxy에 의해서 iptables 규칙이 모든 노드에 추가됨을 확인할 수 있습니다.
ClusterIP의 부족한 점
- 외부에서 접근 불가능: ClusterIP는 클러스터 내부에서만 접근할 수 있어, 외부 클라이언트가 직접 접속할 수 없습니다. ⇒ NodePort 또는 LoadBalancer 서비스로 외부 접근 가능하도록 설정해야 합니다.
- Pod 상태에 따른 트래픽 제어 불가:
iptables
를 사용하는 경우 헬스체크가 없어, 문제가 있는 Pod에도 트래픽이 전달될 수 있습니다. ⇒ Readiness Probe 설정으로 Pod 비정상 시 엔드포인트에서 제거 필요합니다. - 단순한 로드밸런싱: 기본적으로 랜덤 또는 세션 어피니티 분산 방식만 지원되어, 복잡한 로드밸런싱 정책 적용이 어렵습니다.
⇒IPVS
를 사용하면 라운드 로빈, 최소 연결 등 다양한 알고리즘 지원 가능합니다. - Node 간 비효율적 라우팅: 목적지 Pod가 같은 노드에 위치해도 트래픽이 다른 노드로 전달될 수 있어 네트워크 성능 저하 가능합니다.
2. NodePort
개요
- 외부 접근 가능: 외부 클라이언트가 노드의 IP와 NodePort를 통해 서비스에 접근할 수 있으며, 이후 트래픽 처리는 ClusterIP와 동일하게 내부 Pod로 전달됩니다.
- SNAT/DNAT 처리: 외부 트래픽은 해당 노드의
iptables
규칙에 의해 SNAT/DNAT 처리되어 목적지 Pod와 통신 후, 리턴 트래픽은 최초로 접근한 노드를 경유해 외부로 나갑니다. - 모든 노드에 규칙 설정: 모든 노드(마스터 포함)에 NodePort 관련
iptables
규칙이 설정되어, 어느 노드로 접근해도 분산 접속 가능합니다. - NodePort 포트 범위 고정: 기본적으로 30000-32767 범위를 사용하며, 필요 시 설정 변경 가능합니다.
실습 구성
NodePort
의 통신 원리를 이해하기 위해 실습을 위한 리소스를 생성합니다.
목적지(backend) 디플로이먼트(Pod) 파일 생성 : echo-deploy.yaml
cat <<EOT> echo-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 3
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: kans-websrv
image: mendhak/http-https-echo
ports:
- containerPort: 8080
EOT
서비스(NodePort) 파일 생성 : svc-nodeport.yaml
cat <<EOT> svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-nodeport
spec:
ports:
- name: svc-webport
port: 9000 # 서비스 ClusterIP 에 접속 시 사용하는 포트 port 를 의미
targetPort: 8080 # 타킷 targetPort 는 서비스를 통해서 목적지 파드로 접속 시 해당 파드로 접속하는 포트를 의미
selector:
app: deploy-websrv
type: NodePort
EOT
외부 클라이언트(mypc) 컨테이너 생성 : NodePort 통신 확인을 위해 노드와 같은 네트워크 대역의 컨테이너 생성
docker run -d --rm --name mypc --network kind --ip <노드_CIDR> nicolaka/netshoot sleep infinity
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
생성 및 확인
# 생성
kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml
# pod 확인
kubectl get deploy,pod -o wide
# 서비스(NodePort) 확인
kubectl get svc svc-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-nodeport NodePort 10.200.1.78 <none> 9000:32364/TCP 3m14s
kubectl get endpoints svc-nodeport
NAME ENDPOINTS AGE
svc-nodeport 10.10.1.4:8080,10.10.2.3:8080,10.10.3.3:8080 48s
kubectl describe svc svc-nodeport
# mypc(외부클라이언트) 확인
docker ps | grep mypc
3960affdcfb4 nicolaka/netshoot "sleep infinity" 2 days ago Up 2 day
서비스(NodePort) 접속 확인
변수정의
용이한 실습을 위해노드와 서비스 포트를 변수로 지정합니다.
CNODE=172.17.0.2
NODE1=172.17.0.3
NODE2=172.17.0.4
NODE3=172.17.0.5
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPOR
CIP=$(kubectl get service svc-nodeport -o jsonpath="{.spec.clusterIP}")
CIPPORT=$(kubectl get service svc-nodeport -o jsonpath="{.spec.ports[0].port}")
외부 클라이언트(mypc 컨테이너)에서 접속 시도NodePort
는 ClusterIP
를 포함하기 때문에 두 개의 서비스 타입 모두 통신이 잘되는 것을 확인할 수 있습니다.
# ClusterIP 접속 확인
docker exec -it mypc curl -s $CIP:$CIPPORT
{
"path": "/",
"headers": {
"host": "10.200.1.78:9000",
"user-agent": "curl/7.88.1",
"accept": "*/*"
},
"method": "GET",
"body": "",
"fresh": false,
"hostname": "10.200.1.78",
"ip": "::ffff:172.17.0.2",
"ips": [],
"protocol": "http",
"query": {},
"subdomains": [],
"xhr": false,
"os": {
"hostname": "deploy-echo-5c689d5454-g5dcf"
},
"connection": {}
}
# NodePort 접속 확인
docker exec -it myk8s-control-plane curl -s $CNODE:$NPORT | jq
{
"path": "/",
"headers": {
"host": "172.17.0.2:32364",
"user-agent": "curl/7.88.1",
"accept": "*/*"
},
"method": "GET",
"body": "",
"fresh": false,
"hostname": "172.17.0.2",
"ip": "::ffff:172.17.0.2",
"ips": [],
"protocol": "http",
"query": {},
"subdomains": [],
"xhr": false,
"os": {
"hostname": "deploy-echo-5c689d5454-g5dcf"
},
"connection": {}
}
서비스(NodePort) 부하분산 접속 확인
NodePort도 부하분산 룰이 동일하게 적용되는 것을 확인할 수 있습니다.
노드 TCP 포트 확인
과거 쿠버네티스 버전은 각 노드에 노드포트가 Listen 되지만, 현재 쿠버네티스 버전에는 iptables rules 처리되어 포트가 노출되지 않습니다.
서비스(NodePort) Iptables 분석
쿠버네티스 NodePort
서비스의 트래픽이 클러스터 내에서 어떻게 라우팅되는지 자세하게 살펴봅니다.
- PREROUTING
모든 외부에서 들어오는 패킷은 먼저PREROUTING
체인에서 처리됩니다.- 모든 외부 트래픽(
PREROUTING
)은KUBE-SERVICES
체인으로 전달됩니다.KUBE-SERVICES
는 모든 서비스 관련 트래픽을 처리하는 체인입니다.
- 모든 외부 트래픽(
- KUBE-SERVICES
KUBE-SERVICES
체인은 서비스(ClusterIP 또는 NodePort) 관련 트래픽을 처리합니다. NodePort는 ClusterIP를 포함합니다.ClusterIP
가9000
포트로 들어오는 패킷이KUBE-SVC-VTR7MTHHNMFZ3OFSF
라는 서비스 관련 체인으로 전달됩니다.NodePort
서비스와 관련된 트래픽은 마지막에KUBE-NODEPORTS
체인으로 전달됩니다.
- KUBE-NODEPORTS
KUBE-NODEPORTS
체인은NodePort
서비스와 관련된 트래픽을 처리합니다. 이 체인에서NodePort
서비스에 대한 규칙이 존재하고, 트래픽이 올바른 서비스로 전달되도록 설정합니다.NodePort
서비스는 외부 포트32364
로 연결되며,KUBE-EXT-VTR7MTHHNMFZ3OFS
체인으로 전달됩니다.- 이 체인은
NodePort
서비스를 처리하기 위한 추가적인 NAT 및 라우팅 규칙을 포함하고 있습니다.
- KUBE-EXT-XXX
KUBE-EXT
체인은 외부에서 들어오는NodePort
트래픽을 처리하고, 백엔드 Pod로 전달되기 전에 마스커레이딩 (MASQUERADE)을 수행합니다. (EXT는 External에 약자.)- 외부에서
NodePort
로 들어오는 트래픽은MASQUERADE
를 통해 출발지 IP가 변경됩니다 (KUBE-MARK-MASQ
). - 이후
KUBE-SVC-VTR7MTHHNMFZ3OFS
체인으로 전달되어, 서비스의 백엔드로 트래픽이 분배됩니다
- 외부에서
- KUBE-SVC-XXX
KUBE-SVC
체인은 서비스의 백엔드 Pod로 트래픽을 분배하는 역할을 합니다.- 로드밸런싱 알고리즘(
statistic --mode random --probability
)을 사용하여, 특정 백엔드 Pod로 트래픽을 분배합니다. - 예를 들어,
10.10.1.5:8080
,10.10.2.4:8080
,10.10.3.4:8080
의 백엔드 Pod로 확률 기반의 분산 로드밸런싱을 수행합니다.
- 로드밸런싱 알고리즘(
- KUBE-SEP-XXX
KUBE-SEP
체인은 서비스의 최종 백엔드 Pod로 트래픽을 전달합니다.- 각
KUBE-SEP
체인은 특정 백엔드 Pod로의DNAT
(Destination NAT)를 수행하여 최종적으로 패킷의 목적지 IP를 해당 Pod IP로 변경합니다. - 이 예시에서는
10.10.1.5
,10.10.2.4
,10.10.3.4
의 Pod로 패킷이 전달됩니다.
- 각
- POSTROUTING
POSTROUTING
체인은 패킷이 클러스터에서 나가기 전에 출발지 IP를 마스커레이딩하여 외부 네트워크에서도 올바르게 패킷을 처리할 수 있도록 합니다.0x4000
마크가 없는 경우,- 패킷에
0x4000
마크가 없는 경우, 더 이상KUBE-POSTROUTING
체인을 처리하지 않고 다음 규칙으로 전달(RETURN
)
- 패킷에
0x4000
마크가 있는 경우,KUBE-POSTROUTING
체인 내에서 모든 패킷에0x4000
마크를 설정MASQUERADE
규칙을 통해 쿠버네티스 서비스 트래픽의 출발지 IP를 변경하여 올바른 SNAT 처리를 적용.
externalTrafficPolicy
목적지 파드의 로그를 확인해보면 ClusterIP
와 다르게, 출발지 IP가 파드가 아닌, 노드의 IP가 출력됩니다.
즉, NodePort에서는 기본적으로 노드의 IP로 SNAT 되어서 웹서버에서 클라이언트의 IP를 수집할 수 없습니다.
하지만, 중요 서비스를 처리하는 경우 법적인 보안 요구사항으로, 최초 접속자(외부 클라이언트)의 IP를 수집해야 합니다.
이러한 문제를 해결할 수 있는 방법이 바로 externalTrafficPolicy
입니다.
externalTrafficPolicy: Local
은 NodePort 통신 시에, 해당 노드에 배치된 파드로만 전달이 되며, 다른 노드에 배포된 파드로는 전달이 되지 않습니다.
실습 : externalTrafficPolicy: Local
svc-nodeport
의 externalTrafficPolicy을 변경합니다 Cluster
-> Local
# externalTrafficPolicy: local 설정 변경
kubectl patch svc svc-nodeport -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl get svc svc-nodeport -o json | grep 'TrafficPolicy"'
"externalTrafficPolicy": "Local",
"internalTrafficPolicy": "Cluster",
외부 클라이언트에서 다시 접속을 시도하고 로그를 확인합니다.
# 파드 로그 실시간 확인 (웹 파드에 접속자의 IP가 출력)
kubectl logs -f -l app=deploy-websrv
외부 클라이언트(mypc)에서 접속 시도
docker exec -it mypc curl $NODE1:$NPORT | jq
응답헤더의 ip
를 살펴보면 노드 IP가 아닌, 외부 클라이언트(mypc)의 IP가 출력됨을 확인할 수 있습니다.
파드 3개를 2개로 줄이고, 각 노드로부터 NodePort 통신을 시도해 보면 컨트롤플레인과 노드2는 파드가 배치되지 않았기 때문에 접속되지 않습니다.
kubectl scale deployment deploy-echo --replicas=2
docker exec -it mypc curl $CODE:$NPORT | jq
docker exec -it mypc curl $NODE1:$NPORT | jq
docker exec -it mypc curl $NODE2:$NPORT | jq
docker exec -it mypc curl $NODE3:$NPORT | jq
서비스(NodePort) 부족한 점
- 보안 취약성: NodePort 서비스는 외부에서 노드의 IP와 포트를 통해 직접 접근해야 하므로 내부망이 노출되어 보안에 취약합니다.
- 클라이언트 IP 손실 :
externalTrafficPolicy: Local
을 설정하면 클라이언트 IP를 보존할 수 있습니다. 하지만 이 경우, 해당 NodePort로 연결된 노드에 요청을 수신할 수 있는 파드가 존재하지 않으면 트래픽이 실패하게 됩니다. - 유연성 문제: 고정된 포트 범위(30000-32767)로 인해 포트 충돌 및 관리 어렵습니다.
결론
ClusterIP와
NodePort
서비스가 이렇게 복잡하게 동작하는 이유는, 초기 쿠버네티스 설계에서 개발자가 직접 서버를 관리하면서도 손쉽게 부하 분산과 서비스 연결을 설정할 수 있도록 하기 위함이었습니다.
하지만 이러한 구조는 각 노드에 iptables
규칙을 생성하고, 트래픽을 여러 노드 간 분산시키는 과정에서 비효율성을 초래하기도 합니다. 즉, 개발자 편의성을 위해 설계되었지만, 결과적으로 관리와 성능 측면에서 복잡하고 최적화되지 않은 동작 방식이 된 것입니다.
다음 포스팅에서는, 이러한 문제를 해결하기 위해 도입된 LoadBalancer 등 다양한 서비스에 대해 알아봅니다.