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

  1. Test reachability of the host: `nc -vz <host> 22` or `ssh -v <user>@<host>`; fix security group/firewall rules to permit SSH.
  2. Confirm the node IP is current (re-query cloud API / cluster state); stale IPs after node replacement are the most common cause.
  3. Check SSH credentials: correct private key for the cluster and correct username for the image (ubuntu/admin/ec2-user).
  4. If useBastion=true, ensure a bastion instance exists and is reachable, otherwise dial the node directly via its public address.
  5. 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

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


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