DRA 기능

DRA 기능 (DRA Features)

이 페이지는 고급 사용 사례를 위한 선택적 DRA 기능을 설명해요. 이러한 기능 중 일부는 DRA 드라이버의 지원이 필요해요. 각 기능은 그 성숙도와 그것을 활성화하는 기능 게이트(feature gate)를 명시해요.

출처: 문서

본문

파티셔너블 디바이스 (Partitionable devices)

DRA에 표시된 디바이스가 반드시 단일 머신에 연결된 단일 유닛일 필요는 없으며, 여러 머신에 연결된 여러 디바이스로 구성된 논리적 디바이스일 수도 있어요. 이러한 디바이스는 기반 물리적 디바이스의 겹치는 리소스를 소비할 수 있는데, 즉 하나의 논리적 디바이스가 할당되면 다른 디바이스는 더 이상 사용할 수 없게 된다는 뜻이에요.

ResourceSlice API에서 이것은 명명된 CounterSet 목록으로 표현되며, 각각은 명명된 카운터 집합을 포함해요. 카운터는 DRA를 통해 광고되는 논리적 디바이스가 사용하는 물리적 디바이스에서 사용 가능한 리소스를 나타내요.

논리적 디바이스는 ConsumesCounters 목록을 지정할 수 있어요. 각 항목은 CounterSet에 대한 참조와 디바이스가 소비할 양이 담긴 명명된 카운터 집합을 포함해요. 따라서 디바이스가 할당 가능하려면, 참조된 카운터 집합이 디바이스가 참조하는 카운터에 대해 충분한 수량을 가져야 해요.

CounterSet은 디바이스와 별도의 ResourceSlice에 지정되어야 해요. 디바이스는 디바이스와 같은 리소스 풀에 정의된 어떤 CounterSet에서든 카운터를 소비할 수 있어요.

다음은 8Gi 메모리의 공유 카운터에서 각각 6Gi 메모리를 소비하는 두 디바이스의 예시예요. 따라서 어느 시점에든 두 디바이스 중 하나만 할당될 수 있어요. 스케줄러가 이를 처리하며, ResourceClaim API는 영향을 받지 않으므로 소비자에게 투명해요.

apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: resourceslice-with-countersets
spec:
  nodeName: worker-1
  pool:
    name: pool
    generation: 1
    resourceSliceCount: 2
  driver: dra.example.com
  sharedCounters:
  - name: gpu-1-counters
    counters:
      memory:
        value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: resourceslice-with-devices
spec:
  nodeName: worker-1
  pool:
    name: pool
    generation: 1
    resourceSliceCount: 2
  driver: dra.example.com
  devices:
  - name: device-1
    consumesCounters:
    - counterSet: gpu-1-counters
      counters:
        memory:
          value: 6Gi
  - name: device-2
    consumesCounters:
    - counterSet: gpu-1-counters
      counters:
        memory:
          value: 6Gi

파티셔너블 디바이스는 kube-apiserver와 kube-scheduler의 DRAPartitionableDevices 기능 게이트로 제어돼요.

디바이스 호환성 그룹 (Device compatibility groups)

이 기능을 사용하려면 (또는) 클러스터 관리자가 클러스터의 모든 관련 컴포넌트에 대해 DRADeviceCompatibilityGroups 기능 게이트를 활성화해야 해요.

기능 게이트 활성화 또는 비활성화에 대한 자세한 내용은 기능 게이트 활성화/비활성화를 참조하세요.

디바이스 호환성 그룹을 사용하면 DRA 드라이버가 같은 물리적 하드웨어에서 공동 할당(co-allocate)할 수 있는 파티셔너블 디바이스를 선언할 수 있어요. 이 기능이 없으면 호환되지 않는 디바이스 조합은 kubelet이 노드에서 파드를 준비할 때만 감지돼요 — 그 결과 준비가 실패하게 돼요. 호환성 그룹을 사용하면 스케줄러가 노드 측 작업이 시작되기 전에 스케줄링 시간에 호환되지 않는 조합을 거부해요.

이것은 상호 배타적인 작동 모드를 지원하는 하드웨어에 가장 유용해요. 예를 들어 MIG 모드 또는 vGPU 모드에서 실행될 수 있는 GPU가 있어요: MIG 모드의 디바이스와 vGPU 모드의 디바이스는 호환되지 않는 방식으로 겹치는 물리적 리소스를 소비하므로 공동 할당될 수 없어요. compatibilityGroups를 선언함으로써 드라이버는 이 제약을 스케줄러에 보이게 해요.

이 기능은 파티셔너블 디바이스 위에 구축돼요: compatibilityGroups 필드는 device.consumesCounters[] 항목에 있으며, 이 항목은 파티셔너블 디바이스에만 존재해요. DRADeviceCompatibilityGroupsDRAPartitionableDevices 기능 게이트 모두 kube-apiserver와 kube-scheduler에서 활성화되어야 해요.

작동 방식

드라이버는 ResourceSlice의 각 device.consumesCounters[] 항목에 대한 compatibilityGroups 목록을 정의해요. 목록은 그 특정 카운터 집합에서 그 디바이스의 작동 모드 또는 파티션 유형을 나타내는 최대 2개의 불투명한 문자열 이름을 포함해요.

스케줄러가 같은 카운터 집합에서 끌어온 여러 디바이스를 할당할 때, 그것들의 compatibilityGroups의 교집합을 계산해요. 할당은 그 교집합이 비어 있지 않을 때만 성공해요 — 즉 모든 공동 할당 디바이스가 적어도 하나의 공통 그룹 이름을 공유함을 의미해요. 서로 다른 카운터 집합에서 끌어온 디바이스는 서로 비교되지 않아요.

그룹을 선언하지 않은 디바이스(설정되지 않음, nil, 빈 목록)는 특수한 경우로 취급돼요: 같은 카운터 집합에서 그룹이 없는 다른 디바이스와만 공동 할당할 수 있어요. 하나 이상의 그룹을 선언한 디바이스와는 절대 공동 할당될 수 없어요.

이 제약은 단일 스케줄링 주기에 할당되는 모든 클레임에 걸쳐 적용돼요: 두 클레임이 각각 같은 카운터 집합에서 디바이스를 할당한다면, 클레임 간 그룹 교집합도 강제돼요.

예시

MIG 모드 또는 vGPU 모드에서 작동할 수 있는 GPU를 고려해 봐요. 드라이버는 각각 8Gi의 동일한 공유 메모리 카운터에서 4GiB를 소비하는 두 디바이스를 발행해요. 카운터 용량만으로는 두 디바이스가 함께 할당될 수 있어요. 각 디바이스는 그 작동 모드를 호환성 그룹으로 선언해 두 모드를 상호 배타적으로 만들어요:

apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: gpu-counters
spec:
  nodeName: worker-1
  pool:
    name: gpu-pool
    generation: 1
    resourceSliceCount: 2
  driver: gpu.example.com
  sharedCounters:
  - name: gpu-0-memory
    counters:
      memory:
        value: 8Gi
---
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: gpu-devices
spec:
  nodeName: worker-1
  pool:
    name: gpu-pool
    generation: 1
    resourceSliceCount: 2
  driver: gpu.example.com
  devices:
  - name: gpu-0-mig
    consumesCounters:
    - counterSet: gpu-0-memory
      counters:
        memory:
          value: 4Gi
      compatibilityGroups:
      - mig
  - name: gpu-0-vgpu
    consumesCounters:
    - counterSet: gpu-0-memory
      counters:
        memory:
          value: 4Gi
      compatibilityGroups:
      - vgpu

이 예시에서:

  • gpu-0-migmig 그룹에 속해요.
  • gpu-0-vgpuvgpu 그룹에 속해요.

파드나 파드 그룹이 이 풀에서 두 디바이스를 요청하면, 스케줄러는 선택된 두 디바이스가 gpu-0-memory 카운터 집합에서 공통 호환성 그룹을 공유하는지 확인해요. {"mig"} ∩ {"vgpu"} = ∅이므로, 카운터 집합에 두 디바이스 모두를 위한 충분한 메모리가 있더라도 그 쌍은 거부돼요. 두 요청은 그러한 쌍이 존재하는 풀에서 두 MIG 디바이스(또는 두 vGPU 디바이스)로만 충족될 수 있어요.

제약

  • consumesCounters[] 항목은 최대 2개의 그룹 이름을 선언할 수 있어요.
  • 그룹 이름은 단일 항목 내에서 고유해야 해요.
  • 그룹 이름은 쿠버네티스에 불투명해요; 발행 드라이버의 풀 내에서만 의미가 있어요.
  • 그룹은 카운터 집합별로 비교돼요: 한 카운터 집합의 그룹은 다른 카운터 집합에 대한 공동 할당 결정에 효과가 없어요.

버전 스큐 안전성 (Version-skew safety)

DRADeviceCompatibilityGroups 기능 게이트가 비활성화되면(알파의 기본값), kube-apiserver는 이전 객체가 이미 필드를 설정하지 않은 한 새롭거나 업데이트된 ResourceSlice에서 compatibilityGroups 필드를 제거해요. 그러면 스케줄러는 이전에 그룹화된 디바이스가 있었던 풀의 디바이스를 불완전한 풀에 속한 것으로 취급하고 완전히 건너뛰어요.

비어 있지 않은 목록만이 필드가 설정된 것으로 간주돼요: compatibilityGroups: nullcompatibilityGroups: []는 필드를 생략한 것과 동일하게 취급돼요. 그것들을 가진 디바이스는 그룹이 없는 디바이스와 정확히 동일하게 동작해요 — 스케줄러가 풀을 불완전한 것으로 취급하게 하지 않아요.

디바이스 호환성 그룹은 kube-apiserver와 kube-scheduler의 DRADeviceCompatibilityGroups 기능 게이트로 제어돼요. DRAPartitionableDevices 기능 게이트도 활성화되어야 해요.

소비 가능 용량 (Consumable capacity)

소비 가능 용량 기능은 같은 디바이스가 여러 독립 ResourceClaim에 의해 소비될 수 있게 하며, 쿠버네티스 스케줄러가 디바이스 용량 중 얼마나 많은 부분을 각 클레임이 사용하는지 관리해요. 이것은 파드가 노드의 리소스를 공유할 수 있는 방식과 유사해요; ResourceClaim은 디바이스의 리소스를 공유할 수 있어요.

디바이스 드라이버는 ResourceSlice의 .spec.devices에 추가된 allowMultipleAllocations 필드를 설정해 그 디바이스를 여러 독립 ResourceClaim 또는 ResourceClaim 내의 여러 요청에 할당할 수 있게 할 수 있어요.

ResourceClaim에서 정확히 한 종류의 디바이스를 요청할 때, 각 할당에 대한 디바이스 리소스 요구 사항을 지정하려면 spec.devices.requests[].exactly.capacity를 설정해요. 서로 다른 대안의 우선순위 목록에 대해서는 관련 spec.devices.requests[].firstAvailable[] 항목 내에서 capacity를 설정해요.

여러 할당을 허용하는 디바이스의 경우, 요청된 용량은 총 용량에서 끌어오거나 소비되는데, 이를 소비 가능 용량(consumable capacity)이라고 해요. 그러면 스케줄러는 모든 클레임에 걸친 총 소비 용량이 디바이스의 전체 용량을 초과하지 않도록 보장해요. 또한 드라이버 작성자는 개별 디바이스 용량에 대한 requestPolicy 제약을 사용해 그 용량이 어떻게 소비되는지 제어할 수 있어요. 예를 들어 드라이버 작성자는 주어진 용량이 1Gi 단위로만 소비되도록 지정할 수 있어요.

다음은 여러 할당을 허용하고 소비 가능 대역폭 용량을 포함하는 네트워크 디바이스의 예시예요.

kind: ResourceSlice
apiVersion: resource.k8s.io/v1
metadata:
  name: resourceslice
spec:
  nodeName: worker-1
  pool:
    name: pool
    generation: 1
    resourceSliceCount: 1
  driver: dra.example.com
  devices:
  - name: eth1
    allowMultipleAllocations: true
    attributes:
      name:
        string: "eth1"
    capacity:
      bandwidth:
        requestPolicy:
          default: "1M"
          validRange:
            min: "1M"
            step: "8"
        value: "10G"

소비 가능 용량은 아래 예시와 같이 요청될 수 있어요.

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: bandwidth-claim-template
spec:
  spec:
    devices:
      requests:
      - name: req-0
        exactly:
          deviceClassName: resource.example.com
          capacity:
            requests:
              bandwidth: 1G

할당 결과는 소비된 용량과 공유(share)의 식별자를 포함할 거예요.

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
  allocation:
    devices:
      results:
      - consumedCapacity:
          bandwidth: 1G
        device: eth1
        shareID: "a671734a-e8e5-11e4-8fde-42010af09327"

이 예시에서 다중 할당 가능 디바이스가 선택됐어요. 하지만 요청된 1G 대역폭 이상을 가진 어떤 resource.example.com 디바이스든 요구 사항을 충족했을 수 있어요. 다중 할당 불가 디바이스가 선택됐다면 할당 결과는 전체 디바이스가 됐을 거예요. 다중 할당 가능 디바이스만 사용하도록 강제하려면 CEL 기준 device.allowMultipleAllocations == true를 사용할 수 있어요.

DistinctAttribute 제약

ResourceClaim에서 여러 디바이스를 요청할 때, DistinctAttribute 제약을 사용해 각 할당된 디바이스가 지정된 속성에 대해 다른 값을 갖도록 보장할 수 있어요. 이 제약은 소비 가능 용량 기능과 함께 도입됐어요.

DistinctAttribute 제약은 다중 할당 가능 디바이스와 함께 작업할 때 특히 유용해요. 디바이스가 여러 할당을 허용하더라도 단일 ResourceClaim 내에서 스케줄러가 같은 디바이스를 여러 번 할당하지 못하게 해요.

중복 할당 방지 외에도, 이 제약은 디바이스가 그 속성에 따라 분산되도록 보장해 성능을 최적화하는 데 도움을 줘요. 예를 들어, 메모리 대역폭을 최적화하고 경합을 줄이기 위해 서로 다른 NUMA 노드에 디바이스를 분산시키는 데 사용할 수 있어요.

세분화된 상태 권한 부여 (Granular status authorization)

쿠버네티스 v1.36부터 DRA는 합성 하위 리소스(synthetic subresources)와 노드 인식 동사(node-aware verbs)를 사용해 ResourceClaim 상태에 대한 업데이트에 세분화된 인증 검사를 강제해요.

스케줄러와 DRA 드라이버에 대한 RBAC 예시를 포함한 보안 강화 지침은 보안 강화 가이드 - 동적 리소스 할당을 참조하세요.

단계별 클러스터 관리자 절차는 클러스터에서 동적 리소스 할당 보안 강화하기를 참조하세요.

선택적 노드 작업 (Optional node operations)

이 기능을 사용하려면 (또는) 클러스터 관리자가 클러스터의 모든 관련 컴포넌트에 대해 DRAOptionalNodeOperations 기능 게이트를 활성화해야 해요.

기능 게이트 활성화 또는 비활성화에 대한 자세한 내용은 기능 게이트 활성화/비활성화를 참조하세요.

DRA(동적 리소스 할당)에서 kubelet은 컨테이너 시작 전에 할당된 디바이스를 준비하기 위해(NodePrepareResources), 그리고 파드 종료 시 그들을 준비 해제하기 위해(NodeUnprepareResources) gRPC를 통해 노드 로컬 드라이버와 조정해요. 이 설정은 GPU나 FPGA 같은 노드 로컬 하드웨어에 중요하지만, 일부 리소스는 완전히 제어 플레인에서 관리되며 노드 로컬 설정이 필요 없어요.

선택적 노드 작업 기능을 사용하면 리소스 드라이버가 특정 노드 로컬 gRPC 작업을 건너뛸 수 있다고 선언할 수 있어요. 구성되면 kubelet은 그 디바이스들에 대해 드라이버 조회와 gRPC 호출을 우회해, 모든 워커 노드에 빈 노드 로컬 드라이버를 배포하고 유지할 필요를 없애요.

드라이버 구성

드라이버 작성자는 ResourceSlice의 .spec.skipNodeOperationsskipNodeOperations 필드를 지정할 수 있어요. 이 필드는 그 슬라이스의 모든 디바이스에 대해 우회할 노드 로컬 작업을 지정하는 고유 문자열 목록이에요.

유효한 값은 다음과 같아요:

  • "NodePrepareResources": NodePrepareResources gRPC 호출을 건너뜀. 이 값은 "NodeUnprepareResources"도 나열되어 있지 않으면("*"가 지정되지 않는 한) 지정할 수 없어요. 이 제한은 준비를 건너뛸 때 노드 로컬 플러그인이 파드 시작 중에 확인되지 않으므로, 노드 로컬 플러그인이 없을 때 파드가 Terminating에 갇히는 것을 방지해요.
  • "NodeUnprepareResources": NodeUnprepareResources gRPC 호출을 건너뜀.
  • "*": 모든 노드 로컬 리소스 작업을 건너뜀.

다음은 모든 노드 로컬 작업을 건너뛰는 제어 플레인 리소스용 ResourceSlice 예시예요:

apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: control-plane-resources
spec:
  nodeName: worker-1
  pool:
    name: central-pool
    generation: 1
    resourceSliceCount: 1
  driver: control-plane.example.com
  skipNodeOperations:
  - "*"
  devices:
  - name: virtual-device-1

할당 결과와 실행

쿠버네티스 스케줄러가 디바이스를 ResourceClaim에 할당할 때, ResourceSlice에서 할당 결과로 skipNodeOperations 목록을 복사해요:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
...
status:
  allocation:
    devices:
      results:
      - device: virtual-device-1
        driver: control-plane.example.com
        pool: central-pool
        skipNodeOperations:
        - "*"

파드가 노드에서 실행될 때 kubelet은 할당 결과를 읽어요. ResourceClaim 내에서 주어진 드라이버에 대한 모든 할당된 디바이스가 특정 작업을 건너뛰면, kubelet은 그 드라이버에 대해 그 gRPC 훅 호출을 완전히 우회해요.

운영 고려 사항

제자리 드라이버 업데이트

skipNodeOperations 설정이 할당 시점에 ResourceSlice에서 ResourceClaim으로 복사되므로, 실행 중인 파드와 활성 할당은 스케줄링되었을 당시의 설정을 그대로 유지해요.

드라이버의 노드 작업 요구 사항이 제자리에서 업데이트되면(예: 노드 작업 필요에서 건너뛰기로 변경), 기존 클레임은 여전히 이전 구성을 사용해요. 종료되는 파드가 폐기된 노드 플러그인을 기다리며 매달리는 것 같은 문제를 피하려면, 클러스터 관리자는 드라이버의 노드 작업 요구 사항을 변경하거나 노드 로컬 드라이버 DaemonSet을 제거하기 전에 그 드라이버에 대한 활성 클레임이 없는지 확인해야 해요.

노드 선언 기능 통합

kubelet이 DRA 작업 건너뛰기를 지원하지 않는 노드에 파드가 스케줄링되는 것을 방지하기 위해(누락된 노드 플러그인을 기다리며 kubelet이 실패하게 만들 것임), 이 기능은 노드 선언 기능과 통합돼요. 파드가 skipNodeOperations가 구성된 ResourceClaim을 사용하면, 쿠버네티스 스케줄러는 파드를 스케줄링하기 전에 대상 노드가 .status.declaredFeatures에서 DRAOptionalNodeOperations 기능을 지원한다고 선언하는지 확인해요.

선택적 노드 작업은 kube-apiserver, kube-scheduler, kubelet의 DRAOptionalNodeOperations 기능 게이트로 제어돼요.

더 알아보기 (Learn more)