k3s/pkg/cluster/managed.go
Brad Davidson d8790220ff
Some checks are pending
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Move node password secrets into dedicated controller
Move the node password secret cleanup into its own dedicated controller
that also handles auth. We now use a filtered cache of only
node-password secrets, instead of using the wrangler secret cache,
which stores all secrets from all namespaces.

The coredns node-hosts controller also now uses a single-resource
watch cache on the coredns configmap, instead of reading it from
the apiserver every time a node changes.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2025-10-27 15:06:45 -07:00

158 lines
5.5 KiB
Go

package cluster
// A managed database is one whose lifecycle we control - initializing the cluster, adding/removing members, taking snapshots, etc.
// This is currently just used for the embedded etcd datastore. Kine and other external etcd clusters are NOT considered managed.
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"sync"
"time"
"github.com/gorilla/mux"
"github.com/k3s-io/k3s/pkg/cluster/managed"
"github.com/k3s-io/k3s/pkg/etcd"
"github.com/k3s-io/k3s/pkg/nodepassword"
"github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version"
"github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/wait"
)
// start starts the database, unless a cluster reset has been requested, in which case
// it does that instead.
func (c *Cluster) start(ctx context.Context, wg *sync.WaitGroup) error {
if c.managedDB == nil {
return nil
}
rebootstrap := func() error {
return c.storageBootstrap(ctx)
}
resetDone, err := c.managedDB.IsReset()
if err != nil {
return err
}
if c.config.ClusterReset {
// If we're restoring from a snapshot, don't check the reset-flag - just reset and restore.
if c.config.ClusterResetRestorePath != "" {
return c.managedDB.Reset(ctx, wg, rebootstrap)
}
// If the reset-flag doesn't exist, reset. This will create the reset-flag if it succeeds.
if !resetDone {
return c.managedDB.Reset(ctx, wg, rebootstrap)
}
// The reset-flag exists, ask the user to remove it if they want to reset again.
return fmt.Errorf("Managed etcd cluster membership was previously reset, please remove the cluster-reset flag and start %s normally. "+
"If you need to perform another cluster reset, you must first manually delete the file at %s", version.Program, c.managedDB.ResetFile())
}
if resetDone {
// If the cluster was reset, we need to delete the node passwd secret in case the node
// password from the previously restored snapshot differs from the current password on disk.
c.config.Runtime.ClusterControllerStarts["node-password-secret-cleanup"] = c.deleteNodePasswdSecret
}
// Starting the managed database will clear the reset-flag if set
return c.managedDB.Start(ctx, wg, c.clientAccessInfo)
}
// registerDBHandlers registers managed-datastore-specific callbacks, and installs additional HTTP route handlers.
// Note that for etcd, controllers only run on nodes with a local apiserver, in order to provide stable external
// management of etcd cluster membership without being disrupted when a member is removed from the cluster.
func (c *Cluster) registerDBHandlers(handler http.Handler) (http.Handler, error) {
if c.managedDB == nil {
return handlerNoEtcd(handler), nil
}
return c.managedDB.Register(handler)
}
// assignManagedDriver assigns a driver based on a number of different configuration variables.
// If a driver has been initialized it is used.
// If no specific endpoint has been requested and creating or joining has been requested,
// we use the default driver.
// If none of the above are true, no managed driver is assigned.
func (c *Cluster) assignManagedDriver(ctx context.Context) error {
// Check all managed drivers for an initialized database on disk; use one if found
for _, driver := range managed.Registered() {
if err := driver.SetControlConfig(c.config); err != nil {
return err
}
if ok, err := driver.IsInitialized(); err != nil {
return err
} else if ok {
c.managedDB = driver
return nil
}
}
// If we have been asked to initialize or join a cluster, do so using the default managed database.
if c.config.Datastore.Endpoint == "" && (c.config.ClusterInit || (c.config.Token != "" && c.config.JoinURL != "")) {
c.managedDB = managed.Default()
}
return nil
}
// setupEtcdProxy starts a goroutine to periodically update the etcd proxy with the current list of
// cluster client URLs, as retrieved from etcd.
func (c *Cluster) setupEtcdProxy(ctx context.Context, etcdProxy etcd.Proxy) {
if c.managedDB == nil {
return
}
// We use Poll here instead of Until because we want to wait the interval before running the function.
go wait.PollUntilContextCancel(ctx, 30*time.Second, false, func(ctx context.Context) (bool, error) {
clientURLs, err := c.managedDB.GetMembersClientURLs(ctx)
if err != nil {
logrus.Warnf("Failed to get etcd ClientURLs: %v", err)
return false, nil
}
// client URLs are a full URI, but the proxy only wants host:port
for i, c := range clientURLs {
u, err := url.Parse(c)
if err != nil {
logrus.Warnf("Failed to parse etcd ClientURL: %v", err)
return false, nil
}
clientURLs[i] = u.Host
}
etcdProxy.Update(clientURLs)
return false, nil
})
}
// deleteNodePasswdSecret wipes out the node password secret after restoration
func (c *Cluster) deleteNodePasswdSecret(ctx context.Context) {
nodeName := os.Getenv("NODE_NAME")
if err := nodepassword.Delete(nodeName); err != nil {
if apierrors.IsNotFound(err) {
logrus.Debugf("Node password secret is not found for node %s", nodeName)
return
}
logrus.Warnf("failed to delete old node password secret: %v", err)
}
}
// handlerNoEtcd wraps a handler with an error message indicating that etcd is not deployed.
func handlerNoEtcd(handler http.Handler) http.Handler {
r := mux.NewRouter().SkipClean(true)
// Wildcard route for anything after /db/
r.HandleFunc("/db/{_:.*}", func(resp http.ResponseWriter, r *http.Request) {
util.SendError(errors.New("etcd datastore disabled"), resp, r, http.StatusBadRequest)
})
// Needs to come at the end, otherwise wildcard routes won't work
r.NotFoundHandler = handler
return r
}