개요
AWS EKS에서 VPC CNI를 학습하기 위한 실습
실습 환경 준비
구성
•
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
복사
인스턴스 타입 정보
위 계산 식에 따라 t3.medium의 기준으로는 개의 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
복사
--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-name도 ab-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
복사
조건 확인
# 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
복사