kubernetes/kops · error

expecting exactly 1 SSH key for %q, found %d: %+v

Error message

expecting exactly 1 SSH key for %q, found %d: %+v

What it means

kOps models exactly one SSH public key per VMSS (the cluster's sshPublicKey). Find() enforces len(publicKeys)==1; if Azure reports 0 or multiple keys on the scale set, reconciliation cannot map them back to the single kops-managed key and this error names the scale set plus the keys found.

Source

Thrown at upup/pkg/fi/cloudup/azuretasks/vmscaleset.go:179

			if err != nil {
				return nil, fmt.Errorf("failed to parse loadbalancer ID %s", *i.ID)
			}
		}
	}

	osProfile := profile.OSProfile
	if osProfile.LinuxConfiguration == nil {
		return nil, fmt.Errorf("found VMSS without Linux config")
	}
	if osProfile.LinuxConfiguration.SSH == nil {
		return nil, fmt.Errorf("found VMSS without SSH config")
	}
	if osProfile.LinuxConfiguration.SSH.PublicKeys == nil {
		return nil, fmt.Errorf("found VMSS without SSH public keys")
	}
	sshKeys := osProfile.LinuxConfiguration.SSH.PublicKeys
	if len(sshKeys) != 1 {
		return nil, fmt.Errorf("expecting exactly 1 SSH key for %q, found %d: %+v", *s.Name, len(sshKeys), sshKeys)
	}

	var userData []byte
	if profile.UserData != nil {
		userData, err = base64.StdEncoding.DecodeString(*profile.UserData)
		if err != nil {
			return nil, fmt.Errorf("failed to decode user data: %w", err)
		}
	}

	vmss := &VMScaleSet{
		Name:      s.Name,
		Lifecycle: s.Lifecycle,
		ResourceGroup: &ResourceGroup{
			Name: s.ResourceGroup.Name,
		},
		VirtualNetwork: &VirtualNetwork{
			Name: to.Ptr(subnetID.VirtualNetworkName),

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Remove extra keys so exactly one remains, or better: set the desired key in the cluster spec (`kops edit cluster` sshPublicKey / `kops replace`) and run `kops update cluster --yes` then `kops rolling-update cluster`.
  2. For temporary access, use Azure Bastion/just-in-time access instead of adding VMSS-level keys.
  3. If a rotation left both old and new keys, roll to the new key and delete the old VMSS instances.
  4. Align test fixtures to a single key.

Example fix

// before
"publicKeys": [ {"keyData": "ssh-rsa OLD"}, {"keyData": "ssh-rsa NEW"} ]
// after
"publicKeys": [ {"keyData": "ssh-rsa NEW"} ]
Defensive patterns

Strategy: validation

Validate before calling

keys := vmss.Properties.VirtualMachineProfile.OSProfile.LinuxConfiguration.SSH.PublicKeys
if len(keys) != 1 {
  return fmt.Errorf("VMSS %s must have exactly 1 SSH key, found %d", fi.ValueOf(vmss.Name), len(keys))
}

Type guard

func hasExactlyOneKey(lc *compute.VirtualMachineScaleSetLinuxConfiguration) bool {
  return lc != nil && lc.SSH != nil && len(lc.SSH.PublicKeys) == 1
}

Prevention

When it happens

Trigger: Find() on a VMSS whose linuxConfiguration.ssh.publicKeys has length != 1 — typically 2+ keys after someone appended extra keys manually, or 0 handled by the preceding nil/empty cases.

Common situations: Manual key additions to nodes via portal/CLI; Bastion or JIT tools injecting extra keys; key rotation done directly on the scale set instead of through kops; multi-key cluster specs edited by hand.

Related errors


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