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
- 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`.
- For temporary access, use Azure Bastion/just-in-time access instead of adding VMSS-level keys.
- If a rotation left both old and new keys, roll to the new key and delete the old VMSS instances.
- 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
- Use Bastion/JIT for ad-hoc access instead of adding VMSS keys
- Rotate keys only through kops cluster spec
- Audit VMSS osProfile for drift before kops operations
- Keep test fixtures to a single key
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
- found VMSS without Linux config
- found VMSS without SSH config
- found VMSS without SSH public keys
- expected exactly one subnet for InstanceGroup %q; subnets wa
- unexpected subnet type: for InstanceGroup %q; type was %s
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/a518c28b778ad305.
Report an issue: GitHub.