EKS 2주차 — EKS Networking

VPC CNI부터 Ingress까지

Sigrid Jin
111 min readMar 16, 2024

들어가기 전에

  • 쿠버네티스의 대표적인 워크로드 리소스인 Deployment, ReplicaSet, StatefulSet, DaemonSet의 4가지의 차이점에 대하여 다소 혼동이 있었기 때문에 정리하고 들어간다.
  • IPTable에 대한 내용도 정리하고 들어간다.

ReplicaSet & Deployment

  • 상용 환경에서 애플리케이션을 오직 하나의 파드로만 운영하는 경우는 없다. 다운 타임이 없는 안정적인 서비스를 위해서는 여러 파드를 동시에 운영하게 된다. 이 때 같은 애플리케이션을 위해 동시에 구동되는 파드들의 집합을 레플리카라고 부른다. ReplicaSet은 레플리카 구성을 갖춘 파드들의 배포 규격을 정의하고 이를 관리하며 규격에 정의된 수만큼의 파드가 항상 정상적으로 구동될 수 있도록 보장하는 역할을 한다.
  • Deployment는 ReplicaSet과 Pod에 대한 선언적인 업데이트를 제공한다. 애플리케이션의 버전 및 배포 관리에 주로 사용되며 Rolling Update를 할 수 있다는 것이 가장 큰 특징이다. Deployment는 ReplicaSet을 포함하는 상위 객체로서 서비스 단위의 관리에 사용된다. Deployment 리소스로 배포된 파드들은 deployment_name - replicaset_num - random_hash_value 형태의 식별자를 가진다. ReplicaSet의 설정이 변경되거나 Pod가 삭제 후 재배포된 상황이라면 식별자도 함께 달라진다.

StatefulSet

  • StatefulSet은 애플리케이션의 상태를 저장하고 관리하는데 사용되는 리소스이다. 파드의 구성과 유지 그리고 스케일링의 측면에서 Deployment와 거의 동일한 특성을 지닌다. StatefulSet은 Deployment와 달리 각 파드의 순서와 고유성을 유지하며 각 파드 별로 영구적인 스토리지(볼륨)을 할당한다.
  • Deployment와의 주요 차이점은 먼저 모든 파드가 각각 고유하며 영구적인 식별자를 갖는다는 점이다. 모든 파드는 삭제 후 재생성 시 해당 식별자를 그대로 유지하게 된다. 실제로 모든 파드의 식별자 끝에 번호가 붙게 되는데 해당 번호는 Replica의 수를 기준으로 0에서 N-1까지 존재하게 된다.
  • 모든 파드의 생성과 스케일링, 업데이트, 그리고 삭제는 위에서 부여된 번호에 따라 순서대로 이루어진다. 생성, 스케일링, 업데이트 등은 순차적으로, 삭제는 역순으로 진행된다. 각각의 파드에는 각각의 영구적인 스토리지(볼륨)가 할당된다. 파드가 삭제되어도 볼륨은 남으며, 그 자리에 새로 생성된 파드는 해당 볼륨을 다시 이어 받는다.
  • 쿠버네티스에서 돌아가는 마이크로서비스 중 상당수는 **스테이트리스(Stateless)**의 특징을 갖는다. Nginx 같은 웹서버를 생각해보자. 요청이 들어오면 응답을 보내는 방식으로 정해진 역할을 수행한다. 어느 파드나 역할이 동일하므로 스토리지(볼륨) 역시 하나를 공용으로 쓰면 된다. 만약 어느 파드가 죽었다면, 동일한 파드를 다시 만들어 대체하면 그만이다. 쿠버네티스의 디플로이먼트는 정확히 이 역할을 자동화하는 유형의 객체다.
  • PostgreSQL 같은 DB를 클러스터에 구축한다고 가정해보자. 데이터의 안정성과 무결성 유지를 위해서는 보다 특별한 설계가 필요하다. 이를테면 CRUD가 모두 가능한 파드, Read만 수행하는 파드, 그리고 데이터의 무결성 유지 여부를 감시할 파드로 나누는 방안을 생각해 볼 수 있다. 데이터의 안정성 제고 차원에서 스토리지 또한 각각의 파드에 나뉘어 있어야 할 것이며, 이 스토리지는 파드의 상태와 무관하게 언제나 유지되어야 할 것이다.
  • 스테이트풀셋을 만들면 각 파드마다 **퍼시스턴트 볼륨(PV; Persistent Volume)**도 함께 만들어진다. 그러나 스테이트풀셋이 삭제될 때 이 볼륨은 삭제되지 않고 남는다.
  • 스테이트풀셋의 파드에 사용할 스토리지는 오직 **PVC(Persistent Volume Claim)**를 통해서만 가능하다. 만약 파드가 늘어나면 그때마다 볼륨이 동적으로 붙어줘야 하기 때문이다. 이를 위해 클러스터에 미리 **PV(Persistent Volume)**을 만들어 놓거나, **StorageClass**를 이용해 동적으로 프로비저닝을 해줘야 한다.
  • 스테이트풀셋의 특정 파드에 요청(Request)을 전달하기 위한 매개체로 **헤드리스 서비스(Headless Service)**가 필요하다. 이 서비스는 오직 해당 파드의 고유한 네트워크 위치를 파악하기 위해서만 쓰이므로 별도의 IP가 부여되지 않으며, 그렇기 때문에 헤드리스(Headless)라 불린다.
  • 스테이트풀셋은 일반적으로 노드에 장애가 발생하더라도 파드를 다른 노드로 옮기지 않는다. 볼륨에 탑재된 데이터의 보호를 우선시하기 때문이다.

ReadWriteMany Migration

  • ReadWriteMany 볼륨 생성 후 StatefulSets -> Deployment: 상태를 유지하는 애플리케이션의 아키텍처를 StatefulSet에서 Deployment로 변경하는 시나리오다. 이 경우, ReadWriteMany 액세스 모드를 지원하는 볼륨을 생성하여 여러 파드가 동시에 해당 볼륨에 접근하고 데이터를 읽고 쓸 수 있도록 설정된다. ReadWriteMany 액세스 모드는 파일 시스템을 여러 파드와 공유할 수 있게 해주므로, 이 방식을 사용하면 Deployment를 통해 상태를 유지하는 애플리케이션을 운영할 수 있게 된다.
  • 이러한 변경은 일반적으로 확장성과 관리 용이성을 높이기 위해 수행됩니다. Deployment를 사용하면 상태가 없는(stateless) 서비스처럼 애플리케이션을 더 쉽게 관리하고 확장할 수 있으며, ReadWriteMany 볼륨을 사용함으로써 여러 파드가 동일한 데이터에 액세스할 수 있게 되어, 애플리케이션 아키텍처를 더 유연하게 구성할 수 있다.

DaemonSet

  • 데몬셋은 모든 노드, 또는 특정 레이블을 가진 노드에 하나씩의 동일한 파드를 구동하게 해주는 리소스다.
  • 해당되는 노드에 오직 하나씩만 배치한다는 점에서 디플로이먼트 또는 스테이트풀셋과 다르며, 따라서 별도의 레플리카 설정을 하지 않는다. 데몬셋이 구동 중인 클러스터에서 노드가 추가되면 해당 노드에도 데몬셋의 파드가 배치되지만, 삭제된 노드에 있던 데몬셋 파드가 다른 노드로 옮겨지지는 않는다.
  • 데몬셋은 주로 워커 노드에 리소스 모니터링용 애플리케이션, 또는 로그 수집기를 배포할 때 쓰인다.

IP Table

  • IPTable 규칙은 리눅스 기반 시스템에서 네트워크 트래픽을 관리하고 제어하기 위해 사용되는 규칙들을 말한다.
  • IPTables는 패킷 필터링, 네트워크 주소 변환(NAT), 포트 변환 및 기타 네트워크 관련 작업을 수행하는 커널 레벨의 방화벽 시스템이다. 이를 통해 시스템 관리자는 들어오고 나가는 네트워크 트래픽을 세밀하게 제어할 수 있다.
  • filter 테이블: 이 테이블은 패킷 필터링 규칙을 담당합니다. 기본적으로 네트워크 보안을 관리하는 데 사용되며, 트래픽을 허용하거나 차단하는 규칙을 포함한다.
  • nat 테이블: 이 테이블은 네트워크 주소 변환(NAT)을 처리합니다. 주로 IP 주소를 변경하거나 포트 번호를 변환하는 데 사용된다. 예를 들어, 사설 네트워크에서 인터넷으로 나가는 트래픽의 IP 주소를 변환하는데 사용된다.
  • 각 테이블 내에는 여러 체인(chain)이 있으며, 이 체인들은 규칙(rule)의 집합이다. 체인은 특정 조건에 따라 트래픽을 처리하는 규칙의 시퀀스로 구성되어 있다. 대표적인 체인으로는 INPUT, OUTPUT, FORWARD가 있다.
  • INPUT: 호스트로 들어오는 트래픽을 처리합니다.
  • OUTPUT: 호스트에서 나가는 트래픽을 처리합니다.
  • FORWARD: 호스트를 통과하는 트래픽을 처리합니다(라우팅).
  • IPTables 규칙은 특정 조건에 맞는 트래픽에 대해 수행할 작업을 정의하고 규칙은 다음과 같은 정보를 기반으로 트래픽을 매칭할 수 있다.
  • 소스 IP 주소
  • 목적지 IP 주소
  • 프로토콜(예: TCP, UDP)
  • 소스 및 목적지 포트 번호
  • 인터페이스(예: eth0)
  • 작업은 패킷을 허용(ACCEPT), 차단(DROP), 거부(REJECT), 로그 기록(LOG), 또는 다른 체인으로 이동하는 등 다양할 수 있다.
  • 특정 IP 주소에서 오는 모든 트래픽을 차단하는 규칙:
iptables -A INPUT -s 123.456.789.0 -j DROP
  • 이 명령은 INPUT 체인에 새로운 규칙을 추가하여, 소스 IP 주소가 123.456.789.0인 모든 들어오는 트래픽을 차단한다.
  • IPTables 규칙을 통해 네트워크 보안 정책을 세밀하게 조정하고, 시스템 및 네트워크 리소스에 대한 액세스를 제어할 수 있다.
  • 시스템 관리자는 이러한 규칙을 사용하여 네트워크 보안을 강화하고, 원하지 않는 트래픽으로부터 시스템을 보호할 수 있다.

AWS Addon이란?

AWS EKS (Elastic Kubernetes Service) Add-ons은 AWS에서 관리하는 Kubernetes 클러스터에 통합되어 운영될 수 있는 확장 기능이나 애플리케이션들을 의미한다. 이러한 Add-ons을 사용함으로써, Kubernetes 클러스터를 더욱 효율적으로 관리하고, 보안, 네트워킹, 모니터링 같은 기능을 쉽게 추가할 수 있다.

Add-on의 주요 목적은 클러스터의 기능을 확장하고, 운영을 간소화하는 것이다. AWS에서 제공하는 일부 Add-ons은 자동으로 업데이트되며, AWS가 직접 관리하기 때문에 사용자는 성능과 보안에 대한 걱정을 줄일 수 있다.

# (참고) eks 설치 yaml 중 addon 내용
tail -n11 myeks.yaml
addons:
- name: vpc-cni # no version is specified so it deploys the default version
version: latest # auto discovers the latest available
attachPolicyARNs:
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
configurationValues: |-
enableNetworkPolicy: "true"
- name: kube-proxy
version: latest
- name: coredns
version: latest
  1. vpc-cni: Amazon VPC CNI (Container Network Interface) 플러그인은 Amazon EKS 클러스터에서 Kubernetes 파드 네트워킹을 관리합니다. 이를 통해 파드가 Amazon VPC 네트워크 내에서 자체 IP 주소를 가질 수 있게 되어, 보안 그룹과 네트워크 ACL을 사용한 더 세밀한 네트워크 제어가 가능해집니다. enableNetworkPolicy: "true" 설정은 네트워크 정책을 활성화하여 파드 간의 트래픽 흐름을 제어할 수 있게 합니다.
  2. kube-proxy: Kube-proxy는 Kubernetes 클러스터 내의 네트워크 프록시로 작동하며, 서비스를 통한 네트워크 통신을 가능하게 합니다. 이 Add-on은 Kubernetes 서비스의 네트워크 규칙을 관리하고, 요청을 적절한 파드로 전달합니다.
  3. coredns: CoreDNS는 서비스 디스커버리를 위한 DNS 서버로, Kubernetes 클러스터 내에서 이름 해석을 담당합니다. CoreDNS는 파드와 서비스의 이름을 IP 주소로 변환하여, 클러스터 내부 또는 외부의 리소스를 찾는데 사용됩니다.

이미 존재하고 있는 클러스터에 설치하기 가장 좋은 방법은 eksctl을 사용하는 것이다.

eksctl create addon --name vpc-cni --version latest --cluster <클러스터 이름> --attach-policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy --force

AWS VPC CNI 소개

k8s CNI

  • Container Network Interface가 쿠버네티스의 네트워크 환경을 구성한다. 다양한 플러그인이 존재하는데 AWS VPC CNI를 사용하는 것이 권장된다. 파드의 IP를 할당하는 통신 환경을 만들어주고, AWS VPC와 유기적으로 연결된다고 보면 된다. 파드의 IP 네트워크 대역과 워커 노드의 IP 대역이 같기 때문에 직접 통신이 가능하다.
  • 온프레미스에서 쿠버네티스를 사용하면 물리 네트워크 대역이랑 파드 네트워크 대역이 다르다. on-prem의 CNI는 워커 노드들 간의 통신을 위해서는 워커 노드를 빠져나갈 때 터널링이나 오버헤드 등을 사용한다 (네이티브 라우팅). 그래서 오버레이 통신 즉 터널링을 한다. 원본 패킷은 10.1.1.2가 받고 리턴 패킷은 헤드를 오버레이에서 씌워서 터널링 구간을 거치고 처리한다. 따라서 패킷 사이즈도 늘어나고 불필요한 오버헤드가 들어가기 때문에 비효율적이다. 따라서 VPC CNI의 동일대역 장점이 빛난다.
  • 워커 노드에 생성 가능한 최대 파드의 개수가 제한이 있다. 인스턴스의 CPU 스펙에 제한이 있고 ENI의 Secondary IP를 할당할 수 있는 개수 제한이 있다. t3.medium은 3개의 네트워크 인터페이스를 가질 수 있는데, 각각 5개의 Secondary IP를 할당할 수 있다.
  • 파드를 더 배포하고 싶다면 IPv4 Prefix Delegation의 방법이 있다. 해당 방법을 사용하면 110개 파드를 c5.large 워커 노드 1대에 배포할 수 있다.

실습

  • 먼저 변수를 설정하는데 있어 나는 개인적으로 가지고 있는 Terraform을 사용했기 때문에 조금 변수 지정하는 방법이 다르다.
N1=$(kubectl get nodes --selector=topology.kubernetes.io/zone=ap-northeast-1a -o jsonpath="{.items[0].status.addresses[?(@.type=='InternalIP')].address}" 2>/dev/null)
N2=$(kubectl get nodes --selector=topology.kubernetes.io/zone=ap-northeast-1a -o jsonpath="{.items[1].status.addresses[?(@.type=='InternalIP')].address}" 2>/dev/null)
N3=$(kubectl get nodes --selector=topology.kubernetes.io/zone=ap-northeast-1c -o jsonpath="{.items[0].status.addresses[?(@.type=='InternalIP')].address}" 2>/dev/null)
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3
aws ec2 authorize-security-group-ingress --group-id {sigrid_cluster_node_sg_id} --protocol tcp --port 22 --cidr 125.131.69.33/32
  • DaemonSet에 aws-node라고 하는 파드가 있다. 아래를 보면 Daemon Set 총 3개가 배포가 되어 있고 vpc-cni를 사용하며 컨테이너 이미지 정보가 출력되어 있다.
(iam-root-account@sigrid-myeks-240309:default) [root@myeks-host ~]# kubectl describe daemonset aws-node --namespace kube-system | grep Image
Image: 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon-k8s-cni-init:v1.16.4-eksbuild.2
Image: 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon-k8s-cni:v1.16.4-eksbuild.2
Image: 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon/aws-network-policy-agent:v1.0.8-eksbuild.
  • kube-proxy가 로드 밸런서나 ip table 등 네트워크 통신 중간에 주로 관여해준다. kube-proxy config의 경우 iptables 라는 모드를 사용하는 것을 알 수 있고 ipvs 모드를 사용하지는 않는다.
**kubectl describe cm -n kube-system kube-proxy-config
...**
mode: "iptables"
**...**
  • 노드 IP와 파드 IP를 비교해보자. 같은 대역을 쓴다는 것을 알 수 있다. 즉 인스턴스의 IP 대역과 파드 IP 대역이 같으므로 부가적인 터널링이 필요 없다.
[root@myeks-host ~]# 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
---------------------------------------------------------------------------------------------------------
| DescribeInstances |
+---------------------------------------------------------+----------------+----------------+-----------+
| InstanceName | PrivateIPAdd | PublicIPAdd | Status |
+---------------------------------------------------------+----------------+----------------+-----------+
| sigrid-myeks-240309-sigrid-myeks-240309-nodegroup-Node | 192.168.2.163 | 15.164.220.2 | running |
| sigrid-myeks-240309-sigrid-myeks-240309-nodegroup-Node | 192.168.1.241 | 13.124.176.26 | running |
| myeks-host | 192.168.1.100 | 3.36.50.27 | running |
+---------------------------------------------------------+----------------+----------------+-----------+
[root@myeks-host ~]# kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
NAME                       IP              STATUS
aws-node-5zmzj 192.168.2.163 Running
aws-node-z2pjs 192.168.1.241 Running
coredns-55474bf7b9-llcvp 192.168.1.62 Running
coredns-55474bf7b9-rtkhj 192.168.1.144 Running
kube-proxy-b584x 192.168.1.241 Running
kube-proxy-kgvz6 192.168.2.163 Running

워커 노드 1 기본 네트워크 구성 살펴보기

  • aws-node, kube-proxy의 IP가 호스트의 IP와 같다. 호스트의 IP를 공유되기 때문이다. 이와 달리 CoreDNS는 분리되기 때문에 IP가 다르다. 따라서 네트워크의 네임스페이스에서는 호스트와 파드 별로 구분된다.
  • 192.168.2.163, 192.168.1.241를 주목하여 보면 된다. aws-node와 kube-proxy는 호스트 루트 네임스페이스를 그대로 사용, 즉 ENI0를 같이 사용한다. 포트만 구분해서 서빙하면 되기 때문이다.
  • 파드와 노드의 IP 주소가 다른데, 쿠버네티스는 각 파드에 대해 격리된 네트워크 환경을 제공합니다. 이는 파드 간의 격리를 강화하고, 동일한 노드에서 실행되는 다른 파드와의 포트 충돌을 방지하기 위함이다. 쿠버네티스 클러스터 내에서는 CNI(Container Network Interface) 플러그인을 사용하여 네트워킹을 구성하며, 이를 통해 각 파드에 고유한 IP 주소를 할당한다. 이 때문에, 노드 자체의 IP 주소와는 별개로, 클러스터 내에서 파드 각각이 고유한 IP 주소를 가지게 된다.
  • 노드의 IP 주소는 노드 자체를 식별하는 데 사용되며, 외부에서 노드로의 통신이나 노드 간 통신에 사용된다. 반면, 파드의 IP 주소는 클러스터 내부에서 파드 간 통신에 사용되며, 각 파드는 해당 IP를 통해 서로를 찾고 데이터를 교환할 수 있다.
(iam-root-account@sigrid-myeks-240309:default) [root@myeks-host ~]# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default mario-7f7ddc97f4-2d54f 1/1 Running 0 6d21h 192.168.1.225 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
default my-webs-68f75cf8df-88kjh 1/1 Running 0 6d21h 192.168.1.76 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
default my-webs-68f75cf8df-czmbf 1/1 Running 0 6d21h 192.168.2.144 ip-192-168-2-163.ap-northeast-2.compute.internal <none> <none>
default my-webs-68f75cf8df-t6bfx 1/1 Running 0 6d21h 192.168.1.184 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-5zmzj 2/2 Running 0 74m 192.168.2.163 ip-192-168-2-163.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-z2pjs 2/2 Running 0 73m 192.168.1.241 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-55474bf7b9-llcvp 1/1 Running 0 66m 192.168.1.62 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-55474bf7b9-rtkhj 1/1 Running 0 66m 192.168.1.144 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-b584x 1/1 Running 0 68m 192.168.1.241 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-kgvz6 1/1 Running 0 68m 192.168.2.163 ip-192-168-2-163.ap-northeast-2.compute.internal <none> <none>
  • 네트워크 네임스페이스가 다르기 때문에 coredns와 파드의 IP는 다르다.
(iam-root-account@sigrid-myeks-240309:default) [root@myeks-host ~]# k get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-192-168-1-241.ap-northeast-2.compute.internal Ready <none> 6d23h v1.28.5-eks-5e0fdde 192.168.1.241 13.124.176.26 Amazon Linux 2 5.10.210-201.852.amzn2.x86_64 containerd://1.7.11
ip-192-168-2-163.ap-northeast-2.compute.internal Ready <none> 6d23h v1.28.5-eks-5e0fdde 192.168.2.163 15.164.220.2 Amazon Linux 2 5.10.210-201.852.amzn2.x86_64 containerd://1.7.11
(iam-root-account@sigrid-myeks-240309:default) [root@myeks-host ~]# k get pod -n kube-system -l k8s-app=kube-dns -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-55474bf7b9-llcvp 1/1 Running 0 70m 192.168.1.62 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
coredns-55474bf7b9-rtkhj 1/1 Running 0 70m 192.168.1.144 ip-192-168-1-241.ap-northeast-2.compute.internal <none> <none>
  • 워커 노드에 CoreDNS는 1번 워커 노드에 라우팅 테이블을 보면 파드의 IP가 veth 인터페이스에 통신하라고 되어 있는데, 이는 kube-proxy와 aws-node에 의해서 처리 가능하다. CoreDNS가 없으면 specific한 라우팅 정보가 없다. 참고: https://정영호.kr/306
  • 다시 한번 핵심 개념을 보면 아래와 같다.
  • 워커 노드: Kubernetes 클러스터에서 실제 컴퓨팅 작업을 수행하는 노드입니다. 파드(Pods)가 이 노드들 위에서 실행됩니다.
  • CoreDNS: 클러스터 내의 DNS 서버로, 이름 해석(Name resolution)을 담당합니다. 예를 들어, 서비스 이름을 IP 주소로 변환하는 역할을 합니다.
  • veth (Virtual Ethernet) 인터페이스: Linux 네트워크 네임스페이스 간의 통신을 가능하게 하는 가상 네트워크 인터페이스입니다. 파드가 외부 세계와 통신할 때 사용됩니다.
  • kube-proxy: 클러스터 내의 네트워크 트래픽을 관리하고, 서비스의 IP 주소를 실제 파드의 IP 주소로 라우팅하는 역할을 합니다.
  • aws-node: AWS 환경에서 실행되는 Kubernetes 클러스터를 위한 CNI(컨테이너 네트워크 인터페이스) 플러그인입니다. EKS(Elastic Kubernetes Service)에서는 이를 통해 파드 간의 네트워킹과 AWS 자원 간의 네트워킹을 관리합니다.
  • 워커 노드에서 CoreDNS는 파드의 네트워크 통신을 가능하게 하는 중요한 역할을 한다. 특히, **1번 워커 노드의 라우팅 테이블**을 통해 볼 때, 파드의 IP 주소가 veth 인터페이스를 통해 통신하도록 설정되어 있는 것을 알 수 있다. 이는 파드가 클러스터 내외의 다른 서비스나 리소스와 통신하는 구성이다.
  • 이러한 라우팅 설정은 **kube-proxy**와 **aws-node**에 의해 처리된다. kube-proxy는 Kubernetes 서비스의 IP 주소를 실제 파드의 IP 주소로 매핑하는 역할을 하며, aws-node는 AWS 환경에서의 네트워크 통신을 관리한다.
  • CoreDNS가 없으면, 클러스터 내에서 특정한 라우팅 정보(즉, 서비스 이름을 특정 파드의 IP 주소로 변환하는 정보)가 부재하게 된다. 이는 클러스터 내의 파드가 서로를 찾거나 외부 서비스와 통신하는 데 필요한 DNS 조회 기능이 없다는 의미이다.
  • 노드의 라우팅 정보를 확인해본다. EC2 네트워크 정보의 보조 프라이빗 IPv4 주소와 비교한다.
  • 특히 아래 192.168.1.109를 보면 eni가 없고 랜카드가 1개만 꽂혀있다라고 할 수 있는데, 이렇게 되면 private IPv4 주소가 5개 있다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
>> node 192.168.1.109 <<
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.109
>> node 192.168.2.12 <<
default via 192.168.2.1 dev eth0
169.254.169.254 dev eth0
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.12
192.168.2.84 dev enibeb83894e21 scope link
>> node 192.168.3.211 <<
default via 192.168.3.1 dev eth0
169.254.169.254 dev eth0
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.211
192.168.3.40 dev enic5dd49b042a scope link
  • 노드의 IP 정보를 확인한다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-192-168-1-109.ap-northeast-2.compute.internal Ready <none> 3h5m v1.28.5-eks-5e0fdde 192.168.1.109 3.34.40.55 Amazon Linux 2 5.10.210-201.852.amzn2.x86_64 containerd://1.7.11
ip-192-168-2-12.ap-northeast-2.compute.internal Ready <none> 3h5m v1.28.5-eks-5e0fdde 192.168.2.12 15.165.228.74 Amazon Linux 2 5.10.210-201.852.amzn2.x86_64 containerd://1.7.11
ip-192-168-3-211.ap-northeast-2.compute.internal Ready <none> 3h5m v1.28.5-eks-5e0fdde 192.168.3.211 15.164.163.236 Amazon Linux 2 5.10.210-201.852.amzn2.x86_64 containerd://1.7.11
  • CoreDNS의 파드 IP 정보를 확인한다. 노드의 정보와는 다르다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
NAME                       READY   STATUS    RESTARTS   AGE    IP             NODE                                               NOMINATED NODE   READINESS GATES
coredns-55474bf7b9-652tf 1/1 Running 0 3h2m 192.168.2.84 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
coredns-55474bf7b9-jqp57 1/1 Running 0 3h2m 192.168.3.40 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]#
  • aws node의 파드 IP 정보와 확인한다. 노드의 IP 대역폭과 동일하다는 것을 알 수 있다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pods -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system aws-node-f5srp 2/2 Running 0 3h6m 192.168.2.12 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-snsjd 2/2 Running 0 3h6m 192.168.3.211 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-vmwpk 2/2 Running 0 3h6m 192.168.1.109 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-55474bf7b9-652tf 1/1 Running 0 3h4m 192.168.2.84 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-55474bf7b9-jqp57 1/1 Running 0 3h4m 192.168.3.40 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-7qj7d 1/1 Running 0 3h5m 192.168.2.12 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-8lwf2 1/1 Running 0 3h5m 192.168.3.211 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-hd26m 1/1 Running 0 3h5m 192.168.1.109 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]#
  • IP 테이블을 보자.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
>> node 192.168.1.109 <<
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.109
>> node 192.168.2.12 <<
default via 192.168.2.1 dev eth0
169.254.169.254 dev eth0
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.12
192.168.2.84 dev enibeb83894e21 scope link
>> node 192.168.3.211 <<
default via 192.168.3.1 dev eth0
169.254.169.254 dev eth0
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.211
192.168.3.40 dev enic5dd49b042a scope link
  • 파드의 IP가 veth 인터페이스로 통신하라고 되어 있다. kube-proxy (aws-node) 에 의해서 꺾여져서 들어오게 된다. 192.168.1.109는 없으므로 specific한 라우팅 정보가 없다.
192.168.2.84 dev enibeb83894e21 scope link # coredns 1
192.168.3.40 dev enic5dd49b042a scope link # coredns 2
  • 이제 netshoot 파드를 테스트용으로 생성해보자.
# [터미널1~3] 노드 모니터링
ssh ec2-user@$N1
**watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"**
ssh ec2-user@$N2
**watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"**
ssh ec2-user@$N3
**watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
# eni가 있는 노드가 있고 없는 노드가 있다.Every 2.0s: ip link | egrep 'eth|eni' ;echo;echo [ROUTE TABLE]; route -n | gr... Sat Mar 16 15:09:39 20242: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 06:9f:64:8c:47:17 brd ff:ff:ff:ff:ff:ff
3: enibeb83894e21@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT grou
p default
link/ether 62:1d:52:b1:6c:9f brd ff:ff:ff:ff:ff:ff link-netns cni-b9876cd0-4307-67c2-bf7b-54ef24eec8da
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 06:c5:a4:6b:cc:3f brd ff:ff:ff:ff:ff:ff
[ROUTE TABLE]
192.168.2.84 0.0.0.0 255.255.255.255 UH 0 0 0 enibeb83894e21
Every 2.0s: ip link | egrep 'eth|eni' ;echo;echo [ROUTE TABLE]; route -n | gr... Sat Mar 16 15:09:49 20242: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 02:e1:f3:cc:4f:3d brd ff:ff:ff:ff:ff:ff
[ROUTE TABLE]**
  • netshoot 파드 테스트용을 할당하면 아래와 같이 신규로 할당됨을 확인할 수 있다. 동일하게 AWS 콘솔에 가서 ng1-Node를 보면 Private IPv4가 추가된 것을 알 수 있다.
Every 2.0s: ip link | egrep 'eth|eni' ;echo;echo [ROUTE TABLE]; route -n | gr...  Sat Mar 16 15:10:11 2024
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 02:e1:f3:cc:4f:3d brd ff:ff:ff:ff:ff:ff
6: enie2dacdbffd2@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT grou
p default
link/ether f2:d0:ee:69:8a:eb brd ff:ff:ff:ff:ff:ff link-netns cni-c04ce667-32fd-2c12-24dd-36ae1484725b
[ROUTE TABLE]
192.168.1.90 0.0.0.0 255.255.255.255 UH 0 0 0 enie2dacdbffd2(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netshoot-pod-79b47d6c48-dffd4 1/1 Running 0 70s 192.168.1.90 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-g7kvn 1/1 Running 0 70s 192.168.3.238 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-hmsqz 1/1 Running 0 70s 192.168.2.156 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
netshoot-pod-79b47d6c48-dffd4 192.168.1.90
netshoot-pod-79b47d6c48-g7kvn 192.168.3.238
netshoot-pod-79b47d6c48-hmsqz 192.168.2.156
  • 라우팅 테이블을 확인해보고 또한 1번 워커 노드에 확인해본다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
>> node 192.168.1.109 <<
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.109
192.168.1.90 dev enie2dacdbffd2 scope link
>> node 192.168.2.12 <<
default via 192.168.2.1 dev eth0
169.254.169.254 dev eth0
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.12
192.168.2.84 dev enibeb83894e21 scope link
192.168.2.156 dev enif8a738822ac scope link
>> node 192.168.3.211 <<
default via 192.168.3.1 dev eth0
169.254.169.254 dev eth0
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.211
192.168.3.40 dev enic5dd49b042a scope link
192.168.3.238 dev eni47876b365ac scope link
[ec2-user@ip-192-168-3-211 ~]$ ip -br -c addr show
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.3.211/24 fe80::8cf:86ff:feff:5925/64
enic5dd49b042a@if3 UP fe80::d89a:76ff:fe94:d77b/64
eth1 UP 192.168.3.74/24 fe80::80b:9fff:fedd:add5/64
eni47876b365ac@if3 UP fe80::3c73:17ff:fe54:24d/64
[ec2-user@ip-192-168-3-211 ~]$ ip -c link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 0a:cf:86:ff:59:25 brd ff:ff:ff:ff:ff:ff
3: enic5dd49b042a@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT group default
link/ether da:9a:76:94:d7:7b brd ff:ff:ff:ff:ff:ff link-netns cni-f1e3779a-9d6b-67cb-3e2f-2f3cd648703a
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 0a:0b:9f:dd:ad:d5 brd ff:ff:ff:ff:ff:ff
5: eni47876b365ac@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT group default
link/ether 3e:73:17:54:02:4d brd ff:ff:ff:ff:ff:ff link-netns cni-2fb88a1e-f7d3-77cc-cffd-05e0dd38dd78
[ec2-user@ip-192-168-3-211 ~]$ ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 0a:cf:86:ff:59:25 brd ff:ff:ff:ff:ff:ff
inet 192.168.3.211/24 brd 192.168.3.255 scope global dynamic eth0
valid_lft 2943sec preferred_lft 2943sec
inet6 fe80::8cf:86ff:feff:5925/64 scope link
valid_lft forever preferred_lft forever
3: enic5dd49b042a@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether da:9a:76:94:d7:7b brd ff:ff:ff:ff:ff:ff link-netns cni-f1e3779a-9d6b-67cb-3e2f-2f3cd648703a
inet6 fe80::d89a:76ff:fe94:d77b/64 scope link
valid_lft forever preferred_lft forever
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 0a:0b:9f:dd:ad:d5 brd ff:ff:ff:ff:ff:ff
inet 192.168.3.74/24 brd 192.168.3.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::80b:9fff:fedd:add5/64 scope link
valid_lft forever preferred_lft forever
5: eni47876b365ac@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether 3e:73:17:54:02:4d brd ff:ff:ff:ff:ff:ff link-netns cni-2fb88a1e-f7d3-77cc-cffd-05e0dd38dd78
inet6 fe80::3c73:17ff:fe54:24d/64 scope link
valid_lft forever preferred_lft forever
[ec2-user@ip-192-168-3-211 ~]$ ip route # 혹은 route -n
default via 192.168.3.1 dev eth0
169.254.169.254 dev eth0
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.211
192.168.3.40 dev enic5dd49b042a scope link
192.168.3.238 dev eni47876b365ac scope link
[ec2-user@ip-192-168-3-211 ~]$
[ec2-user@ip-192-168-3-211 ~]$ # 마지막 생성된 네임스페이스 정보 출력 -t net(네트워크 타입)
[ec2-user@ip-192-168-3-211 ~]$ sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1
67569
[ec2-user@ip-192-168-3-211 ~]$
[ec2-user@ip-192-168-3-211 ~]$ # 마지막 생성된 네임스페이스 net PID 정보 출력 -t net(네트워크 타입)를 변수 지정
[ec2-user@ip-192-168-3-211 ~]$ MyPID=$(sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1)
[ec2-user@ip-192-168-3-211 ~]$
[ec2-user@ip-192-168-3-211 ~]$ # PID 정보로 파드 정보 확인
[ec2-user@ip-192-168-3-211 ~]$ sudo nsenter -t $MyPID -n ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether ce:cb:9c:81:5a:7b brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.3.238/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::cccb:9cff:fe81:5a7b/64 scope link
valid_lft forever preferred_lft forever
  • 테스트용 파드에 접속해서 확인해본다. 아래 3번을 보면 192.168.1.90/32 를 확인 가능하다.
  • 워커 1번에 대하여 2c에 있는 노드에 10개 풀 중에서 private ip를 할당 가능하고 이를 받은 것이라고 할 수 있다. 특히 netshoot-pod-79b47d6c48-dffd4 를 보면 192.168.1.90 이다.
netshoot-pod-79b47d6c48-dffd4  ~  ip -c addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether c6:78:69:f9:50:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.90/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::c478:69ff:fef9:50b1/64 scope link
valid_lft forever preferred_lft forever

(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netshoot-pod-79b47d6c48-dffd4 1/1 Running 0 6m46s 192.168.1.90 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-g7kvn 1/1 Running 0 6m46s 192.168.3.238 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-hmsqz 1/1 Running 0 6m46s 192.168.2.156 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
  • 다른 파드랑 통신도 된다. 각각 안에 들어가서 네트워크를 확인해보자. 워커 노드에 ENI가 할당이 되고 워커노드에 보조 IP가 할당이 된다.
netshoot-pod-79b47d6c48-dffd4  ~  ip -c route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
 netshoot-pod-79b47d6c48-dffd4  ~  ping 192.168.3.238
PING 192.168.3.238 (192.168.3.238) 56(84) bytes of data.
64 bytes from 192.168.3.238: icmp_seq=1 ttl=125 time=1.35 ms
64 bytes from 192.168.3.238: icmp_seq=2 ttl=125 time=0.870 ms
64 bytes from 192.168.3.238: icmp_seq=3 ttl=125 time=0.851 ms
64 bytes from 192.168.3.238: icmp_seq=4 ttl=125 time=0.869 ms
64 bytes from 192.168.3.238: icmp_seq=5 ttl=125 time=0.885 ms
netshoot-pod-79b47d6c48-dffd4  ~  cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
nameserver 10.100.0.10
options ndots:5
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl exec -it $PODNAME2 -- ip -c addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether ce:cb:9c:81:5a:7b brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.3.238/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::cccb:9cff:fe81:5a7b/64 scope link
valid_lft forever preferred_lft forever

(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl exec -it $PODNAME3 -- ip -br -c addr
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0@if5 UP 192.168.2.156/32 fe80::a071:47ff:feca:fb51/64

파드에서 통신

내부 통신

  • 다른 파드들 간에 직접 통신이 가능하다. 물론 AWS Virtual Router가 있긴 하지만 별도로 라우팅이 없어 오버헤드 없이 통신이 가능하다는 것을 tcpdump로 증명해보자.
  • 출력된 로그에서는 파드1(192.168.1.90)이 파드2(192.168.3.238)로 ICMP 에코 요청을 보내고, 파드2가 이에 에코 응답을 보내는 것을 볼 수 있다. **tcpdump**는 네트워크 인터페이스를 통해 전달되는 패킷을 캡처하여 이를 표시한다. 여기서는 ICMP 패킷이 캡처되었으며, 이는 파드 간의 직접적인 통신이 있음을 증명한다.
**# 워커 노드 EC2 :** TCPDUMP 확인
sudo tcpdump -i **any** -nn icmp
sudo tcpdump -i **eth1** -nn icmp
sudo tcpdump -i **eth0** -nn icmp
sudo tcpdump -i **eniYYYYYYYY** -nn icmp
# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
# netshoot pod
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netshoot-pod-79b47d6c48-dffd4 1/1 Running 0 16m 192.168.1.90 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-g7kvn 1/1 Running 0 16m 192.168.3.238 ip-192-168-3-211.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-79b47d6c48-hmsqz 1/1 Running 0 16m 192.168.2.156 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
[ec2-user@ip-192-168-1-109 ~]$ sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
15:22:58.334452 IP 192.168.1.90 > 192.168.3.238: ICMP echo request, id 226, seq 1, length 64
15:22:58.334505 IP 192.168.1.90 > 192.168.3.238: ICMP echo request, id 226, seq 1, length 64
15:22:58.335575 IP 192.168.3.238 > 192.168.1.90: ICMP echo reply, id 226, seq 1, length 64
15:22:58.335760 IP 192.168.3.238 > 192.168.1.90: ICMP echo reply, id 226, seq 1, length 64
15:22:59.335908 IP 192.168.1.90 > 192.168.3.238: ICMP echo request, id 226, seq 2, length 64
15:22:59.335955 IP 192.168.1.90 > 192.168.3.238: ICMP echo request, id 226, seq 2, length 64
15:22:59.336750 IP 192.168.3.238 > 192.168.1.90: ICMP echo reply, id 226, seq 2, length 64
15:22:59.336763 IP 192.168.3.238 > 192.168.1.90: ICMP echo reply, id 226, seq 2, length 64
[워커 노드1]
# routing policy database management 확인
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
# routing table management 확인
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 172.17.0.0 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
broadcast 192.168.1.0 dev eth0 proto kernel scope link src 192.168.1.100
local 192.168.1.100 dev eth0 proto kernel scope host src 192.168.1.100
broadcast 192.168.1.255 dev eth0 proto kernel scope link src 192.168.1.100
# 디폴트 네트워크 정보를 eth0 을 통해서 빠져나간다
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# ip route show table main
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.100

외부 통신

  • iptable 에 SNAT 을 통하여 노드의 eth0 IP로 변경되어서 외부와 통신하게 되는데, 이 때 VPC CNI의 External Source Network Address Translation (SNAT) 설정에 따라 외부와 통신 시에 SNAT하거나 또는 SNAT 없이 통신을 할 수 있다.
  • VPC CNI (Container Network Interface): Amazon EKS 클러스터에서 사용되는 네트워크 플러그인이다. 이 플러그인은 AWS의 Virtual Private Cloud(VPC) 내에서 파드 네트워킹을 가능하게 한다.
  • SNAT (Source Network Address Translation): 네트워크 주소 변환의 한 형태로, 특히 소스 IP 주소를 변경하는 방법이다. 이 기술을 통해 여러 개의 내부 IP 주소가 하나의 외부 IP 주소를 공유하여 인터넷과 같은 외부 네트워크에 접근할 수 있다.
  • eth0: 일반적으로 리눅스 시스템에서 첫 번째 이더넷 인터페이스를 지칭한다. EKS 워커 노드에서는 이 인터페이스를 통해 VPC 내외부와의 네트워크 통신이 이루어진다.
  • SNAT을 통한 통신: EKS 클러스터에서 파드가 클러스터 외부와 통신할 때, VPC CNI는 파드의 IP 주소를 노드의 eth0 인터페이스 IP 주소로 변경(SNAT)하여 통신한다. 이 방식을 사용하면, 클러스터 외부의 시스템은 오로지 노드의 IP 주소만을 볼 수 있으며, 파드가 실제로 사용하는 내부 IP 주소는 숨겨진다. 이는 외부와의 통신을 가능하게 하는 동시에 클러스터 내부의 네트워크 구조를 보호한다.
  • SNAT 설정 옵션: VPC CNI는 SNAT를 사용하여 외부와 통신할 것인지, 아니면 SNAT 없이 파드의 실제 IP 주소를 사용하여 통신할 것인지를 설정할 수 있게 한다.
  • SNAT 사용 시의 장점: SNAT를 사용하면 클러스터 내부의 IP 주소 체계가 외부로부터 보호되며, 또한 하나의 외부 IP 주소를 통해 여러 파드가 외부와 통신할 수 있게 된다. 이는 IP 주소의 효율적인 사용과 네트워크 보안 관리에 유리하다.
  • SNAT 없이 통신하는 경우: 일부 경우에는 파드가 VPC 내부 또는 연결된 네트워크의 리소스와 직접 통신해야 할 필요가 있을 수 있다. 이런 경우, 파드의 실제 IP 주소를 그대로 사용하여 통신함으로써, 네트워크 경로 추적이나 접근 제어 리스트(ACL) 등을 보다 세밀하게 적용할 수 있다.
  • 실습으로 구글과 통신해본다. 유동 공인 IP로 소스 IP가 NAT 되어서 나가는데 파드가 통신했을 때의 공인 IP는?
**# 작업용 EC2 :** pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl exec -it $PODNAME1 -- ping -c 1 www.google.comkubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
PING www.google.com (172.217.25.164) 56(84) bytes of data.
64 bytes from kix06s19-in-f4.1e100.net (172.217.25.164): icmp_seq=1 ttl=104 time=17.8 ms
--- www.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 17.757/17.757/17.757/0.000 ms
0 packets dropped by kernel
[ec2-user@ip-192-168-1-109 ~]$ sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
15:29:49.999945 IP 192.168.1.90 > 172.217.25.164: ICMP echo request, id 238, seq 1, length 64
15:29:49.999972 IP 192.168.1.109 > 172.217.25.164: ICMP echo request, id 32337, seq 1, length 64
15:29:50.017603 IP 172.217.25.164 > 192.168.1.109: ICMP echo reply, id 32337, seq 1, length 64
15:29:50.017646 IP 172.217.25.164 > 192.168.1.90: ICMP echo reply, id 238, seq 1, length 64
  • 파드 1번에서 netshoot으로 ipinfo에 접근한 내용. 워커 노드가 외부에 통신할 때는 이더넷 0번에 매핑되어 있는 유동 공인 IP로 소스 NAT이 된다.
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i curl -s ipinfo.io/ip; echo; echo; done
>> node 192.168.1.109 <<
3.34.40.55>> node 192.168.2.12 <<
15.165.228.74
>> node 192.168.3.211 <<
15.164.163.236
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# for i in $PODNAME1 $PODNAME2 $PODNAME3; do echo ">> Pod : $i <<"; kubectl exec -it $i -- curl -s ipinfo.io/ip; echo; echo; done
>> Pod : netshoot-pod-79b47d6c48-dffd4 <<
3.34.40.55>> Pod : netshoot-pod-79b47d6c48-g7kvn <<
15.164.163.236
>> Pod : netshoot-pod-79b47d6c48-hmsqz <<
15.165.228.74
  • 워커 노드의 첫 번째 인터넷을 사용하는 파드가 외부와 통신할 때, 매스커레이딩을 통해 해당 워커 노드의 첫 번째 네트워크 인터페이스에 할당된 유동 공인 IP 주소로 트래픽이 매핑되어 들어갔다 나온다. 이 과정에서 사용되는 iptables 규칙을 관리하는 건 kube-proxy이다.
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
Every 2.0s: sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN...  Sat Mar 16 15:36:35 2024Chain AWS-SNAT-CHAIN-0 (1 references)
pkts bytes target prot opt in out source destination
1748 167K RETURN all -- * * 0.0.0.0/0 192.168.0.0/16 /* AWS SNAT
CHAIN */
4938 315K SNAT all -- * !vlan+ 0.0.0.0/0 0.0.0.0/0 /* AWS, SNAT
*/ ADDRTYPE match dst-type !LOCAL to:192.168.1.109 random-fully
Chain KUBE-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
1782 122K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 mark match !
0x4000/0x4000
0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK xor 0x4
000
0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernet
es service traffic requiring SNAT */ random-fully
Chain POSTROUTING (policy ACCEPT 807 packets, 60691 bytes)
pkts bytes target prot opt in out source destination
9356 643K KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* ku
bernetes postrouting rules */
9320 640K AWS-SNAT-CHAIN-0 all -- * * 0.0.0.0/0 0.0.0.0/0 /* AW
S SNAT CHAIN */

노드에 파드 생성 개수를 제한한다

  • 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 env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
# 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"}'
  • Secondary IPv4 addresses 는 기본값으로 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정한다. 인스턴스 타입 별로 ENI의 최대 개수와 할당 가능한 최대 IP 개수에 따라서 파드의 배치 갯수가 결정되는데, 이 때 aws-node와 kube-proxy의 파드는 호스트 IP를 사용하기 때문에 최대 개수에서 제외된다고 한다.
  • 최대 파드 생성 갯수 : aws-node 와 kube-proxy 파드는 host-networking 사용으로 IP 2개 남음 → ((MaxENI * (IPv4addr-1)) + 2)
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \\
> --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \\
> --output table
--------------------------------------
| DescribeInstanceTypes |
+----------+----------+--------------+
| IPv4addr | MaxENI | Type |
+----------+----------+--------------+
| 6 | 3 | t3.medium |
| 12 | 3 | t3.large |
| 15 | 4 | t3.2xlarge |
| 15 | 4 | t3.xlarge |
| 2 | 2 | t3.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
  • t3.medium의 MaxENI는 3개까지 가질 수 있고 IPv4 주소는 6개 할당 가능하므로, 자기 자신을 제외한 (6–1) x 3 = 15개이다. 단, aws-node 와 kube-proxy 2개를 포함하면 17개이다.
  • 최대 파드를 생성해본다.
  • kube-ops-view에서는 kube-system의 경우 하단에 뜨고 나머지 파드는 상단에 뜬다.
# 워커 노드 EC2 - 모니터링
while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
--------------
2024-03-17 00:47:47
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.1.100/24 fe80::f8:3aff:fe2f:e467/64
docker0 DOWN 172.17.0.1/16
# 작업용 EC2 - 터미널2
# 디플로이먼트 생성
curl -s -O <https://raw.githubusercontent.com/gasida/PKOS/main/2/nginx-dp.yaml>
**kubectl apply -f nginx-dp.yaml
# 파드 확인
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-f7f5c78c5-p4gc5 1/1 Running 0 48s 192.168.2.156 ip-192-168-2-12.ap-northeast-2.compute.internal <none> <none>
nginx-deployment-f7f5c78c5-vrm2p 1/1 Running 0 48s 192.168.1.142 ip-192-168-1-109.ap-northeast-2.compute.internal <none> <none>
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
nginx-deployment-f7f5c78c5-p4gc5 192.168.2.156
nginx-deployment-f7f5c78c5-vrm2p 192.168.1.142**
  • 15개까지 증가. 3번째 랜카드가 떴다. t3-medium의 한계는 3 랜카드에 각 랜카드당 5개 private ip까지만 가능하다.
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
kubectl scale deployment nginx-deployment --replicas=8
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=15
--------------
2024-03-16 15:51:48
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.2.12/24 fe80::49f:64ff:fe8c:4717/64
enibeb83894e21@if3 UP fe80::601d:52ff:feb1:6c9f/64
eth1 UP 192.168.2.184/24 fe80::4c5:a4ff:fe6b:cc3f/64
eni27fc54ef1c3@if3 UP fe80::98e2:88ff:feec:7127/64
enieacee70df7a@if3 UP fe80::9c30:abff:fea6:2da4/64
enia19c2b19a15@if3 UP fe80::2cc3:59ff:fe06:17ab/64
enie8aa41076d4@if3 UP fe80::b49e:34ff:fe60:9400/64
eni696b05fcc11@if3 UP fe80::d47b:aaff:fe5a:5ce6/64
eth2 UP 192.168.2.170/24 fe80::48d:89ff:fe92:3af/64
  • 이제 30개, 50개를 해보자.
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=30
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
**kubectl scale deployment nginx-deployment --replicas=50
# 이제 pending 상태에 빠진다
(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl get pods | grep Pending
nginx-deployment-f7f5c78c5-ddv2k 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-fbmgh 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-jwzzx 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-lfkns 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-nr2xv 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-ptvbg 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-v52q7 0/1 Pending 0 20s
nginx-deployment-f7f5c78c5-zrjjl 0/1 Pending 0 20s
# describe pod - FailedScheduling(iam-root-account@sigridjin-ekscluster-c:N/A) [root@sigridjin-ekscluster-c-bastion-EC2 ~]# kubectl describe pod nginx-deployment-f7f5c78c5-ptvbg
Name: nginx-deployment-f7f5c78c5-ptvbg
Namespace: default
Priority: 0
Service Account: default
Node: <none>
Labels: app=nginx
pod-template-hash=f7f5c78c5
Annotations: <none>
Status: Pending
IP:
IPs: <none>
Controlled By: ReplicaSet/nginx-deployment-f7f5c78c5
Containers:
nginx:
Image: nginx:alpine
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-mfkq7 (ro)
Conditions:
Type Status
PodScheduled False
Volumes:
kube-api-access-mfkq7:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 36s (x2 over 38s) default-scheduler 0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod..**
  • 이제 잘 알고 운영을 하면 되겠다. 마지막으로 디플로이먼트를 삭제한다. 그러면 eni가 어떻게 되는지 살펴본다. 랜카드 하나가 사라진 것을 알 수 있다. 여기서는 eth1이 사라졌다.
**# while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
2024-03-16 15:54:52
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.2.12/24 fe80::49f:64ff:fe8c:4717/64
enibeb83894e21@if3 UP fe80::601d:52ff:feb1:6c9f/64
eth1 UP 192.168.2.184/24 fe80::4c5:a4ff:fe6b:cc3f/64
eth2 UP 192.168.2.170/24 fe80::48d:89ff:fe92:3af/64
kubectl delete deploy nginx-deployment--------------
2024-03-16 15:55:07
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.2.12/24 fe80::49f:64ff:fe8c:4717/64
enibeb83894e21@if3 UP fe80::601d:52ff:feb1:6c9f/64
eth2 UP 192.168.2.170/24 fe80::48d:89ff:fe92:3af/64**
  • 경량화된 파드여서 엄청 많이 떠져도 괜찮다고 판단하면 어떻게 파드 개수 제한을 우회할 수 있을지 생각해보자. 방법은 접두사 위임이다. 보조 IP를 받는 것이 아니라 대역을 받을 수 있다. /28 이면 16개의 IP를 받을 수 있는데, 대역을 위임을 해서 받게 되면 더 많은 파드를 배치할 수 있다.
  • 이 외에도 커스텀 네트워크 등을 설정할 수도 있다. 자세한 사항은 AWS EKS Workshop에서 Prefix Delegation이라고 내용이 나와 있으니 참고하면 되겠다.

Service와 AWS LoadBalancer Controller

  • 쿠버네티스 서비스는 파드의 경우 언제든 휘발 가능하므로 파드의 IP를 목적지로 타겟을 설계할 수 없다. 파드가 다시 뜨면 IP가 다시 할당되기 때문이다. 하지만 서비스를 사용하면 고정 진입점 내지 접속 도메인 주소를 통해서 접근 가능하도록 할 수 있다. 부하 분산 시에 유리하다.
  • ClusterIP 타입 → 고정 진입점의 IP와 고정 진입점의 도메인이 있다.
  • NodePort 타입
  • AWS의 기본은 LoadBalancer 타입이다. MetalLB를 쓴다고 가정했을 때 노드포트 들어오고 분산을 쓰고 하는 복잡한 단계와 분산 룰을 거치게 된다. 아래는 NLB 타입이다.
  • Service (LoadBalancer Controller) : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI: 파드의 IP를 로드 밸런서가 지속적으로 알아야 한다. 이를 위해서는 로드 밸런서의 컨트롤러 파드가 역할을 하며 Service, NodePort, iptables를 우회할 수 있다. 노드포트를 안쓰고 파드 IP로 직접 통신이 가능한데 그 이유는 VPC CNI 덕분이다. 대상 타겟의 IP로 바로 들어오며 불필요한 노드포트나 iptables 룰을 모두 사용하지 않는다.
  • 인증 관련 이슈로 인해 OIDC가 필요하다. LBC 파드가 로드 밸런서를 조작할 수 있도록 (파드 밖에 이므로) IAM Policy가 필요하다.
# aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
<https://oidc.eks.ap-northeast-2.amazonaws.com/id/1179ECEAB3893F4F30FA2F02B39CC008># aws iam list-open-id-connect-providers | jq{
"OpenIDConnectProviderList": [
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/0881F2829D5A68E873E712C3E676168D"
},
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/1179ECEAB3893F4F30FA2F02B39CC008"
},
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/549EBE24E232973ED0EF059580BA7542"
},
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/897BAA2B72002A11BA1906EE5DB00910"
},
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/B72737D468EEDEB312FCBAB3445B0FC2"
},
{
"Arn": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/C50DD749CA5A8EB083E57999CE08073A"
}
]
}
  • IAM Policy를 생성한다. ELB에 파드 IP 등 지속적인 정보 제공을 해야 한다. 따라서 LBC 파드가 IAM Policy를 가지고 있어야 해당 로드밸런서 조작이 가능하다.
aws iam list-policies --scope **Local** | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/**AWSLoadBalancerControllerIAMPolicy** | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/**AWSLoadBalancerControllerIAMPolicy** --query 'Policy.Arn'
# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 >> 자동으로 매칭되는 IAM Role 을 CloudFormation 으로 생성됨!
# IAM 역할 생성. AWS Load Balancer Controller의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할의 이름으로 Kubernetes 서비스 계정에 주석을 답니다
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller --role-name AmazonEKSLoadBalancerControllerRole \\
> --attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
2024-03-17 01:03:48 [ℹ] 1 iamserviceaccount (kube-system/aws-load-balancer-controller) was included (based on the include/exclude rules)
2024-03-17 01:03:48 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set
2024-03-17 01:03:48 [ℹ] 1 task: {
2 sequential sub-tasks: {
create IAM role for serviceaccount "kube-system/aws-load-balancer-controller",
create serviceaccount "kube-system/aws-load-balancer-controller",
} }2024-03-17 01:03:48 [ℹ] building iamserviceaccount stack "eksctl-sigridjin-ekscluster-c-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-03-17 01:03:48 [ℹ] deploying stack "eksctl-sigridjin-ekscluster-c-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-03-17 01:03:48 [ℹ] waiting for CloudFormation stack "eksctl-sigridjin-ekscluster-c-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-03-17 01:04:18 [ℹ] waiting for CloudFormation stack "eksctl-sigridjin-ekscluster-c-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-03-17 01:04:18 [ℹ] created serviceaccount "kube-system/aws-load-balancer-controller"
****## IRSA 정보 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE NAME ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::712218945685:role/AmazonEKSLoadBalancerControllerRole
## 서비스 어카운트 확인
kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::712218945685:role/AmazonEKSLoadBalancerControllerRole
creationTimestamp: "2024-03-16T16:04:18Z"
labels:
app.kubernetes.io/managed-by: eksctl
name: aws-load-balancer-controller
namespace: kube-system
resourceVersion: "48279"
uid: dc05dc89-dd41-46e4-9e47-ac90a5b09c25
  • AWS Load Balancer Pod를 설치한다. Target Group Binding은 AWS 대상 그룹 타겟을 만들고 생성하면 역으로 AWS에 있는 대상을 modify하거나 추가할 수 있다. ingress 관리까지 수행하게 된다. 이후에 생성된 IAM Role의 신뢰 관계를 확인해야 한다. (Federation, Conditions, StringEquals)
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 \\
--set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller**
## 설치 확인 : aws-load-balancer-controller:v2.7.1
kubectl get crd
NAME CREATED AT
cninodes.vpcresources.k8s.aws 2024-03-16T11:51:14Z
eniconfigs.crd.k8s.amazonaws.com 2024-03-16T11:52:22Z
ingressclassparams.elbv2.k8s.aws 2024-03-16T16:05:39Z
policyendpoints.networking.k8s.aws 2024-03-16T11:51:16Z
securitygrouppolicies.vpcresources.k8s.aws 2024-03-16T11:51:14Z
targetgroupbindings.elbv2.k8s.aws 2024-03-16T16:05:39Z
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 get deployment -n kube-system aws-load-balancer-controller
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 64s
# 클러스터롤, 롤 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
Name: aws-load-balancer-controller-rolebinding
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.7.1
helm.sh/chart=aws-load-balancer-controller-1.7.1
Annotations: meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
Role:
Kind: ClusterRole
Name: aws-load-balancer-controller-role
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount aws-load-balancer-controller kube-system
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
Name: aws-load-balancer-controller-role
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.7.1
helm.sh/chart=aws-load-balancer-controller-1.7.1
Annotations: meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
  • 서비스와 파드 베포 테스트를 NLB와 함께 해본다.
# 모니터링
watch -d kubectl get pod,svc,ep
# 작업용 EC2 - 디플로이먼트 & 서비스 생성
curl -s -O <https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml>
cat echo-service-nlb.yaml | yh
**kubectl apply -f echo-service-nlb.yaml
---
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
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb # 여기를 보면 로드밸런서 클래스가 있다!!
selector:
app: deploy-websrv

kubectl apply -f echo-service-nlb.yaml
---
kubectl get svc,ep,ingressclassparams,targetgroupbindings
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4h20m
service/svc-nlb-ip-type LoadBalancer 10.100.94.126 <pending> 80:32485/TCP 2m3s
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.1.245:443,192.168.3.173:443 4h20m
endpoints/svc-nlb-ip-type 192.168.1.90:8080,192.168.3.188:8080 2m3s
NAME GROUP-NAME SCHEME IP-ADDRESS-TYPE AGE
ingressclassparams.elbv2.k8s.aws/alb 5m28s
---
kubectl get targetgroupbindings -o json | jq
{
"apiVersion": "v1",
"items": [],
"kind": "List",
"metadata": {
"resourceVersion": ""
}**
  • 생성된 IAM Role의 신뢰 관계를 확인한다
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::712218945685:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/1179ECEAB3893F4F30FA2F02B39CC008"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/1179ECEAB3893F4F30FA2F02B39CC008:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/1179ECEAB3893F4F30FA2F02B39CC008:sub": "system:serviceaccount:kube-system:aws-load-balancer-controller"
}
}
}
]
}
  • 추가적인 IAM 정책 (ELB AddTags) 이 필요하기에 다음과 같이 만들어준다.
vim elb-controller-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:AddTags",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
],
"Resource": "*"
}
]
}
# aws iam create-policy --policy-name ELBControllerAdditionalPermissions --policy-document file://elb-controller-policy.json# aws iam attach-role-policy --role-name AmazonEKSLoadBalancerControllerRole --policy-arn arn:aws:iam::$ACCOUNT_I
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:AddTags",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:ModifyLoadBalancerAttributes"
],
"Resource": "*"
}
]
}
# helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller --set clusterName=$CLUSTER_NAME --namespace kube-system --reuse-values
Release "aws-load-balancer-controller" has been upgraded. Happy Helming!
NAME: aws-load-balancer-controller
LAST DEPLOYED: Sun Mar 17 01:26:15 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 4
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!
# kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller -f
{"level":"info","ts":"2024-03-16T16:25:38Z","logger":"controllers.service","msg":"modified loadBalancer attributes","stackID":"default/svc-nlb-ip-type","resourceID":"LoadBalancer","arn":"arn:aws:elasticloadbalancing:ap-northeast-2:712218945685:loadbalancer/net/k8s-default-svcnlbip-5db924efdc/75b5030f9d8325e7"}
{"level":"info","ts":"2024-03-16T16:25:38Z","logger":"controllers.service","msg":"creating listener","stackID":"default/svc-nlb-ip-type","resourceID":"80"}
{"level":"info","ts":"2024-03-16T16:25:38Z","logger":"controllers.service","msg":"created listener","stackID":"default/svc-nlb-ip-type","resourceID":"80","arn":"arn:aws:elasticloadbalancing:ap-northeast-2:712218945685:listener/net/k8s-default-svcnlbip-5db924efdc/75b5030f9d8325e7/c3aaf830a872c27f"}
# 서비스를 통해서 배포 여부를 확인한다
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4h38m
svc-nlb-ip-type LoadBalancer 10.100.94.126 k8s-default-svcnlbip-5db924efdc-75b5030f9d8325e7.elb.ap-northeast-2.amazonaws.com 80:32485/TCP 20m
# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output textALB_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 | jqTARGET_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.3.188",
"Port": 8080,
"AvailabilityZone": "ap-northeast-2c"
},
"HealthCheckPort": "8080",
"TargetHealth": {
"State": "healthy"
}
},
{
"Target": {
"Id": "192.168.1.90",
"Port": 8080,
"AvailabilityZone": "ap-northeast-2a"
},
"HealthCheckPort": "8080",
"TargetHealth": {
"State": "healthy"
}
}
]
}
# kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'Pod Web URL = <http://k8s-default-svcnlbip-5db924efdc-75b5030f9d8325e7.elb.ap-northeast-2.amazonaws.com>
  • 이제 빠른 실습을 위하여 등록 취소 지연 즉 드레이닝 간격을 수정한다. 60초로 설정을 했더니 Kubernetes의 설정 변화가 로드 밸런서 설정 변화를 만들어냈다. 롤을 매핑해서 가지고 있기 때문에 내가 연결된 NLB를 modify할 수 있다는 뜻이다. 쿠버네티스에서 변화가 있었지마는 AWS 쪽으로도 변화가 생기게 된다.
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
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
  • 이제 분산 접속을 확인한다. 바로 파드로 bypass로 잡아서 직접 꽂으며 매우 효율적인 상태다.
# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f
# 주소
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
curl -s $NLBHostname: deploy-echo-7f579ff9d7-bx49xPod Information:
-no pod information available-
Server values:
server_version=nginx: 1.13.0 - lua: 10008
Request Information:
client_address=192.168.2.168
method=GET
real path=/
query=
request_version=1.1
request_uri=http://k8s-default-svcnlbip-5db924efdc-75b5030f9d8325e7.elb.ap-northeast-2.amazonaws.com:8080/
Request Headers:
accept=*/*
host=k8s-default-svcnlbip-5db924efdc-75b5030f9d8325e7.elb.ap-northeast-2.amazonaws.com
user-agent=curl/8.3.0
Request Body:
-no body in request-
  • 이제 분산 접속이 가능하다!
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
51 Hostname: deploy-echo-7f579ff9d7-bx49x
49 Hostname: deploy-echo-7f579ff9d7-l68qq

192.168.2.168 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.1.152 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.3.68 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 689 "-" "curl/8.3.0"
192.168.2.168 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.1.152 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.3.68 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 689 "-" "curl/8.3.0"
192.168.2.168 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.1.152 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.3.68 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 689 "-" "curl/8.3.0"
192.168.2.168 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.1.152 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.3.68 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 689 "-" "curl/8.3.0"
192.168.2.168 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.1.152 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 690 "-" "curl/8.3.0"
192.168.3.68 - - [16/Mar/2024:16:51:31 +0000] "GET / HTTP/1.1" 200 689 "-" "curl/8.3.0"
  • 파드가 2개가 되었다가, 1개가 되었다가, 3개로 설정하게 되면 Auto Discovery하게 되어 Target Group에 동적으로 등록하게 된다. 이러한 경우 ALB에서 Target Group으로 Listen하게 되고 파드의 개수에 따라서 동적으로 target을 register하거나 drain하게 된다.
  • 이 외에도 readiness gate를 통해서 mutating webhook에 맞는 pod를 modify하게될 수 있다.
# (신규 터미널) 모니터링
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
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
# 작업용 EC2 - 파드 3개 설정
kubectl scale deployment deploy-echo --replicas=3
# 확인 : NLB 대상 타켓이 아직 initial 일 때 100번 반복 접속 시 어떻게 되는지 확인해보자!
kubectl get deploy,pod,svc,ep
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
  • Istio의 관점에서 Ingress gateway를 설정할 때 다음과 같은 인입 구성을 생각해볼 수 있다.
  • NLB → istio-ingressgateway , ALB → istio-ingressgateway
  1. NLB(IP mode) → istio-ingressgateway : 파드 IP로 직접 연결, Client IP 수집 시 PPv2 활성화 및 envoy 옵션 수정 필요 — 링크
  2. *ALB(Instance mode) → (NodePort) istio-ingressgateway : 노드의 NodePort로 연결(약간 비효율적인 연결 가능), Client IP는 XFF로 수집
  3. ALB(IP mode) → istio-ingressgateway : 가능 할 것으로 보임, 테스트 해 볼 것
  • 아래 Istio 실습 전 사전 준비 사항 : AWS LoadBalancer Controller, ExternanDNS
  • 이것과 관련된 이야기는 추후 다시 생각해보기로 하였다. https://brunch.co.kr/@topasvga/3242

--

--

No responses yet