-
Notifications
You must be signed in to change notification settings - Fork 227
PMM-15227 Remove stale HA replicas from Inventory #5738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
4nte
wants to merge
28
commits into
main
Choose a base branch
from
PMM-15227-remove-stale-ha-nodes-from-inventory
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
0c3d3e9
PMM-15227 Remove scaled-down HA replicas from Inventory
4nte 15aa2c5
Merge branch 'main' into PMM-15227-remove-stale-ha-nodes-from-inventory
4nte b82c657
PMM-15227 Let the HA cleanup remove stale PMM Server Nodes
4nte 0c04e87
Merge branch 'main' into PMM-15227-remove-stale-ha-nodes-from-inventory
4nte 1a1a3c5
PMM-15227 Log HA cleanup with structured fields
4nte d52d51a
PMM-15227 Correct the IsPMMServerNode comment
4nte c62e178
PMM-15227 Reject unbracketed IPv6 HA peers
4nte b2cbb72
Merge branch 'main' into PMM-15227-remove-stale-ha-nodes-from-inventory
4nte eb6abb1
PMM-15227 Improve wording
4nte 433b524
PMM-15227 Keep the pre-HA PMM Server Node
4nte ac9e496
PMM-15227 Pin PMM Server Node guards to a const
4nte dafc590
PMM-15227 Never let HA node cleanup block startup
4nte 82eefa4
PMM-15227 Tighten the HA node cleanup
4nte a31b548
PMM-15227 Fail fast in the HA node test helper
4nte 4b09b46
PMM-15227 Cover the Node ID ban under a lifted flag
4nte 56a34df
PMM-15227 Widen the stale HA Node safety check
4nte dcaf91e
PMM-15227 Skip blank PMM_HA_PEERS entries
4nte 18a6c9b
PMM-15227 Select only PMM Server nodes instead of all
4nte 4168236
PMM-15227 Document when a stale Node is kept
4nte 326f45e
PMM-15227 Fix typo
4nte b7b543e
PMM-15227 Improve docs for stale node removal
4nte 25e9b85
PMM-15227 Add test for scale-to-one-replica sweep scenario
4nte 79324be
PMM-15227 Bind the HA node cleanup to the startup ctx
4nte 940d5d6
PMM-15227 Fix the advice for a Node we keep
4nte a8e4a51
PMM-15227 Close three gaps in the HA cleanup tests
4nte 0a3f7b4
PMM-15227 Cover the HA peer parser directly
4nte 799bfdb
Merge remote-tracking branch 'origin/main' into PMM-15227-remove-stal…
4nte 34de666
Merge branch 'main' into PMM-15227-remove-stale-ha-nodes-from-inventory
4nte File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,10 +18,12 @@ package models | |
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "strings" | ||
|
|
||
| "github.com/AlekSi/pointer" | ||
| "github.com/google/uuid" | ||
| "github.com/sirupsen/logrus" | ||
| "google.golang.org/grpc/codes" | ||
| "google.golang.org/grpc/status" | ||
| "gopkg.in/reform.v1" | ||
|
|
@@ -252,13 +254,19 @@ func CreateNode(q *reform.Querier, nodeType NodeType, params *CreateNodeParams) | |
| } | ||
|
|
||
| // RemoveNode removes single Node. | ||
| func RemoveNode(q *reform.Querier, id string, mode RemoveMode) error { //nolint:gocognit | ||
| func RemoveNode(q *reform.Querier, id string, mode RemoveMode) error { | ||
| return removeNode(q, id, mode, false) | ||
| } | ||
|
|
||
| // removeNode removes a single Node. The allowPMMServerNode flag lifts the ban on Nodes flagged as PMM | ||
| // Server Nodes; only the HA cleanup sets it, to reap replicas that are no longer part of the cluster. | ||
| func removeNode(q *reform.Querier, id string, mode RemoveMode, allowPMMServerNode bool) error { //nolint:gocognit | ||
| n, err := FindNodeByID(q, id) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if n.IsPMMServerNode || id == PMMServerNodeID { | ||
| if id == PMMServerNodeID || (!allowPMMServerNode && n.IsPMMServerNode) { | ||
|
4nte marked this conversation as resolved.
Outdated
|
||
| return status.Error(codes.PermissionDenied, "PMM Server node can't be removed.") | ||
| } | ||
|
|
||
|
|
@@ -334,3 +342,119 @@ func RemoveNode(q *reform.Querier, id string, mode RemoveMode) error { //nolint: | |
| } | ||
| return nil | ||
| } | ||
|
|
||
| // RemoveStaleHANodes removes the PMM Server Nodes of HA replicas that are no longer configured peers, | ||
| // e.g. after a scale-down. Peers are the source of truth because they are regenerated from the replica | ||
| // count and restart every replica, while a missing memberlist member may just be restarting. | ||
| func RemoveStaleHANodes(q *reform.Querier, haNodeID string, haPeers []string) error { | ||
| if len(haPeers) == 0 { | ||
|
4nte marked this conversation as resolved.
|
||
| return nil | ||
| } | ||
|
|
||
| l := logrus.WithFields(logrus.Fields{"component": "ha", "ha_node_id": haNodeID}) | ||
|
|
||
| expected := make(map[string]struct{}, len(haPeers)) | ||
| for _, peer := range haPeers { | ||
| name, ok := haPeerNodeName(peer) | ||
| if !ok { | ||
|
4nte marked this conversation as resolved.
|
||
| // Trusting the rest would treat a partial list as the whole cluster and remove live replicas. | ||
| l.WithField("peer", peer).Warn("Can't read a node name from a PMM_HA_PEERS entry, skipping the removal of stale HA nodes.") | ||
| return nil | ||
| } | ||
| expected[name] = struct{}{} | ||
| } | ||
|
|
||
| if _, ok := expected[haNodeID]; !ok { | ||
| l.WithField("ha_peers", haPeers).Warn("PMM_HA_PEERS doesn't list this node, skipping the removal of stale HA nodes.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. log message states |
||
| return nil | ||
| } | ||
|
|
||
| nodes, err := FindNodes(q, NodeFilters{}) | ||
|
4nte marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return fmt.Errorf("failed to list Nodes for stale HA node cleanup: %w", err) | ||
| } | ||
|
|
||
| for _, node := range nodes { | ||
| // Set by HA replicas, and by the PMM Server Node of a non-HA deployment; every other | ||
| // Node is one the user monitors. | ||
| if !node.IsPMMServerNode { | ||
|
4nte marked this conversation as resolved.
Outdated
|
||
| continue | ||
| } | ||
| if _, ok := expected[node.NodeName]; ok { | ||
| continue | ||
| } | ||
|
|
||
| nodeL := l.WithFields(logrus.Fields{"node_id": node.NodeID, "node_name": node.NodeName}) | ||
|
|
||
| monitored, err := haNodeMonitoredServices(q, node.NodeID) | ||
| if err != nil { | ||
| return err | ||
|
4nte marked this conversation as resolved.
Outdated
|
||
| } | ||
| if len(monitored) != 0 { | ||
| nodeL.WithField("service_ids", monitored).Warn("Keeping stale HA node: it still monitors services, which would be removed with it. " + | ||
| "Re-add them from a running replica and remove the node from Inventory.") | ||
| continue | ||
| } | ||
|
|
||
| err = removeNode(q, node.NodeID, RemoveCascade, true) | ||
| switch { | ||
| case err == nil: | ||
| nodeL.Info("Removed stale HA node, it is not a part of the cluster anymore.") | ||
| case errors.Is(err, reform.ErrNoRows), status.Code(err) == codes.NotFound: | ||
| nodeL.Info("Stale HA node was already removed by another replica.") | ||
| default: | ||
| return fmt.Errorf("failed to remove stale HA node %q: %w", node.NodeName, err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // haPeerNodeName maps a PMM_HA_PEERS entry ("pmm-ha-0.pmm-ha.pmm.svc.cluster.local:9761") to a Node | ||
| // name: the first label is the pod's PMM_HA_NODE_ID. Reports false for entries with no name, like | ||
| // bare IPv4 or IPv6 addresses. | ||
| func haPeerNodeName(peer string) (string, bool) { | ||
| peer = strings.TrimSpace(peer) | ||
| // Test the whole entry before cutting at ":": an unbracketed IPv6 literal would otherwise be cut | ||
| // into its first group, and the "2001" of "2001:db8::7" reads like a node name. Only IPv6 entries | ||
| // hold more than one colon, bracketed or not, and none of them starts with a name. | ||
| if strings.Count(peer, ":") > 1 || net.ParseIP(peer) != nil { | ||
| return "", false | ||
| } | ||
| host, _, _ := strings.Cut(peer, ":") | ||
| if net.ParseIP(host) != nil { | ||
| return "", false | ||
| } | ||
| // "/" is memberlist's "name/address" form, "[" a bracketed address; such a label mixes a name | ||
| // with an address instead of being one. | ||
| label, _, _ := strings.Cut(host, ".") | ||
| if label == "" || strings.ContainsAny(label, "/[") { | ||
| return "", false | ||
| } | ||
| return label, true | ||
| } | ||
|
|
||
| // haNodeMonitoredServices returns the IDs of Services whose exporters run under a replica's pmm-agent. | ||
| // Remote instances bind theirs to the replica that added them (see management.RDSService), so removing | ||
| // that replica's Node takes them with it. | ||
| func haNodeMonitoredServices(q *reform.Querier, nodeID string) ([]string, error) { | ||
|
4nte marked this conversation as resolved.
|
||
| pmmAgents, err := FindPMMAgentsRunningOnNode(q, nodeID) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var serviceIDs []string | ||
| for _, pmmAgent := range pmmAgents { | ||
| agents, err := FindAgents(q, AgentFilters{PMMAgentID: pmmAgent.AgentID}) | ||
|
4nte marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, agent := range agents { | ||
| if agent.ServiceID != nil { | ||
| serviceIDs = append(serviceIDs, *agent.ServiceID) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return serviceIDs, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.