Repository navigation
check-operator: optionally skip not-Ready nodes in the Agent upgrade check - #3650
AliDatadog wants to merge 7 commits into
Conversation
…check Prototype for BARX-2137. With AGENT_SKIP_NOT_READY_NODES=true, when the Agent threshold is not reached, the check discounts existing not-Ready nodes whose oldest Agent pod is out of date. Kubernetes cannot replace these pods, so they hold the check until its timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
❌ ErrorsYour PR has failed checks. Please review the issues below and take necessary action before merging. 🚦 2 Pipeline jobs failed
Useful? React with 👍 / 👎 This comment will be updated automatically if new data arrives.🔗 Commit SHA: ad7bf82 | Docs | View more details | Give us feedback! |
… calls when the check cannot pass - Do not skip a not-Ready node if the DaemonSet pod does not tolerate its NoSchedule/NoExecute taints: the DaemonSet status does not count it. - Return early, with no API call, if the check fails even with 10% of the nodes skipped. - Break creation-time ties by pod name, like the controller. - Get the current revision hash once per DaemonSet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a0fde1e8e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Same order as the DaemonSet controller: creation time, then name. | ||
| if oldest == nil || pod.CreationTimestamp.Before(&oldest.CreationTimestamp) || | ||
| (pod.CreationTimestamp.Equal(&oldest.CreationTimestamp) && pod.Name < oldest.Name) { | ||
| oldest = pod |
There was a problem hiding this comment.
Match the DaemonSet controller's phase-aware pod ordering
When a node has multiple counted DaemonSet pods in different phases—for example, an older out-of-date Pending or terminating pod and a newer up-to-date Running pod—Kubernetes's updateDaemonSetStatus orders Running pods ahead of other phases before comparing creation times. This timestamp-only ordering can therefore report the node as outdated even though UpdatedNumberScheduled counts it as updated; the caller then subtracts it from Current without subtracting it from UpToDate, allowing the opt-in upgrade check to pass before the configured threshold is actually reached.
Useful? React with 👍 / 👎.
…e when the revision hash is unknown - AGENT_SKIP_NOT_READY_MAX_NODE_LOOKUPS (default 300, was a fixed 200) and AGENT_SKIP_NOT_READY_MAX_PCT (default 10) replace the hard-coded caps. - Print the effective settings when the check starts. - If a DaemonSet has no pod on the current template generation yet, its current revision hash is unknown. After a template revert, pods that the DaemonSet status counts as updated would then be skipped, so skip none for that DaemonSet. - Document Ready=False nodes as skippable, and add tests for the new settings, the API error paths and the helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| if !agentDone { | ||
| agentDone = o.isAgentDone(status.GetAgentStatus()) | ||
| agentDone = o.isAgentDoneSkippingNotReadyNodes(ctx, status.GetAgentStatus()) |
There was a problem hiding this comment.
If we are worried about performance and/or API server load from querying all "old" Agent pods as well as their node conditions... we could potentially just calculate the "notReadyDiscount" one time and re-use the same discount value for every future reconciliation loop? We can either do it at the very start of the rollout/check OR at the inflection point wherever the max allowed discount is pivotal in the decision to complete the job.
…nership before skipping - Only skip a node that the DaemonSet status counts in Current, like nodeShouldRunDaemonPod: the current template's nodeName, required node affinity and nodeSelector, and taints. The pod on the node may come from an older template that matched, for example before a profile label was added to the node. - Agent DaemonSets can share a selector, so ignore the revision hash of a pod that another DaemonSet owns. - Return the skipped DaemonSet/node pairs, parse each selector once, fetch the current revision hash once per DaemonSet and only when it has candidates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Serve the out-of-date and per-node pod Lists from the API server watch cache (resourceVersion 0). Before Kubernetes 1.31, a List with a field selector otherwise reads every pod of the namespace from etcd. - Compute the fewest skipped nodes that would pass, and stop the API calls as soon as there are too few candidates, or once the count goes over the cap. This halves the calls of a stuck rollout. - Ignore AGENT_SKIP_NOT_READY_MAX_NODE_LOOKUPS values of 0 or less, and store the skip cap as a percent so that the node count rounds exactly. - Match the failing pod List in tests by its options, not by call order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: the skip only applies when the Agent tolerates the not-ready and unreachable NoSchedule taints, follows the current template's targeting, which API calls it makes, and that get on nodes needs a ClusterRole. - Log the skipped DaemonSet/node pairs (up to 20) and the adjusted node count. - Use appsv1.DeprecatedTemplateGeneration, and drop the Options field comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What does this PR do?
Adds opt-in setting to
check-operator upgrade:AGENT_SKIP_NOT_READY_NODES.The check does not count nodes that are not Ready and whose oldest Agent pod is out of date. These nodes (
ReadyconditionFalseorUnknown) usually cannot run the new pod, so today they hold the check until its timeout.Two more settings tune the safety limits:
AGENT_SKIP_NOT_READY_MAX_PCT10AGENT_SKIP_NOT_READY_MAX_NODE_LOOKUPS300Motivation
The check runs as a Helm post-upgrade hook. On some internal clusters, significant % of the Agent pods sit on nodes that are
Unknown(the kubelet stopped reporting). So the check waits much longer than the rollout of the healthy nodes:kubernetes_state.daemonset.*metrics matches the logs. It also shows that the gap stayed at 45–64 pods for 30 min, close to the number of pods on dead nodes.Evidence: https://app.datadoghq.com/notebook/16020842
How it counts
The check removes nodes from
Current(currentNumberScheduled):Terminatingon a dead node still counts. This is the usual case after the controller deletes an unavailable old pod.pod-template-generationor itscontroller-revision-hashmatches. The check reads the current hash from a pod on the current generation. If there is no such pod yet, or the pod belongs to another Agent DaemonSet with the same selector, the hash is unknown, so the check skips no node of that DaemonSet. Otherwise, after a template revert, it could skip pods that the status counts as updated.nodeShouldRunDaemonPod: itsnodeName, required node affinity andnodeSelector, and itsNoSchedule/NoExecutetaints (template tolerations plus the DaemonSet default tolerations). The pod on the node may come from an older template, for example before a profile label was added to the node. The status does not count these nodes either.node.kubernetes.io/not-ready:NoScheduleandnode.kubernetes.io/unreachable:NoScheduletaints that not-Ready nodes get (for exampleoperator: Exists). Otherwise the status already leaves these nodes out.flowchart TD A[Plain threshold reached?] -->|yes| P[Agent done] A -->|no, setting off| W[Wait] A -->|no, setting on| G{Passes with at most MAX_PCT of Current removed? Find the fewest nodes N that would pass} G -->|no| W G -->|yes| B[List out-of-date pods per Agent DaemonSet] B --> C{More than MAX_NODE_LOOKUPS nodes?} C -->|yes| W C -->|no| D[GET each node. Keep not-Ready nodes that the status counts and whose oldest pod is out of date] D --> E{Fewer than N, or more than MAX_PCT of Current?} E -->|yes| W E -->|no| F[Current -= skipped nodes. Check the threshold again]Safety limits:
AGENT_SKIP_NOT_READY_MAX_PCT(default 10%) of the scheduled nodes would be skipped, the check skips none. A large number of not-Ready nodes is more likely an outage than background churn.AGENT_SKIP_NOT_READY_MAX_PCTof the nodes removed, it makes no API call. Early in a rollout, the out-of-date pod list would return almost every Agent pod.AGENT_SKIP_NOT_READY_MAX_NODE_LOOKUPS(default 300) nodes run an out-of-date Agent pod, the check skips none.resourceVersion=0). Before Kubernetes 1.31, a list with a field selector otherwise reads every pod of the namespace from etcd.<DaemonSet>/<node>pairs (up to 20) and the adjusted node count.Additional Notes
listonpodsanddaemonsetsin the DatadogAgent namespace, andgetonnodes(a ClusterRole, as nodes are cluster-scoped). The hook RBAC lives in the chart that deploys the check, not in this repo.Minimum Agent Versions
None.
Describe your test plan
Unit tests.
How to reproduce on a 5-node kind cluster
Goal: prove the upgrade check skips only not-Ready nodes that hold an out-of-date Agent pod, and that
each safety limit (
AGENT_SKIP_NOT_READY_MAX_PCT,AGENT_SKIP_NOT_READY_MAX_NODE_LOOKUPS, missingRBAC, broken rollout) keeps the check strict.
Prerequisites
kind create cluster --name skip-notready).operator-checkimage built from this PR, loaded in kind. SetCHECK_IMAGEto its name (defaultoperator-check:skip-notready).datadog(the Operator itself is unchanged by this PR; any recent version works):Why the threshold settings below: the check runs as a Job like the Helm hook, with
AGENT_COMPLETION_MIN=0(the default10would pass on a 5-node cluster without skipping anything) andCHECK_TIMEOUT_MINUTES=2so failing cases end quickly. With 5 Agent nodes, the default 10% cap allowsint(5 × 0.10) = 0skipped nodes, so the passing cases raiseAGENT_SKIP_NOT_READY_MAX_PCT.Files
dda.yaml:check-rbac.yaml:run-check.sh(runs one check as a Job, prints PASSED/FAILED and its logs):Steps
1. Deploy the Agent
chmod +x run-check.sh kubectl apply -f dda.yaml -f check-rbac.yaml kubectl -n datadog rollout status ds/datadog-agent --timeout=5m kubectl -n datadog get pods -l agent.datadoghq.com/component=agent -o wide # 5 pods, one per nodeBaseline:
./run-check.sh baseline true 25 300→PASSED, nothing skipped.The first log line must list the settings:
skip not-Ready nodes: true, max node lookups: 300, max skipped pct: 25.2. One dead node during a rollout
Expected state: 4 new pods; the old pod on
worker3staysTerminatingforever (its kubelet is gone)../run-check.sh 1-skip-off false 25 300FAILED(timeout)[Agent] nb pods: 5, nb updated pods: 4on every poll./run-check.sh 2-default-10pct true 10 300FAILED./run-check.sh 3-pct-25 true 25 300PASSEDon the first poll[Agent] skipping 1 not-Ready nodes with an out-of-date Agent pod, counting 4 nodes instead of 5: datadog-agent/skip-notready-worker3, thenreached threshold): 4, nb updated pods: 4./run-check.sh 4-lookup-cap-0 true 25 0FAILED1 nodes have an out-of-date Agent pod, above the 0 node lookups cap: not skipping anyFAILEDunable to count not-Ready nodes, not skipping any: ... cannot get resource "nodes"Case 5:
3. Two dead nodes: the skip cap
./run-check.sh 6-two-dead-cap-25 true 25 300 1FAILED2 not-Ready nodes have an out-of-date Agent pod, above the 1 cap (25% of scheduled nodes): not skipping any./run-check.sh 7-two-dead-pct-40 true 40 300PASSEDskipping 2 not-Ready nodes ... counting 3 nodes instead of 5, thenreached threshold): 3, nb updated pods: 3(Case 6 passes
AGENT_COMPLETION_MIN=1so the check gets past its pre-check and actually counts the 2 nodes.)4. A broken rollout still blocks the check
./run-check.sh 8-broken-config-pct-100 true 100 300FAILEDskipping 1 not-Ready nodes(onlyworker3), then stillnb updated pods: 1. Ready nodes with old pods are never skipped, even with a 100% cap.Cleanup
Notes for the reviewer
node.kubernetes.io/unreachable:NoScheduletaint (the DDA usesoperator: Exists). Without it, the DaemonSet status already drops the dead node and there is nothing to skip.Error: unable to list DatadogAgentInternals: context deadline exceeded. That is how the existing check reports its timeout, not a new error.Terminatingpod uses onemaxUnavailableslot. If a cluster has more dead nodes thanmaxUnavailable, the DaemonSet rollout itself stalls, and this setting cannot help.Results on kind (2026-10-23)
skipping 1get nodesRBACskipping 2Cases 0–8 ran on
e8cd9e2a. Cases 1–4 ran again onad7bf82f(affinity, ownership and API-load changes), with the same results, during a template revert: generation-3 pods counted as up to date by their revision hash, and onlyworker3was skipped.🤖 Generated with Claude Code