kubernetes/kops · error

image has no root device name

Error message

image %q has no root device name

What it means

The AMI resolved fine but its RootDeviceName field was empty, so kops cannot generate a root-volume block device mapping for the Karpenter EC2NodeClass. The root device name is required to override the image's root volume instead of attaching an extra volume.

Solutions

  1. Verify the AMI's block device mappings with `aws ec2 describe-images` — rootDeviceName should be set
  2. Choose a different/standard AMI that declares a root device
  3. Re-run kops update cluster in case of a transient partial API response
  4. If it is a custom AMI, rebuild it with a proper root EBS mapping
Defensive patterns

Strategy: validation

Validate before calling

// check rootDeviceName before rendering
img, err := cloud.ResolveImage(image)
if err == nil && img != nil && fi.ValueOf(img.RootDeviceName) == "" {
	return fmt.Errorf("AMI %s has no root device mapping", image)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "has no root device name") {
	return fmt.Errorf("choose an AMI with a defined root device: %w", err)
}

Prevention

When it happens

Trigger: cloud.ResolveImage returns a non-nil ec2.Image whose RootDeviceName is unset — typically an anomalous/incomplete DescribeImages response or an image with no block device mappings.

Common situations: Rare: corrupted/edge-case AMI metadata, custom-built images lacking rootDeviceName in their block device mappings, or AWS API returning a partial image record.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/4a6913d20146e778. Report an issue: GitHub.

Appendix: source

Thrown at upup/pkg/fi/cloudup/template_functions_karpenter.go:399

// karpenterRootDeviceName resolves the root device name of the InstanceGroup image, so
// that the generated block device mapping overrides the image's root volume rather than
// attaching an additional one. The name varies between images (/dev/xvda, /dev/sda1),
// so it has to come from the image itself.
func (tf *TemplateFunctions) karpenterRootDeviceName(image string) (string, error) {
	cloud, ok := tf.cloud.(awsup.AWSCloud)
	if !ok {
		return "", fmt.Errorf("expected an AWS cloud, got %T", tf.cloud)
	}
	resolved, err := cloud.ResolveImage(image)
	if err != nil {
		return "", fmt.Errorf("unable to resolve image %q: %w", image, err)
	}
	if resolved == nil {
		return "", fmt.Errorf("unable to resolve image %q: not found", image)
	}
	rootDeviceName := fi.ValueOf(resolved.RootDeviceName)
	if rootDeviceName == "" {
		return "", fmt.Errorf("image %q has no root device name", image)
	}
	return rootDeviceName, nil
}

func (tf *TemplateFunctions) buildKarpenterNodePool(ig *kops.InstanceGroup) (*karpenterNodePool, error) {
	labels, err := nodelabels.BuildNodeLabels(tf.Cluster, ig)
	if err != nil {
		return nil, fmt.Errorf("building node labels for %q: %w", ig.Name, err)
	}
	labels = karpenterNodePoolTemplateLabels(labels)

	template := karpenterNodeClaimTemplate{
		Spec: karpenterNodeClaimSpec{
			Requirements: tf.karpenterRequirements(ig),
			NodeClassRef: karpenterNodeClassRef{
				Group: karpenterAWSAPIGroup,
				Kind:  "EC2NodeClass",
				Name:  ig.Name,

View on GitHub (pinned to 4c8573c808)