Search

AWS EKS: VPC CNI 실습

Tags
aws
eks
cni
Created
2024/10/31 21:16
Created time
2024/10/31 12:16
category
aws

개요

AWS EKS에서 VPC CNI를 학습하기 위한 실습

실습 환경 준비

구성 환경은 아래와 같으며, IAM 사용자 설정AWS CLI 설정 그리고 CloudFormation 관련 명령어를 참고

구성

VPC 1개 (퍼블릭 서브넷 3개, 프라이빗 서브넷 3개)
EKS 클러스터
관리형 노드 그룹 (EC2 3대)

클러스터 생성

# CloudFormation 다운로드 # https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/eks-oneclick.yaml curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/eks-oneclick.yaml # CloudFormation 배포 aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=jseo.d MyIamUserAccessKeyID=<access-key> MyIamUserSecretAccessKey=<secret-key> ClusterBaseName=myeks --region ap-northeast-2
Shell
복사

클러스터 생성 확인

# 생성된 EC2 인스턴스 IP 주소 확인 aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text # CloudFormation 스택 상태 확인 while true; do date AWS_PAGER="" aws cloudformation list-stacks \ --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \ --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \ --output table sleep 1 done
Shell
복사

서버 접속

k8s 관련 명령어 수행 및 EKS 관리를 위해 myeks라는 노드에 접속
# Key 이름을 적절히 변경 ssh -i ./jseo.d.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
Shell
복사

워커 노드 접속

EKS를 구성하고 있는 워커 노드에 접속하기 위해선 myeks 노드에 먼저 접속 필요
# 서버 접속 후 # 변수 지정 N1=$(kubectl get node -o jsonpath={.items[0].status.addresses[0].address}) N2=$(kubectl get node -o jsonpath={.items[1].status.addresses[0].address}) N3=$(kubectl get node -o jsonpath={.items[2].status.addresses[0].address}) # 서버 접속 ssh ec2-user@$(N1|N2|N3)
Shell
복사

네트워크 정보 확인

k8s 설정

# DaemonSet인 AWS 노드를 통해 CNI 확인 kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2 # kube-proxy의 설정 확인 kubectl describe cm -n kube-system kube-proxy-config
Shell
복사

노드 IP 주소

# 클러스터의 노드 IP 확인 # EKS 셋업이 되어 있다보니 aws 명령어 이용 가능 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
Shell
복사
# CoreDNS의 Pod IP 주소를 확인 # AWS 콘솔에서 EC2 > 네트워킹 > 보조 프라이빗 IPv4 주소를 이용하는지 확인 kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
Shell
복사

통신 흐름

리소스 생성

# 리소스 생성 cat << EOT | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: netshoot-pod spec: replicas: 3 selector: matchLabels: app: netshoot-pod template: metadata: labels: app: netshoot-pod spec: containers: - name: netshoot-pod image: nicolaka/netshoot command: ["tail"] args: ["-f", "/dev/null"] terminationGracePeriodSeconds: 0 EOT # 변수 할당 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}) 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})
Shell
복사

Pod 간 통신

AWS VPC CNI는 별도의 오버레이 계층 없이 VPC 상에서 네이티브하게 Pod 간의 직접적인 통신이 가능
# 워커 노드에서 tcpdump 수행하여 Pod간 통신이 이뤄지는 흐름 확인 sudo tcpdump -i any -nn icmp sudo tcpdump -i eth1 -nn icmp sudo tcpdump -i eth0 -nn icmp sudo tcpdump -i eniYYYYYYYY -nn icmp # myeks에서 ping 수행 kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2 kubectl exec -it $PODNAME2 -- ping -c 2 $PODIP3 kubectl exec -it $PODNAME3 -- ping -c 2 $PODIP1
Shell
복사

Pod에서 외부로의 통신

Pod에서는 iptables의 sNAT을 통해 노드의 eth0 IP 주소로 변경되어 외부와 통신을 수행
# 워커 노드에서 tcpdump 수행하여 외부로 통신이 이뤄지는 흐름 확인 sudo tcpdump -i any -nn icmp sudo tcpdump -i eth0 -nn icmp # myeks에서 ping 수행 kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com kubectl exec -it $PODNAME2 -- ping -c 1 www.google.com kubectl exec -it $PODNAME3 -- ping -c 1 www.google.com
Shell
복사
# 워커 노드의 iptables 확인 # Pod가 외부와 통신할 때는 AWS-SNAT-CHAIN-0 규칙에 의해 sNAT 처리 # iptables로 확인되는 IP 주소는 eth0의 IP 주소 sudo iptables -S -t nat | grep 'A AWS-SNAT-CHAIN'
Shell
복사

리소스 삭제

kubectl delete deploy netshoot-pod
Shell
복사

Pod 생성 개수 제한

Kube Ops View 설치

# https://geek-cookbook.github.io/charts/ 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 # 접속 확인 (ALB 생성까지 일부 시간 소요) 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"}'
Shell
복사

인스턴스 타입 정보

MaxENI×(IPv4Address1)+2MaxENI \times (IPv4Address - 1) + 2
위 계산 식에 따라 t3.medium의 기준으로는 3×(61)+2=173 \times (6 - 1) + 2 = 17개의 Pod를 생성 가능
최종적으로는 aws-node와 kube-proxy를 제외하면 15개의 Pod를 생성 가능
MaxENI는 인스턴스 타입 별 네트워크 인터페이스 수
IPv4Address는 네트워크 인터페이스 당 IP 주소 수
# t3 타입 확인 aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \ --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \ --output table
Shell
복사
# c5 타입 확인 aws ec2 describe-instance-types --filters Name=instance-type,Values=c5*.* \ --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \ --output table
Shell
복사

워커 노드 상세 정보 확인

# 워커 노드의 할당 가능한 Pod 수를 확인 # t3.medium이기 때문에 계산식처럼 17개임을 확인 가능 kubectl describe node | grep Allocatable: -A6
Shell
복사

Pod 최대 개수 생성

ENI 당 IP 주소를 6개까지 지원 가능하므로 기존 2개의 ENI에서 ENI가 증가하는 것을 확인할 수 있음
계산한 것보다 더 많은 ENI가 생성되거나 더 많은 Pending이 발생하는 것은, 실습에서 사용하는 Pod 말고도 다른 Pod들이 클러스터에서 동작 중이기 때문
# 리소스 생성 # https://raw.githubusercontent.com/gasida/PKOS/main/2/nginx-dp.yaml curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/nginx-dp.yaml kubectl apply -f nginx-dp.yaml
Shell
복사
# 다른 터미널에서 myeks 접속 # Pod 모니터링 watch -d 'kubectl get pods -o wide'
Shell
복사
# 다른 터미널에서 워커 노드 접속 # eth와 eni를 모니터링 while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
Shell
복사
# 배포를 통해 스케일 아웃하여 Pod와 eth, eni를 확인 kubectl scale deployment nginx-deployment --replicas=8
Shell
복사
# 배포를 통해 스케일 아웃하여 Pod와 eth, eni를 확인 kubectl scale deployment nginx-deployment --replicas=15
Shell
복사
# 배포를 통해 스케일 아웃하여 Pod와 eth, eni를 확인 kubectl scale deployment nginx-deployment --replicas=30
Shell
복사
# 배포를 통해 스케일 아웃하여 Pod와 eth, eni를 확인 kubectl scale deployment nginx-deployment --replicas=50 # 워커 노드는 3개였고, aws-node와 kube-proxy를 제외하면 노드 당 15개의 Pod 생성 가능 kubectl get pods | grep Pending
Shell
복사

리소스 삭제

kubectl delete deploy nginx-deployment
Shell
복사
위와 같이 적은 수의 Pod를 생성할 수 밖에 없었던 것은 AWS VPC CNI의 기본 값이 Secondary IPv4 Addresses를 이용하기 때문
Prefix Delegation 방식을 이용하면 t3.medium 기준 15개의 Pod를 생성할 수 있었던 것과 달리 더 많은 Pod를 생성 가능
# 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
Shell
복사
# CRD 조회 kubectl get crd # AWS LB Controller 조회 kubectl get deployment -n kube-system aws-load-balancer-controller kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account' kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
Shell
복사
# NLB 생성 # https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml kubectl apply -f echo-service-nlb.yaml
Shell
복사
# CRD 조회 kubectl get deploy,pod kubectl get svc,ep,ingressclassparams,targetgroupbindings kubectl get targetgroupbindings -o json | jq
Shell
복사

등록 취소 지연 (드레이닝) 주기 수정

빠른 실습을 위해 기본 드레이닝 주기 300초를 60초로 수정
# metadata.annotations에 아래 값 추가 # service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: deregistration_delay.timeout_seconds=60 vi echo-service-nlb.yaml
Shell
복사
# 설정 값 반영 kubectl apply -f echo-service-nlb.yaml
Shell
복사

AWS ELB (NLB) 정보 확인

# ELB 상태 확인 aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text # ALB ARN 변수 지정 ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-default-svcnlbip`) == `true`].LoadBalancerArn' | jq -r '.[0]') # ALB ARN을 이용하여 Target Group 확인 aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq # Target Group ARN 변수 지정 TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn') # Target Group ARN을 이용하여 타겟 상태 확인 aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq
Shell
복사
타겟 정보 확인 시 Pod의 IP 주소와 동일한 것을 확인 가능

접속 확인

# 접속 주소 확인 kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }' # 접속 주소 변수 지정 NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname}) # 분산 접속 확인 for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
Shell
복사

스케일링

# myeks 노드 # 타겟 상태 확인 while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done
Shell
복사
# Pod 1개로 스케일링 kubectl scale deployment deploy-echo --replicas=1 # 접속 확인 for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
Shell
복사
# Pod 3개로 스케일링 kubectl scale deployment deploy-echo --replicas=3 # 접속 확인 for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
Shell
복사
# Service의 metadata.annotations에 아래 구문 추가 service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" # Proxy Protocol 활성화 확인 kubectl exec deploy/<apache-deployment> -- apachectl -t -D DUMP_MODULES kubectl exec deploy/<apache-deployment> -- cat /usr/local/apache2/conf/httpd.conf
Shell
복사

리소스 삭제

kubectl delete deploy deploy-echo; kubectl delete svc svc-nlb-ip-type
Shell
복사

리소스 생성

# Service 및 Ingress 생성 (Ingress 생성에 일부 시간 소요) # https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml kubectl apply -f ingress1.yaml # ing, svc, ep, po, targetgroupbindings 확인 kubectl get ing,svc,ep,po -n game-2048 kubectl get targetgroupbindings -n game-2048
Shell
복사
# ALB 생성 확인 # ALB ARN 변수 지정 ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`].LoadBalancerArn' | jq -r '.[0]') # ALB ARN을 이용하여 Target Group 확인 aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq # Target Group ARN 변수 지정 TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn') # Target Group ARN을 이용하여 타겟 상태 확인 aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq
Shell
복사
# Ingress 확인 kubectl describe ingress -n game-2048 ingress-2048 # 접속 확인 kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'
Shell
복사

LB 확인

스케일링

# Pod 3개로 증가 kubectl scale deployment -n game-2048 deployment-2048 --replicas 3 # 타겟 확인 aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text
Shell
복사
# Pod 1개로 감소 kubectl scale deployment -n game-2048 deployment-2048 --replicas 1 # 타겟 확인 aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text
Shell
복사

리소스 삭제

kubectl delete ing ingress-2048 -n game-2048 kubectl delete svc service-2048 -n game-2048 && kubectl delete deploy deployment-2048 -n game-2048 && kubectl delete ns game-2048
Shell
복사

도메인 지정

# 도메인 변수 지정 MyDomain=jseo.cc # Route 53 도메인 ID 조회 및 변수 지정 MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text) # 변수 확인 echo $MyDomain, $MyDnzHostedZoneId
Shell
복사

ExternalDNS 배포

# 리소스 생성 # https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
Shell
복사
ExternalDNS를 배포하는 과정에서 기존에 A 및 TXT 레코드가 있는 존을 대상으로 한다면, Policy 정책을 upsert-only로 설정 필요
--policy=upsert-only** # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
Shell
복사

NLB 생성 및 도메인 연동

# 테트리스 Deployment 배포 cat <<EOF | kubectl apply -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 # NLB에 ExternalDNS 애너테이션을 추가하여 도메인 연결 kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
Shell
복사
도메인 전파 체크는 여기를 이용

도메인 확인

# Route 53에서 A 레코드 확인 aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq # DNS 서버 확인 dig +short tetris.$MyDomain @8.8.8.8 # 도메인 전파 확인 echo -e "My Domain Checker = https://www.whatsmydns.net/#A/tetris.$MyDomain" # 접속 확인 echo -e "Tetris Game URL = http://tetris.$MyDomain"
Shell
복사

리소스 삭제

# 리소스 삭제 시 ExternalDNS에 의해 A 레코드도 함께 삭제 kubectl delete deploy,svc tetris
Shell
복사
Topology Mode는 이전엔 Aware Hint로 불렸었고, 힌트는 엔드포인트가 트래픽을 제공해야 하는 영역을 지칭
트래픽이 인입되면 kube-proxy에서는 힌트에 따라 엔드포인트에 해당되는 영역으로 라우팅 수행

AZ 확인

# 노드 AZ 확인 kubectl get node --label-columns=topology.kubernetes.io/zone
Shell
복사

리소스 생성

# 리소스 생성 cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: deploy-echo spec: replicas: 3 selector: matchLabels: app: deploy-websrv template: metadata: labels: app: deploy-websrv spec: terminationGracePeriodSeconds: 0 containers: - name: websrv image: registry.k8s.io/echoserver:1.5 ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: svc-clusterip spec: ports: - name: svc-webport port: 80 targetPort: 8080 selector: app: deploy-websrv type: ClusterIP EOF
Shell
복사
# 접속 테스트 용도 Pod 생성 cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: netshoot-pod spec: containers: - name: netshoot-pod image: nicolaka/netshoot command: ["tail"] args: ["-f", "/dev/null"] terminationGracePeriodSeconds: 0 EOF
Shell
복사
# 리소스 확인 kubectl get deploy,svc,ep,endpointslices
Shell
복사

기본 설정 부하 분산 확인

# 접속 대상 Pod의 AZ 확인 kubectl get pod -l app=deploy-websrv -owide # 반복 접속 수행 시 랜덤 부하 분산 확인 kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
Shell
복사

Topology Mode 부하 분산 확인

# Topology Aware Routing 설정 kubectl annotate service svc-clusterip "service.kubernetes.io/topology-mode=auto" # 반복 접속 수행 시 지정된 힌트에 따라 같은 AZ의 Pod로만 접속 # netshoot-pod는 현재 실습 상 192-168-3-18에 존재, 요청도 192-168-3-18에 있는 Pod로만 처리 kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
Shell
복사
# endpointslices 확인 시 기존에 없던 hints가 추가되어 있음 # describe로 endpointslices 확인 시엔 hints 확인 불가 kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml | grep hint -A3
Shell
복사

리소스 삭제

kubectl delete deploy deploy-echo; kubectl delete svc svc-clusterip
Shell
복사

AWS LB Controller를 이용한 배포

배포 전략은 Ingress 애너테이션에 alb.ingress.kubernetes.io/actions.${action-name}과 같이 지정 가능
action-name으로는 blue-green 혹은 ab-testing을 이용할 수 있으며, Blue/Green 배포와 Canary 배포는 action-name을 blue-green으로 지정 후 weight 조정으로 설정 가능
ALB 이용 간 serviceName가 적절히 일치해야 동작하며, servicePort는 use-annotation이라는 값을 이용
# 레포지토리 클론 git clone https://github.com/paulbouwer/hello-kubernetes.git # v1 애플리케이션 배포 helm install --create-namespace --namespace hello-kubernetes v1 \ ./hello-kubernetes/deploy/helm/hello-kubernetes \ --set message="You are reaching hello-kubernetes version 1" \ --set ingress.configured=true \ --set service.type="ClusterIP" # v2 애플리케이션 배포 helm install --create-namespace --namespace hello-kubernetes v2 \ ./hello-kubernetes/deploy/helm/hello-kubernetes \ --set message="You are reaching hello-kubernetes version 2" \ --set ingress.configured=true \ --set service.type="ClusterIP"
Shell
복사
# 리소스 생성 확인 kubectl get pod -n hello-kubernetes --label-columns=app.kubernetes.io/instance,pod-template-hash
Shell
복사

Ingress 생성

현재 맥락은 v2 서비스 배포 직전이며 v1으로만 트래픽이 오고 있는 상황, v2로의 업그레이드 상황을 가정
# Ingress 생성 (생성에 일부 시간 소요) cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: "hello-kubernetes" namespace: "hello-kubernetes" annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/actions.blue-green: | { "type":"forward", "forwardConfig":{ "targetGroups":[ { "serviceName":"hello-kubernetes-v1", "servicePort":"80", "weight":100 }, { "serviceName":"hello-kubernetes-v2", "servicePort":"80", "weight":0 } ] } } labels: app: hello-kubernetes spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Prefix backend: service: name: blue-green port: name: use-annotation EOF
Shell
복사
# 접속 주소 변수 지정 ELB_URL=$(kubectl get ingress -n hello-kubernetes -o=jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') # 반복 접속 for i in {1..100}; do curl -s --connect-timeout 1 $ELB_URL | grep version ; done | sort | uniq -c | sort -nr
Shell
복사

Blue/Green 배포

# Ingress 갱신 (갱신에 일부 시간 소요) cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: "hello-kubernetes" namespace: "hello-kubernetes" annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/actions.blue-green: | { "type":"forward", "forwardConfig":{ "targetGroups":[ { "serviceName":"hello-kubernetes-v1", "servicePort":"80", "weight":0 }, { "serviceName":"hello-kubernetes-v2", "servicePort":"80", "weight":100 } ] } } labels: app: hello-kubernetes spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Prefix backend: service: name: blue-green port: name: use-annotation EOF
Shell
복사
# 접속 주소 변수 지정 ELB_URL=$(kubectl get ingress -n hello-kubernetes -o=jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') # 반복 접속 for i in {1..100}; do curl -s --connect-timeout 1 $ELB_URL | grep version ; done | sort | uniq -c | sort -nr
Shell
복사

Canary 배포

카나리 배포는 일반적으로 적은 증가분으로 트래픽이 이동되는데, 매번 증가분을 수기로 적용하지 않고 자동화된 방법으로 점진적 트래픽 이동을 수행
이런 자동화 방법은 성능 모니터링 시스템 등을 통합하는 등 각 단계마다 오류가 없거나 오류를 허용할만한 임계치 내인지 확인하며 이뤄지는데, AWS에서는 AWS LB Controller가 애너테이션 기반 트래픽 조절 기능을 이용하여 Argo Rollouts와 통합을 지원하면서 이뤄짐
Argo Rollouts는 다양한 플랫폼과의 통합을 통해 메트릭을 해석하고 검증하여 업데이트 중 자동 승격 혹은 되돌리기를 수행 가능
# Ingress 갱신 (갱신에 일부 시간 소요) cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: "hello-kubernetes" namespace: "hello-kubernetes" annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/actions.blue-green: | { "type":"forward", "forwardConfig":{ "targetGroups":[ { "serviceName":"hello-kubernetes-v1", "servicePort":"80", "weight":90 }, { "serviceName":"hello-kubernetes-v2", "servicePort":"80", "weight":10 } ] } } labels: app: hello-kubernetes spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Prefix backend: service: name: blue-green port: name: use-annotation EOF
Shell
복사
# 접속 주소 변수 지정 ELB_URL=$(kubectl get ingress -n hello-kubernetes -o=jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') # 반복 접속 for i in {1..100}; do curl -s --connect-timeout 1 $ELB_URL | grep version ; done | sort | uniq -c | sort -nr
Shell
복사

A/B 테스팅

A/B 테스팅에서는 alb.ingress.kubernetes.io/actions.${action-name} 뿐만 아니라 alb.ingress.kubernetes.io/conditions.${conditions-name} 애너테이션 지정도 필요
A/B 테스팅을 적용하기 위해선 conditions-nameab-testing으로 지정하여야 조건에 맞는 라우팅을 수행할 수 있게 되는데, 라우팅 조건으로는 http-header, http-request-method, query-string, source-ip 등을 지정 가능
AWS LB Controller는 인입되는 트래픽을 백엔드로 라우팅 하는 과정에서 리스너 규칙을 애너테이션에 따라 처리하게 되는데, 다음 실습에서는 HeaderName=HeaderValue인 경우 라우팅 동작
# Ingress 갱신 (갱신에 일부 시간 소요) cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: "hello-kubernetes" namespace: "hello-kubernetes" annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/conditions.ab-testing: > [{"field":"http-header","httpHeaderConfig":{"httpHeaderName": "HeaderName", "values":["kans-study-end"]}}] alb.ingress.kubernetes.io/actions.ab-testing: > {"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"hello-kubernetes-v2","servicePort":80}]}} labels: app: hello-kubernetes spec: ingressClassName: alb rules: - http: paths: - path: / pathType: Prefix backend: service: name: ab-testing port: name: use-annotation - path: / pathType: Prefix backend: service: name: hello-kubernetes-v1 port: name: http EOF
Shell
복사
# 접속 주소 변수 지정 ELB_URL=$(kubectl get ingress -n hello-kubernetes -o=jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') # 반복 접속 for i in {1..100}; do curl -s --connect-timeout 1 $ELB_URL | grep version ; done | sort | uniq -c | sort -nr # 헤더 적용한 반복 접속 for i in {1..100}; do curl -s --connect-timeout 1 -H "HeaderName: kans-study-end" $ELB_URL | grep version ; done | sort | uniq -c | sort -nr
Shell
복사

리소스 삭제

kubectl delete ingress -n hello-kubernetes hello-kubernetes && kubectl delete ns hello-kubernetes
Shell
복사
제시된 AWS VPC CNI의 데모 예시를 통해 Network Policy 적용을 확인

조건 확인

# Network Policy 이용을 위한 조건 확인 # 1. EKS 1.25 버전 이상 kubectl get no # 2. AWS VPC CNI 1.14 이상 kubectl get ds aws-node -n kube-system -o yaml | k neat | grep aws-vpc-cni- # 3. OS 커널 5.10 이상 (워커 노드 접속 필요) N1=$(kubectl get node -o jsonpath={.items[0].status.addresses[0].address}) ssh ec2-user@$N1 uname -r
Shell
복사

활성화 확인

# Network Policy가 기본 값으로는 비활성화이므로 활성화 필요 (미리 적용 되어 있는데 표기된 구문 추가 필요) tail -n 11 myeks.yaml | grep enableNetworkPolicy -A5 -B5 # Node Agent 확인하여 Network Policy 활성화 확인 kubectl get ds aws-node -n kube-system -o yaml | k neat | grep enable-network-policy
Shell
복사

eBPF 프로그램 확인

# 변수 지정 N1=$(kubectl get node -o jsonpath={.items[0].status.addresses[0].address}) N2=$(kubectl get node -o jsonpath={.items[1].status.addresses[0].address}) N3=$(kubectl get node -o jsonpath={.items[2].status.addresses[0].address}) # 실행 중인 eBPF 프로그램 확인 for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo /opt/cni/bin/aws-eks-na-cli ebpf progs; echo; done # 각 노드에 eBPF 파일 시스템 탑재 확인 for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo mount | grep -i bpf; echo; done
Shell
복사

리소스 생성

# 레포지토리 클론 git clone https://github.com/aws-samples/eks-network-policy-examples.git # manifest 적용 cd eks-network-policy-examples kubectl apply -f advanced/manifests/
Shell
복사
# 리소스 확인 kubectl get po,svc -n another-ns
Shell
복사

통신 확인

# 통신 확인 kubectl exec -it client-one -- curl demo-app | grep "Welcome to Amazon" kubectl exec -it client-two -- curl demo-app | grep "Welcome to Amazon" kubectl exec -it another-client-one -n another-ns -- curl demo-app.default | grep "Welcome to Amazon" kubectl exec -it another-client-two -n another-ns -- curl demo-app.default.svc | grep "Welcome to Amazon"
Shell
복사

deny-all-ingress 적용

# 우측) 정책 적용 cd ~/eks-network-policy-examples/ kubectl apply -f advanced/policies/01-deny-all-ingress.yaml kubectl get networkpolicy # 좌측) 반복적인 통신 요청 # 통신이 타임아웃 되는 것을 확인 가능 while true; do kubectl exec -it client-one -- curl --connect-timeout 1 demo-app ; date; sleep 1; done
Shell
복사
# 변수 지정 N1=$(kubectl get node -o jsonpath={.items[0].status.addresses[0].address}) N2=$(kubectl get node -o jsonpath={.items[1].status.addresses[0].address}) N3=$(kubectl get node -o jsonpath={.items[2].status.addresses[0].address}) # 실행 중인 eBPF 프로그램 확인 # Ingress / Egress 정책 추가된 것 확인 가능 for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo /opt/cni/bin/aws-eks-na-cli ebpf progs; echo; done for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo /opt/cni/bin/aws-eks-na-cli ebpf loaded-ebpfdata; echo; done
Shell
복사

allow-ingress-from-same-ns-client-one 추가

# 우측) 정책 적용 cd ~/eks-network-policy-examples/ kubectl apply -f advanced/policies/03-allow-ingress-from-samens-client-one.yaml kubectl get networkpolicy # 좌측) 클라이언트2 수신 확인 # 클라이언트2는 여전히 통신 불가 kubectl exec -it client-two -- curl --connect-timeout 1 demo-app
Shell
복사

allow-ingress-from-other-ns 추가

# 우측) 정책 적용 cd ~/eks-network-policy-examples/ kubectl apply -f advanced/policies/04-allow-ingress-from-xns.yaml kubectl get networkpolicy # 좌측) 클라이언트2 수신 확인 # 클라이언트2가 드디어 통신 가능 kubectl exec -it another-client-two -n another-ns -- curl --connect-timeout 1 demo-app.default
Shell
복사

deny-egress-from-same-ns-client-one 추가

# 우측) 정책 적용 cd ~/eks-network-policy-examples/ kubectl apply -f advanced/policies/06-deny-egress-from-client-one.yaml kubectl get networkpolicy # 좌측) 클라이언트1 nslookup 확인 # 클라이언트1의 통신 불가 kubectl exec -it client-one -- nslookup demo-app
Shell
복사

allow-egress-to-demo-app 추가

# 우측) 정책 적용 cd ~/eks-network-policy-examples/ kubectl apply -f advanced/policies/08-allow-egress-to-demo-app.yaml kubectl get networkpolicy # 좌측) 클라이언트1 nslookup 확인 # 요청은 실패했지만, 클라이언트1가 드디어 통신 가능 kubectl exec -it client-one -- nslookup demo-app
Shell
복사

리소스 삭제

kubectl delete networkpolicy --all cd ~/eks-network-policy-examples/ kubectl delete -f advanced/manifests/
Shell
복사

실습 환경 삭제

# myeks 노드에서 수행 # 기본적으로 세션 내에 CLUSTER_NAME이 지정되어 있긴 하지만, echo $CLUSTER_NAME으로 확인이 안 된다면 아래처럼 할당 CLUSTER_NAME=myeks # EKS 클러스터 삭제 및 CloudFormation 삭제 eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
Shell
복사
# 로컬에서 삭제 여부 확인 while true; do date AWS_PAGER="" aws cloudformation list-stacks \ --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \ --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \ --output table sleep 1 done
Shell
복사