AWS VPC CNI
CNI(Container Network Interface)
는 K8S에서 네트워크 환경을 구성해 주는 플러그인입니다. 다양한 플러그인들이 존재하는데 온프레미스 쿠버네티스에서는 Calico나 Cillium 등이 많이 사용됩니다. AWS EKS에서는 AWS VPC CNI라는 네트워크 플러그인을 사용합니다. AWS에서 지원하는 플러그인이기 때문에 VPC와 통합하여 VPC Flow logs, 라우팅 정책, 보안 그룹을 사용하는 등 다양한 장점을 가집니다.
노드와 파드의 네트워크 대역이 같습니다
Calico CNI vs AWS VPC CNI.
Calio CNI
는 파드와 노드 간의 IP 대역이 다르기 때문에 오버레이(VXLAN,IP-IP) 통신을 함으로써 오버헤드가 발생합니다.AWS VPC CNI
는 파드와 노드가 동일 대역으로 직접 통신하기 때문에 효율적인 통신을 할 수 있습니다.
실습을 통해서 좀 더 자세히 알아보겠습니다.
파드의 IP는 노드의 PrivateIP와 대역이 같습니다. 파드의 IP는 동작하는 노드의 IP대역으로 할당되는 것을 볼 수 있습니다.
노드가 가지고 있는 인터페이스가 AWS Console에서도 동일하게 나타나는 것을 확인할 수 있습니다.
참고로, kube-proxy
, aws-node
는 파드의 Host Network 옵션을 사용하여 호스트(Root)의 IP를 그대로 사용합니다.
파드 추가 후 IP 정보를 확인해 보면, 할당받은 IP가 노드의 라우팅테이블에 추가된 것을 확인할 수 있습니다.
워커 노드에 생성 가능한 최대 파드 개수 제한이 있습니다
인스턴스 스펙에 따라 ENI의 개수를 가집니다. 최대 파드의 생성 개수는 (Number of network interfaces for the instance type × (the number of IP addressess per network interface - 1)) + 2
t3.medium을 사용하는 워커 노드의 접속하여 확인합니다.
# t3 타입의 정보(필터) 확인
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 |
+----------+----------+--------------+
| 15 | 4 | t3.2xlarge |
| 6 | 3 | t3.medium |
| 12 | 3 | t3.large |
| 15 | 4 | t3.xlarge |
| 2 | 2 | t3.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
# c5 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=c5*.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# 파드 사용 가능 계산 예시 : aws-node 와 kube-proxy 파드는 host-networking 사용으로 IP 2개 남음
((MaxENI * (IPv4addr-1)) + 2)
t3.medium 경우 : ((3 * (6 - 1) + 2 ) = 17개 >> aws-node 와 kube-proxy 2개 제외하면 15개
# 워커노드 상세 정보 확인 : 노드 상세 정보의 Allocatable 에 pods 에 17개 정보 확인
kubectl describe node | grep Allocatable: -A6
Allocatable:
cpu: 1930m
ephemeral-storage: 27905944324
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3388360Ki
pods: 17
노드 및 파드 통신 분석
노드 간 파드 통신 확인하기
위에서 언급한 대로 AWS의 VPC CNI는 노드 간 파드가 통신할 때 오버레이, NAT 없이 직접 연결되어 오버헤드 없이 통신합니다.
tcpdump를 이용하여 서로 다른 노드에서 동작하는 파드 간 통신을 살펴봅니다. 파드 1에서 파드 2로 ping을 날려보았을 때, 파드 1의 파드 2의 IP만 출력되는 것을 볼 수 있습니다. 노드를 거치지 않고 바로 파드를 통신합니다.
파드에서 외부(인터넷망) 통신 확인하기
파드에서 google.com 도메인으로 외부 통신 시에, 노드에서의 외부 유동공인 아이피로 소스 NAT가 되는 것을 볼 수 있습니다.
AWS LoadBalancer Controller
EKS는 쿠버네티스의 서비스 타입인 LoadBalancer
를 선택하면 기본적으로 NLB 기본모드 를 지원한다
쿠버네티스의 서비스를 직접 Loadblancer 타입으로 배포하면 아래와 같이 로드밸런서가 생성되는 것을 확인할 수 있습니다.
kubectl run nginx --image=nginx
k expose pod nginx --type LoadBalancer --port 80
service/nginx exposed
k get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.100.242.253 a7aa4d5f7f6284aca86d4d9c9e718495-1560722862.ap-northeast-2.elb.amazonaws.com 80:31654/TCP 7s
Service (LoadBalancer Controller) : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI
AWS Load Balancer Controller를 이용하면 서비스를 거치지 않고 EKS 노드의 파드로 바로 통신할 수 있습니다.
AWS LoadBalancer Controller 배포 with IRSA
# 모니터링
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
# 확인
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq
# (옵션) 빠른 실습을 위해서 등록 취소 지연(드레이닝 간격) 수정 : 기본값 300초
vi 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
...
:wq!
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-default-svcnlbip`) == `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
{
"TargetHealthDescriptions": [
{
"Target": {
"Id": "192.168.2.153",
"Port": 8080,
"AvailabilityZone": "ap-northeast-2b"
},
"HealthCheckPort": "8080",
"TargetHealth": {
"State": "initial",
"Reason": "Elb.RegistrationInProgress",
"Description": "Target registration is in progress"
}
},
...
# 웹 접속 주소 확인
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
# 분산 접속 확인
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
52 Hostname: deploy-echo-55456fc798-2w65p
48 Hostname: deploy-echo-55456fc798-cxl7z
# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
# 실습 리소스 삭제
kubectl delete deploy deploy-echo; kubectl delete svc svc-nlb-ip-type
ExternalDNS
쿠버네티스에서 서비스나 인그레스 생성 시, 도메인을 설정하면, AWS(Route53), Azure, GCP, PowerDNS 등 DNS 프로바이더에 A레코드(TXT 레코드)를 자동 생성/삭제해준다.
AWS Route53 정보를 확인하고 변수를 지정합니다. 사전에 Public 도메인을 소유하고 있어야 합니다.
# 자신의 도메인 변수 지정 : 소유하고 있는 자신의 도메인을 입력하시면 됩니다
MyDomain=<자신의 도메인>
MyDomain=jdg98.com
echo "export MyDomain=jdg98.com" >> /etc/profile
# 자신의 Route 53 도메인 ID 조회 및 변수 지정
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." | jq
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Name"
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text
MyDnzHostedZoneId=`aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text`
echo $MyDnzHostedZoneId
# (옵션) NS 레코드 타입 첫번째 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
# (옵션) A 레코드 타입 모두 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']"
# A 레코드 타입 조회
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" --output text
# A 레코드 값 반복 조회
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
ExternalDNS 설치
# EKS 배포 시 Node IAM Role 설정되어 있음
# eksctl create cluster ... --external-dns-access ...
#
MyDomain=<자신의 도메인>
MyDomain=jdb98.com
# 자신의 Route 53 도메인 ID 조회 및 변수 지정
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
# 변수 확인
echo $MyDomain, $MyDnzHostedZoneId
# ExternalDNS 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
sed -i "s/0.13.4/0.14.0/g" externaldns.yaml
cat externaldns.yaml | yh
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
# 확인 및 로그 모니터링
kubectl get pod -l app.kubernetes.io/name=external-dns -n kube-system
kubectl logs deploy/external-dns -n kube-system -f
# 기존에 ExternalDNS를 통해 사용한 A/TXT 레코드가 있는 존의 경우에 policy 정책을 upsert-only 로 설정 후 사용합니다.
# - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
테트리스 게임을 배포해보고 Service(NLB) + ExternalDNS(도메인연동) 하여 노출해봅니다.
# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
kubectl logs deploy/external-dns -n kube-system -f
# 테트리스 디플로이먼트 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
labels:
app: tetris
spec:
replicas: 1
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
name: tetris
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-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
#service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
selector:
app: tetris
ports:
- port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
EOF
# 배포 확인
kubectl get deploy,svc,ep tetris
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/tetris 0/1 1 0 7s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/tetris LoadBalancer 10.100.88.92 k8s-default-tetris-0cfc61a90d-cd0c7e30e96bac2e.elb.ap-northeast-2.amazonaws.com 80:30549/TCP 6s
# NLB에 ExternanDNS 로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
# Route53에 A레코드 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq .[]
# 확인
dig +short tetris.$MyDomain @8.8.8.8
dig +short tetris.$MyDomain
# 도메인 체크
echo -e "My Domain Checker = https://www.whatsmydns.net/#A/tetris.$MyDomain"
# 웹 접속 주소 확인 및 접속
echo -e "Tetris Game URL = http://tetris.$MyDomain"
설정한 도메인(tetris.jdg98.com)으로 잘 접속이 되는 것을 볼수 있습니다.
'AWS > EKS' 카테고리의 다른 글
EKS 오토스케일링(Autoscaling) - AEWS 5주차 (0) | 2024.04.07 |
---|---|
EKS 옵저버빌리티(Obsivability) - AEWS 4주차 (0) | 2024.03.31 |
EKS 노드그룹(Nodegroup) - AEWS 3주차 2 (0) | 2024.03.23 |
EKS 스토리지(Storage) - AEWS 3주차 1 (0) | 2024.03.23 |
EKS 설치 및 기본사용 + 클러스터 엔드포인트 액세스 - AEWS 1주차 (0) | 2024.03.08 |