pkg/gkedisk/gkedisk.go
Rama main · réplica de lectura del repositorio original.
// Package gkedisk knows which Compute Engine disk types a GKE node's machine
// series can attach. Newer machine series attach only Hyperdisk, so a
// Persistent Disk (pd-*) volume scheduled onto one of their nodes never
// attaches: the CSI driver retries forever and the kubelet's later events
// carry only a backoff echo of the original Compute Engine error.
package gkedisk
import (
"fmt"
"regexp"
"strings"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
)
const (
CSIDriver = "pd.csi.storage.gke.io"
InTreeProvision = "kubernetes.io/gce-pd"
InstanceTypeLabel = "node.kubernetes.io/instance-type"
provisionedByAnnotation = "pv.kubernetes.io/provisioned-by"
// Both provisioners default an unset "type" parameter to pd-standard.
defaultDiskType = "pd-standard"
)
// hyperdiskOnlySeries lists machine series that support none of pd-standard,
// pd-balanced, pd-ssd or pd-extreme, per the Compute Engine disk-support
// matrix (https://cloud.google.com/compute/docs/disks/persistent-disks).
// Series with partial PD support (C3, C3D, H3) are deliberately absent: a
// missing series is never flagged, so the table can only under-report.
var hyperdiskOnlySeries = map[string]bool{
"a4": true, "a4x": true,
"c4": true, "c4a": true, "c4d": true, "c4n": true,
"g4": true,
"h4d": true,
"m4": true, "m4n": true,
"n4": true, "n4a": true, "n4d": true,
"x4": true, "x5": true,
"z4d": true,
}
// MachineSeries returns the series of a Compute Engine machine type:
// "n4d-standard-4" → "n4d", "n4-custom-4-8192" → "n4".
func MachineSeries(machineType string) string {
series, _, _ := strings.Cut(strings.ToLower(strings.TrimSpace(machineType)), "-")
return series
}
// HyperdiskOnly reports whether machineType belongs to a series that cannot
// attach any Persistent Disk type.
func HyperdiskOnly(machineType string) bool {
return hyperdiskOnlySeries[MachineSeries(machineType)]
}
// IsPersistentDisk reports whether a disk type is a Persistent Disk type
// (pd-standard, pd-balanced, pd-ssd, pd-extreme) rather than Hyperdisk.
func IsPersistentDisk(diskType string) bool {
return strings.HasPrefix(strings.ToLower(diskType), "pd-")
}
var incompatibleAttachRE = regexp.MustCompile(`([a-z0-9-]+) disk type cannot be used by ([a-z0-9-]+) machine type`)
// ParseIncompatibleAttach extracts the disk and machine type from Compute
// Engine's attach rejection, e.g. "googleapi: Error 400: pd-ssd disk type
// cannot be used by n4d-standard-4 machine type., badRequest".
func ParseIncompatibleAttach(message string) (diskType, machineType string, ok bool) {
m := incompatibleAttachRE.FindStringSubmatch(strings.ToLower(message))
if m == nil {
return "", "", false
}
return m[1], m[2], true
}
// IsAttachBackoffEcho reports whether an attach-failure message is the CSI
// external-attacher's retry notice ("… due to backoff condition"), which
// repeats until the next real attempt and carries no cause of its own.
func IsAttachBackoffEcho(message string) bool {
return strings.Contains(strings.ToLower(message), "backoff condition")
}
// DiskType returns the Compute Engine disk type a PersistentVolume was
// provisioned as. ok is false when the PV isn't a dynamically provisioned GCE
// Persistent Disk or Hyperdisk volume, or its StorageClass is unknown.
func DiskType(pv *corev1.PersistentVolume, storageClass func(name string) *storagev1.StorageClass) (string, bool) {
if pv == nil {
return "", false
}
switch {
case pv.Spec.CSI != nil && pv.Spec.CSI.Driver == CSIDriver:
case pv.Spec.GCEPersistentDisk != nil:
default:
return "", false
}
// A StorageClass's "type" describes disks it provisions. A pre-existing
// disk can be bound under any class name, so without provisioning
// provenance the class says nothing about the disk.
if p := pv.Annotations[provisionedByAnnotation]; p != CSIDriver && p != InTreeProvision {
return "", false
}
if pv.Spec.StorageClassName == "" || storageClass == nil {
return "", false
}
sc := storageClass(pv.Spec.StorageClassName)
if sc == nil || (sc.Provisioner != CSIDriver && sc.Provisioner != InTreeProvision) {
return "", false
}
for key, value := range sc.Parameters {
if strings.EqualFold(key, "type") {
if t := strings.TrimSpace(value); t != "" {
return strings.ToLower(t), true
}
}
}
return defaultDiskType, true
}
// IncompatibilityCause explains why a disk type can't attach to a machine type.
func IncompatibilityCause(diskType, machineType string) string {
if IsPersistentDisk(diskType) && HyperdiskOnly(machineType) {
return fmt.Sprintf("%s volumes can't attach to %s nodes: the %s machine series supports only Hyperdisk.",
diskType, machineType, strings.ToUpper(MachineSeries(machineType)))
}
return fmt.Sprintf("Compute Engine doesn't allow %s disks on %s machines, so the volume never attaches.", diskType, machineType)
}
// IncompatibilityAction names the two fixes for an incompatible attach.
func IncompatibilityAction(diskType string) string {
pin := fmt.Sprintf("Pin the workload to a node pool whose machine type supports %s (a nodeSelector or required node affinity on %s or cloud.google.com/machine-family)",
diskType, InstanceTypeLabel)
if IsPersistentDisk(diskType) {
return pin + ", or move the data to a Hyperdisk StorageClass whose type every node pool the workload can run on supports."
}
return pin + ", or move the data to a StorageClass whose disk type every node pool the workload can run on supports."
}