Commit 6c544a46 authored by Brad Davidson's avatar Brad Davidson Committed by Brad Davidson

Add jitter to client config retry

Also: * Replaces labeled for/continue RETRY loops with wait helpers for improved readability * Pulls secrets and nodes from cache for node password verification * Migrate nodepassword tests to wrangler mocks for better code reuse Signed-off-by: 's avatarBrad Davidson <brad.davidson@rancher.com>
parent fa4c1806
...@@ -104,6 +104,7 @@ require ( ...@@ -104,6 +104,7 @@ require (
github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/go-test/deep v1.0.7 github.com/go-test/deep v1.0.7
github.com/golang/mock v1.6.0
github.com/google/cadvisor v0.47.3 github.com/google/cadvisor v0.47.3
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
...@@ -257,7 +258,6 @@ require ( ...@@ -257,7 +258,6 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.16.1 // indirect github.com/google/cel-go v0.16.1 // indirect
......
...@@ -33,6 +33,7 @@ import ( ...@@ -33,6 +33,7 @@ import (
"github.com/rancher/wrangler/pkg/slice" "github.com/rancher/wrangler/pkg/slice"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/wait"
utilsnet "k8s.io/utils/net" utilsnet "k8s.io/utils/net"
) )
...@@ -42,22 +43,28 @@ const ( ...@@ -42,22 +43,28 @@ const (
// Get returns a pointer to a completed Node configuration struct, // Get returns a pointer to a completed Node configuration struct,
// containing a merging of the local CLI configuration with settings from the server. // containing a merging of the local CLI configuration with settings from the server.
// Node configuration includes client certificates, which requires node password verification,
// so this is somewhat computationally expensive on the server side, and is retried with jitter
// to avoid having clients hammer on the server at fixed periods.
// A call to this will bock until agent configuration is successfully returned by the // A call to this will bock until agent configuration is successfully returned by the
// server. // server.
func Get(ctx context.Context, agent cmds.Agent, proxy proxy.Proxy) *config.Node { func Get(ctx context.Context, agent cmds.Agent, proxy proxy.Proxy) *config.Node {
ticker := time.NewTicker(5 * time.Second) var agentConfig *config.Node
defer ticker.Stop() var err error
RETRY:
for { // This would be more clear as wait.PollImmediateUntilWithContext, but that function
agentConfig, err := get(ctx, &agent, proxy) // does not support jittering, so we instead use wait.JitterUntilWithContext, and cancel
// the context on success.
ctx, cancel := context.WithCancel(ctx)
wait.JitterUntilWithContext(ctx, func(ctx context.Context) {
agentConfig, err = get(ctx, &agent, proxy)
if err != nil { if err != nil {
logrus.Infof("Waiting to retrieve agent configuration; server is not ready: %v", err) logrus.Infof("Waiting to retrieve agent configuration; server is not ready: %v", err)
for range ticker.C { } else {
continue RETRY cancel()
}
} }
return agentConfig }, 5*time.Second, 1.0, true)
} return agentConfig
} }
// KubeProxyDisabled returns a bool indicating whether or not kube-proxy has been disabled in the // KubeProxyDisabled returns a bool indicating whether or not kube-proxy has been disabled in the
...@@ -65,42 +72,40 @@ RETRY: ...@@ -65,42 +72,40 @@ RETRY:
// after all startup hooks have completed, so a call to this will block until after the server's // after all startup hooks have completed, so a call to this will block until after the server's
// readyz endpoint returns OK. // readyz endpoint returns OK.
func KubeProxyDisabled(ctx context.Context, node *config.Node, proxy proxy.Proxy) bool { func KubeProxyDisabled(ctx context.Context, node *config.Node, proxy proxy.Proxy) bool {
ticker := time.NewTicker(5 * time.Second) var disabled bool
defer ticker.Stop() var err error
RETRY:
for { wait.PollImmediateUntilWithContext(ctx, 5*time.Second, func(ctx context.Context) (bool, error) {
disabled, err := getKubeProxyDisabled(ctx, node, proxy) disabled, err = getKubeProxyDisabled(ctx, node, proxy)
if err != nil { if err != nil {
logrus.Infof("Waiting to retrieve kube-proxy configuration; server is not ready: %v", err) logrus.Infof("Waiting to retrieve kube-proxy configuration; server is not ready: %v", err)
for range ticker.C { return false, nil
continue RETRY
}
} }
return disabled return true, nil
} })
return disabled
} }
// APIServers returns a list of apiserver endpoints, suitable for seeding client loadbalancer configurations. // APIServers returns a list of apiserver endpoints, suitable for seeding client loadbalancer configurations.
// This function will block until it can return a populated list of apiservers, or if the remote server returns // This function will block until it can return a populated list of apiservers, or if the remote server returns
// an error (indicating that it does not support this functionality). // an error (indicating that it does not support this functionality).
func APIServers(ctx context.Context, node *config.Node, proxy proxy.Proxy) []string { func APIServers(ctx context.Context, node *config.Node, proxy proxy.Proxy) []string {
ticker := time.NewTicker(5 * time.Second) var addresses []string
defer ticker.Stop() var err error
RETRY:
for { wait.PollImmediateUntilWithContext(ctx, 5*time.Second, func(ctx context.Context) (bool, error) {
addresses, err := getAPIServers(ctx, node, proxy) addresses, err = getAPIServers(ctx, node, proxy)
if err != nil { if err != nil {
logrus.Infof("Failed to retrieve list of apiservers from server: %v", err) logrus.Infof("Failed to retrieve list of apiservers from server: %v", err)
return nil return false, err
} }
if len(addresses) == 0 { if len(addresses) == 0 {
logrus.Infof("Waiting for apiserver addresses") logrus.Infof("Waiting for apiserver addresses")
for range ticker.C { return false, nil
continue RETRY
}
} }
return addresses return true, nil
} })
return addresses
} }
type HTTPRequester func(u string, client *http.Client, username, password, token string) ([]byte, error) type HTTPRequester func(u string, client *http.Client, username, password, token string) ([]byte, error)
......
...@@ -13,15 +13,14 @@ import ( ...@@ -13,15 +13,14 @@ import (
func Register(ctx context.Context, func Register(ctx context.Context,
modCoreDNS bool, modCoreDNS bool,
secretClient coreclient.SecretClient, secrets coreclient.SecretController,
configMap coreclient.ConfigMapController, configMaps coreclient.ConfigMapController,
nodes coreclient.NodeController, nodes coreclient.NodeController,
) error { ) error {
h := &handler{ h := &handler{
modCoreDNS: modCoreDNS, modCoreDNS: modCoreDNS,
secretClient: secretClient, secrets: secrets,
configCache: configMap.Cache(), configMaps: configMaps,
configClient: configMap,
} }
nodes.OnChange(ctx, "node", h.onChange) nodes.OnChange(ctx, "node", h.onChange)
nodes.OnRemove(ctx, "node", h.onRemove) nodes.OnRemove(ctx, "node", h.onRemove)
...@@ -30,10 +29,9 @@ func Register(ctx context.Context, ...@@ -30,10 +29,9 @@ func Register(ctx context.Context,
} }
type handler struct { type handler struct {
modCoreDNS bool modCoreDNS bool
secretClient coreclient.SecretClient secrets coreclient.SecretController
configCache coreclient.ConfigMapCache configMaps coreclient.ConfigMapController
configClient coreclient.ConfigMapClient
} }
func (h *handler) onChange(key string, node *core.Node) (*core.Node, error) { func (h *handler) onChange(key string, node *core.Node) (*core.Node, error) {
...@@ -78,7 +76,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b ...@@ -78,7 +76,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
return nil return nil
} }
configMapCache, err := h.configCache.Get("kube-system", "coredns") configMapCache, err := h.configMaps.Cache().Get("kube-system", "coredns")
if err != nil || configMapCache == nil { if err != nil || configMapCache == nil {
logrus.Warn(errors.Wrap(err, "Unable to fetch coredns config map")) logrus.Warn(errors.Wrap(err, "Unable to fetch coredns config map"))
return nil return nil
...@@ -120,7 +118,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b ...@@ -120,7 +118,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
} }
configMap.Data["NodeHosts"] = newHosts configMap.Data["NodeHosts"] = newHosts
if _, err := h.configClient.Update(configMap); err != nil { if _, err := h.configMaps.Update(configMap); err != nil {
return err return err
} }
...@@ -135,5 +133,5 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b ...@@ -135,5 +133,5 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
} }
func (h *handler) removeNodePassword(nodeName string) error { func (h *handler) removeNodePassword(nodeName string) error {
return nodepassword.Delete(h.secretClient, nodeName) return nodepassword.Delete(h.secrets, nodeName)
} }
...@@ -51,9 +51,9 @@ func getSecretName(nodeName string) string { ...@@ -51,9 +51,9 @@ func getSecretName(nodeName string) string {
return strings.ToLower(nodeName + ".node-password." + version.Program) return strings.ToLower(nodeName + ".node-password." + version.Program)
} }
func verifyHash(secretClient coreclient.SecretClient, nodeName, pass string) error { func verifyHash(secretClient coreclient.SecretController, nodeName, pass string) error {
name := getSecretName(nodeName) name := getSecretName(nodeName)
secret, err := secretClient.Get(metav1.NamespaceSystem, name, metav1.GetOptions{}) secret, err := secretClient.Cache().Get(metav1.NamespaceSystem, name)
if err != nil { if err != nil {
return &passwordError{node: nodeName, err: err} return &passwordError{node: nodeName, err: err}
} }
...@@ -67,7 +67,7 @@ func verifyHash(secretClient coreclient.SecretClient, nodeName, pass string) err ...@@ -67,7 +67,7 @@ func verifyHash(secretClient coreclient.SecretClient, nodeName, pass string) err
} }
// Ensure will verify a node-password secret if it exists, otherwise it will create one // Ensure will verify a node-password secret if it exists, otherwise it will create one
func Ensure(secretClient coreclient.SecretClient, nodeName, pass string) error { func Ensure(secretClient coreclient.SecretController, nodeName, pass string) error {
err := verifyHash(secretClient, nodeName, pass) err := verifyHash(secretClient, nodeName, pass)
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
var hash string var hash string
...@@ -88,12 +88,12 @@ func Ensure(secretClient coreclient.SecretClient, nodeName, pass string) error { ...@@ -88,12 +88,12 @@ func Ensure(secretClient coreclient.SecretClient, nodeName, pass string) error {
} }
// Delete will remove a node-password secret // Delete will remove a node-password secret
func Delete(secretClient coreclient.SecretClient, nodeName string) error { func Delete(secretClient coreclient.SecretController, nodeName string) error {
return secretClient.Delete(metav1.NamespaceSystem, getSecretName(nodeName), &metav1.DeleteOptions{}) return secretClient.Delete(metav1.NamespaceSystem, getSecretName(nodeName), &metav1.DeleteOptions{})
} }
// MigrateFile moves password file entries to secrets // MigrateFile moves password file entries to secrets
func MigrateFile(secretClient coreclient.SecretClient, nodeClient coreclient.NodeClient, passwordFile string) error { func MigrateFile(secretClient coreclient.SecretController, nodeClient coreclient.NodeController, passwordFile string) error {
_, err := os.Stat(passwordFile) _, err := os.Stat(passwordFile)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return nil
...@@ -108,11 +108,9 @@ func MigrateFile(secretClient coreclient.SecretClient, nodeClient coreclient.Nod ...@@ -108,11 +108,9 @@ func MigrateFile(secretClient coreclient.SecretClient, nodeClient coreclient.Nod
} }
nodeNames := []string{} nodeNames := []string{}
nodeList, _ := nodeClient.List(metav1.ListOptions{}) nodeList, _ := nodeClient.Cache().List(nil)
if nodeList != nil { for _, node := range nodeList {
for _, node := range nodeList.Items { nodeNames = append(nodeNames, node.Name)
nodeNames = append(nodeNames, node.Name)
}
} }
if len(nodeNames) == 0 { if len(nodeNames) == 0 {
nodeNames = append(nodeNames, passwd.Users()...) nodeNames = append(nodeNames, passwd.Users()...)
......
...@@ -431,8 +431,8 @@ type nodeInfo struct { ...@@ -431,8 +431,8 @@ type nodeInfo struct {
func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper { func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper {
runtime := config.ControlConfig.Runtime runtime := config.ControlConfig.Runtime
deferredNodes := map[string]bool{} deferredNodes := map[string]bool{}
var secretClient coreclient.SecretClient var secretClient coreclient.SecretController
var nodeClient coreclient.NodeClient var nodeClient coreclient.NodeController
var mu sync.Mutex var mu sync.Mutex
return nodePassBootstrapper(func(req *http.Request) (string, int, error) { return nodePassBootstrapper(func(req *http.Request) (string, int, error) {
...@@ -535,9 +535,9 @@ func verifyRemotePassword(ctx context.Context, config *Config, mu *sync.Mutex, d ...@@ -535,9 +535,9 @@ func verifyRemotePassword(ctx context.Context, config *Config, mu *sync.Mutex, d
return node.Name, http.StatusOK, nil return node.Name, http.StatusOK, nil
} }
func verifyNode(ctx context.Context, nodeClient coreclient.NodeClient, node *nodeInfo) error { func verifyNode(ctx context.Context, nodeClient coreclient.NodeController, node *nodeInfo) error {
if nodeName, isNodeAuth := identifier.NodeIdentity(node.User); isNodeAuth { if nodeName, isNodeAuth := identifier.NodeIdentity(node.User); isNodeAuth {
if _, err := nodeClient.Get(nodeName, metav1.GetOptions{}); err != nil { if _, err := nodeClient.Cache().Get(nodeName); err != nil {
return errors.Wrap(err, "unable to verify node identity") return errors.Wrap(err, "unable to verify node identity")
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment