kubernetes/kops · error
unable to SSH to %q: %v
Error message
unable to SSH to %q: %v
What it means
Returned by logDumper.connectToNode (pkg/dump/dumper.go:360) when sshClientFactory.Dial fails to establish an SSH connection to the given host. It names the target host and embeds the low-level dial error (Dialer, x/crypto/ssh handshake, or the test double). It is subsequently wrapped by 'connecting: %w' in dumpNode, which swallows it into a klog warning path — this error means no logs could be collected for that node at all.
Source
Thrown at pkg/dump/dumper.go:360
// HasBastion returns true if the sshClientFactory has a bastion configured.
// Calling Dial with useBastion=true will return an error if there is no bastion.
HasBastion() bool
}
// logDumperNode holds state for a particular node we are dumping
type logDumperNode struct {
client sshClient
dumper *logDumper
dir string
}
// connectToNode makes an SSH connection to the node and returns a logDumperNode
func (d *logDumper) connectToNode(ctx context.Context, nodeName string, host string, useBastion bool) (*logDumperNode, error) {
client, err := d.sshClientFactory.Dial(ctx, host, useBastion)
if err != nil {
return nil, fmt.Errorf("unable to SSH to %q: %v", host, err)
}
return &logDumperNode{
client: client,
dir: filepath.Join(d.artifactsDir, nodeName),
dumper: d,
}, nil
}
// logDumperNode cleans up any state in the logDumperNode
func (n *logDumperNode) Close() error {
return n.client.Close()
}
// dump captures the well-known set of logs
func (n *logDumperNode) dump(ctx context.Context) []error {
if ctx.Err() != nil {
return []error{ctx.Err()}
}View on GitHub (pinned to 4c8573c808)
Solutions
- Test reachability of the host: `nc -vz <host> 22` or `ssh -v <user>@<host>`; fix security group/firewall rules to permit SSH.
- Confirm the node IP is current (re-query cloud API / cluster state); stale IPs after node replacement are the most common cause.
- Check SSH credentials: correct private key for the cluster and correct username for the image (ubuntu/admin/ec2-user).
- If useBastion=true, ensure a bastion instance exists and is reachable, otherwise dial the node directly via its public address.
- Retry after transient network issues; ensure the per-node timeout (nodeDumpTimeout) is not expiring mid-dial.
Example fix
// before
client, err := d.sshClientFactory.Dial(ctx, host, useBastion)
if err != nil {
return nil, fmt.Errorf("unable to SSH to %q: %v", host, err)
}
// after (caller fallback: skip bastion when none configured)
useBastion := useBastion && d.sshClientFactory.HasBastion()
client, err := d.sshClientFactory.Dial(ctx, host, useBastion)
if err != nil {
return nil, fmt.Errorf("unable to SSH to %q: %w", host, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate reachability and credentials before Dial
if err := exec.Command("ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", user+"@"+host, "true").Run(); err != nil {
return fmt.Errorf("SSH to %s pre-check failed: %w", host, err)
} Try / catch
client, err := d.sshClientFactory.Dial(ctx, host, useBastion)
if err != nil {
var netErr net.Error
switch {
case errors.As(err, &netErr) && netErr.Timeout():
klog.Warningf("SSH to %q timed out; check SG/firewall on port 22", host)
case errors.Is(err, context.DeadlineExceeded):
klog.Warningf("SSH to %q hit dump timeout", host)
default:
klog.Warningf("SSH to %q refused/auth failed: %v", host, err)
}
return nil, err
} Prevention
- Test `ssh -o BatchMode=yes user@host true` before running dumps to catch auth/key issues early.
- Refresh node IPs from the cloud API; never reuse IPs cached from previous runs.
- Configure security groups to allow SSH from the dump host or bastion.
- Check HasBastion() before dialing with useBastion=true.
When it happens
Trigger: d.sshClientFactory.Dial(ctx, host, useBastion) fails: TCP connect refused/timeout to the node IP, SSH authentication rejected (bad key/user), SSH version handshake failure, or Dial called with useBastion=true when no bastion is configured (HasBastion()==false).
Common situations: Node terminated/replaced so the cached IP no longer exists; security group or network ACL blocks port 22; dumping private nodes without a bastion; cluster SSH key deleted or ~/.ssh key not matching; newer node images changed the default SSH username.
Related errors
- failed to SSH to %q (with user %q): %w
- connecting: %w
- reading kops-channels manifest %s: %w
- error trying to locate asset %q: %v
- error downloading file %q: %v
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/0e9ec723e68db96b.
Report an issue: GitHub.