[Study 4주차] 쿠버네티스 Service - NodePort
- -
CoudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 4주차 스터디 내용 정리
이번 글에는 Service 타입 중 NodePort 에 대해서 알아보겠습니다.
1. Service NodePort
k8s 클러스터 외부에서 k8s 내부 파드에 접근하기 위해 사용되는 NodePort 입니다.
말 그대로 k8s 노드의 포트를 하나 뚫어서 외부와 통신이 가능하게 만드는 구조입니다.
외부 클라이언트가 NodeIP:NodePort 로 접속 시,
해당 노드의 iptables 규칙에 의해 SNAT/DNAT 되어 목적지 파드와 통신 후 리턴 트래픽은 최초 인입 노드를 경유하여 외부로 되돌아갑니다.
NodePort 의 기본 할당 포트 범위는 30000 ~ 32767 입니다.
1.1. 실습 환경 구성
실습은 kind 클러스터를 사용했습니다.
kind 클러스터 생성
cat <<EOT> kind-svc-1w.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"InPlacePodVerticalScaling": true
"MultiCIDRServiceAllocator": true
nodes:
- role: control-plane
labels:
mynode: control-plane
topology.kubernetes.io/zone: ap-northeast-2a
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
runtime-config: api/all=true
controllerManager:
extraArgs:
bind-address: 0.0.0.0
etcd:
local:
extraArgs:
listen-metrics-urls: http://0.0.0.0:2381
scheduler:
extraArgs:
bind-address: 0.0.0.0
- |
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0
- role: worker
labels:
mynode: worker1
topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
labels:
mynode: worker2
topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
labels:
mynode: worker3
topology.kubernetes.io/zone: ap-northeast-2c
networking:
podSubnet: 10.10.0.0/16
serviceSubnet: 10.200.1.0/24
EOT
# k8s 클러스터 설치
kind create cluster --config kind-svc-1w.yaml --name myk8s --image kindest/node:v1.31.0
# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping git vim arp-scan -y'
for i in worker worker2 worker3; do echo ">> node myk8s-$i <<"; docker exec -it myk8s-$i sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools ipset ipvsadm nfacct tcpdump ngrep iputils-ping arping -y'; echo; done
# mypc 컨테이너 기동 : kind 도커 브리지를 사용하고, 컨테이너 IP를 직접 지정
docker run -d --rm --name mypc --network kind --ip 172.18.0.100 nicolaka/netshoot sleep infinity
서비스 생성
# Pod 생성
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
# Service 생성
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
# 배포
kubectl apply -f echo-deploy.yaml,svc-nodeport.yaml
1.2. NodePort 접속 테스트
외부 클라이언트 (mypc 컨테이너) 에서 접속 테스트 & 서비스(NodePort) 부하분산 접속을 확인해보겠습니다.
# NodePort 확인
kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}'
# NodePort 를 변수에 지정
NPORT=$(kubectl get service svc-nodeport -o jsonpath='{.spec.ports[0].nodePort}')
echo $NPORT
# 노드의 IP와 NodePort를 변수에 지정
CNODE=172.18.0.3
NODE1=172.18.0.2
NODE2=172.18.0.4
NODE3=172.18.0.5
# MyPC 컨테이너에서 서비스(NodePort) 부하분산 접속 확인
docker exec -it mypc curl -s $CNODE:$NPORT | jq
NodePort 를 사용할 경우, Pod 가 없는 노드에서도 접속이 가능합니다.
현재, Control Plane 에는 Pod 가 없지만 노드포트로 접근할 경우 파드와 통신이 됩니다.
# Control Plane 의 노드포트로 100번 호출
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $CNODE:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
또한 NodePort 타입일지라도 서비스의 ClusterIP 로 접근 시 통신이 잘 되는 것을 알 수 있습니다.
# ClusterIP 와 Port 를 변수로 등록
CIP=$(kubectl get service svc-nodeport -o jsonpath="{.spec.clusterIP}")
CIPPORT=$(kubectl get service svc-nodeport -o jsonpath="{.spec.ports[0].port}")
echo $CIP $CIPPORT
# 서비스 ClusterIP 호출
docker exec -it myk8s-control-plane curl -s $CIP:$CIPPORT | jq
하지만, 클러스터 외부에서는 ClusterIP 로 호출이 되지 않습니다.
docker exec -it mypc curl -s $CIP:$CIPPORT
1.3. NodePort iptables 확인
ClusterIP 의 iptables 와 다른 점은 KUBE-NODEPORT, KUBE-MARK-MASQ, POSTROUTING 이 추가된 것입니다.
외부에서 NodePort 를 통한 서비스 접근 시에는 출발지 IP 가 접근한 노드의 IP 로 변환(SNAT) 되어 목적지 파드로 접속하게 됩니다.
그렇기 때문에 목적지 파드에서 응답할 경우, 무조건 진입한 노드를 거치게 되는 단점이 있습니다.
iptables 확인
# Control Plane 노드에 접근
docker exec -it myk8s-control-plane bash
# 1단계) PREROUTING 테이블 정보 확인
iptables -v --numeric --table nat --list PREROUTING
# 2단계) KUBE-NODEPORTS 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-NODEPORTS
# 3단계) KUBE-EXT 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-EXT-VTR7MTHHNMFZ3OFS
# 4단계) KUBE-SVC 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-SVC-VTR7MTHHNMFZ3OFS
# 5단계) KUBE-SEP 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-SEP-XEXGJWEWSC2GPNPZ
# 6단계) POST-ROUTING 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-POSTROUTING
1.3. externalTrafficPolicy
externalTrafficPolicy 설정을 통해 NodePort 로 접속 시 해당 노드에 배치된 파드로만 접속할 수 있습니다.
# 기본 정보 확인
kubectl get svc svc-nodeport -o json | grep 'TrafficPolicy"'
externalTrafficPolicy 적용
kubectl patch svc svc-nodeport -p '{"spec":{"externalTrafficPolicy": "Local"}}'
kubectl get svc svc-nodeport -o json | grep 'TrafficPolicy"'
# 파드 3개를 2개로 줄임
kubectl scale deployment deploy-echo --replicas=2
# pod 확인
kubectl get pod -o wide
이제 파드는 노드1, 노드3 에만 배포되어 있습니다.
# 파드가 배포되지 않은 컨트롤 플레인의 노드 포트로 접속 -> 실패
docker exec -it mypc curl -s --connect-timeout 1 $CNODE:$NPORT | jq
## 각 노드의 포트에 접근
# 컨트롤플레인
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $CNODE:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
# 노드1
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE1:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
# 노드2
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE2:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
# 노드3
docker exec -it mypc zsh -c "for i in {1..100}; do curl -s $NODE3:$NPORT | grep hostname; done | sort | uniq -c | sort -nr"
externalTrafficPolicy 로 인해 다른 노드의 파드로 부하 분산이 되지 않는 것을 알 수 있습니다.
externalTrafficPolicy 에는 치명적인 단점이 존재합니다.
바로 파드가 배포되지 않은 노드에 접근할 경우, 서비스 연결이 불가능하다는 것입니다.
또한, 파드가 있는 노드의 연결 트래픽이 가중될 수 있다는 점이 있습니다.
1.4. Endpoint Slices
기존 Endpoints 는 단일 리소스로 모든 엔드포인트를 관리했지만 대규모 백엔드 파드를 운영할 경우,
Endpoints 에 상당한 네트워크 트래픽이 발생하여 성능 저하와 CPU 비용 발생의 원인이 되었습니다.
Endpoint Slices 를 사용하게 되면서 Endpoints 를 여러 조각으로 나누어 관리함으로써 Endpoints 의 단점을 해결할 수 있었습니다.
Endpoint Slices 로 인해 Dual Stack Networking 및 Topology Aware Routing 기능이 가능하게 되었습니다.
Endpoint Slice 예시
apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
name: demo-slice-1
labels:
kubernetes.io/service-name: demo
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.0.0.1"
conditions:
ready: true
Endpoint Slices 실습
kubectl get endpointslice
# Endpoint Slices Service 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: mario
labels:
app: mario
spec:
replicas: 1
selector:
matchLabels:
app: mario
template:
metadata:
labels:
app: mario
spec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
effect: "NoSchedule"
nodeSelector:
node-role.kubernetes.io/control-plane: ""
containers:
- name: mario
image: pengbai/docker-supermario
readinessProbe:
exec:
command:
- cat
- healthcheck
---
apiVersion: v1
kind: Service
metadata:
name: mario
spec:
ports:
- name: mario-webport
port: 80
targetPort: 8080
nodePort: 30001
selector:
app: mario
type: NodePort
externalTrafficPolicy: Local
EOF
kubectl get pod,svc,ep,endpointslice -o wide
기존의 엔드포인트는 Probe 실패 시, IP 가 출력되지 않지만 엔드포인트 슬라이스는 IP 가 출력되는 차이점이 있습니다.
'Kubernetes' 카테고리의 다른 글
[Study 5주차] 쿠버네티스 Service - IPVS Proxy 모드 (2) | 2024.10.02 |
---|---|
[Study 5주차] 쿠버네티스 Service - LoadBalancer (MetalLB) (0) | 2024.10.02 |
[Study 4주차] 쿠버네티스 Service - ClusterIP (0) | 2024.09.28 |
[Study 3주차] 쿠버네티스 Calico 네트워크 모드 (1) | 2024.09.22 |
[Study 3주차] 쿠버네티스 Calico CNI 기본 통신 이해 (1) | 2024.09.22 |