mirror of https://github.com/k3s-io/k3s.git
Improve etcd load-balancer startup behavior
Prefer the address of the etcd member being joined, and seed the full address list immediately on startup. Signed-off-by: Brad Davidson <brad.davidson@rancher.com>pull/9902/head
parent
fe465cc832
commit
7d9abc9f07
|
@ -43,23 +43,37 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) {
|
||||||
ready := make(chan struct{})
|
ready := make(chan struct{})
|
||||||
defer close(ready)
|
defer close(ready)
|
||||||
|
|
||||||
// try to get /db/info urls first before attempting to use join url
|
// try to get /db/info urls first, for a current list of etcd cluster member client URLs
|
||||||
clientURLs, _, err := etcd.ClientURLs(ctx, c.clientAccessInfo, c.config.PrivateIP)
|
clientURLs, _, err := etcd.ClientURLs(ctx, c.clientAccessInfo, c.config.PrivateIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(clientURLs) < 1 {
|
// If we somehow got no error but also no client URLs, just use the address of the server we're joining
|
||||||
|
if len(clientURLs) == 0 {
|
||||||
clientURL, err := url.Parse(c.config.JoinURL)
|
clientURL, err := url.Parse(c.config.JoinURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
clientURL.Host = clientURL.Hostname() + ":2379"
|
clientURL.Host = clientURL.Hostname() + ":2379"
|
||||||
clientURLs = append(clientURLs, clientURL.String())
|
clientURLs = append(clientURLs, clientURL.String())
|
||||||
|
logrus.Warnf("Got empty etcd ClientURL list; using server URL %s", clientURL)
|
||||||
}
|
}
|
||||||
etcdProxy, err := etcd.NewETCDProxy(ctx, c.config.SupervisorPort, c.config.DataDir, clientURLs[0], utilsnet.IsIPv6CIDR(c.config.ServiceIPRanges[0]))
|
etcdProxy, err := etcd.NewETCDProxy(ctx, c.config.SupervisorPort, c.config.DataDir, clientURLs[0], utilsnet.IsIPv6CIDR(c.config.ServiceIPRanges[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// immediately update the load balancer with all etcd addresses
|
||||||
|
// 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 {
|
||||||
|
return nil, errors.Wrap(err, "failed to parse etcd ClientURL")
|
||||||
|
}
|
||||||
|
clientURLs[i] = u.Host
|
||||||
|
}
|
||||||
|
etcdProxy.Update(clientURLs)
|
||||||
|
|
||||||
|
// start periodic endpoint sync goroutine
|
||||||
c.setupEtcdProxy(ctx, etcdProxy)
|
c.setupEtcdProxy(ctx, etcdProxy)
|
||||||
|
|
||||||
// remove etcd member if it exists
|
// remove etcd member if it exists
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/k3s-io/k3s/pkg/version"
|
"github.com/k3s-io/k3s/pkg/version"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testClusterDB returns a channel that will be closed when the datastore connection is available.
|
// testClusterDB returns a channel that will be closed when the datastore connection is available.
|
||||||
|
@ -132,28 +133,25 @@ func (c *Cluster) setupEtcdProxy(ctx context.Context, etcdProxy etcd.Proxy) {
|
||||||
if c.managedDB == nil {
|
if c.managedDB == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
// We use Poll here instead of Until because we want to wait the interval before running the function.
|
||||||
t := time.NewTicker(30 * time.Second)
|
go wait.PollUntilWithContext(ctx, 30*time.Second, func(ctx context.Context) (bool, error) {
|
||||||
defer t.Stop()
|
clientURLs, err := c.managedDB.GetMembersClientURLs(ctx)
|
||||||
for range t.C {
|
if err != nil {
|
||||||
newAddresses, err := c.managedDB.GetMembersClientURLs(ctx)
|
logrus.Warnf("Failed to get etcd ClientURLs: %v", err)
|
||||||
if err != nil {
|
return false, nil
|
||||||
logrus.Warnf("Failed to get etcd client URLs: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// client URLs are a full URI, but the proxy only wants host:port
|
|
||||||
var hosts []string
|
|
||||||
for _, address := range newAddresses {
|
|
||||||
u, err := url.Parse(address)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("Failed to parse etcd client URL: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hosts = append(hosts, u.Host)
|
|
||||||
}
|
|
||||||
etcdProxy.Update(hosts)
|
|
||||||
}
|
}
|
||||||
}()
|
// 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
|
// deleteNodePasswdSecret wipes out the node password secret after restoration
|
||||||
|
|
|
@ -725,7 +725,6 @@ func getClientConfig(ctx context.Context, control *config.Control, endpoints ...
|
||||||
DialTimeout: defaultDialTimeout,
|
DialTimeout: defaultDialTimeout,
|
||||||
DialKeepAliveTime: defaultKeepAliveTime,
|
DialKeepAliveTime: defaultKeepAliveTime,
|
||||||
DialKeepAliveTimeout: defaultKeepAliveTimeout,
|
DialKeepAliveTimeout: defaultKeepAliveTimeout,
|
||||||
AutoSyncInterval: defaultKeepAliveTimeout,
|
|
||||||
PermitWithoutStream: true,
|
PermitWithoutStream: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1387,35 +1386,50 @@ func (e *ETCD) defragment(ctx context.Context) error {
|
||||||
// The list is retrieved from the remote server that is being joined.
|
// The list is retrieved from the remote server that is being joined.
|
||||||
func ClientURLs(ctx context.Context, clientAccessInfo *clientaccess.Info, selfIP string) ([]string, Members, error) {
|
func ClientURLs(ctx context.Context, clientAccessInfo *clientaccess.Info, selfIP string) ([]string, Members, error) {
|
||||||
var memberList Members
|
var memberList Members
|
||||||
resp, err := clientAccessInfo.Get("/db/info")
|
|
||||||
if err != nil {
|
|
||||||
return nil, memberList, &MemberListError{Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(resp, &memberList); err != nil {
|
// find the address advertised for our own client URL, so that we don't connect to ourselves
|
||||||
return nil, memberList, err
|
|
||||||
}
|
|
||||||
ip, err := getAdvertiseAddress(selfIP)
|
ip, err := getAdvertiseAddress(selfIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, memberList, err
|
return nil, memberList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find the client URL of the server we're joining, so we can prioritize it
|
||||||
|
joinURL, err := url.Parse(clientAccessInfo.BaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, memberList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the full list from the server we're joining
|
||||||
|
resp, err := clientAccessInfo.Get("/db/info")
|
||||||
|
if err != nil {
|
||||||
|
return nil, memberList, &MemberListError{Err: err}
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp, &memberList); err != nil {
|
||||||
|
return nil, memberList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a list of client URLs. Learners and the current node are excluded;
|
||||||
|
// the server we're joining is listed first if found.
|
||||||
var clientURLs []string
|
var clientURLs []string
|
||||||
members:
|
|
||||||
for _, member := range memberList.Members {
|
for _, member := range memberList.Members {
|
||||||
// excluding learner member from the client list
|
var isSelf, isPreferred bool
|
||||||
if member.IsLearner {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, clientURL := range member.ClientURLs {
|
for _, clientURL := range member.ClientURLs {
|
||||||
u, err := url.Parse(clientURL)
|
if u, err := url.Parse(clientURL); err == nil {
|
||||||
if err != nil {
|
switch u.Hostname() {
|
||||||
continue
|
case ip:
|
||||||
}
|
isSelf = true
|
||||||
if u.Hostname() == ip {
|
case joinURL.Hostname():
|
||||||
continue members
|
isPreferred = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !member.IsLearner && !isSelf {
|
||||||
|
if isPreferred {
|
||||||
|
clientURLs = append(member.ClientURLs, clientURLs...)
|
||||||
|
} else {
|
||||||
|
clientURLs = append(clientURLs, member.ClientURLs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clientURLs = append(clientURLs, member.ClientURLs...)
|
|
||||||
}
|
}
|
||||||
return clientURLs, memberList, nil
|
return clientURLs, memberList, nil
|
||||||
}
|
}
|
||||||
|
@ -1545,7 +1559,21 @@ func GetAPIServerURLsFromETCD(ctx context.Context, cfg *config.Control) ([]strin
|
||||||
// GetMembersClientURLs will list through the member lists in etcd and return
|
// GetMembersClientURLs will list through the member lists in etcd and return
|
||||||
// back a combined list of client urls for each member in the cluster
|
// back a combined list of client urls for each member in the cluster
|
||||||
func (e *ETCD) GetMembersClientURLs(ctx context.Context) ([]string, error) {
|
func (e *ETCD) GetMembersClientURLs(ctx context.Context) ([]string, error) {
|
||||||
return e.client.Endpoints(), nil
|
ctx, cancel := context.WithTimeout(ctx, testTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
members, err := e.client.MemberList(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientURLs []string
|
||||||
|
for _, member := range members.Members {
|
||||||
|
if !member.IsLearner {
|
||||||
|
clientURLs = append(clientURLs, member.ClientURLs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientURLs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMembersNames will list through the member lists in etcd and return
|
// GetMembersNames will list through the member lists in etcd and return
|
||||||
|
|
Loading…
Reference in New Issue