본문 바로가기

k8s/AWS EKS

[AWS] EKS Networking

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)

CloudFormation 배포
operator-host 접속 확인

 

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

Cluster 확인
context rename
node 정보 확인
pod, pdb 확인
관리형 노드 그룹 확인
EKS addon 확인

 

관리형 노드 그룹 접속

# 인스턴스 공인 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 에 추가된 규칙 정보 확인

인스턴스 공인 IP 확인
Securit Group Rule 추가
Security Group 확인

# 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

 

ping 확인

 

노드 정보 확인

# 노드 정보 확인
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

노드 hostname 확인
노드 ip 정보 확인
노드 라우팅 정보 확인
노드 iptables 정보 확인

# 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

노드 cgroup 확인
노드 cgroup2 마운트 정보 확인

 

# 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

노드 kubelet 상태 확인
노드 containerd 프로세스 확인
노드 kubelet 트리 구조 확인
conf 확인
nodeadm conf 확인

 

# 노드 블록 장치 확인, 마운트 확인
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

 

CNI 확인
kube-proxy-config 확인
node, pod IP 확인

 

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

ipvs 모드로 변경

 

노드에서 기본 네트워크 정보 확인

워커 노드 기본 네트워크 구성

  • Network 네임스페이스는 호스트(Root)와 파드 별(Per Pod)로 구분된다
  • 특정한 파드(kube-proxy, aws-node)는 호스트(Root)의 IP를 그대로 사용한다 ⇒ 파드의 Host Network 옵션
  • ENI0, ENI1으로 2개의 ENI는 자신의 IP 이외에 추가적으로 5개의 보조 프라이빗 IP를 가질 수 있다
  • coredns 파드는 veth으로 호스트에는 eniY@ifN 인터페이스와 파드에 eth0과 연결되어 있다

1개의 ENI당 5개의 보조 프라이빗 IP 할당

 

보조 프라이빗 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

워커 노드1,3 의 보조 프라이빗 IP를 파드가 사용하고 있다.
노드 라우팅 정보 확인

 

파드가 생성됐을 경우 노드의 라우팅 변화를 확인해 보자

테스트용 파드 생성
신규 생성 파드 IP 확인
노드 라우팅 정보 확인

각 노드에 신규 생성된 파드 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를 확인해 보자

Node IP 확인
Pod IP 확인

Pod에서 확인되는 IP가 Node IP와 같은 걸 확인할 수 있다.

 

외부 통신 시 SNAT 되고 있는 건데 자세히 확인해 보자

node route table 확인

라우팅 정책에 의해 외부와 통신할 때는 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

운영서버 EC2에서 Pod로 Ping을 날렸을 때는 NAT 되지 않는다.

 

Pod > 운영서버 EC2 Ping

반면, 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"}'

kube-ops-view Pod 확인

 

워커 노드의 인스턴스 타입 별 파드 생성 개수 제한

  • 인스턴스 타입 별 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

t3 type 확인
c5 type 확인

# 파드 사용 가능 계산 예시 : 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

t3.medium 확인

 

최대 파드 생성 및 확인

# 워커 노드 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

Pod 30개로 설정 후 node 1번 eth, eni 확인

 

위에서 알아본 t3.medium 노드 Pod 생성 한도는 17개까지고 kube-proxy, aws-node 2개는 디폴트로 있는 Pod라 빼야 해서 15개이다.

30개까지 늘렸을 때는 정상적으로 Pod가 생성되고 그에 맞게 eth, eni가 추가된다. (eth 1개당 eni5개)

 

Pod 50개로 설정 후 Pending 확인

 

50개까지 늘렸을 경우 10개의 Pending 된 Pod가 보인다.

 

한도 테스트용 Pod 생성 전 내 Pod 개수

 

총 51개의 Pod를 생성할 수 있는데 내가 원래 띄워놓은 Pod가 11개이므로 10개 Pending 되면 딱 맞는다.

Too many Pods 경고 확인

더보기

노드에 배포할 수 있는 파드의 최대 개수는 지원하는 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

Pod 1개
Pod 2개
Pod 3개

 

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

ingress, svcm endpoint, pod 생성 확인

# 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 생성 확인

 

Ingress 확인
LB 주소로 2048 게임 접속

 

ALB 대상 그룹에 등록된 대상 확인

Pod IP 확인
LB 리소스 맵 확인

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://xn--vj5b11biyw.kr/306

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://aws.amazon.com/blogs/networking-and-content-delivery/deploying-aws-load-balancer-controller-on-amazon-eks/

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.11/guide/service/nlb/#security-group

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