Kubernetes

[ Kans 3 Study - 9w ] 2. Service & AWS LoadBalancer Controller / Ingress

su''@ 2024. 11. 2. 17:44
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