kubernetes/kops · error

failed to get metadata URL %s: %v

Error message

failed to get metadata URL %s: %v

What it means

getMetadata performs a plain http.Get against the DigitalOcean Droplet metadata endpoint and returns the body. A transport-level failure (connection refused, timeout, no route to the link-local address, DNS) is wrapped as "failed to get metadata URL %s: %v". Non-200 responses produce a separate status-code error, so this one always means the request never completed.

Source

Thrown at pkg/nodeidentity/do/identify.go:101

	return getMetadata(dropletRegionMetadataURL)
}

// NewCloud returns a godo client, expecting the env var DIGITALOCEAN_ACCESS_TOKEN to be set.
func NewCloud(region string) (*godo.Client, error) {
	accessToken := os.Getenv("DIGITALOCEAN_ACCESS_TOKEN")
	if accessToken == "" {
		return nil, errors.New("DIGITALOCEAN_ACCESS_TOKEN is required")
	}

	tokenSource := &TokenSource{AccessToken: accessToken}
	oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
	return godo.NewClient(oauthClient), nil
}

func getMetadata(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", fmt.Errorf("failed to get metadata URL %s: %v", url, err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("droplet metadata returned non-200 status code: %d", resp.StatusCode)
	}

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("failed to read metadata information %s: %v", url, err)
	}

	return string(bodyBytes), nil
}

// IdentifyNode queries DigitalOcean for the node identity information.
func (i *nodeIdentifier) IdentifyNode(ctx context.Context, node *corev1.Node) (*nodeidentity.Info, error) {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Confirm you are on a Droplet and curl the exact URL from the error message; if that fails, fix network access first.
  2. Allow egress to 169.254.169.254 in host/container firewall rules.
  3. Use host networking for containers (docker --net=host / hostNetwork: true in k8s).
  4. Check the DO status page for metadata service incidents and add a short retry with backoff for transient failures.

Example fix

// before: containerized agent with bridge networking
docker run myorg/kops-do-agent

// after: host network so link-local metadata is reachable
docker run --net=host myorg/kops-do-agent
Defensive patterns

Strategy: retry

Validate before calling

url := "http://169.254.169.254/metadata/v1/region"
if err := wait.PollImmediate(2*time.Second, 10*time.Second, func() (bool, error) {
    resp, err := http.Get(url)
    if err != nil {
        return false, nil
    }
    resp.Body.Close()
    return resp.StatusCode == http.StatusOK, nil
}); err != nil {
    return fmt.Errorf("metadata endpoint %s unreachable", url)
}

Try / catch

val, err := getMetadata(url)
if err != nil && strings.Contains(err.Error(), "failed to get metadata URL") {
    return retryWithBackoff(3, func() error {
        val, err = getMetadata(url)
        return err
    })
}

Prevention

When it happens

Trigger: http.Get to http://169.254.169.254/metadata/v1/... fails: not running on a DO Droplet, firewall blocks the link-local metadata address, no route on the interface, or request times out due to metadata service outage.

Common situations: Developer tooling executed off-Droplet; containers without host networking; overly strict nftables/iptables OUTPUT rules; metadata service degraded during DO incidents; VPN or network namespace intercepting 169.254.169.254.

Related errors


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