2주차 주제는 네트워킹이다.
AWS에서 쿠버네티스를 구축하면 CNI(Container Network Interface)는 Calico 같은 오픈소스를 사용하거나 AWS에서 지원하는 VPC CNI를 사용할 수 있다.
이번에 구축할 쿠버네티스에는 VPC CNI를 사용할 것이다.
VPC CNI의 가장 큰 특징은 노드와 파드의 IP 대역이 같다는 것이다. 즉, 쿠버네티스 네트워크 특징인 오버레이 네트워크가 없다는 것이다! 그래서 pod 간 통신을 하게 되는 경우 오버헤드가 없다는 장점이 있다!
VPC CNI는 노드마다 daemonset으로 aws-node라는 이름으로 배포되며, L-IPAM(Local IP Address Manager)과 CNI 플러그인으로 구성된다.
L-IPAM은 사용 가능한 IP 주소들을 가지고 있는 warm-pool을 유지 관리하고 그 IP를 pod에 할당해 주는 역할을 한다. L-IPAM은 EC2 인스턴스의 메타데이터를 통해 사용 가능한 ENI와 해당 보조 IP를 알 수 있다. 그래서 kubelet이 생성할 pod에 대해 요청이 오면 L-IPAM은 warm-pool에서 보조 IP 하나를 해당 pod에게 할당해 줄 수 있다.
https://github.com/aws/amazon-vpc-cni-k8s
각 node에 접속해서 메타데이터를 검색해 보면 두 개의 MAC 주소를 확인할 수 있다. 이는 각 ENI의 주소이다.
그래서 아래와 같은 주소에 curl을 날리면 현재 node가 가지고 있는 ENI IP를 알 수 있다.
http://169.254.169.254/latest/meta-data/network/interfaces/macs/02:5c:67:39:51:01/local-ipv4s
인스턴스 메타데이터 검색 명령어는 아래 링크를 참조하였다.
인스턴스 메타데이터 검색 - Amazon Elastic Compute Cloud
node는 ENI를 인스턴스 사양에 따라 추가할 수 있다. 여기서 기본 ip와 보조 ip가 있는데 둘의 차이는 아래 사진과 같다. 보조ip는 한번 할당받아도 다른 서버에 재할당 받을 수 있다. 기본ip는 node가 가지게 되는 private ip이고 보조ip는 L-IPAM에서 할당받게 되는 pod의 ip이다.
Amazon EC2 인스턴스 IP 주소 지정 - Amazon Elastic Compute Cloud
가시다님이 제공해 주신 클포로 쿠버네티스 구축은 원클릭 배포했다.
현재 사용중인 CNI 이미지 정보는 아래와 같다.
# kubectl describe daemonsets.apps 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.1
VPC CNI는 daemonsets으로 각 노드에 하나씩 구성되어 있으며 aws-node라는 이름을 가지고 있다.
# kubectl describe cm -n kube-system kube-proxy-config | grep mode
mode: "iptables"
kubeproxy는 iptables를 사용하고 있다. ipvs 모드를 사용하지 않는 이유는 ipvs에서 동작하지 않는 기능이 있기 때문이라고 한다. 또 iptables는 오랜 기간 사용한 만큼 안정성이 있다고 한다.
node와 pod의 ip를 확인해 보면 둘 다 같은 대역을 사용하고 있다는 것이 보인다
그리고 VPC CNI와 kube proxy는 node의 ip를 사용하고 있다. 이는 pod가 Host Network를 사용하기 때문이다.
# kubectl get daemonsets.apps kube-proxy --namespace kube-system -o yaml | grep hostNetwork
hostNetwork: true
hostNetwork는 node의 ip, port를 사용하여 말 그대로 host 서버의 네크워크를 사용하는 것이다. VPC CNI와 kube proxy는 node와 같은 네트워크를 사용하면서 node 내 pod의 IP 관리와 트래픽 처리를 효율적으로 할 수 있다.
그래서 hostNetwork: true
로 설정하면 ports부분이 설정되어야 하는데, 아래 사진에서 hostIP
는 node의 ip를 말하고, hostPort는 호스트 네트워크를 사용하기 때문에 containerPort
와 동일한 값이어야 한다. containerPort
만 지정하면 나머지 두 값은 필수 값은 아니다
[Kubernetes] Pod 관련 Host Network 옵션과 동작원리
위 링크를 참고하였다.
반면 coredns는 192.168.1.0/24 대역의 node와 192.168.2.0/24 대역을 가진 node의 보조 ip를 할당받았다.
coredns는 hostnetwork를 사용하지 않고 각 node의 보조 ip를 할당받았다
node 1,2에 있는 192.168.2.150, 192.168.1.88가 coredns의 ip인데 각 node 1,2의 라우팅테이블에 설정되어 있다.
이제 테스트 pod를 하나 띄워볼 것이다.
이제 각 node에 테스트 pod가 할당되었고 라우팅테이블에도 설정되었다.
그리고 원래 한 node는 ENI가 하나밖에 없었는데 ENI1이 추가로 생겼다.
그래서 만들어진 현재 node의 ENI 및 ip 정보들이다.
테스트로 만든 pod3에서 pod1으로 ping 테스트를 할 것이다.
tcpdump로 확인해 보면 패킷이 pod3에서 pod1로 향한 것을 알 수 있다.
이제 외부 통신에 대해 확인해 볼 것이다.
pod1에서 구글 주소(142.250.76.132)로 ping해 보았는데 tcpdump로는 pod에서 외부로 바로 나간 것만 보인다.
그래서 iptables 룰을 확인해 보면 SNAT되어 외부와 통신했다는 것을 찾을 수 있었다.
VPC CNI의 SNAT 설정에 따라, 외부 통신 시 SNAT하거나 혹은 SNAT 없이 통신할 수 있다.
현재는 AWS_VPC_K8S_CNI_EXTERNALSNAT
값이 기본적으로 false로 되어 있다. 이를 true로 바꾸게 되면 external NAT를 사용하겠다는 의미로 SNAT 설정이 없어지게 된다. 그래서 sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN’
를 입력하면 아무 것도 출력되지 않는다.
인스턴스 사양에 따라 최대 pod 개수가 달라진다. ENI 개수와 보조 IP 할당 개수가 달라진다.
현재 인스턴스 사양은 t3.medium을 사용하고 있는데 이는 ENI는 3개까지, ENI 당 가질 수 있는 IP 개수는 6개이다 (기본IP를 빼면 보조IP는 5개)
최대 pod 개수를 구하는 공식은 ENI 개수 X (IP 개수 - 1) 이다.
그럼 t3.medium 기준으로 3 X (6 - 1) = 15개이다. 15개는 aws-node와 kube-proxy를 제외한 개수이다.
또는 node를 확인해 보면 Allocatable에서 최대 몇 개까지 pod를 생성할 수 있는지 알 수 있다.
17로 나와있는데 이는 15개에서 aws-node와 kube-proxy의 개수 2개를 더한 값이다.
그래서 pod 개수가 몇 개까지 만들어 지나 테스트를 해보았다.
pod는 43개까지 생성되고 그 이후로는 Pending 상태가 되었다.
원인은 Too many pods였다.
ENI를 확인해 보면 3개의 node 모두 3개의 ENI를 가지게 되었다.
ENI 3개와 그 ENI에 연결된 15개의 pod들이다.
계산식으로하면 3 X 3 X (6 - 1) = 45개까지 만들 수 있다.
여기서 나머지 두 개는 coredns가 있으니 43개 딱 맞다.
여기서 최대 개수를 늘리는 방법이 있다. IPv4 Prefix Delegation를 조정하는 방법이다.
prefix를 /28로 변경해서 16개씩 할당받는 방법이다.
계산 법은 ENI 개수 X (IP 개수 - 1) X 16 이다.
하지만 이는 Nitro 계열 인스턴스에만 해당된다. 또한 vCPU 30코어 미만은 110개로 제한되고 그 외는 250개가 최대값이다.
처음에 기본값으로 ENABLE_PREFIX_DELEGATION 값이 false로 되어 있다. 그래서 이를 true로 변경하고 각 node를 재부팅해 주었다. 처음에 재부팅을 안하니 변경된 사항이 없어 당황했다. aws-node가 재생성되었는데도 node 자체의 prefix가 바뀌는 거라 그런지 node의 재부팅이 필요한 것 같다.
원래 아무 것도 안뜨던 부분이 /28을 붙인 채로 변경되었다.
그래도 아직 최대 개수가 변경되지 않았다.
이번엔 kubectl cordon으로 node에 pod가 스케줄링되지 않게 disable 해 놓고 ASG를 0으로 줄였다가 다시 3으로 늘렸다.
하지만 분명 prefix는 28로 바꼈는데 아래 pods 수가 17에서 바뀌지 않는다….
왜인지 좀 더 확인해 봐야 할 것 같다…..
이번 시간에는 VPC CNI를 중점으로 확인했던 것 같다. 쿠버네티스의 ingress, service, networkpolicy 부분은 좀 알았어도 VPC CNI는 생소했다.
근데 max pods가 실패해서 아쉬움이 많이 남는다…
가시다님이 주신 키워드에 따라 다시 내용 추가하였다
kubelet이 max-pods 값을 가지고 있고 이 값을 수정해 줘야 하는데 eks에서 수정하려면 노드그룹에 옵션 값으로 줄 수 있다.
기존 노드에서 max pods를 조절하려고 했지만 못찾고 새로운 노드그룹에 마이그레이션하는 방법으로 진행하였다
nodegroup 생성할 때 --max-pods-per-node
에 최대 개수를 넣어주면 된다!
eksctl create nodegroup --cluster myeks --max-pods-per-node 110
prefix도 28로 바뀐걸 확인했고 max pods가 110으로 늘어난 것도 확인하였다. 그래서 테스트 파드를 생성해서 100개까지 생성되는 것을 확인하였다
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html
https://www.eksworkshop.com/docs/networking/vpc-cni/prefix/configure/