Overview
쿠버네티스 서비스 모드 Iptable 를 알아보고, 확장 서비스 옵션, 서비스 트러블슈팅 방법과 최적화 방안을 정리합니다.
IPtables
Iptables은 서버의 firewall software입니다. 네트워크 트래픽을 룰을 통해 관리하여 실제 처리는 커널 계층의 netfilter 에서 각 hook에 해당 룰과 체인에 따라 modules을 등록해두고 상호작용합니다.
netfilter의 Hook은 5개로 구분되며 각 hook에 iptables의 트래픽 정책(rule)을 언제 처리할 건지를 정의하는 Chain이 있습니다. Chain 안에는 룰의 집합체인 Table이 있습니다.
- PEEROUTING HOOK은 라우팅 전인 패킷이 인터페이스에 들어 올 때 동작 합니다. 패킷의 주소 변환 및 필터링을 수행합니다.
- 반면, INPUT은 라우팅 후 로컬 시스템에 동작할 때 동작합니다. 패킷 필터링 및 방화벽 규칙 적용에 사용됩니다.
- FORWARD는 로컬 시스템이 출발지도 목적지도 아닌, 포워딩해야 하는 패킷에 대해 호출 훅입니다.
트래픽을 처리를 담당하는 테이블에서는 다음과 같이 룰을 구분합니다.
- Filter: 패킷의 통과 여부(허용/차단)를 결정합니다.
- NAT: 패킷의 출발지 또는 목적지 IP 주소를 변경합니다.
- Mangle: 패킷의 헤더 정보를 수정하고 추가 마킹을 적용합니다.
- Raw: 패킷이 커넥션 추적을 무시할지 결정합니다.
- Security: 패킷에 보안 레이블을 추가하여 보안 정책을 적용합니다.
시스템 서버에서 IPTABLES 확인은 다음의 명령어로 확인합니다.
iptables -h
---------
iptables v1.8.7
Usage: iptables -[ACD] chain rule-specification [options]
iptables -I chain [rulenum] rule-specification [options]
iptables -R chain rulenum rule-specification [options]
iptables -D chain rulenum [options]
iptables -[LS] [chain [rulenum]] [options]
iptables -[FZ] [chain] [options]
iptables -[NX] chain
iptables -E old-chain-name new-chain-name
iptables -P chain target [options]
iptables -h (print this help information)
Commands:
Either long or short options are allowed.
--append -A chain Append to chain
--check -C chain Check for the existence of a rule
--delete -D chain Delete matching rule from chain
--delete -D chain rulenum
Delete rule rulenum (1 = first) from chain
--insert -I chain [rulenum]
Insert in chain as rulenum (default 1=first)
--replace -R chain rulenum
Replace rule rulenum (1 = first) in chain
--list -L [chain [rulenum]]
List the rules in a chain or all chains
--list-rules -S [chain [rulenum]]
Print the rules in a chain or all chains
--flush -F [chain] Delete all rules in chain or all chains
--zero -Z [chain [rulenum]]
Zero counters in chain or all chains
--new -N chain Create a new user-defined chain
--delete-chain
-X [chain] Delete a user-defined chain
--policy -P chain target
Change policy on chain to target
--rename-chain
-E old-chain new-chain
Change chain name, (moving any references)
Options:
--ipv4 -4 Nothing (line is ignored by ip6tables-restore)
--ipv6 -6 Error (line is ignored by iptables-restore)
[!] --proto -p proto protocol: by number or name, eg. `tcp'
[!] --source -s address[/mask][...]
source specification
[!] --destination -d address[/mask][...]
destination specification
[!] --in-interface -i input name[+]
network interface name ([+] for wildcard)
--jump -j target
target for rule (may load target extension)
--goto -g chain
jump to chain with no return
--match -m match
extended match (may load extension)
--numeric -n numeric output of addresses and ports
[!] --out-interface -o output name[+]
network interface name ([+] for wildcard)
--table -t table table to manipulate (default: `filter')
--verbose -v verbose mode
--wait -w [seconds] maximum wait to acquire xtables lock before give up
--wait-interval -W [usecs] wait time to try to acquire xtables lock
default is 1 second
--line-numbers print line numbers when listing
--exact -x expand numbers (display exact values)
[!] --fragment -f match second or further fragments only
--modprobe=<command> try to insert modules using this command
--set-counters PKTS BYTES set the counter during insert/append
[!] --version -V print package version.
# EC2 서버 IPtables nat, filter 조회
# -t 는 필터, -L은 규칙 목록 조회, -n은 IP 주소 출력, -v 상세 정보 조회
iptables -t filter -L -n -v
iptables -t nat -L
쿠버네티스 서비스
쿠버네티스에서는 네트워크 처리 동작에 따라 iptables, ipvs, nftables, eBPF 로 나뉘어 집니다. doc (chatGPT)
- iptables: 전통적인 패킷 필터링 및 NAT를 사용하여 서비스 트래픽을 처리.
- IPVS (IP Virtual Server): 커널 레벨에서 더 효율적인 로드밸런싱을 제공, 대규모 서비스에 적합.
- nftables: iptables를 대체하는 최신 프레임워크, 더 유연하고 성능 최적화.
- eBPF: 커널 내부에서 직접 트래픽을 관리하여 매우 높은 성능과 유연한 로직 제공.
쿠버네티스 서비스 기본 설정은 iptables 모드입니다.
위에서 확인한 ipables를 통해 서비스 통신을 관리합니다.
아래는 테스트 파드를 3개 배포하였을 때의 NAT 구성 테이블입니다.
라우팅 전 패킷이 들어올 때 PREROUTING → KUBE-SERVICES → KUBE-SVC-### → KUBE-SEP-#<파드1> 순으로 IPTABLE 정책이 적용됩니다.
쿠버네티스 서비스는 기본적으로 파드들에 대해 부하분산이 됩니다.
kind를 통해 k8s클러스터를 구성하고 서비스를 통해 Iptables을 확인하겠습니다.
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
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
runtime-config: api/all=true
- role: worker
labels:
mynode: worker1
- role: worker
labels:
mynode: worker2
- role: worker
labels:
mynode: worker3
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
# 네트워크 모드 확인
kubectl describe cm -n kube-system kube-proxy
배포 노드 네임 확인
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
cat <<EOT> svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 9000 # 서비스 접근 포트
targetPort: 80 # 파드 접근 포트
selector:
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"
# 파드, 서비스 IP 대역 확인
kubectl get pod -owide
kubectl get svc svc-clusterip
# 서비스 정보 확인
kubectl describe svc svc-clusterip
컨트롤 노드에 접근하여 IPTABLES 구성 정보를 확인하겠습니다.
# 컨트롤 노드 접속docker exec -it myk8s-control-plane bash
docker exec -it myk8s-control-plane bash
# PREROUNTING HOOK 훅 확인
iptables -t nat -L PREROUTING
# kuber-service 훅 확인
iptables -t nat -L KUBE-SERVICES
# 서비스 파드 훅 확인
iptables -t nat -L KUBE-SVC-KBDEBIL6IU6WL7RF
- 부하 분산은 KUBE-SVC-KBDEBIL6IU6WL7RF hook을 통해 동작합니다. 처음 파드에서 0.33, 두번째 파드에서 0.5 확률로 매칭하여 백엔드 파드로 보내고 나머지 패킷을 하나의 파드로 보냅니다.
쿠버네티스 서비스 옵션별 Iptables
위에서 확인한 내용은 서비스 중 ClusteIP 타입이였습니다.
그 외 서비스 타입이 NodePort인 경우나 서비스 타입 옵션 sessionAffinity, externalTrafficPolicy 인 경우의 동작 원리를 확인하겠습니다.
sessionAffinity
sessionAffinty는 특정 클라이언트의 연결이 매번 동일한 Pod로 전달하는 옵션입니다.
# sessionAffinity: ClientIP 설정 변경
kubectl patch svc svc-clusterip -p '{"spec":{"sessionAffinity":"ClientIP"}}'
# 적용 확인
kubectl get svc svc-clusterip -o yaml
....
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 기본 3시간동안 같은 클라이언트의 연결로 통신함
type: ClusterIP
# 클라이언트(TestPod) Shell 실행
SVC1=$(kubectl get svc svc-clusterip -o jsonpath={.spec.clusterIP})
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"
최근 IPTables 수정 룰 확인하면 다음과 같이 룰을 확인할 수 있습니다.
iptables -t nat -S | grep recent
- --reap: 오래된 IP 항목을 자동으로 정리하여 기록에서 삭제(10800 초)
- --mask: P 주소를 특정 범위로 마스킹하여, 동일 네트워크 대역에서 온 트래픽을 동일하게 취급하도록 설정
NodePort
NodePort 는 클러스터 외부에서 서비스를 접근할 수 있도록 설정해주는 옵션입니다.
아래 그림을 보면 NodePort는 SNAT 처리를 통해 다른 노드로 트래픽을 전달합니다.
트래픽 return시 전달받았던 노드를 모르고 가면 기존 노드들의 룰이 깨집니다.
스터디에서 제공해준 예제 파드를 배포하여 NodePort 동작 원리를 확인하겠습니다.
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
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 svc-nodeport.yaml,echo-deploy.yaml
kubectl get pods -o wide
kubectl get svc svc-nodeport
kubectl get endpoints svc-nodeport
- ClusterIP와 달리 포트 32518이라는 포트를 확인함
Iptable을 따라가면 노드 포트 룰이라는 정책을 통해 SNAT되는 것을 확인할 수 있습니다.
실제 파드 분산은 iptalbes에서 찾으면 확인할 수 있습니다.
# 컨트롤 노드 접속docker exec -it myk8s-control-plane bash
docker exec -it myk8s-control-plane bash
# PREROUNTING HOOK 훅 확인
iptables -t nat -L PREROUTING
# kuber-service 훅 확인
iptables -t nat -L KUBE-SERVICES
# 노드 포트 확인
iptables -t nat -L KUBE-NODEPORTS
# 분산 처리 확인
iptables -t nat -L KUBE-EXT-VTR7MTHHNMFZ3OFS
iptables -t nat -S | grep KUBE-SVC-VTR7MTHHNMFZ3OFS
externalTrafficPolicy
externalTrafficPolicy는 외부 클라이언트 접근 IP를 보존하기 위해 사용되는 노드 포트 내 옵션입니다.
한 번 특정파드에 접속하게 되면 그 이후의 접속도 같은 특정 파드로 통신하게 됩니다.
kubectl patch svc svc-nodeport -p '{"spec":{"externalTrafficPolicy": "Local"}}'
노드포트 Iptable를 확인하면 --src-type LOCAL 옵션을 통해 같은 노드에서 접근한 트래픽들을 똑같이 전송하라는 룩이 추가된 것을 확인할 수 있습니다.
AWS 보안그룹과 비교
firewall 기능 측면에서 AWS 보안그룹과 IPTABLE과 유사한 점이 있지만, 차이가 있습니다.
AWS 보안 그룹은 hypervisor 레벨에서 제공하는 iptables입니다.
같은 Iptable이지만, AWS 서버(EC2)는 Hypervisor 위 계층에서 제공하는 서버로 계층이 구분됩니다.
- 기본적으로 Iptable 설정이 없습니다. 또한, amazon linux 2023 OS는 Iptable 이 기본적으로 설치되어 있지 않습니다.
서비스 트러블슈팅
쿠버네티스 공식문서, 서비스 트러블슈팅 가이드가 있어 정리합니다.
트러블슈팅 처리는 다음과 같이 도식화할 수 있습니다.
마지막 kube-proxy 확인은 모드 마다 트러블슈팅 명령어가 다릅니다.
공식 문서를 참고하여 kube-proxy 동작을 확인해주세요.
필자는 위에서 설정한 iptables 모드로 노드에서 명령어를 실행하면 다음과 같이 프로세스 정상 동작을 확인할 수 있습니다.
대규모 클러스터에서의 IPtables 모드 성능 최적화
Iptable 모드를 통해 쿠버네티스 서비스를 관리하지만 서비스 하나당 iptables 룰이 약 10개정도 생성됩니다.
큰 규모의 클러스터를 관리하는 경우 iptables 규칙이 그만큼 늘어남에 따라 서비스 반영 및 통신에 영향이 갈 수 있습니다. 이를 최적화하기 위한 방법들을 정리하겠습니다.
minSyncPeriod, syncPeriod 설정 doc
kube-proxy iptables 모드는 iptables를 일정 주기마다 관리합니다.
일정 주기는 minSyncPeriod, syncPeriod 옵션을 통해 설정됩니다.
- minSyncPeriod : kube-proxy가 iptables 규칙을 다시 동기화하는 최소 시간 간격, 기본 1초로 설정되어 있으나, 클러스터가 큰 경우 5~10초로 설정하는 것을 고려할 수 있습니다.
- syncPeriod : 수동으로 iptables 규칙을 수정할 경우, kube-proxy가 자동으로 감지하는 하여 복구하는 간격, 기본 30초 설정
# kube-proxy itpables 모드 옵션 확인
kubectl describe cm -n kube-system kube-proxy
...
# 옵션 수정
kubectl edit cm -n kube-system kube-proxy
- 옵션 수정시 kube-proxy 파드가 재시작되며 서비스 일시적 순단이 발생합니다.
EndpointSlice
각 노드에서는 kube-proxy를 통해 변경사항을 확인하고 Endpoint 리소스를 watch 합니다.
Watch 에 필요한 네트워크 대역폭과 요청 수는 노드 수의 제곱(N^2)만큼 사용되고, 클러스터 규모에 따라 1GB/s 이상의 대역폭이 요구합니다. (커피고래님 클러스터 사례)
이러한 경우 endpointslice를 통해 watch 부하를 대폭 감소시킬 수 있습니다.
endpointslice 는 엔드포인트를 부분집합으로 나누어 관리시켜주는 리소스입니다. 아래 처럼 각 엔드포인트를 나누면 watch 하는 객체가 줄일 수 있습니다.
위에서 구성한 svc-nodeport 를 endpointslice로 나누겠습니다.
cat <<EOT> endpointslice.yaml
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: svc-nodeport-slice
labels:
kubernetes.io/service-name: svc-nodeport
addressType: IPv4
ports:
- name: svc-webport
protocol: TCP
port: 8080
endpoints:
- addresses:
- "10.10.3.3"
- addresses:
- "10.10.2.3"
EOT
# 엔드포인트슬라이스 배포
kubectl apply -f endpointslce.yaml
# 객체 확인
kubectl get endpointslice
- 서비스에서 생성한 엔드포인트slice와 구분되어 생성됩니다. 여기서 서비스가 생성한 endpointslice를 삭제하면 10.10.0.1.3 에 트래픽이 가지 않습니다.
참고
https://netpple.github.io/2021/deep-dive-iptables-netfilter-architecture/
https://netpple.github.io/2022/netfilter-iptables/
https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/
🧬AWS VPC 내부 패킷 미러/덤프 packet mirror/dump
hongkunyoo [번역] 쿠버네티스 7,500개 노드 운영하기
Kubernetes Virtual IPs and Service Proxies
https://kubernetes.io/ko/docs/concepts/services-networking/service/#엔드포인트슬라이스
'Cloud Tech' 카테고리의 다른 글
AWS Load Balancer Controller 기능 분석 (4) | 2024.10.13 |
---|---|
k8s node-shell 원리이해 (0) | 2024.10.04 |
Calico CNI 이해 (0) | 2024.09.20 |
쿠버네티스 네트워크이해하기(Kind, Pause, Ephemeral Container, FlannelCNI) (0) | 2024.09.06 |
컨테이너 구성 이해와 도커 취약점 점검하기 (0) | 2024.08.30 |