CloudNetaStudy - Kubernets Networtk 3기 실습 스터디 게시글입니다.
6. Service & AWS LoadBalancer Controller
[ 서비스 종류 ]
- ClusterIP 타입
- NodePort 타입
- LoadBalancer 타입 (기본 모드) : NLB 인스턴스 유형
- Service (LoadBalancer Controller) : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI
- NLB 모드 전체 정리
- 인스턴스 유형
- externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
- externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP
-
더보기
통신 흐름
요약 : 외부 클라이언트가 '로드밸런서' 접속 시 부하분산 되어 노드 도달 후 iptables 룰로 목적지 파드와 통신됨
- 노드는 외부에 공개되지 않고 로드밸런서만 외부에 공개되어, 외부 클라이언트는 로드밸랜서에 접속을 할 뿐 내부 노드의 정보를 알 수 없다
- 로드밸런서가 부하분산하여 파드가 존재하는 노드들에게 전달한다, iptables 룰에서는 자신의 노드에 있는 파드만 연결한다 (externalTrafficPolicy: local)
- DNAT 2번 동작 : 첫번째(로드밸런서 접속 후 빠져 나갈때), 두번째(노드의 iptables 룰에서 파드IP 전달 시)
- 외부 클라이언트 IP 보존(유지) : AWS NLB 는 타켓이 인스턴스일 경우 클라이언트 IP를 유지, iptables 룰 경우도 externalTrafficPolicy 로 클라이언트 IP를 보존
부하분산 최적화 : 노드에 파드가 없을 경우 '로드밸런서'에서 노드에 헬스 체크(상태 검사)가 실패하여 해당 노드로는 외부 요청 트래픽을 전달하지 않는다
- IP 유형 ⇒ 반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!
https://aws.amazon.com/blogs/networking-and-content-delivery/deploying-aws-load-balancer-controller-on-amazon-eks/
- Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
- Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)
- 인스턴스 유형
- AWS LoadBalancer Controller 배포 - Link
# 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 ## 설치 확인 kubectl get crd kubectl get deployment -n kube-system aws-load-balancer-controller kubectl describe deploy -n kube-system aws-load-balancer-controller kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account' Service Account: aws-load-balancer-controller # 클러스터롤, 롤 확인 kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role ... PolicyRule: Resources Non-Resource URLs Resource Names Verbs --------- ----------------- -------------- ----- targetgroupbindings.elbv2.k8s.aws [] [] [create delete get list patch update watch] events [] [] [create patch] ingresses [] [] [get list patch update watch] services [] [] [get list patch update watch] ingresses.extensions [] [] [get list patch update watch] services.extensions [] [] [get list patch update watch] ingresses.networking.k8s.io [] [] [get list patch update watch] services.networking.k8s.io [] [] [get list patch update watch] endpoints [] [] [get list watch] namespaces [] [] [get list watch] nodes [] [] [get list watch] pods [] [] [get list watch] endpointslices.discovery.k8s.io [] [] [get list watch] ingressclassparams.elbv2.k8s.aws [] [] [get list watch] ingressclasses.networking.k8s.io [] [] [get list watch] ingresses/status [] [] [update patch] pods/status [] [] [update patch] services/status [] [] [update patch] targetgroupbindings/status [] [] [update patch] ingresses.elbv2.k8s.aws/status [] [] [update patch] pods.elbv2.k8s.aws/status [] [] [update patch] services.elbv2.k8s.aws/status [] [] [update patch] targetgroupbindings.elbv2.k8s.aws/status [] [] [update patch] ingresses.extensions/status [] [] [update patch] pods.extensions/status [] [] [update patch] services.extensions/status [] [] [update patch] targetgroupbindings.extensions/status [] [] [update patch] ingresses.networking.k8s.io/status [] [] [update patch] pods.networking.k8s.io/status [] [] [update patch] services.networking.k8s.io/status [] [] [update patch] targetgroupbindings.networking.k8s.io/status [] [] [update patch]
- 서비스/파드 배포 테스트 with NLB - 링크 NLB
# 모니터링 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 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
- (심화) Pod readiness gate : ALB/NLB 대상(ip mode)이 ALB/NLB의 헬스체크에 의해 정상일 경우 해당 파드로 전달할 수 있는 기능 - Link K8S
- 사전 준비
# 바로 위에서 실습 리소스 삭제했다면, 다시 생성 : deregistration_delay.timeout_seconds=60 확인 kubectl apply -f echo-service-nlb.yaml kubectl scale deployment deploy-echo --replicas=1 # kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES deploy-echo-7f579ff9d7-gqdf5 1/1 Running 0 20m 192.168.2.153 ip-192-168-2-108.ap-northeast-2.compute.internal <none> <none> # mutatingwebhookconfigurations 확인 : mutating 대상(네임스페이스에 아래 매칭 시) kubectl get mutatingwebhookconfigurations kubectl get mutatingwebhookconfigurations aws-load-balancer-webhook -o yaml | kubectl neat ... name: mpod.elbv2.k8s.aws namespaceSelector: matchExpressions: - key: elbv2.k8s.aws/pod-readiness-gate-inject operator: In values: - enabled objectSelector: matchExpressions: - key: app.kubernetes.io/name operator: NotIn values: - aws-load-balancer-controller ... # 현재 확인 kubectl get ns --show-labels NAME STATUS AGE LABELS default Active 75m kubernetes.io/metadata.name=default kube-node-lease Active 75m kubernetes.io/metadata.name=kube-node-lease kube-public Active 75m kubernetes.io/metadata.name=kube-public kube-system Active 75m kubernetes.io/metadata.name=kube-system
- 설정 및 확인
# (터미널 각각 2개) 모니터링 watch -d kubectl get pod,svc,ep -owide while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done # kubectl label namespace default elbv2.k8s.aws/pod-readiness-gate-inject=enabled kubectl get ns --show-labels # READINESS GATES 항목 추가 확인 kubectl describe pod kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES deploy-echo-7f579ff9d7-gqdf5 1/1 Running 0 25m 192.168.2.153 ip-192-168-2-108.ap-northeast-2.compute.internal <none> <none> # kubectl delete pod --all kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES deploy-echo-6959b47ddf-h9vhc 1/1 Running 0 3m21s 192.168.1.127 ip-192-168-1-113.ap-northeast-2.compute.internal <none> 1/1 kubectl describe pod ... Readiness Gates: Type Status target-health.elbv2.k8s.aws/k8s-default-svcnlbip-5eff23b37f True Conditions: Type Status target-health.elbv2.k8s.aws/k8s-default-svcnlbip-5eff23b37f True Initialized True Ready True ContainersReady True PodScheduled True ... kubectl get pod -o yaml | yh ... readinessGates: - conditionType: target-health.elbv2.k8s.aws/k8s-default-svcnlbip-5eff23b37f ... status: conditions: - lastProbeTime: null lastTransitionTime: "2024-03-10T02:00:50Z" status: "True" type: target-health.elbv2.k8s.aws/k8s-default-svcnlbip-5eff23b37f ... # 분산 접속 확인 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
- 실습 리소스 삭제: kubectl delete deploy deploy-echo; kubectl delete svc svc-nlb-ip-type
- 사전 준비
- NLB IP Target & Proxy Protocol v2 활성화 : NLB에서 바로 파드로 인입 및 ClientIP 확인 설정 - 링크 image 참고
# 생성 cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: gasida-web spec: replicas: 1 selector: matchLabels: app: gasida-web template: metadata: labels: app: gasida-web spec: terminationGracePeriodSeconds: 0 containers: - name: gasida-web image: gasida/httpd:pp ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: svc-nlb-ip-type-pp 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-proxy-protocol: "*" spec: ports: - port: 80 targetPort: 80 protocol: TCP type: LoadBalancer loadBalancerClass: service.k8s.aws/nlb selector: app: gasida-web EOF # 확인 kubectl get svc,ep kubectl describe svc svc-nlb-ip-type-pp kubectl describe svc svc-nlb-ip-type-pp | grep Annotations: -A5 # apache에 proxy protocol 활성화 확인 kubectl exec deploy/gasida-web -- apachectl -t -D DUMP_MODULES kubectl exec deploy/gasida-web -- cat /usr/local/apache2/conf/httpd.conf # 접속 확인 NLB=$(kubectl get svc svc-nlb-ip-type-pp -o jsonpath={.status.loadBalancer.ingress[0].hostname}) curl -s $NLB # 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등) while true; do curl -s --connect-timeout 1 $NLB; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done # 로그 확인 kubectl logs -l app=gasida-web -f # 삭제 kubectl delete deploy gasida-web; kubectl delete svc svc-nlb-ip-type-pp
- Istio 내부망에서 클라이언트의 소스 IP 주소 확인을 위한 방법 중 Proxy Protocol 을 사용한 방법 실습 및 정리 - Docs
- Proxy protocol on AWS NLB and Istio ingress gateway - Blog
- [Toss 사례] ‘외부사 통신 영역 - L7 장비 - 토스 내부 Istio Gateway’
- 외부사와 토스 Istio Gateway 간 mTLS 통신 사용으로, 중간에 위치한 L7 장비는 HTTPS 암호화된 내용을 알 수 없음. (L4 역할만 하는듯)
- 이로 이해 L7장비는 외부사 클라이언트 IP를 XFF에 담아서 전달하지 못함, 단순 L4 역할로 Istio GW로 전달 시 소스IP를 L7 장비 IP로 SNAT됨
- 결국, 토스 Istio Gateway는 클라이언트의 소스 IP 주소를 L7 장비로만 알게 됨
- 이를 해결 하기 위해 L7장비(현 구성에서는 L4 역할만 함)에서 Proxy protocol 을 활성화하여 Istio GW에게 전달함.
- Istio Gateway는 아마도? Proxy protocol 를 통해 획득한 클라이언트의 소스 IP 주소를 XFF헤더에 추가하는 헤더 조작을 하는 것으로 보임.
- 이후 내부망에 있는 애플리케이션에서는 정상적으로 XFF 헤더에 정보로 클라이언트의 소스 IP 주소 확인 가능.
https://www.youtube.com/watch?v=4sJd6PIkP_s