[Study 4주차] 쿠버네티스 Service - ClusterIP
- -
CoudNet@ 팀의 가시다님께서 리딩하시는 KANS Study (Kubernetes Advanced Networking Study) 4주차 스터디 내용 정리
이번 주차에는 Service 타입 중 ClusterIP 와 NodePort 에 대해 스터디를 진행했습니다.
이번 글에서는 ClusterIP 를 알아보겠습니다.
1. Kubernetes Service
1.1. Service 개요
서비스 동작 발전 과정
1. 파드 생성
서비스가 없을 경우, 파드가 재실행될 시에 파드의 IP 가 변경되기 때문에 아래와 같은 통신 문제가 발생합니다.
2. 서비스 (Cluster Type) 연결
위와 같은 문제로 인해 서비스 (Service) 라는 리소스를 통해 파드에 대한 단일 진입점을 제공해줍니다.
서비스는 파드에 대한 고정 IP 와 도메인네임 (Domain Name) 을 제공해줍니다.
3. 서비스 (NodePort Type) 연결
하지만 Cluster Type 은 k8s 클러스터 내부에서만 접속이 가능하다는 단점이 있습니다.
외부에서도 서비스 접근을 위해 k8s 에서는 NodePort 타입의 서비스를 제공합니다.
1.2. 실습 환경 구성
이번 주차에서 실습은 Kind 를 사용한 k8s 클러스터와 통신 테스트를 위한 docker 컨테이너 1대를 사용했습니다.
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
환경 구성 확인
# 컨테이너 확인
docker ps
# 컨테이너 IP 대역 확인
docker ps -q | xargs docker inspect --format '{{.Name}} {{.NetworkSettings.Networks.kind.IPAddress}}'
2. ClusterIP
클러스터 내부에서만 ClusterIP 로 접근 가능합니다.
모든 노드에 iptables rule 이 설정되므로, 파드에서 접속 시 해당 노드에 존재하는 iptables rule 에 의해서 분산 접속이 됩니다.
2.1. 실습 환경 구성
파드 생성
# 목적지 파드 생성
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
# 클라이언트 파드 생성
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
# 서비스(Cluster IP) 생성
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
파드 환경 확인
# webpod IP 출력
kubectl get pod -l app=webpod -o jsonpath="{.items[*].status.podIP}"
# 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
# net-pod 에서 webpod 로 Curl 접속
for pod in $WEBPOD1 $WEBPOD2 $WEBPOD3; do kubectl exec -it net-pod -- curl -s $pod; done
# 서비스 IP 변수 지정 : svc-clusterip 의 ClusterIP주소
SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})
echo $SVC1
# 위 서비스 생성 시 kube-proxy 에 의해서 iptables 규칙이 모든 노드에 추가됨
docker exec -it myk8s-control-plane iptables -t nat -S | grep $SVC1
서비스 생성 시, 노드 iptables 에 해당 서비스에 대한 규칙이 추가되는 것을 알 수 있습니다.
2.2. service 접근 테스트
# Service 의 80 포트로 접근
kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1
# Service 의 9000 포트로 접근
kubectl exec -it net-pod -- curl -s --connect-timeout 1 $SVC1:9000
80 포트로는 접근이 불가하고 9000 포트로는 접근이 가능한데, 이는 서비스에서 진입 포트를 9000 포트로 지정했기 때문입니다.
Service LoadBalancing 테스트
그렇다면 서비스와 연결된 파드들에 대해 접근과 부하 분산은 어떻게 되는 지 확인해보겠습니다.
# 서비스 10 번 호출
kubectl exec -it net-pod -- zsh -c "for i in {1..10}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
# 서비스 100 번 호출
kubectl exec -it net-pod -- zsh -c "for i in {1..100}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
# 서비스 1000 번 호출
kubectl exec -it net-pod -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
대략 33% 정도로 잘 분산되는 것을 알 수 있습니다.
Service 전송 패킷 확인
Worker Node 1번에서 파드의 패킷을 tcpdump 를 사용하여 확인합니다.
# 80 번 포트 모니터링
docker exec -it myk8s-worker tcpdump -i eth0 tcp port 80 -nnq
# 서비스 접근 테스트
kubectl exec -it net-pod -- zsh -c "for i in {1..10}; do curl -s $SVC1:9000 | grep Hostname; sleep 1; done"
2.3. iptables 정책 확인
앞서 서비스가 추가될 때, kube-proxy 가 iptable 에 규칙을 추가해준다고 하였습니다.
어떤 정책으로 iptable 규칙이 추가되는 지 확인해보겠습니다.
k8s 서비스 통신 시, 노드에는 위와 같은 4 단계의 규칙으로 iptables 라우팅이 이루어집니다.
실제 실습 환경에서 해당 규칙을 확인해보겠습니다.
# Control Plane 노드에 접근
docker exec -it myk8s-control-plane bash
# 1단계) PREROUTING 테이블 정보 확인
iptables -v --numeric --table nat --list PREROUTING
# 2단계) KUBE-SERVICES 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-SERVICES
# 3단계) KUBE-SVC 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF
# 4단계) KUBE-SEP 테이블 정보 확인
iptables -v --numeric --table nat --list KUBE-SEP-TBW2IYJKUCAC7GB3
2.4. 세션어피니티 (sessionAffinity)
세션어피니티는 클라이언트가 접속한 목적지(파드)에 고정적인 접속을 지원하는 기능
Sticky Session 방식과 유사하게 동작하며,
한 번 접근한 파드에 대한 상태 정보를 기록하고 확인하여 이후 연결 시도 시 동일한 파드로 접근할 수 있게 됩니다.
# 기본 정보 확인
kubectl get svc svc-clusterip -o yaml | grep sessionAffinity
세션어피니티 적용
# 세션어피니티 적용
kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinity":"ClientIP"}}'
# 변경 상태 확인
kubectl get svc svc-clusterip -o yaml
세션어피니티 적용 확인
# 서비스에 1000번 접근
kubectl exec -it net-pod -- zsh -c "for i in {1..1000}; do curl -s $SVC1:9000 | grep Hostname; done | sort | uniq -c | sort -nr"
모두 같은 파드로 접근되는 것을 알 수 있습니다.
세션어피니티의 세션 고정 시간의 기본값은 10800초 (3시간)이지만, 이를 수정할 수 있습니다.
# 타임아웃 시간을 30초로 변경
kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinityConfig":{"clientIP":{"timeoutSeconds":30}}}}'
30초가 지나면 다른 파드로 접근이 가능하게 됩니다.
이번에 ClusterIP 타입의 서비스를 아주 딥하게 공부 했는데, 아주 심화 내용이 많이 있었습니다.
그 동안 별 생각 없이 사용하고 있던 ClusterIP 에 이런 내용이 담겨 있을 줄은 상상도 못했는데, 너무나도 좋은 공부가 된 것 같아 너무 기쁩니다.
꼭 이번 스터디도 완주하여 많은 지식을 얻어갈 수 있도록 하겠습니다! :)
'Kubernetes' 카테고리의 다른 글
[Study 5주차] 쿠버네티스 Service - LoadBalancer (MetalLB) (0) | 2024.10.02 |
---|---|
[Study 4주차] 쿠버네티스 Service - NodePort (1) | 2024.09.28 |
[Study 3주차] 쿠버네티스 Calico 네트워크 모드 (1) | 2024.09.22 |
[Study 3주차] 쿠버네티스 Calico CNI 기본 통신 이해 (1) | 2024.09.22 |
[Study 3주차] 쿠버네티스 Calico CNI 소개 (0) | 2024.09.22 |