장치 플러그인

장치 플러그인을 사용하여 GPU, NIC, FPGA, 또는 비휘발성 주 메모리와 같이 공급 업체별 설정이 필요한 장치 또는 리소스를 클러스터에서 지원하도록 설정할 수 있다.
기능 상태: Kubernetes v1.26 [stable]

쿠버네티스는 시스템 하드웨어 리소스를 Kubelet에 알리는 데 사용할 수 있는 장치 플러그인 프레임워크를 제공한다.

공급 업체는 쿠버네티스 자체의 코드를 커스터마이징하는 대신, 수동 또는 데몬셋으로 배포하는 장치 플러그인을 구현할 수 있다. 대상이 되는 장치에는 GPU, 고성능 NIC, FPGA, InfiniBand 어댑터 및 공급 업체별 초기화 및 설정이 필요할 수 있는 기타 유사한 컴퓨팅 리소스가 포함된다.

장치 플러그인 등록

kubelet은 Registration gRPC 서비스를 노출시킨다.

service Registration {
	rpc Register(RegisterRequest) returns (Empty) {}
}

장치 플러그인은 이 gRPC 서비스를 통해 kubelet에 자체 등록할 수 있다. 등록하는 동안, 장치 플러그인은 다음을 보내야 한다.

  • 유닉스 소켓의 이름.
  • 빌드된 장치 플러그인 API 버전.
  • 알리려는 ResourceName. 여기서 ResourceName확장된 리소스 네이밍 체계vendor-domain/resourcetype 의 형식으로 따라야 한다. (예를 들어, NVIDIA GPU는 nvidia.com/gpu 로 알려진다.)

성공적으로 등록하고 나면, 장치 플러그인은 kubelet이 관리하는 장치 목록을 전송한 다음, kubelet은 kubelet 노드 상태 업데이트의 일부로 해당 자원을 API 서버에 알리는 역할을 한다. 예를 들어, 장치 플러그인이 kubelet에 hardware-vendor.example/foo 를 등록하고 노드에 두 개의 정상 장치를 보고하고 나면, 노드 상태가 업데이트되어 노드에 2개의 "Foo" 장치가 설치되어 사용 가능함을 알릴 수 있다.

그러고 나면, 사용자가 장치를 파드 스펙의 일부로 요청할 수 있다(container 참조). 확장 리소스를 요청하는 것은 다른 자원의 요청 및 제한을 관리하는 것과 비슷하지만, 다음과 같은 차이점이 존재한다.

  • 확장된 리소스는 정수(integer) 형태만 지원되며 오버커밋(overcommit) 될 수 없다.
  • 컨테이너간에 장치를 공유할 수 없다.

예제

쿠버네티스 클러스터가 특정 노드에서 hardware-vendor.example/foo 리소스를 알리는 장치 플러그인을 실행한다고 가정해 보자. 다음은 데모 워크로드를 실행하기 위해 이 리소스를 요청하는 파드의 예이다.

---
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
    - name: demo-container-1
      image: registry.k8s.io/pause:3.8
      resources:
        limits:
          hardware-vendor.example/foo: 2
#
# 이 파드는 2개의 hardware-vendor.example/foo 장치가 필요하며
# 해당 요구를 충족할 수 있는 노드에만
# 예약될 수 있다.
#
# 노드에 2개 이상의 사용 가능한 장치가 있는 경우
# 나머지는 다른 파드에서 사용할 수 있다.

장치 플러그인 구현

장치 플러그인의 일반적인 워크플로우에는 다음 단계가 포함된다.

  1. 초기화. 이 단계에서, 장치 플러그인은 공급 업체별 초기화 및 설정을 수행하여 장치가 준비 상태에 있는지 확인한다.

  2. 플러그인은 호스트 경로 /var/lib/kubelet/device-plugins/ (이 경로는 하드코딩되어 있으며 kubelet의 --root-dir 또는 다른 어떠한 설정에도 영향을 받지 않는다) 아래에 유닉스 소켓과 함께 gRPC 서비스를 시작하며, 다음 인터페이스를 구현한다.

service DevicePlugin {
  	    // GetDevicePluginOptions는 장치 관리자와 통신할 옵션을 반환한다.
      rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {}

  		// ListAndWatch는 장치 목록 스트림을 반환한다.
  	    // 장치 상태가 변경되거나 장치가 사라질 때마다, ListAndWatch는
  	    // 새 목록을 반환한다.
      rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}

  			// 컨테이너를 생성하는 동안 Allocate가 호출되어 장치
  			// 플러그인이 장치별 작업을 실행하고 kubelet에 장치를
  			// 컨테이너에서 사용할 수 있도록 하는 단계를 지시할 수 있다.
      rpc Allocate(AllocateRequest) returns (AllocateResponse) {}

      // GetPreferredAllocation은 사용 가능한 장치 목록에서 할당할
  			// 기본 장치 집합을 반환한다. 그 결과로 반환된 선호하는 할당은
  			// devicemanager가 궁극적으로 수행하는 할당이 되는 것을 보장하지
  			// 않는다. 가능한 경우 devicemanager가 정보에 입각한 할당 결정을
  			// 내릴 수 있도록 설계되었다.
      rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {}

      // PreStartContainer는 등록 단계에서 장치 플러그인에 의해 표시되면 각 컨테이너가
  			// 시작되기 전에 호출된다. 장치 플러그인은 장치를 컨테이너에서 사용할 수 있도록 하기 전에
  			// 장치 재설정과 같은 장치별 작업을 실행할 수 있다.
      rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}
}

참고:

GetPreferredAllocation() 또는 PreStartContainer() 에 대한 유용한 구현을 제공하기 위해 플러그인이 필요하지 않다. 이러한 호출(있는 경우) 중 사용할 수 있는 경우를 나타내는 플래그는 GetDevicePluginOptions() 호출에 의해 다시 전송된 DevicePluginOptions 메시지에 설정되어야 한다. kubelet 은 항상 GetDevicePluginOptions() 를 호출하여 사용할 수 있는 선택적 함수를 확인한 후 직접 호출한다.
  1. 플러그인은 호스트 경로 /var/lib/kubelet/device-plugins/kubelet.sock 에서 유닉스 소켓을 통해 kubelet에 직접 등록한다.

    참고:

    워크플로우의 순서는 중요하다. 플러그인은 등록을 성공시키기 위해 kubelet에 자신을 등록하기 전에 반드시 gRPC 서비스를 시작해야 한다.
  2. 성공적으로 등록하고 나면, 장치 플러그인은 서빙(serving) 모드에서 실행되며, 그 동안 플러그인은 장치 상태를 모니터링하고 장치 상태 변경 시 kubelet에 다시 보고한다. 또한 gRPC 요청 Allocate 를 담당한다. Allocate 하는 동안, 장치 플러그인은 GPU 정리 또는 QRNG 초기화와 같은 장치별 준비를 수행할 수 있다. 작업이 성공하면, 장치 플러그인은 할당된 장치에 접근하기 위한 컨테이너 런타임 구성이 포함된 AllocateResponse 를 반환한다. kubelet은 이 정보를 컨테이너 런타임에 전달한다.

    AllocateResponse 는 0개 이상의 ContainerAllocateResponse 오브젝트를 포함한다. 여기서 장치 플러그인은 장치에 접근하기 위해 컨테이너의 정의에 적용되어야 하는 수정 사항을 정의한다. 이러한 수정 사항에는 다음이 포함된다.

    • 어노테이션
    • 장치 노드(device nodes)
    • 환경 변수
    • 마운트
    • 정규화된(fully-qualified) CDI 장치 이름

    참고:

    장치 관리자가 정규화된 CDI 장치 이름을 처리하려면, kubelet과 kube-apiserver 모두에서 DevicePluginCDIDevices 기능 게이트가 활성화되어 있어야 한다. 이 기능은 쿠버네티스 v1.28에서 알파로 추가되었으며, v1.29에서 베타, v1.31에서 GA로 승격되었다.

kubelet 재시작 처리

장치 플러그인은 일반적으로 kubelet의 재시작을 감지하고 새로운 kubelet 인스턴스에 자신을 다시 등록할 것으로 기대된다. 새 kubelet 인스턴스는 시작될 때 /var/lib/kubelet/device-plugins(장치 플러그인을 위한 하드코딩된 경로) 아래에 있는 모든 기존의 유닉스 소켓을 삭제한다. 장치 플러그인은 유닉스 소켓의 삭제를 모니터링하고 이러한 이벤트가 발생하면 다시 자신을 등록할 수 있다.

장치 플러그인과 비정상 장치

장치가 실패하거나 종료되는 경우가 있다. 이 경우 장치 플러그인의 책임은 ListAndWatchResponse API를 사용하여 kubelet에 상황을 알리는 것이다.

장치가 비정상으로 표시되면, kubelet은 새로운 파드 스케줄링에 사용할 수 있는 장치 수를 반영하기 위해 노드의 해당 리소스에 대한 할당 가능(allocatable) 개수를 감소시킨다. 리소스의 용량(capacity) 개수는 변경되지 않는다.

실패한 장치에 할당되었던 파드는 계속해서 해당 장치에 할당된 채로 유지된다. 일반적으로 장치에 의존하는 코드는 실패하기 시작하며, 파드의 restartPolicyAlways 가 아니라면 파드가 Failed 단계로 진입하거나 그 외에는 크래시 루프(crash loop)에 빠질 수 있다.

쿠버네티스 v1.31 이전에는, 파드가 실패한 장치와 연관되어 있는지 여부를 알 수 있는 방법은 PodResources API를 사용하는 것이었다.

기능 상태: Kubernetes v1.36 [beta](enabled by default)

ResourceHealthStatus 기능 게이트가 활성화되어 있으면(v1.36부터 베타이며 기본 활성화), 각 파드의 .status 내 컨테이너 상태에 allocatedResourcesStatus 필드가 추가된다. allocatedResourcesStatus 필드는 컨테이너에 할당된 각 장치에 대한 헬스 정보를 보고한다. 각 리소스 헬스 항목은 헬스 상태에 대한 추가적인 사람이 읽을 수 있는 컨텍스트(예: 오류 세부 정보 또는 실패 원인)를 담은 선택적 message 필드를 포함할 수 있다.

실패한 파드의 경우, 또는 결함이 의심되는 경우, 이 상태를 사용하여 파드 동작이 장치 실패와 관련이 있는지 이해할 수 있다. 예를 들어, 가속기가 과열 이벤트를 보고하고 있다면 allocatedResourcesStatus 필드가 이를 보고할 수 있다.

장치 플러그인 배포

장치 플러그인을 데몬셋, 노드 운영 체제의 패키지 또는 수동으로 배포할 수 있다.

표준 디렉터리 /var/lib/kubelet/device-plugins 에는 특권을 가진 접근이 필요하므로, 장치 플러그인은 특권을 가진 보안 컨텍스트에서 실행해야 한다. 장치 플러그인을 데몬셋으로 배포하는 경우, 플러그인의 PodSpec에서 /var/lib/kubelet/device-plugins볼륨으로 마운트해야 한다.

데몬셋 접근 방식을 선택하면 쿠버네티스를 사용하여 장치 플러그인의 파드를 노드에 배치하고, 장애 후 데몬 파드를 다시 시작하고, 업그레이드를 자동화할 수 있다.

API 호환성

과거에는 장치 플러그인의 API 버전을 반드시 kubelet의 버전과 정확하게 일치시켜야 했다. 해당 기능이 v1.12의 베타 버전으로 올라오면서 이는 필수 요구사항이 아니게 되었다. 해당 기능이 베타 버전이 된 이후로 API는 버전화되었고 그동안 변하지 않았다. 그러므로 kubelet 업그레이드를 원활하게 진행할 수 있을 것이지만, 안정화되기 전까지는 향후 API가 변할 수도 있으므로 업그레이드를 했을 때 절대로 문제가 없을 것이라고는 보장할 수는 없다.

참고:

쿠버네티스의 장치 관리자 컴포넌트는 안정화된(GA) 기능이지만 _장치 플러그인 API_는 안정화되지 않았다. 장치 플러그인 API와 버전 호환성에 대한 정보는 장치 플러그인 API 버전를 참고하라.

프로젝트로서, 쿠버네티스는 장치 플러그인 개발자에게 다음 사항을 권장한다.

  • 향후 릴리스에서 장치 플러그인 API의 변경 사항을 확인하자.
  • 이전/이후 버전과의 호환성을 위해 여러 버전의 장치 플러그인 API를 지원하자.

최신 장치 플러그인 API 버전의 쿠버네티스 릴리스로 업그레이드해야 하는 노드에서 장치 플러그인을 실행하기 위해서는, 해당 노드를 업그레이드하기 전에 두 버전을 모두 지원하도록 장치 플러그인을 업그레이드해야 한다. 이러한 방법은 업그레이드 중에 장치 할당이 지속적으로 동작할 것을 보장한다.

장치 플러그인 리소스 모니터링

기능 상태: Kubernetes v1.28 [stable]

장치 플러그인에서 제공하는 리소스를 모니터링하려면, 모니터링 에이전트가 노드에서 사용 중인 장치 셋을 검색하고 메트릭과 연관될 컨테이너를 설명하는 메타데이터를 얻을 수 있어야 한다. 장치 모니터링 에이전트에 의해 노출된 프로메테우스 지표는 쿠버네티스 Instrumentation 가이드라인을 따라 pod, namespacecontainer 프로메테우스 레이블을 사용하여 컨테이너를 식별해야 한다.

kubelet은 gRPC 서비스를 제공하여 사용 중인 장치를 검색하고, 이러한 장치에 대한 메타데이터를 제공한다.

// PodResourcesLister는 kubelet에서 제공하는 서비스로, 노드의 포드 및 컨테이너가
// 사용한 노드 리소스에 대한 정보를 제공한다.
service PodResourcesLister {
    rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
    rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
    rpc Get(GetPodResourcesRequest) returns (GetPodResourcesResponse) {}
}

List gRPC 엔드포인트

List 엔드포인트는 실행 중인 파드의 리소스에 대한 정보를 제공하며, 독점적으로 할당된 CPU의 ID, 장치 플러그인에 의해 보고된 장치 ID, 이러한 장치가 할당된 NUMA 노드의 ID와 같은 세부 정보를 함께 제공한다. 또한, NUMA 기반 머신의 경우, 컨테이너를 위해 예약된 메모리와 hugepage에 대한 정보를 포함한다.

쿠버네티스 v1.27부터, List 엔드포인트는 DynamicResourceAllocation API에 의해 ResourceClaims에 할당된, 실행 중인 파드의 리소스에 대한 정보를 제공할 수 있다. 쿠버네티스 v1.34부터, 이 기능은 기본적으로 활성화된다.

// ListPodResourcesResponse는 List 함수가 반환하는 응답이다.
message ListPodResourcesResponse {
    repeated PodResources pod_resources = 1;
}

// PodResources에는 파드에 할당된 노드 리소스에 대한 정보가 포함된다.
message PodResources {
    string name = 1;
    string namespace = 2;
    repeated ContainerResources containers = 3;
}

// ContainerResources는 컨테이너에 할당된 리소스에 대한 정보를 포함한다.
message ContainerResources {
    string name = 1;
    repeated ContainerDevices devices = 2;
    repeated int64 cpu_ids = 3;
    repeated ContainerMemory memory = 4;
    repeated DynamicResource dynamic_resources = 5;
}

// ContainerMemory는 컨테이너에 할당된 메모리와 hugepage에 대한 정보를 포함한다.
message ContainerMemory {
    string memory_type = 1;
    uint64 size = 2;
    TopologyInfo topology = 3;
}

// 토폴로지는 리소스의 하드웨어 토폴로지를 설명한다.
message TopologyInfo {
        repeated NUMANode nodes = 1;
}

// NUMA 노드의 NUMA 표현
message NUMANode {
        int64 ID = 1;
}

// ContainerDevices는 컨테이너에 할당된 장치에 대한 정보를 포함한다.
message ContainerDevices {
    string resource_name = 1;
    repeated string device_ids = 2;
    TopologyInfo topology = 3;
}

// DynamicResource는 동적 리소스 할당에 의해 컨테이너에 할당된 장치에 대한 정보를 포함한다.
message DynamicResource {
    string class_name = 1;
    string claim_name = 2;
    string claim_namespace = 3;
    repeated ClaimResource claim_resources = 4;
}

// ClaimResource는 플러그인별 리소스 정보를 포함한다.
message ClaimResource {
    repeated CDIDevice cdi_devices = 1 [(gogoproto.customname) = "CDIDevices"];
}

// CDIDevice는 CDI 장치 정보를 명시한다.
message CDIDevice {
    // 정규화된(fully qualified) CDI 장치 이름
    // 예: vendor.com/gpu=gpudevice1
    // 자세한 내용은 CDI 명세를 참조한다:
    // https://github.com/container-orchestrated-devices/container-device-interface/blob/main/SPEC.md
    string name = 1;
}

참고:

List 엔드포인트의 ContainerResources 내부에 있는 cpu_ids은 특정 컨테이너에 할당된 독점 CPU들에 해당한다. 만약 공유 풀(shared pool)에 있는 CPU들을 확인(evaluate)하는 것이 목적이라면, 해당 List 엔드포인트는 다음에 설명된 것과 같이, GetAllocatableResources 엔드포인트와 함께 사용되어야 한다.

  1. GetAllocatableResources를 호출하여 할당 가능한 모든 CPU 목록을 조회
  2. 시스템의 모든 ContainerResources에서 GetCpuIds를 호출
  3. GetAllocateableResources 호출에서 GetCpuIds 호출로 얻은 모든 CPU를 빼기

GetAllocatableResources gRPC 엔드포인트

기능 상태: Kubernetes v1.28 [stable]

GetAllocatableResources는 워커 노드에서 처음 사용할 수 있는 리소스에 대한 정보를 제공한다. kubelet이 APIServer로 내보내는 것보다 더 많은 정보를 제공한다.

참고:

GetAllocatableResources할당 가능(allocatable) 리소스를 확인(evaluate)하기 위해서만 사용해야 한다. 만약 목적이 free/unallocated 리소스를 확인하기 위한 것이라면 List() 엔드포인트와 함께 사용되어야 한다. GetAllocableResources로 얻은 결과는 kubelet에 노출된 기본 리소스가 변경되지 않는 한 동일하게 유지된다. 이러한 변경은 드물지만, 발생하게 된다면 (예를 들면: hotplug/hotunplug, 장치 상태 변경) 클라이언트가 GetAllocatableResources 엔드포인트를 호출할 것으로 가정한다.

그러나 CPU 및/또는 메모리가 갱신된 경우 GetAllocateableResources 엔드포인트를 호출하는 것만으로는 충분하지 않으며, kubelet을 다시 시작하여 올바른 리소스 용량과 할당 가능(allocatable) 리소스를 반영해야 한다.

// AllocatableResourcesResponses에는 kubelet이 알고 있는 모든 장치에 대한 정보가 포함된다.
message AllocatableResourcesResponse {
    repeated ContainerDevices devices = 1;
    repeated int64 cpu_ids = 2;
    repeated ContainerMemory memory = 3;
}

ContainerDevices 는 장치가 어떤 NUMA 셀과 연관되는지를 선언하는 토폴로지 정보를 노출한다. NUMA 셀은 불분명한(opaque) 정수 ID를 사용하여 식별되며, 이 값은 kubelet에 등록할 때 장치 플러그인이 보고하는 것과 일치한다.

gRPC 서비스는 kubelet의 루트 디렉터리 내의 pod-resources/kubelet.sock (일반적으로 /var/lib/kubelet/pod-resources/kubelet.sock)에서 유닉스 소켓을 통해 제공된다. 장치 플러그인 리소스에 대한 모니터링 에이전트는 데몬 또는 데몬셋으로 배포할 수 있다. kubelet 루트 디렉터리 내의 표준 디렉터리 pod-resources (일반적으로 /var/lib/kubelet/pod-resources)에는 특권을 가진 접근이 필요하므로, 모니터링 에이전트는 특권을 가진 보안 컨텍스트에서 실행해야 한다. 장치 모니터링 에이전트가 데몬셋으로 실행 중인 경우, 해당 장치 모니터링 에이전트의 PodSpec에서 pod-resources 디렉터리를 볼륨으로 마운트해야 한다.

참고:

데몬셋이나 호스트의 컨테이너로 배포된 다른 앱에서 소켓을 볼륨으로 마운트하여 pod-resources/kubelet.sock 에 접근할 때는, 소켓 파일 자체가 아닌 pod-resources 디렉터리를 마운트하는 것이 좋은 방법이다. 이렇게 하면 kubelet 재시작 후에도 컨테이너가 이 소켓에 다시 연결할 수 있다.

일반적인 리눅스 노드에서는, /var/lib/kubelet/pod-resources/kubelet.sock 대신 /var/lib/kubelet/pod-resources/ 를 마운트한다는 의미이다.

컨테이너 마운트는 마운트된 대상에 따라 소켓이나 디렉터리를 참조하는 inode에 의해 관리된다. kubelet이 재시작되면, 소켓은 삭제되고 새로운 소켓이 생성되며, 디렉터리는 그대로 유지된다. 따라서 소켓의 원래 inode는 사용할 수 없게 된다. 디렉터리에 대한 inode는 계속 동작한다.

Get gRPC 엔드포인트

기능 상태: Kubernetes v1.34 [beta]

Get 엔드포인트는 실행 중인 파드의 리소스에 대한 정보를 제공한다. List 엔드포인트에 설명된 것과 유사한 정보를 노출한다. Get 엔드포인트는 실행 중인 파드의 PodNamePodNamespace 를 필요로 한다.

// GetPodResourcesRequest는 파드에 대한 정보를 포함한다.
message GetPodResourcesRequest {
    string pod_name = 1;
    string pod_namespace = 2;
}

Get 엔드포인트는 동적 리소스 할당 API에 의해 할당된 동적 리소스와 관련된 파드 정보를 제공할 수 있다. 쿠버네티스 v1.34부터, 이 기능은 기본적으로 활성화된다.

토폴로지 관리자로 장치 플러그인 통합

기능 상태: Kubernetes v1.27 [stable]

토폴로지 관리자는 kubelet 컴포넌트로, 리소스를 토폴로지 정렬 방식으로 조정할 수 있다. 이를 위해, 장치 플러그인 API가 TopologyInfo 구조체를 포함하도록 확장되었다.

message TopologyInfo {
	repeated NUMANode nodes = 1;
}

message NUMANode {
    int64 ID = 1;
}

토폴로지 관리자를 활용하려는 장치 플러그인은 장치 ID 및 장치의 정상 상태와 함께 장치 등록의 일부로 채워진 TopologyInfo 구조체를 다시 보낼 수 있다. 그런 다음 장치 관리자는 이 정보를 사용하여 토폴로지 관리자와 상의하고 리소스 할당 결정을 내린다.

TopologyInfonodes 필드에 nil(기본값) 또는 NUMA 노드 목록을 설정하는 것을 지원한다. 이를 통해 복수의 NUMA 노드에 연관된 장치에 대해 장치 플러그인을 설정할 수 있다.

특정 장치에 대해 TopologyInfonil로 설정하거나 비어있는 NUMA 노드 목록을 제공하는 것은 장치 플러그인이 해당 장치에 대한 NUMA 선호도(affinity)를 지니지 못함을 시사한다.

장치 플러그인으로 장치에 대해 채워진 TopologyInfo 구조체의 예는 다음과 같다.

pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}

장치 플러그인 예시

참고: 이 섹션은 쿠버네티스에 필요한 기능을 제공하는 써드파티 프로젝트와 관련이 있다. 쿠버네티스 프로젝트 작성자는 써드파티 프로젝트에 책임이 없다. 이 페이지는 CNCF 웹사이트 가이드라인에 따라 프로젝트를 알파벳 순으로 나열한다. 이 목록에 프로젝트를 추가하려면 변경사항을 제출하기 전에 콘텐츠 가이드를 읽어본다.

다음은 장치 플러그인 구현의 예이다.

다음 내용


이 페이지는 쿠버네티스가 필요로 하는 기능을 제공하는 써드파티 프로젝트 또는 제품에 대해 언급하고 있습니다. 쿠버네티스 프로젝트 저자들은 이러한 써드파티 프로젝트 또는 제품에 대해 책임지지 않습니다. CNCF 웹사이트 가이드라인에서 더 자세한 내용을 확인합니다.

다른 써드파티 링크를 추가하는 변경을 제안하기 전에, 컨텐츠 가이드를 확인해야 합니다.