k8stools 2.2.1__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {k8stools-2.2.1 → k8stools-2.4.0}/CHANGELOG.md +93 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/CLAUDE.md +16 -3
- {k8stools-2.2.1 → k8stools-2.4.0}/PKG-INFO +39 -7
- {k8stools-2.2.1 → k8stools-2.4.0}/README.md +38 -6
- {k8stools-2.2.1 → k8stools-2.4.0}/designs/mock-state-capture.md +10 -2
- {k8stools-2.2.1 → k8stools-2.4.0}/pyproject.toml +1 -1
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/__init__.py +5 -1
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/capture.py +15 -2
- k8stools-2.4.0/src/k8stools/fixtures/otel-demo.json +1282 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/k8s_tools.py +747 -7
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mock_state.py +64 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mock_tools.py +15 -0
- k8stools-2.4.0/tests/test_builtin_fixture.py +216 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_capture.py +1 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_duration_format.py +2 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_events.py +3 -2
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_k8s_tools_realk8s.py +96 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mock_tools.py +5 -5
- k8stools-2.4.0/tests/test_node_conditions.py +132 -0
- k8stools-2.4.0/tests/test_workload_history.py +376 -0
- k8stools-2.4.0/tests/test_workloads.py +233 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/uv.lock +1 -1
- k8stools-2.2.1/src/k8stools/fixtures/otel-demo.json +0 -570
- {k8stools-2.2.1 → k8stools-2.4.0}/.claude/settings.local.json +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/.github/workflows/unit-tests.yml +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/.gitignore +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/.python-version +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/.vscode/mcp.json +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/.vscode/settings.json +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/LICENSE +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/chat-with-cluster.png +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/docs/ROADMAP.md +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/envrc.template +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mcp_client.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/mcp_server.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/src/k8stools/redaction.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/conftest.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_cluster_binding.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_k8s_tools.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_log_decoding.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mcp_client.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_mock_state.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_new_tools.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_redaction.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_replicaset_tool.py +0 -0
- {k8stools-2.2.1 → k8stools-2.4.0}/tests/test_version.py +0 -0
|
@@ -3,6 +3,99 @@
|
|
|
3
3
|
All notable changes to k8stools. Versions follow [semantic versioning](https://semver.org/);
|
|
4
4
|
dates are release-tag dates.
|
|
5
5
|
|
|
6
|
+
## 2.4.0 — 2026-10-05
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **`get_workload_history` tool** (issue #11): what changed in a Deployment,
|
|
10
|
+
StatefulSet or DaemonSet, and when. It lists the retained pod-template revisions
|
|
11
|
+
newest first (a Deployment's ReplicaSets; a StatefulSet's or DaemonSet's
|
|
12
|
+
ControllerRevisions), each compared with the one before it:
|
|
13
|
+
- per container, matched by name: image, resource requests and limits,
|
|
14
|
+
command, args, probes, env vars, `envFrom` and volume mounts;
|
|
15
|
+
- for the pod: volumes, node selector and tolerations;
|
|
16
|
+
- any other changed field is named;
|
|
17
|
+
- template label and annotation changes are listed separately, so a Helm
|
|
18
|
+
version-label bump doesn't bury a memory-limit change;
|
|
19
|
+
- a `kubectl rollout restart` is flagged.
|
|
20
|
+
|
|
21
|
+
Env vars are compared by name and shown by name only, never by value. The
|
|
22
|
+
result also lists the ConfigMaps and Secrets the current template uses, and
|
|
23
|
+
how. ConfigMaps get their age and `last_written`. Secrets are named but never
|
|
24
|
+
read. Each result carries the limits of what history can show. `print_workload_history`
|
|
25
|
+
is its companion.
|
|
26
|
+
- Captures store each workload's history (the tool's result, so no env values are
|
|
27
|
+
written); captures from before 2.4.0 replay an images-only history rebuilt from
|
|
28
|
+
their ReplicaSet records, marked `complete: false`. The `--mock` fixture has a
|
|
29
|
+
history for each of its four workloads.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **A capture needs `list` on `controllerrevisions`** (apps), for StatefulSet and
|
|
33
|
+
DaemonSet histories, and fails without it. The README's role includes it.
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
- `get_replicaset_summaries` no longer says the newest replica set's age is always
|
|
37
|
+
when the deployment last changed: a rollback re-activates an older replica set
|
|
38
|
+
under a new revision number.
|
|
39
|
+
|
|
40
|
+
## 2.3.0 — 2026-10-03
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- **`conditions_since` on nodes** (issue #9). `get_node_summaries` now reports,
|
|
44
|
+
for each node condition, the time since its status last changed (the API's
|
|
45
|
+
`lastTransitionTime`, which was dropped), next to the unchanged `conditions`
|
|
46
|
+
map. A condition with no transition time is left out. `print_node_summaries`
|
|
47
|
+
gains a READY-SINCE column.
|
|
48
|
+
- The tool description says what it does not show: a restart in which the node
|
|
49
|
+
comes back Ready without being marked NotReady or Unknown in between does not
|
|
50
|
+
reset it. On a minikube cluster stopped and started with its control plane,
|
|
51
|
+
`Ready` stayed "True since the node was created", 167 days, across a reboot.
|
|
52
|
+
It points to the node's `Starting`/`Rebooted` events and the start times of
|
|
53
|
+
kube-proxy and the control-plane containers for dating a node's last start.
|
|
54
|
+
- Captures store it as `conditions_since_seconds` and replay advances it like
|
|
55
|
+
every other age; captures from before 2.3.0 replay it as an empty map. (The
|
|
56
|
+
capture format had no handling for a map of durations: without it the values
|
|
57
|
+
would have been replayed as fixed spans that never advanced.)
|
|
58
|
+
- **`get_daemonset_summaries` tool** (issue #10), like `kubectl get daemonsets -o wide`:
|
|
59
|
+
desired, current, ready, up-to-date, available and misscheduled counts, the pod
|
|
60
|
+
template's node selector and images, update strategy, and age. Until now a
|
|
61
|
+
DaemonSet was visible only through its pods, whose `<name>-<5 chars>` names
|
|
62
|
+
can't be told apart from other names. Captures store DaemonSets; captures from
|
|
63
|
+
before 2.3.0 replay none. `print_daemonset_summaries` is its companion.
|
|
64
|
+
- **`owner` on pods.** `get_pod_summaries` reports each pod's controlling owner
|
|
65
|
+
as `"Kind/name"` (e.g. `"DaemonSet/otel-collector-agent"`), so pods can be
|
|
66
|
+
grouped into workloads without guessing from name suffixes. It is the direct
|
|
67
|
+
owner: a Deployment's pods name a replica set, whose `owner_deployment` names
|
|
68
|
+
the Deployment. `None` for a pod nobody manages; captures from before 2.3.0
|
|
69
|
+
replay `None`. `print_pod_summaries` gains an OWNER column.
|
|
70
|
+
- **The built-in `--mock` fixture shows the 2.3.0 fields** and now describes a
|
|
71
|
+
cluster that could exist. It gains an `otel-collector-agent` DaemonSet and its
|
|
72
|
+
pod, pod owners, node condition times, and event counts. Every workload now has
|
|
73
|
+
its pods (`test-deployment`'s three, and `postgres-0`, which a PVC already
|
|
74
|
+
named as mounting it), services select the pods they front, and the
|
|
75
|
+
`postgres` StatefulSet's headless service exists. It also fixes
|
|
76
|
+
contradictions in the old data that an agent would reason from:
|
|
77
|
+
- the `ad` pod ran its previous revision's image;
|
|
78
|
+
- its last instance's status said it ran 2 seconds while its log covered 2
|
|
79
|
+
minutes;
|
|
80
|
+
- its 93 restarts were more than its age allows at the 5-minute backoff cap
|
|
81
|
+
(now 67);
|
|
82
|
+
- several logs were dated 14 months before the capture;
|
|
83
|
+
- the namespaces were older than anything else in the cluster;
|
|
84
|
+
- `test-pod-123`'s spec declared one container while its status had two.
|
|
85
|
+
|
|
86
|
+
The `ad` container now shows the case `previous=True` is for: it restarted 58
|
|
87
|
+
seconds ago, so its current log is JVM startup and the OOM is only in the
|
|
88
|
+
previous one. A new test file keeps the fixture consistent.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
- **A capture needs `list` on `daemonsets`** (apps). `k8s-capture-state` now
|
|
92
|
+
captures DaemonSets, so it fails where the cluster role does not grant them,
|
|
93
|
+
rather than writing a capture that replays as if there were none. Add
|
|
94
|
+
`daemonsets` to the role's apps resources (see README, "Permissions").
|
|
95
|
+
|
|
96
|
+
### Documentation
|
|
97
|
+
- README lists the read-only RBAC rules that cover every tool.
|
|
98
|
+
|
|
6
99
|
## 2.2.1 — 2026-09-29
|
|
7
100
|
|
|
8
101
|
### Documentation
|
|
@@ -14,7 +14,7 @@ src/k8stools/
|
|
|
14
14
|
mcp_server.py - MCP server (stdio or streamable-http transport)
|
|
15
15
|
mcp_client.py - MCP client used for manual testing
|
|
16
16
|
fixtures/
|
|
17
|
-
otel-demo.json - Built-in capture served by `--mock` (
|
|
17
|
+
otel-demo.json - Built-in capture served by `--mock` (hand-maintained, modeled on Minikube + OTel Demo)
|
|
18
18
|
tests/
|
|
19
19
|
test_k8s_tools.py - Unit tests for the original tools (mocked K8s API)
|
|
20
20
|
test_new_tools.py - Unit tests for the 1.1.0 tools (ConfigMaps, CronJobs/Jobs, PVCs, StatefulSets, events, log enhancements)
|
|
@@ -26,6 +26,10 @@ tests/
|
|
|
26
26
|
test_mock_tools.py - Tests for mock_tools module (incl. parity with k8s_tools.TOOLS)
|
|
27
27
|
test_events.py - Event aggregation (count/first_seen), incl. the events.k8s.io fallback
|
|
28
28
|
test_cluster_binding.py - Cluster selection: configure(), the shared binding, get_cluster_info, server flags
|
|
29
|
+
test_node_conditions.py - NodeSummary.conditions_since (condition transition times), incl. capture replay
|
|
30
|
+
test_workloads.py - get_daemonset_summaries and PodSummary.owner, incl. capture replay
|
|
31
|
+
test_builtin_fixture.py - The built-in fixture describes a cluster that could exist (owners, images, ages, log windows, histories)
|
|
32
|
+
test_workload_history.py - get_workload_history: template diffs, revisions, config refs, no env values or Secret reads, replay
|
|
29
33
|
test_log_decoding.py - Log decoding at the API boundary (bytes vs str), `previous` plumbing, log redaction
|
|
30
34
|
test_mcp_client.py - MCP client tests
|
|
31
35
|
test_version.py - Asserts pyproject and package __version__ agree
|
|
@@ -224,6 +228,9 @@ Invariants worth preserving when touching this code:
|
|
|
224
228
|
but read back unchanged. Replay tests that freeze the clock can't tell the two
|
|
225
229
|
apart (frozen elapsed time is zero), so test a new duration field on an
|
|
226
230
|
advancing clock with a `server_start_time` in the past.
|
|
231
|
+
A `dict[str, Duration]` (`NodeSummary.conditions_since`) is stored as a map
|
|
232
|
+
under `<field>_seconds` and each value advances (the codec looks through a
|
|
233
|
+
dict to its value type).
|
|
227
234
|
- **`MockState` must match the real tool's documented behavior**, not just its
|
|
228
235
|
types — filter semantics, result ordering (`get_replicaset_summaries` re-sorts
|
|
229
236
|
at query time), and which failures raise vs. return empty.
|
|
@@ -235,7 +242,13 @@ Invariants worth preserving when touching this code:
|
|
|
235
242
|
synthesizes answers for unknown pod names.
|
|
236
243
|
|
|
237
244
|
The built-in fixture lives under `src/k8stools/` (not `tests/`) because
|
|
238
|
-
`--mock` serves it, so it has to ship in the wheel.
|
|
245
|
+
`--mock` serves it, so it has to ship in the wheel. It is **hand-maintained**, not
|
|
246
|
+
a raw capture (it began as the pre-2.0 mock data, re-encoded): a real capture is
|
|
247
|
+
megabytes, and a test cluster has no CronJobs, Jobs or PVCs to show. When a tool
|
|
248
|
+
gains a field, add it to the fixture by hand, with values that agree with the
|
|
249
|
+
rest of it. `tests/test_builtin_fixture.py` checks that it does (owners exist and
|
|
250
|
+
run the pod's images, nothing predates its namespace, logs fall inside the
|
|
251
|
+
instance that wrote them, restarts fit the pod's age at the backoff cap).
|
|
239
252
|
|
|
240
253
|
Captures are written **indented, and uncompressed by default**, on purpose: these
|
|
241
254
|
files get re-captured and re-committed, and git deltas successive versions of
|
|
@@ -310,4 +323,4 @@ Example `mcp.json`:
|
|
|
310
323
|
|
|
311
324
|
- Read-only tools first; state-modifying tools will be kept in a separate module when added
|
|
312
325
|
- Quality over quantity: well-documented, strongly typed, useful for real monitoring/RCA agents
|
|
313
|
-
- Mock data
|
|
326
|
+
- Mock data modeled on a Minikube instance running the OpenTelemetry Demo app, kept internally consistent by tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: k8stools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Tools and an MCP server for building Kubernetes agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/BenedatLLC/k8stools
|
|
6
6
|
Project-URL: Changelog, https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md
|
|
@@ -277,6 +277,33 @@ replayable file).
|
|
|
277
277
|
What changed in each release is in the
|
|
278
278
|
[changelog](https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md).
|
|
279
279
|
|
|
280
|
+
### Permissions
|
|
281
|
+
|
|
282
|
+
The tools only read, with the `get` and `list` verbs. A read-only ClusterRole
|
|
283
|
+
that covers every tool:
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
287
|
+
kind: ClusterRole
|
|
288
|
+
metadata:
|
|
289
|
+
name: k8stools-reader
|
|
290
|
+
rules:
|
|
291
|
+
- apiGroups: [""]
|
|
292
|
+
resources: [namespaces, nodes, pods, pods/log, events, services, configmaps,
|
|
293
|
+
persistentvolumeclaims]
|
|
294
|
+
verbs: [get, list]
|
|
295
|
+
- apiGroups: [apps]
|
|
296
|
+
resources: [deployments, replicasets, statefulsets, daemonsets, controllerrevisions]
|
|
297
|
+
verbs: [get, list]
|
|
298
|
+
- apiGroups: [batch]
|
|
299
|
+
resources: [jobs, cronjobs]
|
|
300
|
+
verbs: [get, list]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`get_cluster_info` also reads the server version (`/version`), which every
|
|
304
|
+
authenticated user can read by default. No tool reads Secrets: `get_workload_history`
|
|
305
|
+
names the Secrets a workload uses, from its pod template, without reading them.
|
|
306
|
+
|
|
280
307
|
## Current tools
|
|
281
308
|
|
|
282
309
|
These are the tools we define:
|
|
@@ -284,17 +311,19 @@ These are the tools we define:
|
|
|
284
311
|
* `get_cluster_info` - which cluster the tools are answering from: kubeconfig context, API server URL and version, or the capture being replayed
|
|
285
312
|
* `get_namespaces` - get a list of namespaces, like `kubectl get namespace`
|
|
286
313
|
* `get_node_summaries` - get a list of nodes, like `kubectl get nodes -o wide` (includes capacity/allocatable/conditions/taints/labels)
|
|
287
|
-
* `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`
|
|
314
|
+
* `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`, with each pod's controlling owner (e.g. `DaemonSet/otel-collector-agent`)
|
|
288
315
|
* `get_pod_container_statuses` - return the status for each of the container in a pod
|
|
289
316
|
* `get_pod_events` - return the events for a pod
|
|
290
317
|
* `get_pod_spec` - retrieves the spec for a given pod
|
|
291
318
|
* `get_logs_for_pod_and_container` - retrieves logs from a pod and container (supports `tail`, `since_seconds`, and `previous`). See [Previous-instance log semantics](#previous-instance-log-semantics) for when `previous=True` returns the same text as `previous=False` — it is Kubernetes behavior, not a bug.
|
|
292
319
|
* `get_deployment_summaries` - get a list of deployments, like `kubectl get deployments`
|
|
293
320
|
* `get_replicaset_summaries` - get a deployment's replica sets with their revision numbers and images. A deployment's replica sets are its change history: use this to see when a workload last changed and what the change was.
|
|
321
|
+
* `get_workload_history` - what changed in a Deployment, StatefulSet or DaemonSet, and when: each retained revision of its pod template compared with the one before (images, resources, probes, command/args, env var *names*, volumes, scheduling), plus the ConfigMaps and Secrets it references. It states what history cannot show.
|
|
294
322
|
* `get_service_summaries` - get a list of services, like `kubectl get services` (includes `selector`/labels/annotations)
|
|
295
323
|
* `get_configmap_summaries` - get a list of ConfigMaps, like `kubectl get configmaps`
|
|
296
324
|
* `get_configmap` - retrieve the full contents of a single ConfigMap
|
|
297
325
|
* `get_statefulset_summaries` - get a list of StatefulSets, like `kubectl get statefulsets`
|
|
326
|
+
* `get_daemonset_summaries` - get a list of DaemonSets, like `kubectl get daemonsets -o wide` (includes node selector and images)
|
|
298
327
|
* `get_cronjob_summaries` - get a list of CronJobs, like `kubectl get cronjobs`
|
|
299
328
|
* `get_job_summaries` - get a list of Jobs, like `kubectl get jobs`
|
|
300
329
|
* `get_logs_for_job` - retrieve logs from a Job's most-recent pod
|
|
@@ -311,10 +340,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
|
|
|
311
340
|
* `print_pod_events`
|
|
312
341
|
* `print_pod_spec`
|
|
313
342
|
* `print_deployment_summaries`
|
|
343
|
+
* `print_workload_history`
|
|
314
344
|
* `print_service_summaries`
|
|
315
345
|
* `print_configmap_summaries`
|
|
316
346
|
* `print_configmap`
|
|
317
347
|
* `print_statefulset_summaries`
|
|
348
|
+
* `print_daemonset_summaries`
|
|
318
349
|
* `print_cronjob_summaries`
|
|
319
350
|
* `print_job_summaries`
|
|
320
351
|
* `print_pvc_summaries`
|
|
@@ -501,11 +532,12 @@ data: {"jsonrpc":"2.0","id":1,"result":{"tools":[.... long text elided ...]}}
|
|
|
501
532
|
## Mock tools
|
|
502
533
|
When building agents, it can be helpful to test them against *mock* versions that do
|
|
503
534
|
not go against a real cluster, but return realistic values. The module
|
|
504
|
-
`k8stools.mock_tools` does just that. The data
|
|
505
|
-
|
|
535
|
+
`k8stools.mock_tools` does just that. The data is a small, hand-maintained
|
|
536
|
+
cluster modeled on a Minikube instance running the
|
|
506
537
|
[Open Telemetry Demo](https://github.com/open-telemetry/opentelemetry-demo)
|
|
507
|
-
application
|
|
508
|
-
|
|
538
|
+
application: a crash-looping `ad` service mid-upgrade, a DaemonSet, a CronJob and
|
|
539
|
+
its Job, a StatefulSet with PVCs, and a few events. When running the MCP server,
|
|
540
|
+
this may be enabled by using the `--mock` command line option.
|
|
509
541
|
|
|
510
542
|
The mock serves a *capture* — a JSON snapshot of one cluster — rather than a set of
|
|
511
543
|
per-tool canned answers, so the tools agree with each other: a pod that is not in the
|
|
@@ -580,7 +612,7 @@ Everything the tools can read, so that every tool answers on replay:
|
|
|
580
612
|
|---|---|
|
|
581
613
|
| Cluster | context name, API server URL and version, so `get_cluster_info` answers on replay (not the kubeconfig path, which names a file on the capturing machine) |
|
|
582
614
|
| Cluster-wide | namespaces, nodes |
|
|
583
|
-
| Per namespace | deployments, replica sets, services, statefulsets, cronjobs, jobs, PVCs, events |
|
|
615
|
+
| Per namespace | deployments, replica sets, services, statefulsets, daemonsets, cronjobs, jobs, PVCs, events, and each workload's change history (`get_workload_history`'s result, so no env values are written) |
|
|
584
616
|
| ConfigMaps | summary **and** full contents, so `get_configmap` works too |
|
|
585
617
|
| Per pod | summary, labels, container statuses, spec, and per-container logs |
|
|
586
618
|
|
|
@@ -57,6 +57,33 @@ replayable file).
|
|
|
57
57
|
What changed in each release is in the
|
|
58
58
|
[changelog](https://github.com/BenedatLLC/k8stools/blob/main/CHANGELOG.md).
|
|
59
59
|
|
|
60
|
+
### Permissions
|
|
61
|
+
|
|
62
|
+
The tools only read, with the `get` and `list` verbs. A read-only ClusterRole
|
|
63
|
+
that covers every tool:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
67
|
+
kind: ClusterRole
|
|
68
|
+
metadata:
|
|
69
|
+
name: k8stools-reader
|
|
70
|
+
rules:
|
|
71
|
+
- apiGroups: [""]
|
|
72
|
+
resources: [namespaces, nodes, pods, pods/log, events, services, configmaps,
|
|
73
|
+
persistentvolumeclaims]
|
|
74
|
+
verbs: [get, list]
|
|
75
|
+
- apiGroups: [apps]
|
|
76
|
+
resources: [deployments, replicasets, statefulsets, daemonsets, controllerrevisions]
|
|
77
|
+
verbs: [get, list]
|
|
78
|
+
- apiGroups: [batch]
|
|
79
|
+
resources: [jobs, cronjobs]
|
|
80
|
+
verbs: [get, list]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`get_cluster_info` also reads the server version (`/version`), which every
|
|
84
|
+
authenticated user can read by default. No tool reads Secrets: `get_workload_history`
|
|
85
|
+
names the Secrets a workload uses, from its pod template, without reading them.
|
|
86
|
+
|
|
60
87
|
## Current tools
|
|
61
88
|
|
|
62
89
|
These are the tools we define:
|
|
@@ -64,17 +91,19 @@ These are the tools we define:
|
|
|
64
91
|
* `get_cluster_info` - which cluster the tools are answering from: kubeconfig context, API server URL and version, or the capture being replayed
|
|
65
92
|
* `get_namespaces` - get a list of namespaces, like `kubectl get namespace`
|
|
66
93
|
* `get_node_summaries` - get a list of nodes, like `kubectl get nodes -o wide` (includes capacity/allocatable/conditions/taints/labels)
|
|
67
|
-
* `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`
|
|
94
|
+
* `get_pod_summaries` - get a list of pods, like `kubectl get pods -o wide`, with each pod's controlling owner (e.g. `DaemonSet/otel-collector-agent`)
|
|
68
95
|
* `get_pod_container_statuses` - return the status for each of the container in a pod
|
|
69
96
|
* `get_pod_events` - return the events for a pod
|
|
70
97
|
* `get_pod_spec` - retrieves the spec for a given pod
|
|
71
98
|
* `get_logs_for_pod_and_container` - retrieves logs from a pod and container (supports `tail`, `since_seconds`, and `previous`). See [Previous-instance log semantics](#previous-instance-log-semantics) for when `previous=True` returns the same text as `previous=False` — it is Kubernetes behavior, not a bug.
|
|
72
99
|
* `get_deployment_summaries` - get a list of deployments, like `kubectl get deployments`
|
|
73
100
|
* `get_replicaset_summaries` - get a deployment's replica sets with their revision numbers and images. A deployment's replica sets are its change history: use this to see when a workload last changed and what the change was.
|
|
101
|
+
* `get_workload_history` - what changed in a Deployment, StatefulSet or DaemonSet, and when: each retained revision of its pod template compared with the one before (images, resources, probes, command/args, env var *names*, volumes, scheduling), plus the ConfigMaps and Secrets it references. It states what history cannot show.
|
|
74
102
|
* `get_service_summaries` - get a list of services, like `kubectl get services` (includes `selector`/labels/annotations)
|
|
75
103
|
* `get_configmap_summaries` - get a list of ConfigMaps, like `kubectl get configmaps`
|
|
76
104
|
* `get_configmap` - retrieve the full contents of a single ConfigMap
|
|
77
105
|
* `get_statefulset_summaries` - get a list of StatefulSets, like `kubectl get statefulsets`
|
|
106
|
+
* `get_daemonset_summaries` - get a list of DaemonSets, like `kubectl get daemonsets -o wide` (includes node selector and images)
|
|
78
107
|
* `get_cronjob_summaries` - get a list of CronJobs, like `kubectl get cronjobs`
|
|
79
108
|
* `get_job_summaries` - get a list of Jobs, like `kubectl get jobs`
|
|
80
109
|
* `get_logs_for_job` - retrieve logs from a Job's most-recent pod
|
|
@@ -91,10 +120,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
|
|
|
91
120
|
* `print_pod_events`
|
|
92
121
|
* `print_pod_spec`
|
|
93
122
|
* `print_deployment_summaries`
|
|
123
|
+
* `print_workload_history`
|
|
94
124
|
* `print_service_summaries`
|
|
95
125
|
* `print_configmap_summaries`
|
|
96
126
|
* `print_configmap`
|
|
97
127
|
* `print_statefulset_summaries`
|
|
128
|
+
* `print_daemonset_summaries`
|
|
98
129
|
* `print_cronjob_summaries`
|
|
99
130
|
* `print_job_summaries`
|
|
100
131
|
* `print_pvc_summaries`
|
|
@@ -281,11 +312,12 @@ data: {"jsonrpc":"2.0","id":1,"result":{"tools":[.... long text elided ...]}}
|
|
|
281
312
|
## Mock tools
|
|
282
313
|
When building agents, it can be helpful to test them against *mock* versions that do
|
|
283
314
|
not go against a real cluster, but return realistic values. The module
|
|
284
|
-
`k8stools.mock_tools` does just that. The data
|
|
285
|
-
|
|
315
|
+
`k8stools.mock_tools` does just that. The data is a small, hand-maintained
|
|
316
|
+
cluster modeled on a Minikube instance running the
|
|
286
317
|
[Open Telemetry Demo](https://github.com/open-telemetry/opentelemetry-demo)
|
|
287
|
-
application
|
|
288
|
-
|
|
318
|
+
application: a crash-looping `ad` service mid-upgrade, a DaemonSet, a CronJob and
|
|
319
|
+
its Job, a StatefulSet with PVCs, and a few events. When running the MCP server,
|
|
320
|
+
this may be enabled by using the `--mock` command line option.
|
|
289
321
|
|
|
290
322
|
The mock serves a *capture* — a JSON snapshot of one cluster — rather than a set of
|
|
291
323
|
per-tool canned answers, so the tools agree with each other: a pod that is not in the
|
|
@@ -360,7 +392,7 @@ Everything the tools can read, so that every tool answers on replay:
|
|
|
360
392
|
|---|---|
|
|
361
393
|
| Cluster | context name, API server URL and version, so `get_cluster_info` answers on replay (not the kubeconfig path, which names a file on the capturing machine) |
|
|
362
394
|
| Cluster-wide | namespaces, nodes |
|
|
363
|
-
| Per namespace | deployments, replica sets, services, statefulsets, cronjobs, jobs, PVCs, events |
|
|
395
|
+
| Per namespace | deployments, replica sets, services, statefulsets, daemonsets, cronjobs, jobs, PVCs, events, and each workload's change history (`get_workload_history`'s result, so no env values are written) |
|
|
364
396
|
| ConfigMaps | summary **and** full contents, so `get_configmap` works too |
|
|
365
397
|
| Per pod | summary, labels, container statuses, spec, and per-container logs |
|
|
366
398
|
|
|
@@ -151,6 +151,8 @@ A single JSON file per capture. Top-level keys:
|
|
|
151
151
|
"services": [ ... ],
|
|
152
152
|
"configmaps": [ ... ],
|
|
153
153
|
"statefulsets": [ ... ],
|
|
154
|
+
"daemonsets": [ ... ],
|
|
155
|
+
"workload_histories": [ ... ],
|
|
154
156
|
"cronjobs": [ ... ],
|
|
155
157
|
"jobs": [ ... ],
|
|
156
158
|
"pvcs": [ ... ],
|
|
@@ -162,7 +164,12 @@ The `configmaps`..`events` keys were added alongside the 1.1.0 tool batch
|
|
|
162
164
|
(ConfigMaps, StatefulSets, CronJobs/Jobs, PVCs, and cluster-wide events). See
|
|
163
165
|
[Resource records added in 1.1.0](#resource-records-added-in-110) below for their
|
|
164
166
|
shapes. `replicasets` was added alongside the 1.2.0 tool batch — see
|
|
165
|
-
[Resource records added in 1.2.0](#resource-records-added-in-120).
|
|
167
|
+
[Resource records added in 1.2.0](#resource-records-added-in-120). `daemonsets` was added
|
|
168
|
+
in 2.3.0: `DaemonSetSummary` fields with `age` → `age_seconds`; captures without it
|
|
169
|
+
replay no DaemonSets. `workload_histories` was added in 2.4.0: one encoded
|
|
170
|
+
`WorkloadHistory` (the tool's result, not raw pod templates, so env values never
|
|
171
|
+
reach the file) per Deployment, StatefulSet and DaemonSet. Captures without it
|
|
172
|
+
replay an images-only history rebuilt from `replicasets`, with `complete: false`.
|
|
166
173
|
|
|
167
174
|
`redacted` records which mode the capture was taken in — see
|
|
168
175
|
[Redaction](#redaction). It exists so a consumer can tell without guessing;
|
|
@@ -448,6 +455,7 @@ class MockState:
|
|
|
448
455
|
def get_configmap_summaries(self, namespace: str | None = None) -> list[ConfigMapSummary]: ...
|
|
449
456
|
def get_configmap(self, name: str, namespace: str) -> dict[str, Any]: ...
|
|
450
457
|
def get_statefulset_summaries(self, namespace: str | None = None) -> list[StatefulSetSummary]: ...
|
|
458
|
+
def get_daemonset_summaries(self, namespace: str | None = None) -> list[DaemonSetSummary]: ... # 2.3.0
|
|
451
459
|
def get_cronjob_summaries(self, namespace: str | None = None) -> list[CronJobSummary]: ...
|
|
452
460
|
def get_job_summaries(self, namespace: str | None = None) -> list[JobSummary]: ...
|
|
453
461
|
def get_logs_for_job(self, job_name: str, namespace: str, container_name: str | None = None,
|
|
@@ -538,7 +546,7 @@ Behavior:
|
|
|
538
546
|
(`configmaps`, `statefulsets`, `cronjobs`, `jobs`, `pvcs`), the 1.2.0 addition
|
|
539
547
|
(`replicasets`), and a namespace/cluster-wide `get_events` sweep. If
|
|
540
548
|
`--namespace` is given, only captures namespaced resources (pods/deployments/
|
|
541
|
-
replicasets/services/configmaps/statefulsets/cronjobs/jobs/pvcs/events) in those
|
|
549
|
+
replicasets/services/configmaps/statefulsets/daemonsets/cronjobs/jobs/pvcs/events) in those
|
|
542
550
|
namespaces; namespaces and nodes are always captured in full.
|
|
543
551
|
3. For each pod and each container, calls `get_logs_for_pod_and_container` unless `--no-logs`. Default log cap: 1000 lines (override with `--max-log-lines`).
|
|
544
552
|
4. For each container whose `restart_count > 0`, calls the same tool with
|
|
@@ -34,10 +34,12 @@ These are the tools we define:
|
|
|
34
34
|
* get_logs_for_pod_and_container - retrieves logs from a pod and container (supports tail/since/previous)
|
|
35
35
|
* get_deployment_summaries - get a list of deployments, like `kubectl get deployments`
|
|
36
36
|
* get_replicaset_summaries - get a deployment's revision history, like `kubectl get replicasets`
|
|
37
|
+
* get_workload_history - a workload's pod-template revisions, compared, and the config it references
|
|
37
38
|
* get_service_summaries - get a list of services, like `kubectl get services`
|
|
38
39
|
* get_configmap_summaries - get a list of ConfigMaps, like `kubectl get configmaps`
|
|
39
40
|
* get_configmap - retrieve the full contents of a single ConfigMap
|
|
40
41
|
* get_statefulset_summaries - get a list of StatefulSets, like `kubectl get statefulsets`
|
|
42
|
+
* get_daemonset_summaries - get a list of DaemonSets, like `kubectl get daemonsets`
|
|
41
43
|
* get_cronjob_summaries - get a list of CronJobs, like `kubectl get cronjobs`
|
|
42
44
|
* get_job_summaries - get a list of Jobs, like `kubectl get jobs`
|
|
43
45
|
* get_logs_for_job - retrieve logs from a Job's most-recent pod
|
|
@@ -55,10 +57,12 @@ We also define a set of associated "print_" functions that are helpful in debugg
|
|
|
55
57
|
* print_pod_spec
|
|
56
58
|
* print_deployment_summaries
|
|
57
59
|
* print_replicaset_summaries
|
|
60
|
+
* print_workload_history
|
|
58
61
|
* print_service_summaries
|
|
59
62
|
* print_configmap_summaries
|
|
60
63
|
* print_configmap
|
|
61
64
|
* print_statefulset_summaries
|
|
65
|
+
* print_daemonset_summaries
|
|
62
66
|
* print_cronjob_summaries
|
|
63
67
|
* print_job_summaries
|
|
64
68
|
* print_pvc_summaries
|
|
@@ -78,4 +82,4 @@ the built-in OTel Demo snapshot by default, or your own via load_mock_state() or
|
|
|
78
82
|
the server's --state-file option.
|
|
79
83
|
"""
|
|
80
84
|
|
|
81
|
-
__version__ = "2.
|
|
85
|
+
__version__ = "2.4.0"
|
|
@@ -185,6 +185,8 @@ def capture_state(namespaces: Optional[list[str]] = None,
|
|
|
185
185
|
"services": [],
|
|
186
186
|
"configmaps": [],
|
|
187
187
|
"statefulsets": [],
|
|
188
|
+
"daemonsets": [],
|
|
189
|
+
"workload_histories": [],
|
|
188
190
|
"cronjobs": [],
|
|
189
191
|
"jobs": [],
|
|
190
192
|
"pvcs": [],
|
|
@@ -192,10 +194,21 @@ def capture_state(namespaces: Optional[list[str]] = None,
|
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
for ns in target_namespaces:
|
|
195
|
-
|
|
197
|
+
deployments = k8s_tools.get_deployment_summaries(ns)
|
|
198
|
+
state["deployments"] += _encode_all(redactor(deployments), captured_at)
|
|
196
199
|
state["replicasets"] += _encode_all(redactor(k8s_tools.get_replicaset_summaries(ns)), captured_at)
|
|
197
200
|
state["services"] += _encode_all(redactor(k8s_tools.get_service_summaries(ns)), captured_at)
|
|
198
|
-
|
|
201
|
+
statefulsets = k8s_tools.get_statefulset_summaries(ns)
|
|
202
|
+
state["statefulsets"] += _encode_all(redactor(statefulsets), captured_at)
|
|
203
|
+
daemonsets = k8s_tools.get_daemonset_summaries(ns)
|
|
204
|
+
state["daemonsets"] += _encode_all(redactor(daemonsets), captured_at)
|
|
205
|
+
# The tool's own result, not raw templates: replay serves it as is, and
|
|
206
|
+
# env values never reach the file.
|
|
207
|
+
for kind, workloads in (("Deployment", deployments), ("StatefulSet", statefulsets),
|
|
208
|
+
("DaemonSet", daemonsets)):
|
|
209
|
+
for w in workloads:
|
|
210
|
+
state["workload_histories"].append(encode_model(
|
|
211
|
+
redactor(k8s_tools.get_workload_history(w.name, ns, kind)), captured_at))
|
|
199
212
|
state["cronjobs"] += _encode_all(redactor(k8s_tools.get_cronjob_summaries(ns)), captured_at)
|
|
200
213
|
state["jobs"] += _encode_all(redactor(k8s_tools.get_job_summaries(ns)), captured_at)
|
|
201
214
|
state["pvcs"] += _encode_all(redactor(k8s_tools.get_pvc_summaries(ns)), captured_at)
|