[AEWS 2주차] EKS Networking
※ AEWS는 '가시다'님이 속한 CloudNet@에서 진행하는 AWS EKS workshop에 대한 스터디 내용입니다.
참고문서
[한글 EKS Hands On LAB] EKS Service - Link , AWS LB Controller - Link
AWS EKS Best Practices Guide - Link
0. 실습 환경 배포
1) macOS 실습을 위한 툴 및 설정
# Install awscli
brew install awscli
aws --version
# Install eksctl
brew install eksctl
eksctl version
# Install kubectl
brew install kubernetes-cli
kubectl version --client=true
# Install Helm
brew install helm
helm version
# krew 툴 및 플러그인 설치
brew install krew
kubectl krew version
kubectl krew install neat get-all df-pv stern
kubectl krew list
# 편리성 툴 설치
brew install kube-ps1
brew install kubectx
# kubectl 단축 및 하이라이트 설정
brew install kubecolor
echo "alias k=kubectl" >> ~/.zshrc
echo "alias kubectl=kubecolor" >> ~/.zshrc
echo "compdef kubecolor=kubectl" >> ~/.zshrc
2) AWS CloudFormation 을 통해 기본 실습 환경 배포
# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-2week.yaml
# 배포
# aws cloudformation deploy --template-file myeks-1week.yaml --stack-name mykops --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region <리전>
예시) aws cloudformation deploy --template-file ~/Downloads/myeks-2week.yaml \
--stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 운영서버 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text
예시) 3.35.137.31
# 운영서버 EC2 에 SSH 접속
예시) ssh ec2-user@3.35.137.31
ssh -i <ssh 키파일> ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
3) eksctl 을 통해 EKS 배포
#
export CLUSTER_NAME=myeks
# myeks-VPC/Subnet 정보 확인 및 변수 지정
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
echo $VPCID
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
echo $PubSubnet1 $PubSubnet2 $PubSubnet3
# 출력된 내용 참고 : 아래 yaml 파일 참고해서 vpc/subnet id, ssh key 경로 수정
eksctl create cluster --name $CLUSTER_NAME --region=ap-northeast-2 --nodegroup-name=ng1 --node-type=t3.medium --nodes 3 --node-volume-size=30 --vpc-public-subnets "$PubSubnet1","$PubSubnet2","$PubSubnet3" --version 1.31 --with-oidc --external-dns-access --full-ecr-access --alb-ingress-access --node-ami-family AmazonLinux2023 --ssh-access --dry-run > myeks.yaml
myeks.yaml 파일 작성
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: myeks
region: ap-northeast-2
version: "1.31"
kubernetesNetworkConfig:
ipFamily: IPv4
iam:
vpcResourceControllerPolicy: true
withOIDC: true
accessConfig:
authenticationMode: API_AND_CONFIG_MAP
vpc:
autoAllocateIPv6: false
cidr: 192.168.0.0/16
clusterEndpoints:
privateAccess: true # if you only want to allow private access to the cluster
publicAccess: true # if you want to allow public access to the cluster
id: vpc-027a62dede58aba5d # 각자 환경 정보로 수정
manageSharedNodeSecurityGroupRules: true # if you want to manage the rules of the shared node security group
nat:
gateway: Disable
subnets:
public:
ap-northeast-2a:
az: ap-northeast-2a
cidr: 192.168.1.0/24
id: subnet-0b87bdfff6473afe4 # 각자 환경 정보로 수정
ap-northeast-2b:
az: ap-northeast-2b
cidr: 192.168.2.0/24
id: subnet-05a7563fc44b9679a # 각자 환경 정보로 수정
ap-northeast-2c:
az: ap-northeast-2c
cidr: 192.168.3.0/24
id: subnet-09b782b279181f8d7 # 각자 환경 정보로 수정
addons:
- name: vpc-cni # no version is specified so it deploys the default version
version: latest # auto discovers the latest available
attachPolicyARNs: # attach IAM policies to the add-on's service account
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
configurationValues: |-
enableNetworkPolicy: "true"
- name: kube-proxy
version: latest
- name: coredns
version: latest
- name: metrics-server
version: latest
privateCluster:
enabled: false
skipEndpointCreation: false
managedNodeGroups:
- amiFamily: AmazonLinux2023
desiredCapacity: 3
disableIMDSv1: true
disablePodIMDS: false
iam:
withAddonPolicies:
albIngress: false # Disable ALB Ingress Controller
appMesh: false
appMeshPreview: false
autoScaler: false
awsLoadBalancerController: true # Enable AWS Load Balancer Controller
certManager: true # Enable cert-manager
cloudWatch: false
ebs: false
efs: false
externalDNS: true # Enable ExternalDNS
fsx: false
imageBuilder: true
xRay: false
instanceSelector: {}
instanceType: t3.medium
preBootstrapCommands:
# install additional packages
- "dnf install nvme-cli links tree tcpdump sysstat ipvsadm ipset bind-utils htop -y"
# disable hyperthreading
- "for n in $(cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list | cut -s -d, -f2- | tr ',' '\n' | sort -un); do echo 0 > /sys/devices/system/cpu/cpu${n}/online; done"
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: ng1
maxSize: 3
minSize: 3
name: ng1
privateNetworking: false
releaseVersion: ""
securityGroups:
withLocal: null
withShared: nullddd
ssh:
allow: true
publicKeyName: jenakey # 각자 환경 정보로 수정
tags:
alpha.eksctl.io/nodegroup-name: ng1
alpha.eksctl.io/nodegroup-type: managed
volumeIOPS: 3000
volumeSize: 30
volumeThroughput: 125
volumeType: gp3
최종 yaml 로 eks 배포
# kubeconfig 파일 경로 위치 지정 :
export KUBECONFIG=$HOME/kubeconfig
혹은 각자 편한 경로 위치에 파일 지정
export KUBECONFIG=~/Downloads/kubeconfig
# 배포
eksctl create cluster -f myeks.yaml --verbose 4
EKS 정보 확인
#
kubectl cluster-info
eksctl get cluster
# 네임스페이스 default 변경 적용
kubens default
#
kubectl ctx
cat $KUBECONFIG | grep current-context
kubectl config rename-context "<각자 자신의 IAM User>@myeks.ap-northeast-2.eksctl.io" "eksworkshop"
kubectl config rename-context "admin@myeks.ap-northeast-2.eksctl.io" "eksworkshop"
cat $KUBECONFIG | grep current-context
#
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get node -v=6
#
kubectl get pod -A
kubectl get pdb -n kube-system
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
coredns N/A 1 1 28m
metrics-server N/A 1 1 28m
# 관리형 노드 그룹 확인
eksctl get nodegroup --cluster $CLUSTER_NAME
aws eks describe-nodegroup --cluster-name $CLUSTER_NAME --nodegroup-name ng1 | jq
# eks addon 확인
eksctl get addon --cluster $CLUSTER_NAME
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES POD IDENTITY ASSOCIATION ROLES
coredns v1.11.4-eksbuild.2 ACTIVE 0
kube-proxy v1.31.3-eksbuild.2 ACTIVE 0
metrics-server v0.7.2-eksbuild.1 ACTIVE 0
vpc-cni v1.19.2-eksbuild.1 ACTIVE 0 arn:aws:iam::911283464785:role/eksctl-myeks-addon-vpc-cni-Role1-WqTaK0VjADcW enableNetworkPolicy: "true
4) 관리형 노드 그룹(EC2) 접속 및 노드 정보 확인
# 인스턴스 공인 IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{InstanceID:InstanceId, PublicIPAdd:PublicIpAddress, PrivateIPAdd:PrivateIpAddress, InstanceName:Tags[?Key=='Name']|[0].Value, Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
# 인스턴스 공인 IP 변수 지정
export N1=<az1 배치된 EC2 공인 IP>
export N2=<az2 배치된 EC2 공인 IP>
export N3=<az3 배치된 EC2 공인 IP>
export N1=43.203.169.0
export N2=13.125.28.29
export N3=13.125.255.7
echo $N1, $N2, $N3
# ping 테스트
ping -c 2 $N1
ping -c 2 $N2
# *nodegroup-ng1* 포함된 보안그룹 ID
export MNSGID=<각자 자신의 관리형 노드 그룹(EC2) 에 보안그룹 ID>
export MNSGID=sg-075e2e6178557c95a
# 해당 보안그룹 inbound 에 자신의 집 공인 IP 룰 추가
aws ec2 authorize-security-group-ingress --group-id $MNSGID --protocol '-1' --cidr $(curl -s ipinfo.io/ip)/32
# 해당 보안그룹 inbound 에 운영서버 내부 IP 룰 추가
aws ec2 authorize-security-group-ingress --group-id $MNSGID --protocol '-1' --cidr 172.20.1.100/32
# AWS EC2 관리 콘솔에서 EC2에 보안 그룹에 inbound rule 에 추가된 규칙 정보 확인
# ping 테스트
ping -c 2 $N1
ping -c 2 $N2
# 워커 노드 SSH 접속
ssh -i <SSH 키> -o StrictHostKeyChecking=no ec2-user@$N1 hostname
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -o StrictHostKeyChecking=no ec2-user@$i hostname; echo; done
ssh ec2-user@$N1
exit
ssh ec2-user@$N2
exit
ssh ec2-user@$N2
exit
------------------
# 운영서버 EC2에서 접속 시
## 인스턴스 공인 IP 변수 지정
export N1=<az1 배치된 EC2 내부 IP>
export N2=<az2 배치된 EC2 내부 IP>
export N3=<az3 배치된 EC2 내부 IP>
export N1=192.168.1.186
export N2=192.168.2.123
export N3=192.168.3.174
echo $N1, $N2, $N3
## ping 테스트
ping -c 2 $N1
ping -c 2 $N2
노드 정보 확인
# 노드 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i hostnamectl; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo iptables -t nat -S; echo; done
# Node cgroup version : v1(tmpfs), v2(cgroup2fs) - Link
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i stat -fc %T /sys/fs/cgroup/; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i findmnt -t cgroup2; echo; done
#
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo systemctl status kubelet; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i ps axf |grep /usr/bin/containerd; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo tree /etc/kubernetes/kubelet/; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo cat /etc/kubernetes/kubelet/config.json | jq; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo cat /etc/kubernetes/kubelet/config.json.d/00-nodeadm.conf | jq; echo; done
#
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i lsblk; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i df -hT /; echo; done
# 컨테이너 리스트 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ctr -n k8s.io container list; echo; done
CONTAINER IMAGE RUNTIME
28b6a15c475e32cd8777c1963ba684745573d0b6053f80d2d37add0ae841eb45 602401143452.dkr.ecr-fips.us-east-1.amazonaws.com/eks/pause:3.5 io.containerd.runc.v2
4f266ebcee45b133c527df96499e01ec0c020ea72785eb10ef63b20b5826cf7c 602401143452.dkr.ecr-fips.us-east-1.amazonaws.com/eks/pause:3.5 io.containerd.runc.v2
...
# 컨테이너 이미지 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ctr -n k8s.io image list --quiet; echo; done
...
# 태스크 리스트 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ctr -n k8s.io task list; echo; done
5) 운영서버 EC2에서 eks 를 사용 할 수 있게 설정
# eks 설치한 iam 자격증명을 설정하기
aws configure
...
# get-caller-identity 확인
aws sts get-caller-identity --query Arn
# kubeconfig 생성
cat ~/.kube/config
aws eks update-kubeconfig --name myeks --user-alias <위 출력된 자격증명 사용자>
aws eks update-kubeconfig --name myeks --user-alias admin
# 추가된 kubeconfig 정보 확인
cat ~/.kube/config
# eks api dig 조회 : VPC 내부에서 질의하는데 왜 그럴까? private hosted zone 의 특징을 알아보자
APIDNS=$(aws eks describe-cluster --name myeks | jq -r .cluster.endpoint | cut -d '/' -f 3)
dig +short $APIDNS
#
kubectl cluster-info
kubectl ns default
kubectl get node -v6
EKS 를 public and private endpoint 으로 구성을 했다.
EKS 가 배포된 VPC 외부에서 API Endpoint 도메인 질의를 했기 때문에 Public IP 가 나온다.
EKS 가 배포된 VPC 내부에서 질의를 하면 Private IP 가 나온다.
/root/.kube/config 을 사용해서 토근 인증을 하고 API Endpoint 에 요청을 보낸다.
1. AWS VPC CNI 소개
1) 설명
K8S CNI : Container Network Interface 는 k8s 네트워크 환경을 구성해준다 - 링크, 다양한 플러그인이 존재 - 링크
AWS VPC CNI : 파드의 IP를 할당해준다, 파드의 IP 네트워크 대역과 노드(워커)의 IP 대역이 같아서 직접 통신이 가능하다 - 링크
- CNI Binary: Pod 네트워크가 Pod-to-Pod 통신을 가능하게 하도록 설정합니다. CNI Binary는 노드 루트 파일 시스템에서 실행되며, 새로운 Pod가 추가되거나 기존 Pod가 노드에서 제거될 때 kubelet에 의해 호출됩니다.
- ipamd는 장기적으로 운영되는 노드-로컬 IP 주소 관리(IPAM) 데몬으로, 다음을 담당합니다:
- 노드에서 ENI 관리
- 사용 가능한 IP 주소 또는 접두사의 웜 풀 유지 관리
Amazon VPC CNI는 작업자 노드에 aws-node라는 Kubernetes Daemonset으로 배포된다.
작업자 노드가 프로비저닝되면 기본 ENI라고 하는 기본 ENI가 노드에 연결되며, CNI는 노드의 기본 ENIs에 연결된 서브넷에서 ENI 및 보조 IP 주소의 웜 풀을 할당한다.
AWS VPC CNI의 특징
- 네트워크 통신의 최적화(성능, 지연)를 위해서 노드와 파드의 네트워크 대역을 동일하게 설정함
- 네트워크 대역을 동일하게 설정하면, 파드와 노드 간 통신이 같은 네트워크 대역 내에서 이루어져 추가적인 라우팅 경로가 필요 없게 된다. 이렇게 설정할 경우, 데이터가 전달되는 경로가 짧아지기 때문에 전송 속도가 빨라지고 지연(latency)도 줄어들게 된다.
AWS VPC CNI 을 통한 최대 파드 개수 산정 공식
2) 실습
네트워크 기본 정보 확인
# CNI 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
# kube-proxy config 확인 : 모드 iptables 사용 >> ipvs 모드로 변경 해보자!
kubectl describe cm -n kube-system kube-proxy-config
...
mode: "iptables"
...
# 노드 IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
# 파드 IP 확인
kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
# 파드 이름 확인
kubectl get pod -A -o name
# 파드 갯수 확인
kubectl get pod -A -o name | wc -l
노드에 네트워크 정보 확인
# CNI 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i tree /var/log/aws-routed-eni; echo; done
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/plugin.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/ipamd.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/egress-v6-plugin.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/ebpf-sdk.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/network-policy-agent.log | jq
# 네트워크 정보 확인 : eniY는 pod network 네임스페이스와 veth pair
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -br -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
ssh ec2-user@$N1 sudo iptables -t nat -S
ssh ec2-user@$N1 sudo iptables -t nat -L -n -v
2. 노드에서 기본 네트워크 정보 확인
1) 노드 네트워크 구성 및 특징
Network 네임스페이스는 호스트(Root)와 파드 별(Per Pod)로 구분된다
특정한 파드(kube-proxy, aws-node)는 호스트(Root)의 IP를 그대로 사용한다 -> 파드의 Host Network 옵션
- t3.medium 의 경우 ENI 마다 최대 6개의 IP를 가질 수 있다
- ENI0, ENI1 으로 2개의 ENI는 자신의 IP 이외에 추가적으로 5개의 보조 프라이빗 IP를 가질수 있다
- coredns 파드는 veth 으로 호스트에는 eniY@ifN 인터페이스와 파드에 eth0 과 연결되어 있다
2) 실습
보조 IPv4 주소를 파드가 사용하는지 확인
# coredns 파드 IP 정보 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6777fcd775-57k77 1/1 Running 0 70m 192.168.1.142 ip-192-168-1-251.ap-northeast-2.compute.internal <none> <none>
coredns-6777fcd775-cvqsb 1/1 Running 0 70m 192.168.2.75 ip-192-168-2-34.ap-northeast-2.compute.internal <none> <none>
# 노드의 라우팅 정보 확인 >> EC2 네트워크 정보의 '보조 프라이빗 IPv4 주소'와 비교해보자
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
coreDNS 의 pod ip 가 노드별 인터페이스에 할당 된것을 확인 할 수 있다.
# [터미널1~3] 노드 모니터링
ssh ec2-user@$N1
watch -d "ip link | egrep 'ens|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
ssh ec2-user@$N2
watch -d "ip link | egrep 'ens|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
ssh ec2-user@$N3
watch -d "ip link | egrep 'ens|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
# 테스트용 netshoot-pod 디플로이먼트 생성
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot-pod
spec:
replicas: 3
selector:
matchLabels:
app: netshoot-pod
template:
metadata:
labels:
app: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[0].metadata.name}')
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[1].metadata.name}')
PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[2].metadata.name}')
# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
# 노드에 라우팅 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
- 파드가 생성되면, 워커 노드에 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가된다
- 테스트용 파드 eniY 정보 확인 - 워커 노드 EC2
# 노드3에서 네트워크 인터페이스 정보 확인
ssh ec2-user@$N3
----------------
ip -br -c addr show
ip -c link
ip -c addr
ip route # 혹은 route -n
# 네임스페이스 정보 출력 -t net(네트워크 타입)
sudo lsns -t net
# PID 정보로 파드 정보 확인
PID=<PID> # PID 높은 것 중 COMMAND가 pause 인것
sudo nsenter -t $PID -n ip -c addr
sudo nsenter -t $PID -n ip -c route
exit
----------------
테스트용 파드 접속 후 확인
# 노드3에서 네트워크 인터페이스 정보 확인
ssh ec2-user@$N3
----------------
ip -br -c addr show
ip -c link
ip -c addr
ip route # 혹은 route -n
# 네임스페이스 정보 출력 -t net(네트워크 타입)
sudo lsns -t net
# PID 정보로 파드 정보 확인
PID=<PID> # PID 높은 것 중 COMMAND가 pause 인것
sudo nsenter -t $PID -n ip -c addr
sudo nsenter -t $PID -n ip -c route
# 테스트용 파드 접속(exec) 후 Shell 실행
kubectl exec -it $PODNAME1 -- zsh
# 아래부터는 pod-1 Shell 에서 실행 : 네트워크 정보 확인
----------------------------
ip -c addr
ip -c route
route -n
ping -c 1 <pod-2 IP>
ps
cat /etc/resolv.conf
exit
----------------------------
# 파드2 Shell 실행
kubectl exec -it $PODNAME2 -- ip -c addr
# 파드3 Shell 실행
kubectl exec -it $PODNAME3 -- ip -br -c addr
pod 에 접속해서 /etc/resolv.conf 를 확인해 보면 coreDNS 의 service ip 와 동일하다.
3. 노드 간 파드 통신
EKS에서는 AWS VPC CNI를 통해 Pod와 노드가 동일한 VPC 네트워크 대역을 사용하여 직접 통신할 수 있다.
이는 Pod가 VPC 내의 ENI로부터 직접 IP를 할당받아 사용하기 때문이다.
파드간 통신 테스트 및 확인: 별도의 NAT 동작 없이 통신 가능!
# 파드 IP 변수 지정
PODIP1=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[0].status.podIP}')
PODIP2=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[1].status.podIP}')
PODIP3=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[2].status.podIP}')
# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
# 파드2 Shell 에서 파드3로 ping 테스트
kubectl exec -it $PODNAME2 -- ping -c 2 $PODIP3
# 파드3 Shell 에서 파드1로 ping 테스트
kubectl exec -it $PODNAME3 -- ping -c 2 $PODIP1
# 워커 노드 EC2 : TCPDUMP 확인
## For Pod to external (outside VPC) traffic, we will program iptables to SNAT using Primary IP address on the Primary ENI.
sudo tcpdump -i any -nn icmp
sudo tcpdump -i ens5 -nn icmp
sudo tcpdump -i ens6 -nn icmp
sudo tcpdump -i eniYYYYYYYY -nn icmp
[워커 노드1]
# routing policy database management 확인
ip rule
# routing table management 확인
ip route show table local
# 디폴트 네트워크 정보를 ens5 을 통해서 빠져나간다
ip route show table main
default via 192.168.1.1 dev ens5
...
NAT 없이 pod to pod 통신이 된 것을 확인 할 수 있다.
192.168.3.104 > 192.168.2.216
4. 파드에서 외부 통신
파드에서 외부 통신 흐름 : iptable 에 SNAT 을 통하여 노드의 eth0(ens5) IP로 변경되어서 외부와 통신됨
VPC CNI 의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다 - 링크
1) 파드에서 외부 통신 테스트 및 확인
파드 shell 실행 후 외부로 ping 테스트 & 워커 노드에서 tcpdump 및 iptables 정보 확인
# pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 8.8.8.8
# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
sudo tcpdump -i ens5 -nn icmp
# 퍼블릭IP 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i curl -s ipinfo.io/ip; echo; echo; done
# 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
for i in $PODNAME1 $PODNAME2 $PODNAME3; do echo ">> Pod : $i <<"; kubectl exec -it $i -- curl -s ipinfo.io/ip; echo; echo; done
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul?format=3
kubectl exec -it $PODNAME1 -- curl -s wttr.in/Moon
kubectl exec -it $PODNAME1 -- curl -s wttr.in/:help
# 워커 노드 EC2
## 출력된 결과를 보고 어떻게 빠져나가는지 고민해보자!
ip rule
ip route show table main
sudo iptables -L -n -v -t nat
sudo iptables -t nat -S
# 파드가 외부와 통신시에는 아래 처럼 'AWS-SNAT-CHAIN-0' 룰(rule)에 의해서 SNAT 되어서 외부와 통신!
# 참고로 뒤 IP는 eth0(ENI 첫번째)의 IP 주소이다
# --random-fully 동작 - 링크1 링크2
sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 ! -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
-A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.1.251 --random-fully
## 아래 'mark 0x4000/0x4000' 매칭되지 않아서 RETURN 됨!
-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
...
# 카운트 확인 시 AWS-SNAT-CHAIN-0에 매칭되어, 목적지가 192.168.0.0/16 아니고 외부 빠져나갈때 SNAT 192.168.1.251(EC2 노드1 IP) 변경되어 나간다!
sudo iptables -t filter --zero; sudo iptables -t nat --zero; sudo iptables -t mangle --zero; sudo iptables -t raw --zero
watch -d 'sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-0; echo ; sudo iptables -v --numeric --table nat --list KUBE-POSTROUTING; echo ; sudo iptables -v --numeric --table nat --list POSTROUTING'
# conntrack 확인 : EC2 메타데이터 주소(169.254.169.254) 제외 출력
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo conntrack -L -n |grep -v '169.254.169'; echo; done
conntrack v1.4.5 (conntrack-tools):
icmp 1 28 src=172.30.66.58 dst=8.8.8.8 type=8 code=0 id=34392 src=8.8.8.8 dst=172.30.85.242 type=0 code=0 id=50705 mark=128 use=1
tcp 6 23 TIME_WAIT src=172.30.66.58 dst=34.117.59.81 sport=58144 dport=80 src=34.117.59.81 dst=172.30.85.242 sport=80 dport=44768 [ASSURED] mark=128 use=1
pod 에서 외부 호출을 할때 ens5(172.217.25.164) ip, 즉 NAT 으로 나가는 것을 확인 할 수 있다.
외부로 패킷이 나갈때 source NAT 되어 나간다.
운영서버 EC2 -> 파드 IP 통신
# 운영서버 EC2 SSH 접속
ssh <운영서버 EC2 공인 IP>
-----------------------
POD1IP=<파드1 IP 지정>
POD1IP=192.168.1.101
ping -c 1 $POD1IP
exit
-----------------------
# 워커노드1 에서 tcpdump 확인 : NAT 동작 적용 여유 확인
sudo tcpdump -i any -nn icmp
파드1 -> 운영서버 EC2 통신
# vpc cni env 정보 확인
kubectl get ds aws-node -n kube-system -o json | jq '.spec.template.spec.containers[0].env'
...
{
"name": "AWS_VPC_K8S_CNI_EXTERNALSNAT",
"value": "false"
},
...
# 운영서버 EC2 SSH 접속
kubectl exec -it $PODNAME1 -- ping 172.20.1.100
# 파드1 배치 워커노드에서 tcpdump 확인 : NAT 동작 적용 여유 확인
sudo tcpdump -i any -nn icmp
# 운영서버 EC2 에서 tcpdump 확인 : NAT 동작 적용 여유 확인
sudo tcpdump -i any -nn icmp
-----------------------------------------------------
# 파드1 배치 워커노드 : NAT 적용 정책 확인
sudo iptables -t filter --zero; sudo iptables -t nat --zero; sudo iptables -t mangle --zero; sudo iptables -t raw --zero
watch -d 'sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-0; echo ; sudo iptables -v --numeric --table nat --list KUBE-POSTROUTING; echo ; sudo iptables -v --numeric --table nat --list POSTROUTING'
파드와 운영 EC2간에는 SNAT을 통해 통신이 이루어지고 있음을 확인할 수 있다.
2) 다음 실습을 위해 디플로이먼트 삭제:
kubectl delete deploy netshoot-pod
5. 노드에 파드 생성 갯수 제한
1) 사전준비: kube-ops-view 설치
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=LoadBalancer --set env.TZ="Asia/Seoul" --namespace kube-system
# kube-ops-view 접속 URL 확인 (1.5 배율)
kubectl get svc -n kube-system kube-ops-view -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "KUBE-OPS-VIEW URL = http://"$1":8080/#scale=1.5"}'
2) 워커노드 인스턴스 타입별 최대 생성 가능한 pod 개수 확인
# t3 타입의 정보(필터) 확인 (Values에 클래스 명을 설정하면 다른 클래스 정보도 확인 가능)
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.\* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# Allocatable Pods 값을 통해 최대로 설정 가능한 파드 수 확인 가능
kubectl describe node | grep Allocatable: -A6
3) 최대 파드 생성 테스트
# 워커 노드 3대 EC2 - 모니터링
while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
# 터미널1
watch -d 'kubectl get pods -o wide'
# 터미널2
## 디플로이먼트 생성
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
EOF
# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
kubectl scale deployment nginx-deployment --replicas=8
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=15
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=30
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=50
# 파드 생성 실패 확인
kubectl get pods | grep Pending
kubectl describe pod <Pending 파드> | grep Events: -A5
# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment
파드 개수를 30개로 늘리자 ENI가 한개 더 붙고 그에따라 보조 ip 도 증가했다.
파드 개수를 50개로 늘리자 생성가능한 개수를 초과하여 더이상 생성되지 않고 pending 상태의 pod가 만들어진다.
6. Service & AWS LoadBalancer Controller
kubernetes service
쿠버네티스에서 서비스는 파드들에 대한 단일 진입점을 제공하는 리소스이다.
서비스가 필요한 이유는 파드는 언제든 생성되거나 종료될 수 있으며, 이 과정에서 IP 주소가 동적으로 변경될 수 있다.
서비스는 고정 IP와 도메인 네임을 제공하며, 여러개의 파드로 부하 분산이 가능하다. (iptables 룰로 처리)
kube proxy
kube-proxy는 쿠버네티스에서 서비스의 네트워크 트래픽을 관리하고 로드밸런싱하는 핵심 컴포넌트로, 각 노드에서 실행되어 서비스 IP를 실제 파드 IP로 변환하는 역할을 수행한다.
kube proxy mode
kube proxy 에서는 iptable 모드와 ipvs모드를 사용할 수 있고 두 모드는 다음과 같은 차이점이 있다.
- iptables 모드
- iptables 모드는 리눅스 커널의 방화벽 기능 (iptable)을 사용하여 패킷 처리 및 필터링을 진행한다.
- 해당 방식은 iptable내 라우팅 규칙을 순차적으로 처리한다.
- 쿠버네티스 설치 시 기본적으로 사용되는 디폴트 방식이다.
서비스 IP 주소에서 각 Backend Pod로의 직접적으로 라우팅한다.
- ipvs 모드
- ipvs모드는 L4 로드 밸런서 기능을 사용하여 해시 테이블 기반의 필터링을 사용한다.
- iptables 모드보다 성능이 더욱 효율적이다.
다이어그램을 통해 확인해보면 레이블과 포트 정보가 명시적으로 표시되지 않는다.
즉 L4 로드밸런서의 특성을 반영하여 Backend Pod들이 하나의 서버 풀로 관리된다.
1) 서비스 종류
- ClusterIP 타입
클러스터 내부에서만 접근 가능한 가상IP를 할당받아 파드 간 내부 통신을 위해 안정적인 엔드포인트를 제공한다. (외부에서 접근 불가)
- NodePort 타입
쿠버네티스 클러스터 외부에서 노드의 IP:NodePort로 접속할 수 있다.
다른 노드에 있는 pod 접근을 위해 내부 iptables를 이용하여 찾아간다. (비효율적)
- LoadBalancer 타입
Cloud Provider 를 통해 로드 밸런서를 자동으로 프로비저닝하고 외부에서 쿠버네티스 클러스터로 접근할 수 있다.
Cloud Controller Manager 를 통해 K8S NodePort 정보를 사용하는 CLB/NLB 프로비저닝 방법
EKS에서 서비스 타입을 loadbalancer로 설정하면 내장된 Service controller를 통해 로드밸런서 타입에 따른 로드밸런서를 프로비저닝한다.
CLB/NLB 를 프로비저닝 할 수 있으며, 서비스 NodePort 만 가능하다. (인스턴스 모드만 가능하다.)
Service (LoadBalancer Controller) : AWS Load Balancer Controller + NLB (파드) IP 모드 동작 with AWS VPC CNI
IP모드 동작은 AWS LoadBalancer Controller 가 eks 클러스터를 통해 동적으로 pod ip 정보를 받아와서 LB 타겟그룹에 바인딩 해줘야 한다. (IP 모드를 사용하려면 LB Cotroller 를 설치해야한다. )
AWS VPC CNI 를 사용하기 때문에 간단한 설정으로 pod ip 로 바로 통신이 가능한 것이지, 다른 CNI 를 사용했으면 추가 설정이 필요하기 때문에 쉽지 않다.
Load Balancer 대상 유형 선택 - Link
- 타겟그룹 유형을 Instance 로 지정하게 되면 노드에 NodePort 로 전달된다.
그리고 서버 내부의 iptables 규칙에 의해 서비스 ClusterIP로 전달된다. 마지막으로 서비스는 등록된 파드를 임의로 선택하고 트래픽을 전달한다. 이 흐름에는 여러 홉이 포함되며, 특히 서비스가 다른 AZ에도 있을 수 노드에서 실행되는 파드를 선택하기 때문에 추가 지연시간이 발생 할 수 있다. - 타겟그룹 유형을 IP 로 지정하면 LB의 트래픽이 다이렉트로 pod 로 전달된다.
이는 노드 및 서비스 ClusterIP 의 추가 홉을 우회하기 때문에 네트워크 경로가 간소화 되기때문에 해당 유형을 사용할 것을 권장한다.
2) 실습
AWS LoadBalancer Controller 배포 - Link
# 설치 전 CRD 확인
kubectl get crd
# Helm Chart 설치
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME
## 설치 확인
kubectl get crd
kubectl explain ingressclassparams.elbv2.k8s.aws
kubectl explain targetgroupbindings.elbv2.k8s.aws
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
Service Account: aws-load-balancer-controller
# 클러스터롤, 롤 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
targetgroupbindings.elbv2.k8s.aws [] [] [create delete get list patch update watch]
events [] [] [create patch]
ingresses [] [] [get list patch update watch]
services [] [] [get list patch update watch]
ingresses.extensions [] [] [get list patch update watch]
services.extensions [] [] [get list patch update watch]
ingresses.networking.k8s.io [] [] [get list patch update watch]
services.networking.k8s.io [] [] [get list patch update watch]
endpoints [] [] [get list watch]
namespaces [] [] [get list watch]
nodes [] [] [get list watch]
pods [] [] [get list watch]
endpointslices.discovery.k8s.io [] [] [get list watch]
ingressclassparams.elbv2.k8s.aws [] [] [get list watch]
ingressclasses.networking.k8s.io [] [] [get list watch]
ingresses/status [] [] [update patch]
pods/status [] [] [update patch]
services/status [] [] [update patch]
targetgroupbindings/status [] [] [update patch]
ingresses.elbv2.k8s.aws/status [] [] [update patch]
pods.elbv2.k8s.aws/status [] [] [update patch]
services.elbv2.k8s.aws/status [] [] [update patch]
targetgroupbindings.elbv2.k8s.aws/status [] [] [update patch]
ingresses.extensions/status [] [] [update patch]
pods.extensions/status [] [] [update patch]
services.extensions/status [] [] [update patch]
targetgroupbindings.extensions/status [] [] [update patch]
ingresses.networking.k8s.io/status [] [] [update patch]
pods.networking.k8s.io/status [] [] [update patch]
services.networking.k8s.io/status [] [] [update patch]
targetgroupbindings.networking.k8s.io/status [] [] [update patch]
aws load balancer controller 를 설치하면 하기 두개의 crd 가 생성된다.
kubectl get crd
kubectl explain ingressclassparams.elbv2.k8s.aws
kubectl explain targetgroupbindings.elbv2.k8s.aws
서비스/파드 배포 테스트 with NLB
# 모니터링
watch -d kubectl get pod,svc,ep,endpointslices
# 디플로이먼트 & 서비스 생성
cat << EOF > echo-service-nlb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
selector:
app: deploy-websrv
EOF
kubectl apply -f echo-service-nlb.yaml
# 확인
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq
# AWS 관리콘솔에서 NLB 정보 확인
# 빠른 실습을 위해서 등록 취소 지연(드레이닝 간격) 수정 : 기본값 300초
echo-service-nlb.yaml 파일 IDE(VS code)에서 수정
..
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: deregistration_delay.timeout_seconds=60
...
kubectl apply -f echo-service-nlb.yaml
# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-default-svcnlbip`) == `true`].LoadBalancerArn' | jq -r '.[0]')
aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn')
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq
{
"TargetHealthDescriptions": [
{
"Target": {
"Id": "192.168.2.153",
"Port": 8080,
"AvailabilityZone": "ap-northeast-2b"
},
"HealthCheckPort": "8080",
"TargetHealth": {
"State": "initial",
"Reason": "Elb.RegistrationInProgress",
"Description": "Target registration is in progress"
}
},
...
# 웹 접속 주소 확인
kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "Pod Web URL = http://"$1 }'
# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f
kubectl stern -l app=deploy-websrv
# 분산 접속 확인
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -s $NLB
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
52 Hostname: deploy-echo-55456fc798-2w65p
48 Hostname: deploy-echo-55456fc798-cxl7z
# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
리소스 생성 확인
NLB 타겟 그룹 대상 유형에 pod ip 가 등록된 것을 확인 할 수 있다.
파드 2개 → 1개 → 3개 설정 시 동작 : auto discovery
# (신규 터미널) 모니터링
while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done
# 작업용 EC2 - 파드 1개 설정
kubectl scale deployment deploy-echo --replicas=1
# 확인
kubectl get deploy,pod,svc,ep
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
# 파드 3개 설정
kubectl scale deployment deploy-echo --replicas=3
# 확인 : NLB 대상 타켓이 아직 initial 일 때 100번 반복 접속 시 어떻게 되는지 확인해보자!
kubectl get deploy,pod,svc,ep
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
#
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep -i 'Service Account'
Service Account: aws-load-balancer-controller
# [AWS LB Ctrl] 클러스터 롤 바인딩 정보 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
# [AWS LB Ctrl] 클러스터롤 확인
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
로드 밸런싱이 되어 두개의 파드로 트래픽이 전달되었다.
7. Ingress
인그레스는 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할
AWS Load Balancer Controller + Ingress (ALB) IP 모드 동작 with AWS VPC CNI
Ingress ALB 통해서 서비스 Traffic 접근할 경우, ALB가 대상 POD에 대한 IP/Port 를 관리하고 있어 NodePort 서비스를 Bypass 하여 Traffic 을 전달한다. 추가 홉이 제거되어 네트워크 경로가 간소화된다.
서비스/파드 배포 테스트 with Ingress(ALB) - ALB
# 게임 파드와 Service, Ingress 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
EOF
# 모니터링
watch -d kubectl get pod,ingress,svc,ep,endpointslices -n game-2048
# 생성 확인
kubectl get ingress,svc,ep,pod -n game-2048
kubectl get-all -n game-2048
kubectl get targetgroupbindings -n game-2048
# ALB 생성 확인
aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`]' | jq
ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`].LoadBalancerArn' | jq -r '.[0]')
aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn')
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq
# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath="{.status.loadBalancer.ingress[*].hostname}{'\n'}"
# 게임 접속 : ALB 주소로 웹 접속
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "Game URL = http://"$1 }'
# 파드 IP 확인
kubectl get pod -n game-2048 -owide
생성된 리소스 확인
ALB에서 pod ip 로 직접 전달한다.
8. CoreDNS
9. Topology Aware Routing - LINK
topology aware hint 기능을 적용하지 않으면 kube proxy 가 설정한 iptables 규칙은 임의의 pod 로 패킷을 전송하여 cross az 통신 비용이 발생한다. 하지만 기능을 적용하면 kube proxy 에 의해 iptables 규칙이 자신과 동일한 AZ에 위치하고 있는 pod에게만 패킷을 전송하도록 설정하기 때문에 cross az 통신 비용이 발생하지 않는다. 하지만 이를 충족하려면 pod 고가용성 구성이 필요하다.
테스트를 위한 디플로이먼트와 서비스 배포
# 현재 노드 AZ 배포 확인
kubectl get node --label-columns=topology.kubernetes.io/zone
NAME STATUS ROLES AGE VERSION ZONE
ip-192-168-1-225.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2a
ip-192-168-2-248.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2b
ip-192-168-3-228.ap-northeast-2.compute.internal Ready <none> 70m v1.24.11-eks-a59e1f0 ap-northeast-2c
# 테스트를 위한 디플로이먼트와 서비스 배포
cat <<EOF | kubectl apply -f -
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: websrv
image: registry.k8s.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-clusterip
spec:
ports:
- name: svc-webport
port: 80
targetPort: 8080
selector:
app: deploy-websrv
type: ClusterIP
EOF
# 확인
kubectl get deploy,svc,ep,endpointslices
kubectl get pod -owide
kubectl get svc,ep svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml
# 접속 테스트를 수행할 클라이언트 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 확인
kubectl get pod -owide
테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인 : AZ(zone) 상관없이 랜덤 확률 부하분산 동작
# 디플로이먼트 파드가 배포된 AZ(zone) 확인
kubectl get pod -l app=deploy-websrv -owide
# 테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인
kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-h9vst
kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-45trg
# 100번 반복 접속 : 3개의 파드로 AZ(zone) 상관없이 랜덤 확률 부하분산 동작
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
35 Hostname: deploy-echo-7f67d598dc-45trg
33 Hostname: deploy-echo-7f67d598dc-hg995
32 Hostname: deploy-echo-7f67d598dc-h9vst
iptables 부하분산 규칙에 의해 AZ 상관없이 랜덤으로 접속하게 된다.
iptables 확인
sep (endpoint) 파드 3개 확인되며, 이는 3개 파드로 램덤 확률에 따라 부하분산된다는 것을 의미한다.
Topology Mode(구 Aware Hint) 설정 후 테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인 : 같은 AZ(zone)의 목적지 파드로만 접속
# Topology Aware Routing 설정 : 서비스에 annotate에 아래처럼 추가
kubectl annotate service svc-clusterip "service.kubernetes.io/topology-mode=auto"
# endpointslices 확인 시, 기존에 없던 hints 가 추가되어 있음 >> 참고로 describe로는 hints 정보가 출력되지 않음
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml
apiVersion: v1
items:
- addressType: IPv4
apiVersion: discovery.k8s.io/v1
endpoints:
- addresses:
- 192.168.2.37
conditions:
ready: true
serving: true
terminating: false
hints: # here!
forZones:
- name: ap-northeast-2b
nodeName: ip-192-168-2-183.ap-northeast-2.compute.internal
targetRef:
kind: Pod
name: deploy-echo-75b7b9558c-mhbjj
namespace: default
uid: 27f7583c-9bd5-4117-9ba7-cba4b82a1f8b
zone: ap-northeast-2b
- addresses:
- 192.168.1.208
conditions:
ready: true
serving: true
terminating: false
hints: # here!
forZones:
- name: ap-northeast-2a
nodeName: ip-192-168-1-71.ap-northeast-2.compute.internal
targetRef:
kind: Pod
name: deploy-echo-75b7b9558c-hscs6
namespace: default
uid: b3fe29c4-1679-4ddd-943a-4c65f2411890
zone: ap-northeast-2a
- addresses:
- 192.168.3.193
conditions:
ready: true
serving: true
terminating: false
hints: # here!
forZones:
- name: ap-northeast-2c
nodeName: ip-192-168-3-65.ap-northeast-2.compute.internal
targetRef:
kind: Pod
name: deploy-echo-75b7b9558c-lkncc
namespace: default
uid: 5728f528-7e16-405e-8527-986d0b26af01
zone: ap-northeast-2c
kind: EndpointSlice
metadata:
creationTimestamp: "2025-02-16T03:57:24Z"
generateName: svc-clusterip-
generation: 9
labels:
endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
kubernetes.io/service-name: svc-clusterip
name: svc-clusterip-9s49p
namespace: default
ownerReferences:
- apiVersion: v1
blockOwnerDeletion: true
controller: true
kind: Service
name: svc-clusterip
uid: 194acba6-ecd9-4d5f-8cc9-a784a9f11deb
resourceVersion: "23942"
uid: 93408658-5995-4935-8dd3-e421b178cb8b
ports:
- name: svc-webport
port: 8080
protocol: TCP
kind: List
metadata:
resourceVersion: ""
# 100번 반복 접속 : 테스트 파드(netshoot-pod)와 같은 AZ(zone)의 목적지 파드로만 접속
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
100 Hostname: deploy-echo-7f67d598dc-45trg
테스트 파드(netshoot-pod)와 같은 AZ의 목적지 파드로만 접속된다!
노드 별 iptables 정책 확인
sep (endpoint) 파드 1개 확인 (해당 노드와 같은 AZ에 배포된 파드만 출력): 동일 AZ 간에서만 접속된다.
실습 후 자원 정리
eksctl delete cluster --name $CLUSTER_NAME
aws cloudformation delete-stack --stack-name myeks