AWS EKS Workshop Study (=AEWS)는 EKS Workshop 실습 스터디입니다.
CloudNet@ Gasida(가시다)님이 진행하시며,공개된 AWS EKS Workshop을 기반으로 진행하고 있습니다.
EKS VPC CNI
CNI(Container Network Interface) 란 네트워크 플러그인 인터페이스로 k8s 네트워크 환경을 구성해준다. EKS 에서는 기본 CNI로 VPC를 사용한다. 여기서 VPC는 AWS 네트워크 서비스인 AWS VPC로 VPC CIDR을 이용하여 파드 IP 할당 및 통신을 지원한다.
EKS 에서 CNI를 VPC로 사용하면 대표적으로 얻을 수 있는 이점은 두 가지이다.
노드 대역과 파드 대역이 동일하여 다른 노드간 통신시 오버레이가 없어 통신 오버헤드가 감소한다.
다른 CNI(Caclico)는 노드와 파드 IP 대역이 달라 노드 간 통신에 오버레이를 사용하는 반면 VPC CNI는 대역이 같아 오버레이없이 원본 패킷 그대로 통신한다.
AWS VPC 의 연동 서비스 (VPC 서브넷, 보안 그룹)를 사용하여 트래픽 제어가 가능하다.
네트워크 세부 정보 확인하기
VPC CNI에 대해 세부적으로 확인해보겠다. VPC CNI는 aws-cni 파드를 통해 각 노드에 배포된다. 이를 확인하여 네트워크 기본 정보를 확인하겠다.
VPC CNI Mode로 userspace ,iptables , ipvs 모드가 존재하나, 보통 iptable 모드를 사용한다. 이유는 userspace 경우, 느리고 비효율적이여서 거의 사용되지 않은 상태이다. 반면 ipvs 의 경우, L4 기반의 로드밸런서로 매우 빠른 이점이 있지만 호환성 문제로 추가 설정이 필요하고 커뮤니티 규모가 적어 트러블슈팅이 동반되어 기본적으로 사용하지 않는다.
# 각 노드 IP 확인 및 변수 설정 kubectl get nodes -o wide
---
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-192-168-1-5.ap-northeast-2.compute.internal Ready <none> 3h10m v1.24.11-eks-a59e1f0 192.168.1.5 3.36.127.19 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
ip-192-168-2-16.ap-northeast-2.compute.internal Ready <none> 3h10m v1.24.11-eks-a59e1f0 192.168.2.16 3.34.30.94 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
ip-192-168-3-162.ap-northeast-2.compute.internal Ready <none> 3h10m v1.24.11-eks-a59e1f0 192.168.3.162 15.164.220.246 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
# 위 External IP 확인하여 노드별 변수 설정N1=3.36.127.19
N2=3.34.30.94
N3=15.164.220.246
# 각 노드에 툴 설치ssh ec2-user@$N1 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump -y
# 네트워크 CNI 로그 경로 확인ssh ec2-user@$N1 tree /var/log/aws-routed-eni
---
/var/log/aws-routed-eni
├── egress-v4-plugin.log # 이그래스 트래픽 관련 로그 파일 ├── ipamd.log # eni IP 주소 할당, 해제, 그리고 오류 등과 관련된 이벤트와 상태 변경 로그 파일 └── plugin.log # 파드의 네트워크 인터페이스 설정, 트래픽 관리, 오류 처리 등과 관련된 이벤트와 상태 변경 로그 파일**# eni IP 주소 할당, 해제, 그리고 오류 등과 관련된 이벤트와 상태 변경 로그 파일ssh ec2-user@$N1 cat /var/log/aws-routed-eni/ipamd.log | jq
---
{"level": "info",
"ts": "2023-05-08T07:16:00.757Z",
"caller": "ipamd/ipamd.go:1599",
"msg": "Adding 192.168.1.190/32 to DS for eni-0d79c0daf04234458"}{"level": "info",
"ts": "2023-05-08T07:16:00.757Z",
"caller": "ipamd/ipamd.go:1599",
"msg": "IP already in DS"}{"level": "info",
"ts": "2023-05-08T07:16:00.757Z",
"caller": "ipamd/ipamd.go:1475",
"msg": "Trying to add 192.168.1.145"}{"level": "info",
"ts": "2023-05-08T07:16:00.757Z",
"caller": "ipamd/ipamd.go:1599",
"msg": "Adding 192.168.1.145/32 to DS for eni-0d79c0daf04234458"}...
# 파드의 네트워크 인터페이스 설정, 트래픽 관리, 오류 처리 등과 관련된 이벤트와 상태 변경 로그 파일ssh ec2-user@$N1 cat /var/log/aws-routed-eni/plugin.log | jq
---
{"level": "debug",
"ts": "2023-05-08T05:59:41.741Z",
"caller": "driver/driver.go:281",
"msg": "Successfully disabled IPv6 RA and ICMP redirects on hostVeth eni25c78048487"}{"level": "debug",
"ts": "2023-05-08T05:59:41.741Z",
"caller": "driver/driver.go:297",
"msg": "Successfully setup container route, containerAddr=192.168.1.190/32, hostVeth=eni25c78048487, rtTable=main"}{"level": "debug",
"ts": "2023-05-08T05:59:41.741Z",
"caller": "driver/driver.go:297",
"msg": "Successfully setup toContainer rule, containerAddr=192.168.1.190/32, rtTable=main"}...
직접 라우터 및 네트워크 인터페이스를 확인하여 파드 IP정보를 확인할 수 있다.
빨간 네모는 노드 IP로 프라이빗 IPv4 주소가 할당되어 있음을 확인할 수 있다. 또한 노란 네모 및 선이 파드 할당이 되면 생기는 IP로 보조 프라이빗 IPv4 주소가 할당됨을 알 수 있다.
노드에 파드가 할당되면 네트워크인터페이스가 eniY@N 의 네트워크 인터페이스가 할당된다.
라우터 정보를 확인했을 때 노드 1의 CIDR이 192.168.1.0/24 로 해당 IP 대역의 트래픽을 받으면 192.168.1.5 로 통신됨을 확인할 수 있다.
통신 흐름 확인하기
이렇게 할당된 파드의 IP가 어떻게 통신하는지 파드간 통신과 외부 통신을 나눠 통신 흐름을 확인해보자. 먼저 클러스터 내부 파드간 통신을 확인하겠다. 테스트 파드를 3개 배포하여 파드 1과 파드2의 IP 통신흐름을 확인할 것이다.
내부 통신은 tcpdump 의 패킷 덤프를 통해 통신 과정을 확인할 수 있다. 파드1에 접속하여 파드2에 직접 트래픽을 쏴서 통신 흐름을 확인하자
패킷에서와 같이 오버레이없이 파드에 할당된 IP로 직접 통신함을 알 수 있다.
이어서 외부 통신을 진행해보자. 외부 통신은 SNAT을 통해 진행하게 되는데 SNAT의 경우 iptable의 룰을 통해 확인할 수 있다.
Iptable 룰에 따라 AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1 에 매칭되어, 목적지가 192.168.0.0/16 아니고 외부 빠져나갈때 SNAT 192.168.1.5로 변경되어 나간다
파드 안에서 외부 네트워크로 통신을 하여 통신 흐름을 확인하자
통신 패킷을 통해 확인하면 iptable의 룰과 같이 192.168.1.5로 IP가 SNAT되어 통신됨을 알 수 있다.
네트워크 Addon 설치(ALB, External DNS)
이어서 EKS에서 제공하는 네트워크 애드온 중 ALB Controller와 External DNS를 살펴보고 배포하겠다. 각 addon을 간략하게 요약해서 표로 나타내면 다음과 같다.
Addon 서비스
사용 AWS 서비스
목적
Load Balancer Controller
AWS Elastic Load Balancer(ELB)
네트워크 로드밸런서(L4, L7) 사용
External DNS Controller
AWS Route53
DNS 도메인 연동 및 사용
표로 확인할 수 있겠지만 네트워크 addon들은 AWS 네트워크 서비스(ELB, Route53)을 연동해서 사용한다. 그렇기 때문에 Addon 설치시 AWS 서비스를 사용하기 위한 보안 정책(IAM) 연동이 필요하다. 이번 글에서는 IAM 정책 연동 방법으로 IAM OIDC를 통해 EKS 서비스어카운트에 IAM 정책을 연동을 하겠다.
EKS 노드는 EC2 인스턴스로 운영된다. 문제는 EC2 인스턴스 타입별로 할당되는 IP 개수의 제한이 존재한다. 이유는 인스턴스 타입당 할당가능한 네트워크 인터페이스의 제한이 있기 때문이다. 인스턴스별 IP 개수 제한 관련하여 다음의 명령어를 통해서도 확인할 수 있다.
1
2
3
4
5
kubectl get nodes -o jsonpath="{range .items[*]}{.metadata.labels['beta\.kubernetes\.io\/instance-type']}{'\t'}{.status.capacity.pods}{'\n'}{end}"---
t3.medium 17t3.medium 17t3.medium 17
기존에는 IP 할당 개수를 늘리기 위해서 인스턴스 타입을 변경해야 했지만, Amazon VPC CNI 추가 기능 버전 1.9.0 이상부터 Prefix Delegation 를 통해 최대 파드 개수까지 IP 할당 개수를 확장할 수 있다. Prefix Delegation 란 IPv6 기능으로 하위 IP에 접두사를 위임하여 최대 IP를 할당할 수 있게 만들어 주는 기능이다. 이를 VPC CNI에도 사용할 수 있다. IP Mode와 Prefix Mode 를 비교하면 다음과 같이 나타낼 수 있다.
오른쪽이 Prefix Mode로 Secondary ENI에 접두사가 위임된 것을 확인할 수 있다. 비교하자면 개별 보조 IP 주소를 할당하는 대신 접두사를 할당하여 최대 IP 개수늘 늘릴 수 있다.
좋은 기능이지만 사용하기에 몇 가지 제한이 존재한다. 기능 도입 전 제한 사항을 꼭 확인하자!
VPC CNI version 1.9.0 이상
Nitro 기반의 인스턴스에서만 가능
/28 접두사를 생성할 수 있는 사용 가능한 IP 주소가 충분하지 않은 경우
바로 VPC CNI 파드를 통해 설정해보겠다. 먼저, 버전 확인과 VPC CNI의 옵션을 살펴보겠다.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 88s 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.
확인해보니 노드에서 MaX 파드 수치가 존재하기 때문이였다. 앞서 본 17개가 해당 인스턴스의 맥스 파드 수였다. 이 MAX 파드 제한을 늘리기 위해서는 워커 노드의 kubelet의 옵션을 수정해야 한다. 방법은 두 가지로 1. EKS 구축시 MAX POD 설정 및 2. 실행 중인 노드 kubelet을 수정하여 MAX POD를 설정할 수 있다.
kubectl apply -f eniconfigs.yaml
---
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2a created
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2b created
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2c created
kubectl get eniconfig
---
NAME AGE
ap-northeast-2a 16s
ap-northeast-2b 16s
ap-northeast-2c 16s
# VPC 서브넷 업데이트kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone
업데이트된 내용이 노드에는 바로 적용되지 않는다. 새로 추가된 노드에만 적용된다. 새로 노드 그룹을 추가하고 확인하도록 하자.
# 버전 확인 kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3---
v1.12.6-eksbuild.1
# EKS VPC ResourceController Role 추가 cluster_role=$(aws eks describe-cluster --name hanhorang --query cluster.roleArn --output text | cut -d / -f 2)# 클러스터에 정책 연결 aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController --role-name $cluster_role# VPC CNI 파라미터 수정(Pod eni 관리 파라미터 허용)kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true# 파드 eni 가능 노드 확인# 위 명령어로 업데이트에 몇 초의 시간이 소요됩니다.kubectl get nodes -o wide -l vpc.amazonaws.com/has-trunk-attached=true---
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-192-168-1-108.ap-northeast-2.compute.internal Ready <none> 20m v1.24.11-eks-a59e1f0 192.168.1.108 3.38.214.241 Amazon Linux 2 5.10.178-162.673.amzn2.aarch64 containerd://1.6.19
ip-192-168-2-45.ap-northeast-2.compute.internal Ready <none> 20m v1.24.11-eks-a59e1f0 192.168.2.45 3.38.188.199 Amazon Linux 2 5.10.178-162.673.amzn2.aarch64 containerd://1.6.19
ip-192-168-3-18.ap-northeast-2.compute.internal Ready <none> 20m v1.24.11-eks-a59e1f0 192.168.3.18 13.125.254.20 Amazon Linux 2 5.10.178-162.673.amzn2.aarch64 containerd://1.6.19
# 파드에서 VPC 외부 주소로 아웃바운드되는 트래픽은 기본 ENI로 가며, 노드 보안 그룹에 대한 규칙이 적용된다, 파드의 eni만 적용시키고 싶다면 아래 파라미터를 추가하자.kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_EXTERNALSNAT=true# 옵션 1. liveness & readyprobe 사용 경우, kubelet이 TCP를 사용하여 파드 eni 와 연결할 수 있도록 TCP 초기 demux 중지kubectl patch daemonset aws-node -n kube-system \
-p '{"spec": {"template": {"spec": {"initContainers": [{"env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}],"name":"aws-vpc-cni-init"}]}}}}'# 옵션 2. externalTrafficPolicy: local, NodeLocal DNSCache, 자체 보안 그룹이 있는 네트워크 정책이 있는 경우 아래 명령어 추가 # 해당 설정을 기존 Pods에 적용하려면, 노드를 재시작해야함 kubectl set env daemonset aws-node -n kube-system POD_SECURITY_GROUP_ENFORCING_MODE=standard
SG for Pod 설정 이후 파드에 직접 보안 그룹을 설정하도록 하겠다. 활용 예제는 EKS Workshop 을 참고하였다.
kubectl describe pods/catalog-mysql-0 -n catalog
---
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 10m default-scheduler Successfully assigned catalog/catalog-mysql-0 to ip-192-168-2-197.ap-northeast-2.compute.internal
Normal Pulling 9m19s (x4 over 10m) kubelet Pulling image "public.ecr.aws/docker/library/mysql:5.7" Warning Failed 9m18s (x4 over 10m) kubelet Failed to pull image "public.ecr.aws/docker/library/mysql:5.7": rpc error: code= NotFound desc= failed to pull and unpack image "public.ecr.aws/docker/library/mysql:5.7": no match for platform in manifest: not found
Warning Failed 9m18s (x4 over 10m) kubelet Error: ErrImagePull
Warning Failed 9m5s (x6 over 10m) kubelet Error: ImagePullBackOff
Normal BackOff 56s (x42 over 10m) kubelet Back-off pulling image "public.ecr.aws/docker/library/mysql:5.7"
이상한 점은 베스천 서버에서는 이미지 풀이 되는 반면, EKS 노드에서는 이미지 풀에 대해 not found가 발생한다.
확인해보니 해당 이미지의 경우(ECR) 에서 linux/arm64/v8 에 대한 이미지 레이어 대한 정보가 없어서 발생한 문제였다. linux/arm64/v8 레이어는 AWS 인스턴스 유형 (A1, M6g,C6g,R6g)에서 사용되는 반면 linux/amd64 는 (x86-64) 아키텍처로 AWS 인스턴스 유형(t2,t3 m4, m5) 기반의 프로세서에서 동작한다.
해결 방법으로 이미지를 직접 빌드 및 Push 할 수 있지만, 인스턴스 유형을 m5.large 로 변경했다.
exportCATALOG_SG_ID=sg-08bb6f8b77a20e693
kubectl apply -k .
---
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
namespace/catalog unchanged
serviceaccount/catalog unchanged
configmap/catalog unchanged
configmap/catalog-env-7gtc4hbmd2 unchanged
configmap/catalog-sg-env-5969b7c958 created
secret/catalog-db unchanged
service/catalog unchanged
service/catalog-mysql unchanged
service/ui-nlb unchanged
deployment.apps/catalog unchanged
statefulset.apps/catalog-mysql unchanged
securitygrouppolicy.vpcresources.k8s.aws/catalog-rds-access created
# 기존 catalog 파드 삭제kubectl delete pod -n catalog -l app.kubernetes.io/component=service
# 보안 그룹 변경 확인 kubectl get events -n catalog | grep SecurityGroupRequested
--
5m22s Normal SecurityGroupRequested pod/catalog-6d688b9f9c-gjhvt Pod will get the following Security Groups [sg-08bb6f8b77a20e693]20s Normal SecurityGroupRequested pod/catalog-6d688b9f9c-vnlgb Pod will get the following Security Groups [sg-08bb6f8b77a20e693]10s Normal SecurityGroupRequested pod/catalog-mysql-0 Pod will get the following Security Groups [sg-08bb6f8b77a20e693]
이후 파드에 접속하여 rds에 접속하면 정상적으로 접근이 된다.(밑 사진의 빨간네모) 반면에 노드에서 rds 접근 시도시 timeout이 발생한다. (밑 사진의 파란네모)
워크샵에 있는 내용을 따라했지만, catalog 파드에서 연결이 안되어 catalog-mysal 에서 RDS 연결 테스트를 진행하였다. 실습 진행에 참고하자!
마치며
이번 글에서는 못 다뤘지만, VPC 관련하여 다룰 주제가 많다! 틈틈히 정리하여 VPC Deep Dive 2탄에서 해당 내용들을 다룰 수 있도록 하겠다.