{"record":{"id":"e463a760252a3562","repo":"kubernetes/kops","slug":"failed-to-get-metadata-url-s-v","errorCode":null,"errorMessage":"failed to get metadata URL %s: %v","messagePattern":"failed to get metadata URL (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nodeidentity/do/identify.go","lineNumber":101,"sourceCode":"\treturn getMetadata(dropletRegionMetadataURL)\n}\n\n// NewCloud returns a godo client, expecting the env var DIGITALOCEAN_ACCESS_TOKEN to be set.\nfunc NewCloud(region string) (*godo.Client, error) {\n\taccessToken := os.Getenv(\"DIGITALOCEAN_ACCESS_TOKEN\")\n\tif accessToken == \"\" {\n\t\treturn nil, errors.New(\"DIGITALOCEAN_ACCESS_TOKEN is required\")\n\t}\n\n\ttokenSource := &TokenSource{AccessToken: accessToken}\n\toauthClient := oauth2.NewClient(context.TODO(), tokenSource)\n\treturn godo.NewClient(oauthClient), nil\n}\n\nfunc getMetadata(url string) (string, error) {\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get metadata URL %s: %v\", url, err)\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"droplet metadata returned non-200 status code: %d\", resp.StatusCode)\n\t}\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read metadata information %s: %v\", url, err)\n\t}\n\n\treturn string(bodyBytes), nil\n}\n\n// IdentifyNode queries DigitalOcean for the node identity information.\nfunc (i *nodeIdentifier) IdentifyNode(ctx context.Context, node *corev1.Node) (*nodeidentity.Info, error) {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/nodeidentity/do/identify.go#L83-L119","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm you are on a Droplet and curl the exact URL from the error message; if that fails, fix network access first.","Allow egress to 169.254.169.254 in host/container firewall rules.","Use host networking for containers (docker --net=host / hostNetwork: true in k8s).","Check the DO status page for metadata service incidents and add a short retry with backoff for transient failures."],"exampleFix":"// before: containerized agent with bridge networking\ndocker run myorg/kops-do-agent\n\n// after: host network so link-local metadata is reachable\ndocker run --net=host myorg/kops-do-agent","handlingStrategy":"retry","validationCode":"url := \"http://169.254.169.254/metadata/v1/region\"\nif err := wait.PollImmediate(2*time.Second, 10*time.Second, func() (bool, error) {\n    resp, err := http.Get(url)\n    if err != nil {\n        return false, nil\n    }\n    resp.Body.Close()\n    return resp.StatusCode == http.StatusOK, nil\n}); err != nil {\n    return fmt.Errorf(\"metadata endpoint %s unreachable\", url)\n}","typeGuard":null,"tryCatchPattern":"val, err := getMetadata(url)\nif err != nil && strings.Contains(err.Error(), \"failed to get metadata URL\") {\n    return retryWithBackoff(3, func() error {\n        val, err = getMetadata(url)\n        return err\n    })\n}","preventionTips":["Verify Droplet metadata reachability at startup with a lightweight probe","Use host networking for anything needing 169.254.169.254","Add short retries with backoff for transient metadata failures","Check DO status page / monitoring for metadata service incidents"],"tags":["digitalocean","metadata-service","http-request","network","kops"],"backgroundTag":"metadata-service-unreachable","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}