Bastion Host 배포
2주 차 스터디에서는 개인 PC에서 실습을 진행할 수 있도록 세팅하는 내용이 포함돼 있지만 내가 가진 PC, 노트북은 너무 무거워서 진짜 집에서만 할 수 있다...
외부에서도 시간 날 때 실습을 진행하기 위해 오라클 클라우드 Free tier로 Ubuntu 24.04 서버를 생성해 실습 환경을 구성했다.
CPU 아키텍처에 따라 다운로드 링크가 다르므로 유의해야 한다.
# Install awscli
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Install eksctl
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_Linux_arm64.tar.gz" | tar xz -C ./
sudo mv ./eksctl /usr/local/bin
eksctl version
# Install kubectl
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.31.2/2024-11-15/bin/linux/arm64/kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client=true
# Install Helm
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm version
# Krew 툴 및 플러그인 설치
curl -sL https://github.com/kubernetes-sigs/krew/releases/download/v0.4.4/krew-linux_arm64.tar.gz | tar xz -C ./
./krew-linux_arm64 install krew
export PATH="$HOME/.krew/bin:$PATH"
echo 'export PATH="$HOME/.krew/bin:$PATH"' >> ~/.bashrc
kubectl krew version
kubectl krew install neat stern
kubectl krew list
# kube-ps1 설치
git clone https://github.com/jonmosco/kube-ps1.git $HOME/kube-ps1
cat <<"EOT" >> ~/.bashrc
source $HOME/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=false
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
# (옵션) kubecolor 설치 : 사용 시에는 kubectl(k) 대신 kubecolor 사용
curl -sL "https://github.com/kubecolor/kubecolor/releases/download/v0.5.0/kubecolor_0.5.0_linux_arm64.tar.gz" | tar xz -C ./
sudo mv ./kubecolor /usr/local/bin
kubecolor -h
arm에서는 get-all 플러그인을 지원하지 않는건지 설치되지 않았다.
추후에 설치 방법이 있는지 찾아보기로하고 넘어간다.
실습 환경 배포
CloudFormation을 통해 기본 실습 환경 배포
VPC, VPC Peering, Subnet, operator-host 등을 CloudFormation을 통해 배포
# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-2week.yaml
# 배포
aws cloudformation deploy --template-file ~/Downloads/myeks-2week.yaml \
--stack-name myeks --parameter-overrides KeyName=<키 파일> 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
# 운영서버 EC2 에 SSH 접속 확인
예시) ssh ec2-user@<EC2 Public IP>
ssh -i <ssh 키파일> ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
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 수정
VPC ID, Subnet ID, Key Pair를 현재 내 AWS 환경에 맞게 수정
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: <MY VPC ID> # 각자 환경 정보로 수정
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: <MY Subnet ID> # 각자 환경 정보로 수정
ap-northeast-2b:
az: ap-northeast-2b
cidr: 192.168.2.0/24
id: <MY Subnet ID> # 각자 환경 정보로 수정
ap-northeast-2c:
az: ap-northeast-2c
cidr: 192.168.3.0/24
id: <MY Subnet ID> # 각자 환경 정보로 수정
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: null
ssh:
allow: true
publicKeyName: <MY KEY PAIR> # 각자 환경 정보로 수정
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=/root/aews/kubeconfig
# 배포
eksctl create cluster -f myeks.yaml --verbose 4
EKS 정보 확인
# 클러스터 확인
kubectl cluster-info
eksctl get cluster
# 네임스페이스 default 변경
kubens default
# context rename
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
# node 정보 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get node -v=6
# pod, pdb 확인
kubectl get pod -A
kubectl get pdb -n kube-system
# 관리형 노드 그룹 확인
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
관리형 노드 그룹 접속
# 인스턴스 공인 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>
echo $N1, $N2, $N3
# *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
ping -c 2 $N3
# 워커 노드 SSH 접속
ssh -i <SSH 키> -o StrictHostKeyChecking=no ec2-user@$N1 hostname
ssh -i <SSH 키> -o StrictHostKeyChecking=no ec2-user@$N2 hostname
ssh -i <SSH 키> -o StrictHostKeyChecking=no ec2-user@$N3 hostname
노드 정보 확인
# 노드 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i hostnamectl; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo ip -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo ip -c route; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> 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 -i <MY PEM KEY> ec2-user@$i stat -fc %T /sys/fs/cgroup/; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i findmnt -t cgroup2; echo; done
# kubelet 상태, containerd 프로세스, kubelet 트리구조 확인, conf 파일 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo systemctl status kubelet; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i ps axf |grep /usr/bin/containerd; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo tree /etc/kubernetes/kubelet/; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo cat /etc/kubernetes/kubelet/config.json | jq; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> 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 -i <MY PEM KEY> ec2-user@$i lsblk; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i df -hT /; echo; done
# 컨테이너 리스트 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo ctr -n k8s.io container list; echo; done
# 컨테이너 이미지 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo ctr -n k8s.io image list --quiet; echo; done
# 태스크 리스트 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh -i <MY PEM KEY> ec2-user@$i sudo ctr -n k8s.io task list; echo; done
AWS VPC CNI
K8S CNI란?
Container Network Interface는 k8s 네트워크 환경을 구성해 줌. Calico, Cilium, Flannel 등 다양한 플러그인이 존재
AWS VPC CNI란?
- 구성요소
- CNI Binary: Pod 네트워크를 설정해 Pod 간 통신을 가능하게 함. CNI Binary는 노드의 root file system에서 실행되며 새 Pod가 노드에 추가되거나 기존 Pod가 노드에서 제거될 때 kubelet에 의해 호출됨
- ipamd: a long-running node-local IP Address Management daemon, 노드에서 ENI를 관리하고 사용 가능한 IP 주소나 prefix warm-pool을 유지 관리함
- 인스턴스 생성 및 ENI 할당
- EC2 인스턴스가 생성되면 기본 서브넷에 연결된 기본 ENI가 생성 및 연결
- hostNetwork 모드로 실행되는 Pod는 노드의 기본 ENI에 할당된 기본 IP 주소를 사용하고, 호스트와 동일한 네트워크 네임스페이스를 공유
- CNI 플러그인과 ENI 관리
- CNI 플러그인은 노드에서 Elastic Network Interfaces (ENI)를 관리
- 노드가 프로비저닝 되면 CNI 플러그인은 기본 ENI에 IP 주소 또는 prefix를 할당하기 위한 슬롯 풀을 자동으로 할당. 이 풀은 wam-pool이라고 하며 인스턴스 타입에 따라 크기가 결정
- ENI 슬롯이 할당되면 CNI는 세컨더리 ENI를 추가로 연결하여 Pod 수에 맞춰 슬롯
- Secondary ENI 및 슬롯 관리
- 각 ENI는 인스턴스 타에 따라 지원할 수 있는 슬롯 수에 제한이 있음
- CNI는 필요한 슬롯 수에 맞게 ENI를 노드에 연결하고 노드가 더 이상 ENI를 추가할 수 없을 때까지 계속됨
- CNI는 Pod 시작을 빠르게 하기 위해 미리 warm ENI와 슬롯을 할당
- Pod 수 및 인스턴스 유형의 제약
- EC2 인스턴스 타입에 따라 연결할 수 있는 ENI 수와 슬롯 수가 달라지므로, 특정 EC2 인스턴스에서 실행할 수 있는 Pod 수는 ENI 수와 각 ENI가 지원하는 슬롯 수에 따라 결정됨
- EKS의 권장 최대 Pod 수를 설정하여 인스턴스의 CPU 및 메모리 리소스 고갈을 피하는 것이 좋음
- hostNetwork 모드를 사용하는 Pod는 이 계산에서 제외
Calico CNI와 AWS VPC CNI 차이
파드간 통신 시 일반적으로 K8S CNI는 오버레이(VXLAN, IP-IP 등) 통신을 하고 AWS VPC CNI는 노드와 파드가 동일 대역이어서 직접 통신을 한다.
Worker Node에 생성 가능한 최대 Pod 개수
- Secondary IPv4 addresses : 인스턴스 유형에 최대 ENI 개수와 할당 가능 IP 수를 조합하여 선정
- IPv4 Prefix Delegation : IPv4 28bit 서브넷(prefix)을 위임하여 할당 가능 IP 수와 인스턴스 유형에 권장하는 최대 개수로 선정
네트워크 기본 정보 확인
# CNI 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
# kube-proxy config 확인
kubectl describe cm -n kube-system kube-proxy-config
# 노드 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
node와 pod에 같은 IP를 가지는 이유는 같은 네트워크 네임스페이스를 공유해서 사용하기 때문임
kube-proxy-config 모드 변경해 보기
kube-proxy-config 모드가 현재 iptables인데 ipvs로 변경을 시도해 봤다.
ipvs 모드로 변경하려면 worker node에 ipvsadm 패키지를 우선 설치해야 한다.
#Amazon Linux 2023과 같은 Fedora 기반 이미지
sudo dnf install -y ipvsadm
#Ubuntu와 같은 Debian 기반 이미지
sudo apt-get install ipvsadm
설치 후 IPVS 구성 옵션에 대한 커널 모듈을 로드 재부팅 후에도 유지되도록 아래 작업을 진행한다.
sudo sh -c 'cat << EOF > /etc/modules-load.d/ipvs.conf
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_lc
ip_vs_wlc
ip_vs_lblc
ip_vs_lblcr
ip_vs_sh
ip_vs_dh
ip_vs_sed
ip_vs_nq
nf_conntrack
EOF'
이후 아래 명령어를 실행해 모듈을 로드한다.
sudo modprobe ip_vs
sudo modprobe ip_vs_rr
sudo modprobe ip_vs_wrr
sudo modprobe ip_vs_lc
sudo modprobe ip_vs_wlc
sudo modprobe ip_vs_lblc
sudo modprobe ip_vs_lblcr
sudo modprobe ip_vs_sh
sudo modprobe ip_vs_dh
sudo modprobe ip_vs_sed
sudo modprobe ip_vs_nq
sudo modprobe nf_conntrack
완료했으면 worker node를 빠져나와서 아래 명령어로 EKS Add on을 업데이트한다.
# addon update 명령어, ipvs 모드 및 rr(Round Robin)로 변경
aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name kube-proxy \
--configuration-values '{"ipvs": {"scheduler": "rr"}, "mode": "ipvs"}' \
--resolve-conflicts OVERWRITE
노드에서 기본 네트워크 정보 확인
워커 노드 기본 네트워크 구성
- Network 네임스페이스는 호스트(Root)와 파드 별(Per Pod)로 구분된다
- 특정한 파드(kube-proxy, aws-node)는 호스트(Root)의 IP를 그대로 사용한다 ⇒ 파드의 Host Network 옵션
- ENI0, ENI1으로 2개의 ENI는 자신의 IP 이외에 추가적으로 5개의 보조 프라이빗 IP를 가질 수 있다
- coredns 파드는 veth으로 호스트에는 eniY@ifN 인터페이스와 파드에 eth0과 연결되어 있다
보조 프라이빗 IP를 Pod가 사용하는지 확인
# coredns 파드 IP 정보 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
# 노드의 라우팅 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
파드가 생성됐을 경우 노드의 라우팅 변화를 확인해 보자
각 노드에 신규 생성된 파드 IP로 라우팅이 추가된 점을 확인할 수 있다.
노드 간 파드 통신
AWS VPC CNI의 경우 오버레이 통신 기술 없이 VPC Native 하게 파드간 통신이 가능
tcpdump를 통해 실제로 직접 통신하는지 확인해 보자
# 파드 이름 변수 지정
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}')
# 파드 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 확인
sudo tcpdump -i any -nn icmp
파드에서 외부 통신
파드에서 외부 통신 시 iptable에 SNAT를 통해 노드의 eth0(ens5) IP로 변경되어서 외부와 통신됨
* VPC CNI의 External source network address translation(SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다
# 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
eni에서 캡처됐을 때는 Pod IP로 찍히고, ens5에서 캡처될 때는 node IP로 찍히는 걸 확인할 수 있다.
Pod에서 외부 통신 시 IP를 확인해 보자
Pod에서 확인되는 IP가 Node IP와 같은 걸 확인할 수 있다.
외부 통신 시 SNAT 되고 있는 건데 자세히 확인해 보자
라우팅 정책에 의해 외부와 통신할 때는 ens5를 통해 나가도록 돼있다.
- -A AWS-SNAT-CHAIN-0 -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
- 192.168.0.0/16 네트워크에 해당하는 패킷은 이 체인을 지나면서 추가적인 NAT 처리 없이 바로 반환
- -A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.1.191 --random-fully
- 목적지가 로컬 네트워크가 아닌 외부로 향하는 패킷은 출발지 IP를 192.168.1.191로 변환
위 정책에 의해 Pod끼리 통신 시에는 NAT 처리되지 않고 외부와 통신할 때는 NAT 처리된다.
Pod와 운영서버 EC2 간 통신
# 운영서버 EC2 SSH 접속
ssh <운영서버 EC2 공인 IP>
POD1IP=<파드1 IP 지정>
ping -c 1 $POD1IP
# 워커노드1 에서 tcpdump 확인 : NAT 동작 적용 여유 확인
sudo tcpdump -i any -nn icmp
운영서버 EC2에서 Pod로 Ping을 날렸을 때는 NAT 되지 않는다.
반면, Pod에서 운영서버 EC2로 Ping을 날리면 위에서 봤던 라우팅 정책에 의해 NAT 되는 걸 확인할 수 있다.
노드에 파드 생성 개수 제한 확인
사전 준비: 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"}'
워커 노드의 인스턴스 타입 별 파드 생성 개수 제한
- 인스턴스 타입 별 ENI 최대 개수와 할당 가능한 최대 IP 개수에 따라서 파드 배치 개수가 결정됨
- 단, aws-node와 kube-proxy 파드는 호스트의 IP를 사용함으로 최대 개수에서 제외함
최대 파드 생성 개수 계산 식 : (Number of network interfaces for the instance type × (the number of IP addressess per network interface - 1)) + 2
워커 노드의 인스턴스 정보 확인
# t3 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.\* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# c5 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=c5\*.\* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# 파드 사용 가능 계산 예시 : aws-node 와 kube-proxy 파드는 host-networking 사용으로 IP 2개 남음
((MaxENI * (IPv4addr-1)) + 2)
t3.medium 경우 : ((3 * (6 - 1) + 2 ) = 17개 >> aws-node 와 kube-proxy 2개 제외하면 15개
# 워커노드 상세 정보 확인 : 노드 상세 정보의 Allocatable 에 pods 에 17개 정보 확인
kubectl describe node | grep Allocatable: -A6
최대 파드 생성 및 확인
# 워커 노드 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
위에서 알아본 t3.medium 노드 Pod 생성 한도는 17개까지고 kube-proxy, aws-node 2개는 디폴트로 있는 Pod라 빼야 해서 15개이다.
30개까지 늘렸을 때는 정상적으로 Pod가 생성되고 그에 맞게 eth, eni가 추가된다. (eth 1개당 eni5개)
50개까지 늘렸을 경우 10개의 Pending 된 Pod가 보인다.
총 51개의 Pod를 생성할 수 있는데 내가 원래 띄워놓은 Pod가 11개이므로 10개 Pending 되면 딱 맞는다.
노드에 배포할 수 있는 파드의 최대 개수는 지원하는 IP 주소가 주요 요인입니다. 다만 vCPU 30개 미만 EC2 인스턴스 유형은 (k8s 확장 권고값에 따라) 노드에 최대 파드 110개 제한이 되고, vCPU 30 이상 EC2 인스턴스 유형은 (AWS 내부 테스트 권고값에 따라) 노드에 최대 파드 250개 제한을 권고합니다. https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/choosing-instance-type.html https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses-procedure.html https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/
해결 방안 : Prefix Delegation, WARM & MIN IP/Prefix Targets, Custom Network

Service & AWS LoadBalancer Controller
K8S 서비스 소개 : kube-proxy 모드 - iptables, ipvs, nftables, eBPF
서비스 종류
- Cluster 타입
- NodePort 타입
- LoadBalancer 타입(기본모드) : NLB 인스턴스 유형 > 노드 IP:노드포트
Cloud Controller Manager
Cloud Controller Manager를 통해 K8S NodePort 정보를 사용하는 CLB/NLB 프로비저닝
Service (LoadBalancer Controller)
AWS Load Balancer Controller + NLB (파드) IP 모드 동작 with AWS VPC CNI
NLB 모드
1. 인스턴스 유형 : 노드에 NodePort로 전달
- externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
- externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함
2. IP 유형 : 반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요
- Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가
- Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)
AWS LoadBalancer Controller 배포 실습
AWS LoadBalancer Controller 배포
# 설치 전 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
서비스/파드 배포 테스트 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
# yaml 파일 apply
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 파일 annotaions에 아래 내용 추가
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-kubesyst-svcnlbip-185944db0a`) == `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
# 웹 접속 주소 확인
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
# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
파드 2개 → 1개 → 3개 설정 시 동작
# (신규 터미널) 모니터링
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
Pod2개에서 Pod 3개로 늘린 후 initial일 때는 2개로만 부하분산을 하고 3개 생성이 완료되면 3개로 부하분산을 한다.
Ingress
- Ingress는 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할을 함
- AWS Load Balancer Controller + Ingress (ALB) IP 모드 동작 with AWS VPC CNI
서비스/파드 배포 테스트 with Ingress(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 대상 그룹에 등록된 대상 확인
ALB에서 파드 IP로 직접 전달하는 걸 확인할 수 있다.
# Target Health 모니터링
while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done
# 파드 3개로 증가
kubectl scale deployment -n game-2048 deployment-2048 --replicas 3
파드를 3개로 증가시키면 자동으로 대상 그룹에 추가된다.
# 파드 1개로 감소
kubectl scale deployment -n game-2048 deployment-2048 --replicas 1
파드를 1개로 감소시키면 자동으로 대상 그룹에서 draining 된다.
ExternalDNS
K8S 서비스/인그레스 생성 시 도메인을 설정하면, AWS(Route 53), Azure(DNS), GCP(Cloud DNS)에 A 레코드(TXT 레코드)로 자동 생성/삭제
* 도메인 구매 후 실습 및 추가 내용 작성 예정
출처
AEWS 3기 스터디
https://kubernetes.io/docs/concepts/cluster-administration/networking/
https://kubernetes.io/docs/concepts/services-networking/
https://aws.github.io/aws-eks-best-practices/networking/vpc-cni/
https://docs.aws.amazon.com/ko_kr/eks/latest/best-practices/ipvs.html
https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
https://youtu.be/E49Q3y9wsUo?si=reLXmCvO1me52lf4&t=375
https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
https://edgehog.blog/a-self-hosted-external-dns-resolver-for-kubernetes-111a27d6fc2c
'k8s > AWS EKS' 카테고리의 다른 글
[AWS] EKS AutoScaling (0) | 2025.03.08 |
---|---|
[AWS] EKS Observability (0) | 2025.03.01 |
[AWS] EKS Storage (0) | 2025.02.23 |
[AWS]EKS Cluster Endpoint 정리 (0) | 2025.02.07 |
[AWS] EKS 생성해보기 (0) | 2025.02.06 |