-
Notifications
You must be signed in to change notification settings - Fork 518
OCPBUGS-111074: Taint TNF nodes on graceful membership loss #6416
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,72 @@ contents: | |
| exit 1 | ||
| fi | ||
|
|
||
| parse_online_nodes() { | ||
| pcs status nodes 2>/dev/null | awk ' | ||
| /^[[:space:]]+Online:/ { | ||
| for (i=2; i<=NF; i++) { gsub(/[][]/,"",$i); if ($i != "") printf "%s ",$i } | ||
| }' || true | ||
| } | ||
|
|
||
| node_is_listed() { | ||
| local target="$1" | ||
| local list="$2" | ||
| local short="${target%%.*}" | ||
| [[ " $list " == *" $target "* ]] || [[ " $list " == *" $short "* ]] | ||
| } | ||
|
|
||
| LOCAL_NAME=$(crm_node -n 2>/dev/null || hostname -s) | ||
| LOCAL_SHORT="${LOCAL_NAME%%.*}" | ||
| FENCED_SHORT="${FENCED_NODE%%.*}" | ||
|
|
||
| if [[ "$FENCED_SHORT" == "$LOCAL_SHORT" ]]; then | ||
| logger -t taint-fenced-node "Refusing to taint local node ${FENCED_NODE}" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Confirm we are the survivor and the target stays off. Node-lost alerts also | ||
| # fire on brief corosync blips; tainting a live peer would force-delete its pods. | ||
| STABLE_REQUIRED=3 | ||
| STABLE_INTERVAL=5 | ||
| MAX_CONFIRM_ATTEMPTS=12 | ||
| STABLE_COUNT=0 | ||
| LAST_ONLINE="" | ||
| for attempt in $(seq 1 $MAX_CONFIRM_ATTEMPTS); do | ||
| LAST_ONLINE=$(parse_online_nodes) | ||
| if [[ -z "$LAST_ONLINE" ]]; then | ||
| logger -t taint-fenced-node "pacemaker Online list empty (attempt ${attempt}/${MAX_CONFIRM_ATTEMPTS}); retrying" | ||
| STABLE_COUNT=0 | ||
| sleep $STABLE_INTERVAL | ||
| continue | ||
| fi | ||
| if ! node_is_listed "$LOCAL_NAME" "$LAST_ONLINE"; then | ||
| logger -t taint-fenced-node "Local node ${LOCAL_NAME} is not pacemaker-Online; skipping taint (this node may be shutting down)" | ||
| exit 0 | ||
| fi | ||
| if node_is_listed "$FENCED_NODE" "$LAST_ONLINE"; then | ||
| logger -t taint-fenced-node "${FENCED_NODE} is still pacemaker-Online (attempt ${attempt}/${MAX_CONFIRM_ATTEMPTS}); waiting to confirm loss" | ||
| STABLE_COUNT=0 | ||
| sleep $STABLE_INTERVAL | ||
| continue | ||
| fi | ||
| STABLE_COUNT=$((STABLE_COUNT + 1)) | ||
| logger -t taint-fenced-node "${FENCED_NODE} is not pacemaker-Online (${STABLE_COUNT}/${STABLE_REQUIRED})" | ||
| if [[ "$STABLE_COUNT" -ge "$STABLE_REQUIRED" ]]; then | ||
| break | ||
| fi | ||
| sleep $STABLE_INTERVAL | ||
| done | ||
|
|
||
| LAST_ONLINE=$(parse_online_nodes) | ||
| if [[ -n "$LAST_ONLINE" ]] && node_is_listed "$FENCED_NODE" "$LAST_ONLINE"; then | ||
| logger -t taint-fenced-node "Skipping taint: ${FENCED_NODE} returned to pacemaker-Online (transient membership loss)" | ||
| exit 0 | ||
| fi | ||
| if [[ "$STABLE_COUNT" -lt "$STABLE_REQUIRED" ]]; then | ||
| logger -t taint-fenced-node "ERROR: Could not confirm ${FENCED_NODE} is stably Offline; will retry via systemd" | ||
| exit 1 | ||
|
Comment on lines
+76
to
+83
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Reject an unsafe final membership observation. If Require a non-empty final list that still includes the local node before applying the taint. Treat any other final state as unsafe. 🤖 Prompt for AI Agents |
||
| fi | ||
|
|
||
| logger -t taint-fenced-node "Applying out-of-service taint to fenced node ${FENCED_NODE}" | ||
|
|
||
| PATCH=$(cat <<PATCHEOF | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,12 @@ contents: | | |
| [Unit] | ||
| Description=Taint fenced node %i in Kubernetes | ||
| After=network.target | ||
| StartLimitIntervalSec=2400 | ||
| StartLimitBurst=3 | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| ExecStart=/usr/local/bin/taint-fenced-node.sh %i | ||
| TimeoutStartSec=660 | ||
| TimeoutStartSec=720 | ||
| Restart=on-failure | ||
| RestartSec=10 | ||
|
Comment on lines
+6
to
+14
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: sed -n '1,120p' templates/master/00-master/two-node-with-fencing/units/taint-node@.service.yaml
printf '\n--- related unit and script references ---\n'
rg -n -C 4 'taint-node@|StartLimit|taint-fenced-node|RestartSec|Offline' templates/master/00-master/two-node-with-fencing
printf '\n--- repository history metadata ---\n'
git diff --stat
git status --shortRepository: openshift/machine-config-operator Length of output: 38973 🏁 Script executed: printf '%s\n' '--- taint script constants and control flow ---'
sed -n '1,115p' templates/master/00-master/two-node-with-fencing/files/taint-fenced-node.yaml
printf '%s\n' '--- taint alert retry/start behavior ---'
sed -n '1,110p' templates/master/00-master/two-node-with-fencing/files/tnf-taint-alert.yaml
printf '%s\n' '--- all references to the taint unit ---'
rg -n -C 5 'systemctl (start|reset-failed|restart)|taint-node@|taint-fenced-node' templates/master/00-master/two-node-with-fencingRepository: openshift/machine-config-operator Length of output: 48372 🏁 Script executed: python3 - <<'PY'
from pathlib import Path
import re
unit = Path("templates/master/00-master/two-node-with-fencing/units/taint-node@.service.yaml").read_text()
script = Path("templates/master/00-master/two-node-with-fencing/files/taint-fenced-node.yaml").read_text()
def value(text, name):
m = re.search(rf'^\s*{re.escape(name)}=(\d+)\s*$', text, re.M)
return int(m.group(1))
burst = value(unit, "StartLimitBurst")
window = value(unit, "StartLimitIntervalSec")
restart = value(unit, "RestartSec")
timeout = value(unit, "TimeoutStartSec")
stable_required = value(script, "STABLE_REQUIRED")
stable_interval = value(script, "STABLE_INTERVAL")
max_confirm = value(script, "MAX_CONFIRM_ATTEMPTS")
max_retries = value(script, "MAX_RETRIES")
retry_interval = value(script, "RETRY_INTERVAL")
# Upper-bound sleep time for the two retry loops, excluding command latency.
membership_sleep = max_confirm * stable_interval
patch_sleep = (max_retries - 1) * retry_interval
print({
"start_limit_burst": burst,
"start_limit_window_sec": window,
"restart_delay_sec": restart,
"service_timeout_sec": timeout,
"membership_wait_sleep_sec": membership_sleep,
"patch_retry_sleep_sec": patch_sleep,
"three_failed_attempts_minimum_elapsed_sec": 2 * restart,
"three_failed_attempts_if_each_hits_timeout_sec": 3 * timeout + 2 * restart,
"three_failed_attempts_if_patch_retries_exhausted_sec": 3 * (membership_sleep + patch_sleep) + 2 * restart,
})
assert burst == 3
assert window == 2400
assert restart == 10
assert timeout == 720
assert membership_sleep == 60
assert patch_sleep == 595
PYRepository: openshift/machine-config-operator Length of output: 511 🌐 Web query:
💡 Result: In systemd, the StartLimitBurst and StartLimitIntervalSec settings define the start rate limiting policy for a unit [1][2]. When a service is configured with Restart=on-failure (or any other restart mode), systemd monitors the number of start attempts within a defined time interval [1][2]. If the number of attempts exceeds the value set in StartLimitBurst within the period defined by StartLimitIntervalSec, systemd stops attempting to restart the service automatically [1][2]. Key details regarding this behavior include: 1. Rate Limiting Logic: Start rate limiting applies to all forms of unit activation, including manual starts, timer-triggered starts, and automatic restarts triggered by the Restart= directive [1][2]. 2. Consequences of Reaching the Limit: Once the start limit is reached, the service enters a failed state and will not be automatically restarted by systemd [1][2]. The restart logic is effectively suspended [1][2]. 3. Recovery: - Manual Reset: You can clear the failed state and reset the rate limit counter using the command systemctl reset-failed [unit-name] [1][2]. - Automatic Recovery: The service may be restarted manually, or via a timer or socket, after the StartLimitIntervalSec interval has elapsed [1][2]. Once the interval passes, the rate limit counter is essentially reset, and the automatic restart logic becomes active again [1][2]. 4. Disabling Limits: To ensure a service attempts to restart indefinitely regardless of failure frequency, you can set StartLimitIntervalSec=0 in the unit file [1][2][3]. Official documentation for these settings can be found in the systemd.unit(5) man page [1][2]. The interaction between Restart= and rate limiting is noted in the systemd.service(5) man page [4][5]. Citations:
Keep retries available after transient membership failures.
🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Normalize every Pacemaker node name before comparison.
Line 31 compares the normalized target only with unmodified list entries. If Pacemaker returns FQDNs while
crm_node -nor the alert returns a short name, the local-node check can fail and exit successfully. A mixed name format can also mark a live fenced node as absent.Normalize each parsed list entry to its short name before comparison.
FENCED_SHORTdoes not currently protect these membership checks.🤖 Prompt for AI Agents