Kubernetes

[ Kans 3 Study - 9w ] 3. Ingress / ExternalDNS / CoreDNS

su''@ 2024. 11. 2. 17:59
CloudNetaStudy - Kubernets Networtk 3기 실습 스터디 게시글입니다.

 

7. Ingress

 

인그레스 소개 : 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할

  • AWS Load Balancer Controller + Ingress (ALB) IP 모드 동작 with AWS VPC CNI
    그림처럼 EKS Cluster 는 없고, AWS kOps 클러스터가 배포되어 있음


  • 서비스/파드 배포 테스트 with Ingress(ALB) - ALB
    # 게임 파드와 Service, Ingress 배포
    curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
    cat ingress1.yaml
    kubectl apply -f ingress1.yaml
    
    # 모니터링
    watch -d kubectl get pod,ingress,svc,ep -n game-2048
    
    # 생성 확인
    kubectl get-all -n game-2048
    kubectl get ingress,svc,ep,pod -n game-2048
    kubectl get targetgroupbindings -n game-2048
    
    # ALB 생성 확인
    aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`]' | jq
    ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`].LoadBalancerArn' | jq -r '.[0]')
    aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN
    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
    
    # Ingress 확인
    kubectl describe ingress -n game-2048 ingress-2048
    kubectl get ingress -n game-2048 ingress-2048 -o jsonpath="{.status.loadBalancer.ingress[*].hostname}{'\n'}"
    
    # 게임 접속 : ALB 주소로 웹 접속
    kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'
    
    # 파드 IP 확인
    kubectl get pod -n game-2048 -owide

    ALB 대상 그룹에 등록된 대상 확인 : ALB에서 파드 IP로 직접 전달
    • 파드 3개로 증가
      # 터미널1
      watch kubectl get pod -n game-2048
      while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done
      
      # 터미널2 : 파드 3개로 증가
      kubectl scale deployment -n game-2048 deployment-2048 --replicas 3
    • 파드 1개로 감소
      # 터미널2 : 파드 1개로 감소
      kubectl scale deployment -n game-2048 deployment-2048 --replicas 1
    • 실습 리소스 삭제
      kubectl delete ingress 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
  • Exposing Kubernetes Applications, Part 1: Service and Ingress Resources - 링크
    • Exposing a Service : In-tree Service Controller
    • Ingress Implementations : External Load Balancer
    • Ingress Implementations : Internal Reverse Proxy
    • Kubernetes Gateway API

 

8. ExternalDNS

 

소개 : K8S 서비스/인그레스 생성 시 도메인을 설정하면, AWS(Route 53), Azure(DNS), GCP(Cloud DNS) 에 A 레코드(TXT 레코드)로 자동 생성/삭제

출처 : https://edgehog.blog/a-self-hosted-external-dns-resolver-for-kubernetes-111a27d6fc2c

 

    • ExternalDNS CTRL 권한 주는 방법 3가지 : Node IAM Role, Static credentials, IRSA
    • AWS Route 53 정보 확인 & 변수 지정 : Public 도메인 소유를 하고 계셔야 합니다!
      # 자신의 도메인 변수 지정 : 소유하고 있는 자신의 도메인을 입력하시면 됩니다
      MyDomain=<자신의 도메인>
      MyDomain=gasida.link
      echo "export MyDomain=gasida.link" >> /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를 통해 사용한 A/TXT 레코드가 있는 존의 경우에 policy 정책을 upsert-only 로 설정 후 사용 하자 - Link
         - #--policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization

  • ExternalDNS 설치 - 링크
    # EKS 배포 시 Node IAM Role 설정되어 있음
    # eksctl create cluster ... --external-dns-access ...
    
    # 
    MyDomain=<자신의 도메인>
    MyDomain=gasida.link
    
    # 자신의 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
    cat externaldns.yaml
    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

  • Service(NLB) + 도메인 연동(ExternalDNS) - 도메인체크
    # 터미널1 (모니터링)
    watch -d 'kubectl get pod,svc'
    kubectl logs deploy/external-dns -n kube-system -f
    
    # 테트리스 디플로이먼트 배포
    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
    
    # 배포 확인
    kubectl get deploy,svc,ep tetris
    
    # 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"
    • 웹 접속(http) → 화살표키, 일시중지(space bar)
    • 리소스 삭제 : kubectl delete deploy,svc tetris ← 삭제 시 externaldns 에 의해서 A레코드도 같이 삭제됨
  • 실습해보기
    • Service(NLB + TLS) + 도메인 연동(ExternalDNS) 
    • Ingress(ALB + HTTPS) + 도메인 연동(ExternalDNS) ← 직접 실습해보시기바랍니다!
  • (참고) ACM 퍼블릭 인증서 요청 및 해당 인증서에 대한 Route53 도메인 검증 설정 with AWS CLI
    # 각자 자신의 도메인 변수 지정
    MyDomain=<각자 자신의 도메인>
    
    # ACM 퍼블릭 인증서 요청
    CERT_ARN=$(aws acm request-certificate \
    --domain-name $MyDomain \
    --validation-method 'DNS' \
    --key-algorithm 'RSA_2048' \
    |jq --raw-output '.CertificateArn')
    
    # 생성한 인증서 CNAME 이름 가져오기
    CnameName=$(aws acm describe-certificate \
    --certificate-arn $CERT_ARN \
    --query 'Certificate.DomainValidationOptions[*].ResourceRecord.Name' \
    --output text)
    
    # 생성한 인증서 CNAME 값 가져오기
    CnameValue=$(aws acm describe-certificate \
    --certificate-arn $CERT_ARN \
    --query 'Certificate.DomainValidationOptions[*].ResourceRecord.Value' \
    --output text)
    
    # 정상 출력 확인하기
    echo $CERT_ARN, $CnameName, $CnameValue
    
    # 레코드 파일
    cat <<EOT > cname.json
    {
      "Comment": "create a acm's CNAME record",
      "Changes": [
        {
          "Action": "CREATE",
          "ResourceRecordSet": {
            "Name": "CnameName",
            "Type": "CNAME",
            "TTL": 300,
            "ResourceRecords": [
              {
                "Value": "CnameValue"
              }
            ]
          }
        }
      ]
    }
    EOT
    
    # CNAME 이름, 값 치환하기
    sed -i "s/CnameName/$CnameName/g" cname.json
    sed -i "s/CnameValue/$CnameValue/g" cname.json
    cat cname.json
    
    # 해당 인증서에 대한 Route53 도메인 검증 설정을 위한 Route53 레코드 생성
    aws route53 change-resource-record-sets --hosted-zone-id $MyDnzHostedZoneId --change-batch file://cname.json
9. CoreDNS

 

Kubernetes에서 DNS 다루는 방법 - 도메인을 찾아서 - 링크

  • (심화) Recent changes to the CoreDNS add-on - Link
    • EKS CoreDNS 애드온 구성 스키마에 topologySpreadConstraints 설정 추가
      # CoreDNS 기본 정보 확인 : volumes - configMap - Corefile - coredns
      kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 
      kubectl get cm -n kube-system coredns -o yaml | kubectl neat
      data: 
        Corefile: |
          .:53 {
              errors
              health {
                  lameduck 5s
                }
              ready
              kubernetes cluster.local in-addr.arpa ip6.arpa {
                pods insecure
                fallthrough in-addr.arpa ip6.arpa
              }
              prometheus :9153
              forward . /etc/resolv.conf
              cache 30
              loop
              reload
              loadbalance
          }
      
      # JSON 구성 스키마에 topologySpreadConstraints 매개변수를 추가
      aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.2 --query 'configurationSchema' --output text | jq .
      aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.2 --query 'configurationSchema' --output text | jq . | grep -A3 topologySpreadConstraints
      
      # check coredns deployment - it returns an empty output because it is not set by default 
      kubectl get deploy -n kube-system coredns -o yaml | grep topologySpreadConstraints -A8
      
      #
      aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns | jq
      {
        "addon": {
          "addonName": "coredns",
          "clusterName": "myeks",
          "status": "ACTIVE",
          "addonVersion": "v1.10.1-eksbuild.7",
          "health": {
            "issues": []
          },
          "addonArn": "arn:aws:eks:ap-northeast-2:911283464785:addon/myeks/coredns/4ec712ea-54eb-05df-5d57-7b52ef1efc6f",
          "createdAt": "2024-03-10T09:47:58.100000+09:00",
          "modifiedAt": "2024-03-10T09:48:06.073000+09:00",
          "tags": {}
        }
      }
      
      # add-on configuration YAML blob
      cat << EOT > topologySpreadConstraints.yaml
      "topologySpreadConstraints":
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              k8s-app: kube-dns
      EOT
      
      # apply change to add-on
      aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name coredns --configuration-values 'file://topologySpreadConstraints.yaml'
      
      # check add-on configuration to see if it is in ACTIVE status
      aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns | jq
      kubectl get deploy coredns -n kube-system -o yaml | kubectl neat
      ...
          topologySpreadConstraints: 
          - labelSelector: 
              matchLabels: 
                k8s-app: kube-dns
            maxSkew: 1
            topologyKey: topology.kubernetes.io/zone
            whenUnsatisfiable: ScheduleAnyway
      ...
    • coredns 애드온에 PDB(Pod Disruption Budget) 추가
      • PDB는 두 개의 복제본 Pod가 있는 coredns와 같이 노드 종료 중에 동시에 작동이 중지되는 복제된 애플리케이션의 Pod 수를 제한
        # "coredns" add-on v1.9.3-eksbuid.5 and v1.9.3-eksbuid.6
        kubectl get pdb -n kube-system coredns
        NAME    MIN AVAILABLE   MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
        coredns 1               N/A              1                    13h
        
        # "coredns" add-on v1.10.1-eksbuild.2 and v1.10.1-eksbuild.3
        kubectl get pdb -n kube-system coredns
        NAME    MIN AVAILABLE   MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
        coredns N/A             1                1                     27h
      • DNS 확인 실패를 최소화하기 위해 기본적으로 coreDNS 플러그인에 lameduck 옵션을 추가
        • lameduck은 상태 엔드포인트가 여전히 200으로 응답하는 동안 DURATION초 동안 종료를 지연합니다.
        • 플러그인에 lameduck을 추가하면 CoreDNS 포드 다시 시작(예: 상태 문제, 노드 종료 등으로 인해) 또는 배포 롤아웃 중에 DNS 확인 실패가 최소화됨
          # CoreDNS 기본 정보 확인
          kubectl get cm -n kube-system coredns -o yaml | kubectl neat
          data: 
            Corefile: |
              .:53 {
                  errors
                  health {
                      lameduck 5s
                    }
                  ready
                  kubernetes cluster.local in-addr.arpa ip6.arpa {
                    pods insecure
                    fallthrough in-addr.arpa ip6.arpa
                  }
                  prometheus :9153
                  forward . /etc/resolv.conf
                  cache 30
                  loop
                  reload
                  loadbalance
              }
      • CoreDNS의 readinessProbe 에서 /health 대신 /ready를 사용
        #
        kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 
        ...
              readinessProbe: 
                failureThreshold: 3
                httpGet: 
                  path: /ready
                  port: 8181
                  scheme: HTTP
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
        ...
        
        # The “ready” plugin is already part of the “coredns” ConfigMap:
        kubectl get cm -n kube-system coredns -o yaml | kubectl neat
        data: 
          Corefile: |
            .:53 {
                errors
                health {
                    lameduck 5s
                  }
                ready
                kubernetes cluster.local in-addr.arpa ip6.arpa {
                  pods insecure
                  fallthrough in-addr.arpa ip6.arpa
                }
                prometheus :9153
                forward . /etc/resolv.conf
                cache 30
                loop
                reload
                loadbalance
            }
      • EKS 관리형 Add-On Pod에 대한 라벨 설정
        # 파드 Labels 확인
        kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 
        
        # pod labels
        aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.3 \
          --query 'configurationSchema' --output text | jq . | grep -A4 '\"podLabels\"'
                "podLabels": {
                  "properties": {},
                  "title": "The podLabels Schema",
                  "type": "object"
                },
        
        # YAML configuration blob
        cat << EOT > podLabels.yaml
        podLabels:
          foo: bar
        EOT
          
        # apply changes : 적용 시 coredns 파드 재시작됨
        aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name coredns --configuration-values 'file://podLabels.yaml'
        watch -d kubectl get pod -n kube-system
        
        # wait a while until the add-on is ACTIVE again
        aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns
        
        # 파드 Labels 확인
        kubectl get deploy coredns -n kube-system -o yaml | kubectl neat
        kubectl get pod -n kube-system -l foo=bar
        kubectl get po -n kube-system -l=k8s-app=kube-dns -o custom-columns="POD-NAME":.metadata.name,"POD-LABELS":.metadata.labels