From 89e307e5847befce2894a295c9479d8101078b6c Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 3 Jun 2026 20:03:31 +0400 Subject: [PATCH] fix: etcd client leak in the (legacy) Upgrade API This doesn't affect new upgrade API. There were two leaks in the validate path, usually they are harmless, as the Upgrade triggers a reboot, so a couple of leaking clients doesn't matter, but if they API is called repeatedly (and it fails to upgrade), the leak accumulates until `etcd` container runs out of file descriptor, eventually preventing new connections to `etcd` from being established. Also put the etcd pre-checks under the `!preserve` condition (means the API requests wipe). The wipe behavior was disabled by default for a long time, and all etcd pre-checks only make sense if the wipe is called, otherwise upgrade doesn't affect etcd membership in any way. Signed-off-by: Andrey Smirnov --- .../app/machined/internal/server/v1alpha1/v1alpha1_server.go | 4 +++- internal/pkg/etcd/etcd.go | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go index f5099106522..98c5576fb0c 100644 --- a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go +++ b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go @@ -531,12 +531,14 @@ func (s *Server) Upgrade(ctx context.Context, in *machine.UpgradeRequest) (*mach return nil, fmt.Errorf("error validating installer image %q: %w", in.GetImage(), err) } - if !inMaintenance && s.Controller.Runtime().Config().Machine().Type() != machinetype.TypeWorker && !in.GetForce() { + if !inMaintenance && s.Controller.Runtime().Config().Machine().Type() != machinetype.TypeWorker && !in.GetForce() && !in.GetPreserve() { etcdClient, err := etcd.NewClientFromControlPlaneIPs(ctx, s.Controller.Runtime().State().V1Alpha2().Resources()) if err != nil { return nil, fmt.Errorf("failed to create etcd client: %w", err) } + defer etcdClient.Close() //nolint:errcheck + // acquire the upgrade mutex unlocker, err := tryLockUpgradeMutex(ctx, etcdClient) if err != nil { diff --git a/internal/pkg/etcd/etcd.go b/internal/pkg/etcd/etcd.go index 43dc079b392..0026d9c9734 100644 --- a/internal/pkg/etcd/etcd.go +++ b/internal/pkg/etcd/etcd.go @@ -145,6 +145,8 @@ func validateMemberHealth(ctx context.Context, memberURIs []string) (err error) return fmt.Errorf("failed to create client to member: %w", err) } + defer c.Close() //nolint:errcheck + return c.ValidateQuorum(ctx) }