{"record":{"id":"ae14e2093ec60b61","repo":"k3s-io/k3s","slug":"no-peer-addresses-available","errorCode":null,"errorMessage":"no peer addresses available","messagePattern":"no peer addresses available","errorType":"http","errorClass":null,"httpStatus":503,"severity":"warning","filePath":"pkg/spegel/spegel.go","lineNumber":339,"sourceCode":"\n// peerInfo sends a peer address retrieved from the bootstrapper via HTTP\nfunc (c *Config) peerInfo() http.HandlerFunc {\n\treturn http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {\n\t\tinfo, err := c.Bootstrapper.Get(req.Context())\n\t\tif err != nil {\n\t\t\thttp.Error(resp, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\taddrs := []string{}\n\t\tfor _, ai := range info {\n\t\t\tfor _, ma := range ai.Addrs {\n\t\t\t\taddrs = append(addrs, fmt.Sprintf(\"%s/p2p/%s\", ma, ai.ID))\n\t\t\t}\n\t\t}\n\n\t\tif len(addrs) == 0 {\n\t\t\thttp.Error(resp, \"no peer addresses available\", http.StatusServiceUnavailable)\n\t\t\treturn\n\t\t}\n\n\t\tclient, _, _ := net.SplitHostPort(req.RemoteAddr)\n\t\tif req.Header.Get(\"Accept\") == \"application/json\" {\n\t\t\tb, err := json.Marshal(addrs)\n\t\t\tif err != nil {\n\t\t\t\thttp.Error(resp, err.Error(), http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogrus.Debugf(\"Serving p2p peer addrs %v to client at %s\", addrs, client)\n\t\t\tresp.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\tresp.WriteHeader(http.StatusOK)\n\t\t\tresp.Write(b)\n\t\t\treturn\n\t\t}\n\n\t\tlogrus.Debugf(\"Serving p2p peer addr %v to client at %s\", addrs[0], client)","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/k3s-io/k3s/blob/6ba341e396edc16b8dcae978a7c5e3ac7ee5606e/pkg/spegel/spegel.go#L321-L357","documentation":"k3s embedded spegel (pkg/spegel/spegel.go, peerInfo handler): the internal HTTP endpoint other nodes contact for p2p registry peers queries the bootstrapper (Bootstrapper.Get) for peer info and formats each peer's multiaddresses as '/p2p/<id>' entries. If peers were returned but none expose any addresses, addrs stays empty and the handler answers HTTP 503 with 'no peer addresses available'. It is a transient availability signal, not a crash: clients retry and succeed once peers register routable addresses.","triggerScenarios":"A node's registry mirror requesting peer addresses right at cluster bootstrap before any spegel peers have registered with the bootstrapper (or before headless-service-based discovery resolves); peers present in the list but with empty Addrs because their overlay/flannel addresses are not yet assigned; effectively single-peer clusters where the only peer is the caller itself.","commonSituations":"Fresh k3s clusters pulling images in the first minutes after boot; nodes joining while CNI/flannel is still converging so peer multiaddresses are missing; clusters where most nodes are drained or cordoned; spegel disabled-by-detection on tiny clusters leaving no peers to serve.","solutions":["Treat it as transient: wait for peer bootstrap (seconds to a couple of minutes) and let the mirror client retry; images then pull from peers or fall back to the upstream registry","Verify peer discovery: check that other nodes' spegel state is healthy and that the discovery backend (headless service / bootstrap list) resolves them with addresses","Confirm CNI/flannel is up on all nodes so peers advertise routable multiaddresses","On clusters where p2p mirroring is not wanted, disable spegel (unsetting the embedded registry mirror envs) so the endpoint is not consulted"],"exampleFix":"// before\nresp, err := http.Get(peerInfoURL)\nif err != nil {\n    return err\n}\naddrs := parsePeers(resp)\n\n// after (client side: honor 503 with backoff until peers register)\nvar addrs []string\nerr := wait.PollUntilContextTimeout(ctx, 2*time.Second, 2*time.Minute, true,\n    func(ctx context.Context) (bool, error) {\n        resp, err := http.Get(peerInfoURL)\n        if err != nil {\n            return false, nil\n        }\n        defer resp.Body.Close()\n        if resp.StatusCode == http.StatusServiceUnavailable {\n            return false, nil // no peer addresses available yet\n        }\n        addrs = parsePeers(resp)\n        return len(addrs) > 0, nil\n    })\nif err != nil {\n    return fmt.Errorf(\"no peer addresses available after retry window: %w\", err)\n}","handlingStrategy":"retry","validationCode":"// caller-side probe: only use the p2p mirror once the peer endpoint serves addresses\nresp, err := http.Head(peerInfoURL)\nif err == nil && resp.StatusCode == http.StatusOK {\n    // peers available; safe to configure the registry mirror with p2p endpoints\n}","typeGuard":null,"tryCatchPattern":"// 503 means 'not yet'; retry with backoff instead of failing the pull\nfor attempt := 0; attempt < 5; attempt++ {\n    resp, err := http.Get(peerInfoURL)\n    if err == nil {\n        defer resp.Body.Close()\n        if resp.StatusCode == http.StatusOK {\n            return parsePeers(resp)\n        }\n        if resp.StatusCode != http.StatusServiceUnavailable {\n            return nil, fmt.Errorf(\"peer info endpoint: %s\", resp.Status)\n        }\n    }\n    time.Sleep(time.Duration(attempt+1) * 2 * time.Second)\n}\nreturn nil, errors.New(\"no peer addresses available after retries\")","preventionTips":["Expect a short warm-up window on fresh clusters; script image pulls to tolerate initial 503s from the peer endpoint","Keep CNI/flannel healthy so peers always advertise routable multiaddresses","If running very small clusters where p2p mirroring adds no value, disable the embedded spegel mirror","Alert on sustained 503s (minutes, not seconds) as a discovery/CNI problem rather than normal bootstrap"],"tags":["spegel","p2p","registry-mirror","networking","k3s"],"backgroundTag":null,"analyzedSha":"6ba341e396edc16b8dcae978a7c5e3ac7ee5606e","analyzedAt":"2026-08-15T16:27:54.286Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}