Kubernetes 스케줄러 문제 해결

이 페이지에서는 Google Distributed Cloud의 Kubernetes 스케줄러(kube-scheduler) 문제를 해결하는 방법을 설명합니다.

추가 지원이 필요하면 Cloud Customer Care에 문의하세요.

Kubernetes는 항상 동일한 노드 집합에 포드를 예약함

이 오류는 몇 가지 다른 방식으로 관찰될 수 있습니다.

  • 불균형한 클러스터 사용률. kubectl top nodes 명령어를 사용하여 각 노드의 클러스터 사용률을 조사할 수 있습니다. 다음 예시 출력은 특정 노드의 현저한 사용률을 보여줍니다.

    NAME                   CPU(cores)   CPU%      MEMORY(bytes)   MEMORY%
    XXX.gke.internal       222m         101%       3237Mi          61%
    YYY.gke.internal       91m          0%         2217Mi          0%
    ZZZ.gke.internal       512m         0%         8214Mi          0%
    
  • 요청이 너무 많습니다. 동일한 노드에 한 번에 많은 포드를 예약하고 이러한 포드가 HTTP 요청을 수행하는 경우 노드 비율이 제한될 수 있습니다. 이 시나리오에서 서버가 반환하는 일반적인 오류는 429 Too Many Requests입니다.

  • 서비스를 사용할 수 없습니다. 예를 들어 부하가 높은 노드에서 호스팅되는 웹 서버는 부하가 줄어들 때까지 503 Service Unavailable 오류가 있는 모든 요청에 응답할 수 있습니다.

항상 동일한 노드에 예약된 포드가 있는지 확인하려면 다음 단계를 수행합니다.

  1. 다음 kubectl 명령어를 실행하여 포드 상태를 확인합니다.

    kubectl get pods -o wide -n default
    

    노드 전체의 포드 분포를 보려면 출력에서 NODE 열을 확인합니다. 다음 출력 예시에서 모든 포드는 동일한 노드에 예약됩니다.

    NAME                               READY  STATUS   RESTARTS  AGE  IP             NODE
    nginx-deployment-84c6674589-cxp55  1/1    Running  0         55s  10.20.152.138  10.128.224.44
    nginx-deployment-84c6674589-hzmnn  1/1    Running  0         55s  10.20.155.70   10.128.226.44
    nginx-deployment-84c6674589-vq4l2  1/1    Running  0         55s  10.20.225.7    10.128.226.44
    

포드에는 예약 동작을 미세 조정할 수 있는 여러 기능이 포함되어 있습니다. 이러한 기능에는 토폴로지 분산 제약조건과 안티-어피니티 규칙이 포함됩니다. 이러한 기능 중 하나 또는 조합해 사용할 수 있습니다. 정의한 요구사항은 kube-scheduler에서 AND와 함께 제공됩니다.

스케줄러 로그는 기본 로깅 세부정보 수준에서 캡처되지 않습니다. 문제 해결을 위해 스케줄러 로그가 필요하면 다음 단계를 수행하여 스케줄러 로그를 캡처합니다.

  1. 로깅 세부정보 수준을 늘립니다.

    1. kube-scheduler 배포를 수정합니다.

      kubectl --kubeconfig ADMIN_CLUSTER_KUBECONFIG edit deployment kube-scheduler \
        -n USER_CLUSTER_NAMESPACE
      
    2. spec.containers.command 섹션 아래에서 --v=5 플래그를 추가합니다.

      containers:
      - command:
      - kube-scheduler
      - --profiling=false
      - --kubeconfig=/etc/kubernetes/scheduler.conf
      - --leader-elect=true
      - --v=5
      
  2. 문제 해결이 완료되면 세부정보 수준을 다시 기본 수준으로 재설정합니다.

    1. kube-scheduler 배포를 수정합니다.

      kubectl --kubeconfig ADMIN_CLUSTER_KUBECONFIG edit deployment kube-scheduler \
        -n USER_CLUSTER_NAMESPACE
      
    2. 세부정보 수준을 다시 기본값으로 설정합니다.

      containers:
      - command:
      - kube-scheduler
      - --profiling=false
      - --kubeconfig=/etc/kubernetes/scheduler.conf
      - --leader-elect=true
      

토폴로지 분산 제약조건

토폴로지 분산 제약조건zones, regions, node 또는 기타 맞춤 정의 토폴로지에 따라 노드 전체에 포드를 고르게 배포하는 데 사용할 수 있습니다.

다음 매니페스트 예시에서는 토폴로지 분산 제약조건을 사용하여 예약 가능한 전체 노드에 복제본을 고르게 분산하는 배포를 보여줍니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: topology-spread-deployment
  labels:
    app: myapp
spec:
  replicas: 30
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      topologySpreadConstraints:
      - maxSkew: 1 # Default. Spreads evenly. Maximum difference in scheduled Pods per Node.
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule # Default. Alternatively can be ScheduleAnyway
        labelSelector:
          matchLabels:
            app: myapp
        matchLabelKeys: # beta in 1.27
        - pod-template-hash
      containers:
      # pause is a lightweight container that simply sleeps
      - name: pause
        image: registry.k8s.io/pause:3.2

토폴로지 분산 제약조건을 사용할 때는 다음 고려사항이 적용됩니다.

  • 포드의 labels.app: myapp이 제약조건의 labelSelector와 일치합니다.
  • topologyKeykubernetes.io/hostname을 지정합니다. 이 라벨은 모든 노드에 자동으로 연결되고 노드의 호스트 이름으로 채워집니다.
  • matchLabelKeys는 포드를 예약할 위치를 계산할 때 새 배포의 출시가 이전 버전의 포드를 고려하는 것을 방지합니다. pod-template-hash 라벨은 배포 시 자동으로 채워집니다.

Pod 안티어피니티

포드 안티-어피니티를 사용하면 동일한 노드에 함께 배치될 수 있는 포드의 제약조건을 정의할 수 있습니다.

다음 매니페스트 예시는 안티어피니티를 사용하여 복제본을 노드당 하나의 포드로 제한하는 배포를 보여줍니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity-deployment
  labels:
    app: myapp
spec:
  replicas: 30
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      name: with-pod-affinity
      labels:
        app: myapp
    spec:
      affinity:
        podAntiAffinity:
          # requiredDuringSchedulingIgnoredDuringExecution
          # prevents Pod from being scheduled on a Node if it
          # does not meet criteria.
          # Alternatively can use 'preferred' with a weight
          # rather than 'required'.
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - myapp
            # Your nodes might be configured with other keys
            # to use as `topologyKey`. `kubernetes.io/region`
            # and `kubernetes.io/zone` are common.
            topologyKey: kubernetes.io/hostname
      containers:
      # pause is a lightweight container that simply sleeps
      - name: pause
        image: registry.k8s.io/pause:3.2

이 예시 배포는 30 복제본을 지정하지만 클러스터에 사용 가능한 노드 수만큼으로 확장됩니다.

포드 안티-어피니티를 사용하는 경우 다음 고려사항이 적용됩니다.

  • 포드의 labels.app: myapp이 제약조건의 labelSelector와 일치합니다.
  • topologyKeykubernetes.io/hostname을 지정합니다. 이 라벨은 모든 노드에 자동으로 연결되고 노드의 호스트 이름으로 채워집니다. 클러스터에서 지원하는 경우 region 또는 zone과 같은 다른 라벨을 사용하도록 선택할 수 있습니다.

컨테이너 이미지 사전 가져오기

다른 제약조건이 없으면 기본적으로 kube-scheduler는 컨테이너 이미지가 이미 다운로드된 노드에서 포드를 예약하는 것을 선호합니다. 이 동작은 모든 노드에서 이미지를 다운로드할 수 있는 다른 예약 구성이 없는 소규모 클러스터에 중요할 수 있습니다. 하지만 이 개념을 사용하는 것은 최후의 수단으로 간주되어야 합니다. 더 나은 해결책은 nodeSelector, 토폴로지 분산 제약조건 또는 어피니티/안티-어피니티를 사용하는 것입니다. 자세한 내용은 노드에 포드 할당을 참조하세요.

컨테이너 이미지가 모든 노드로 미리 가져오기 되었는지 확인하려면 다음 예시와 같이 DaemonSet을 사용하면 됩니다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: prepulled-images
spec:
  selector:
    matchLabels:
      name: prepulled-images
  template:
    metadata:
      labels:
        name: prepulled-images
    spec:
      initContainers:
        - name: prepulled-image
          image: IMAGE
          # Use a command the terminates immediately
          command: ["sh", "-c", "'true'"]
      containers:
      # pause is a lightweight container that simply sleeps
      - name: pause
        image: registry.k8s.io/pause:3.2

포드가 모든 노드에서 Running 상태가 되면 포드를 다시 배포하여 컨테이너가 이제 노드 전체에 고르게 분산되는지 확인합니다.

다음 단계

추가 지원이 필요하면 Cloud Customer Care에 문의하세요.