kdotnet-dump 0.1.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.
- kdotnet_dump-0.1.0/.github/workflows/build-test-images.yml +49 -0
- kdotnet_dump-0.1.0/.github/workflows/publish.yml +35 -0
- kdotnet_dump-0.1.0/.github/workflows/test.yml +54 -0
- kdotnet_dump-0.1.0/.gitignore +4 -0
- kdotnet_dump-0.1.0/.vscode/launch.json +17 -0
- kdotnet_dump-0.1.0/LICENSE +21 -0
- kdotnet_dump-0.1.0/PKG-INFO +239 -0
- kdotnet_dump-0.1.0/pyproject.toml +31 -0
- kdotnet_dump-0.1.0/rbac-ephemeral-containers.yaml +12 -0
- kdotnet_dump-0.1.0/readme.md +227 -0
- kdotnet_dump-0.1.0/src/kdotnet_dump/__init__.py +5 -0
- kdotnet_dump-0.1.0/src/kdotnet_dump/cli.py +476 -0
- kdotnet_dump-0.1.0/src/kdotnet_dump/remote.sh +141 -0
- kdotnet_dump-0.1.0/testapp/Dockerfile +17 -0
- kdotnet_dump-0.1.0/testapp/buildall.sh +16 -0
- kdotnet_dump-0.1.0/tests/manifest.yaml +221 -0
- kdotnet_dump-0.1.0/tests/requirements.txt +2 -0
- kdotnet_dump-0.1.0/tests/test_entry.py +108 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Build Test Images
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
packages: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build-test-image:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
dotnet-version:
|
|
16
|
+
- "10.0"
|
|
17
|
+
rt-image:
|
|
18
|
+
- noble
|
|
19
|
+
- alpine3.22
|
|
20
|
+
- noble-chiseled
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
- name: Login to Docker Hub
|
|
25
|
+
uses: docker/login-action@v3
|
|
26
|
+
with:
|
|
27
|
+
registry: ghcr.io
|
|
28
|
+
username: ${{ github.actor }}
|
|
29
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
30
|
+
- name: Set up QEMU
|
|
31
|
+
uses: docker/setup-qemu-action@v3
|
|
32
|
+
- name: Set up Docker Buildx
|
|
33
|
+
uses: docker/setup-buildx-action@v3
|
|
34
|
+
- name: Build and push
|
|
35
|
+
uses: docker/build-push-action@v6
|
|
36
|
+
with:
|
|
37
|
+
context: ./testimages
|
|
38
|
+
push: true
|
|
39
|
+
build-args: |
|
|
40
|
+
rt_image=${{ format('mcr.microsoft.com/dotnet/aspnet:{0}-{1}', matrix.dotnet-version, matrix.rt-image) }}
|
|
41
|
+
sdk_image=${{ format('mcr.microsoft.com/dotnet/sdk:{0}', matrix.dotnet-version) }}
|
|
42
|
+
tags: ghcr.io/gprossliner/kdotnet-dump-testapp:${{ matrix.dotnet-version }}-${{ matrix.rt-image }}
|
|
43
|
+
platforms: linux/amd64,linux/arm64
|
|
44
|
+
annotations: |
|
|
45
|
+
org.opencontainers.image.source="https://github.com/gprossliner/kdotnet-dump"
|
|
46
|
+
org.opencontainers.image.description="Test application for kdotnet-dump integration tests"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Build and Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- 'v*'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build-and-publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
# IMPORTANT: this permission is mandatory for Trusted Publishing
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.14'
|
|
24
|
+
|
|
25
|
+
- name: Install build tools
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install build hatchling hatch-vcs
|
|
29
|
+
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: python -m build
|
|
32
|
+
|
|
33
|
+
- name: Publish to PyPI
|
|
34
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
35
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Integration Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout code
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.11'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: |
|
|
25
|
+
pip install -r tests/requirements.txt
|
|
26
|
+
|
|
27
|
+
- name: Create kind cluster
|
|
28
|
+
uses: helm/kind-action@v1
|
|
29
|
+
with:
|
|
30
|
+
cluster_name: kdotnet-dump-test
|
|
31
|
+
wait: 120s
|
|
32
|
+
|
|
33
|
+
- name: Verify cluster
|
|
34
|
+
run: |
|
|
35
|
+
kubectl cluster-info
|
|
36
|
+
kubectl get nodes
|
|
37
|
+
|
|
38
|
+
- name: Run integration tests
|
|
39
|
+
run: |
|
|
40
|
+
cd tests
|
|
41
|
+
pytest test_entry.py -v -s --tb=short
|
|
42
|
+
|
|
43
|
+
- name: Upload test results
|
|
44
|
+
if: always()
|
|
45
|
+
uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: test-results
|
|
48
|
+
path: tests/test-results/
|
|
49
|
+
if-no-files-found: ignore
|
|
50
|
+
|
|
51
|
+
- name: Cleanup
|
|
52
|
+
if: always()
|
|
53
|
+
run: |
|
|
54
|
+
kind delete cluster --name kdotnet-dump-test || true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
"name": "Python Debugger: Current File with Arguments",
|
|
10
|
+
"type": "debugpy",
|
|
11
|
+
"request": "launch",
|
|
12
|
+
"program": "${file}",
|
|
13
|
+
"console": "integratedTerminal",
|
|
14
|
+
"args": "-l app=test-noble-root --strategy=debug-container --dump-type mini"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Günter Prossliner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kdotnet-dump
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for dumping dotnet processes running in kubernetes pods
|
|
5
|
+
Author-email: Günter Prossliner <6724584+gprossliner@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# kdotnet-dump
|
|
14
|
+
|
|
15
|
+
A tool to create and download .NET process dumps from Kubernetes pods, supporting both standard and hardened (non-root) containers.
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
|
|
19
|
+
Creating .NET dumps from containers running in Kubernetes can be challenging, especially with:
|
|
20
|
+
- **Non-root containers** - Can't install tools or write to most filesystem locations
|
|
21
|
+
- **Restricted Pod Security Standards** - Limited privileges and capabilities
|
|
22
|
+
- **Large dump files** - Can exceed 350MB, causing kubectl cp to fail
|
|
23
|
+
|
|
24
|
+
`kdotnet-dump` solves these problems by:
|
|
25
|
+
1. Using ephemeral debug containers to isolate diagnostic tooling from application containers
|
|
26
|
+
2. Automatically detecting container UID/GID and matching security context
|
|
27
|
+
3. Downloading dotnet-dump dynamically (no need to bake it into images)
|
|
28
|
+
4. Using chunked base64 transfer to handle large files reliably
|
|
29
|
+
5. Working with `/proc/1/root/tmp` to share filesystem between debug and target containers
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/gprossliner/kdotnet-dump.git
|
|
35
|
+
cd kdotnet-dump
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No additional dependencies required - just Python 3 and `kubectl`.
|
|
39
|
+
|
|
40
|
+
## Command Line Arguments
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
usage: entry.py [-h] [--strategy {same-container,debug-container}]
|
|
44
|
+
[-n NAMESPACE] [-l SELECTOR] [--dump-type {mini,heap,triage,full}]
|
|
45
|
+
[--dump-pid DUMP_PID] [--debug-image DEBUG_IMAGE]
|
|
46
|
+
[pod]
|
|
47
|
+
|
|
48
|
+
positional arguments:
|
|
49
|
+
pod Pod name (or use --selector)
|
|
50
|
+
|
|
51
|
+
optional arguments:
|
|
52
|
+
-h, --help Show this help message and exit
|
|
53
|
+
|
|
54
|
+
--strategy {same-container,debug-container}
|
|
55
|
+
Strategy to create the dump (default: debug-container)
|
|
56
|
+
- same-container: Runs dotnet-dump in the target container (requires root)
|
|
57
|
+
- debug-container: Uses ephemeral debug container (works with non-root)
|
|
58
|
+
|
|
59
|
+
-n, --namespace NAMESPACE
|
|
60
|
+
Kubernetes namespace (default: default)
|
|
61
|
+
|
|
62
|
+
-l, --selector SELECTOR
|
|
63
|
+
Label selector to find pod (e.g., app=myapp)
|
|
64
|
+
Use this instead of specifying pod name directly
|
|
65
|
+
|
|
66
|
+
--dump-type {mini,heap,triage,full}
|
|
67
|
+
Dump type (default: mini)
|
|
68
|
+
- mini: Minimal dump with stacks and exception info
|
|
69
|
+
- heap: Includes heap memory
|
|
70
|
+
- triage: Minimal info for initial diagnosis
|
|
71
|
+
- full: Complete memory dump (can be very large)
|
|
72
|
+
|
|
73
|
+
--dump-pid DUMP_PID Process ID to dump (default: 1)
|
|
74
|
+
Usually PID 1 is the main application process
|
|
75
|
+
|
|
76
|
+
--debug-image DEBUG_IMAGE
|
|
77
|
+
Debug container image to use
|
|
78
|
+
(default: mcr.microsoft.com/dotnet/sdk:latest)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Basic Usage - Find pod by label selector
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python3 entry.py -n production -l app=api --dump-type mini
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This will:
|
|
90
|
+
1. Find a pod with label `app=api` in namespace `production`
|
|
91
|
+
2. Create an ephemeral debug container
|
|
92
|
+
3. Download and run dotnet-dump to create a mini dump
|
|
93
|
+
4. Download the dump to `./latest_dump`
|
|
94
|
+
|
|
95
|
+
### Specify pod name directly
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python3 entry.py -n production my-app-pod-12345 --dump-type full
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Create full heap dump for memory analysis
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python3 entry.py -l app=api --dump-type heap
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Use custom debug image (e.g., specific .NET SDK version)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python3 entry.py -l app=api --debug-image mcr.microsoft.com/dotnet/sdk:8.0
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Use same-container strategy (requires root)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python3 entry.py -l app=api --strategy same-container --dump-type mini
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Analyzing Dumps
|
|
120
|
+
|
|
121
|
+
### On Linux
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
dotnet tool install -g dotnet-dump
|
|
125
|
+
dotnet-dump analyze ./latest_dump
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### On macOS (using Docker)
|
|
129
|
+
|
|
130
|
+
macOS doesn't support analyzing Linux dumps natively. Use a Docker container with the correct platform:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# For Apple Silicon (M1/M2/M3), specify linux/amd64 platform
|
|
134
|
+
docker run --rm -it --platform linux/amd64 \
|
|
135
|
+
-v $(pwd):/dumps \
|
|
136
|
+
mcr.microsoft.com/dotnet/sdk:10.0 \
|
|
137
|
+
bash
|
|
138
|
+
|
|
139
|
+
# Inside the container
|
|
140
|
+
dotnet tool install dotnet-dump
|
|
141
|
+
dotnet tool run dotnet-dump analyze /dumps/latest_dump
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Common dotnet-dump commands:
|
|
145
|
+
```
|
|
146
|
+
clrthreads # List managed threads
|
|
147
|
+
clrstack # Show managed stack trace
|
|
148
|
+
dumpheap -stat # Show heap statistics
|
|
149
|
+
sos help # Show all available commands
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## How It Works
|
|
153
|
+
|
|
154
|
+
### Debug Container Strategy (Recommended)
|
|
155
|
+
|
|
156
|
+
1. **Pod Discovery**: Finds target pod using label selector or name
|
|
157
|
+
2. **Container Detection**: Identifies the default container and extracts UID/GID
|
|
158
|
+
3. **Security Context Matching**: Creates debug container with same UID/GID as target
|
|
159
|
+
4. **Shared Process Namespace**: Uses `--share-processes` to access target container's processes
|
|
160
|
+
5. **Dump Creation**: Downloads dotnet-dump and creates dump in `/proc/1/root/tmp/dumps`
|
|
161
|
+
6. **File Transfer**: Uses chunked base64 encoding to reliably download large files
|
|
162
|
+
|
|
163
|
+
### Same Container Strategy
|
|
164
|
+
|
|
165
|
+
Simpler but requires:
|
|
166
|
+
- Container running as root
|
|
167
|
+
- Writable `/tmp` directory
|
|
168
|
+
- Works by installing dotnet-dump directly in the target container
|
|
169
|
+
|
|
170
|
+
## Requirements
|
|
171
|
+
|
|
172
|
+
### Kubernetes Cluster
|
|
173
|
+
- Kubernetes 1.23+ (for ephemeral containers)
|
|
174
|
+
- Kubernetes 1.28+ recommended (for UID/GID detection from status)
|
|
175
|
+
|
|
176
|
+
### RBAC Permissions
|
|
177
|
+
For debug-container strategy, the user needs:
|
|
178
|
+
```yaml
|
|
179
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
180
|
+
kind: ClusterRole
|
|
181
|
+
metadata:
|
|
182
|
+
name: ephemeral-container-access
|
|
183
|
+
labels:
|
|
184
|
+
# This label causes the ClusterRole to be aggregated to the edit / admin role
|
|
185
|
+
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
|
186
|
+
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
|
187
|
+
rules:
|
|
188
|
+
- apiGroups: [""]
|
|
189
|
+
resources: ["pods/ephemeralcontainers"]
|
|
190
|
+
verbs: ["patch", "create", "get"]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Target Container Requirements
|
|
194
|
+
- .NET application running (tested with .NET 8.0, 9.0, 10.0)
|
|
195
|
+
- Writable `/tmp` directory OR mounted emptyDir volume
|
|
196
|
+
- For `readOnlyRootFilesystem: true`, mount emptyDir at `/tmp`
|
|
197
|
+
|
|
198
|
+
## Troubleshooting
|
|
199
|
+
|
|
200
|
+
### "Access to the path '/.dotnet' is denied"
|
|
201
|
+
This occurs in non-root containers. Use `--strategy debug-container` (default).
|
|
202
|
+
|
|
203
|
+
### "Read-only file system" errors
|
|
204
|
+
For containers with `readOnlyRootFilesystem: true`, ensure `/tmp` has an emptyDir mount:
|
|
205
|
+
```yaml
|
|
206
|
+
volumeMounts:
|
|
207
|
+
- name: tmp
|
|
208
|
+
mountPath: /tmp
|
|
209
|
+
volumes:
|
|
210
|
+
- name: tmp
|
|
211
|
+
emptyDir: {}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Large files fail with websocket errors
|
|
215
|
+
The tool automatically uses chunked transfer for reliability. If issues persist, the chunks are 10MB by default and can't be adjusted without code changes.
|
|
216
|
+
|
|
217
|
+
### "No pods found with selector"
|
|
218
|
+
Verify the label selector matches your pods:
|
|
219
|
+
```bash
|
|
220
|
+
kubectl get pods -l app=myapp -n your-namespace
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Testing
|
|
224
|
+
|
|
225
|
+
Integration tests are available in the `tests/` directory:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pip install -r tests/requirements.txt
|
|
229
|
+
pytest tests/test_entry.py -v
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Tests deploy sample applications and verify dump creation across different configurations:
|
|
233
|
+
- Root vs non-root containers
|
|
234
|
+
- Debian vs Alpine vs Chiseled images
|
|
235
|
+
- With and without `readOnlyRootFilesystem`
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling >= 1.26", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kdotnet-dump"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "CLI tool for dumping dotnet processes running in kubernetes pods"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Günter Prossliner", email = "6724584+gprossliner@users.noreply.github.com" }
|
|
11
|
+
]
|
|
12
|
+
readme = "readme.md"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = []
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
license = "MIT"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
kdotnet-dump = "kdotnet_dump.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.version]
|
|
25
|
+
source = "vcs"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/kdotnet_dump"]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
31
|
+
"src/kdotnet_dump/remote.sh" = "kdotnet_dump/remote.sh"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
2
|
+
kind: ClusterRole
|
|
3
|
+
metadata:
|
|
4
|
+
name: ephemeral-containers-edit
|
|
5
|
+
labels:
|
|
6
|
+
# This label causes the ClusterRole to be aggregated to the edit role
|
|
7
|
+
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
|
8
|
+
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
|
9
|
+
rules:
|
|
10
|
+
- apiGroups: [""]
|
|
11
|
+
resources: ["pods/ephemeralcontainers"]
|
|
12
|
+
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# kdotnet-dump
|
|
2
|
+
|
|
3
|
+
A tool to create and download .NET process dumps from Kubernetes pods, supporting both standard and hardened (non-root) containers.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Creating .NET dumps from containers running in Kubernetes can be challenging, especially with:
|
|
8
|
+
- **Non-root containers** - Can't install tools or write to most filesystem locations
|
|
9
|
+
- **Restricted Pod Security Standards** - Limited privileges and capabilities
|
|
10
|
+
- **Large dump files** - Can exceed 350MB, causing kubectl cp to fail
|
|
11
|
+
|
|
12
|
+
`kdotnet-dump` solves these problems by:
|
|
13
|
+
1. Using ephemeral debug containers to isolate diagnostic tooling from application containers
|
|
14
|
+
2. Automatically detecting container UID/GID and matching security context
|
|
15
|
+
3. Downloading dotnet-dump dynamically (no need to bake it into images)
|
|
16
|
+
4. Using chunked base64 transfer to handle large files reliably
|
|
17
|
+
5. Working with `/proc/1/root/tmp` to share filesystem between debug and target containers
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/gprossliner/kdotnet-dump.git
|
|
23
|
+
cd kdotnet-dump
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
No additional dependencies required - just Python 3 and `kubectl`.
|
|
27
|
+
|
|
28
|
+
## Command Line Arguments
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
usage: entry.py [-h] [--strategy {same-container,debug-container}]
|
|
32
|
+
[-n NAMESPACE] [-l SELECTOR] [--dump-type {mini,heap,triage,full}]
|
|
33
|
+
[--dump-pid DUMP_PID] [--debug-image DEBUG_IMAGE]
|
|
34
|
+
[pod]
|
|
35
|
+
|
|
36
|
+
positional arguments:
|
|
37
|
+
pod Pod name (or use --selector)
|
|
38
|
+
|
|
39
|
+
optional arguments:
|
|
40
|
+
-h, --help Show this help message and exit
|
|
41
|
+
|
|
42
|
+
--strategy {same-container,debug-container}
|
|
43
|
+
Strategy to create the dump (default: debug-container)
|
|
44
|
+
- same-container: Runs dotnet-dump in the target container (requires root)
|
|
45
|
+
- debug-container: Uses ephemeral debug container (works with non-root)
|
|
46
|
+
|
|
47
|
+
-n, --namespace NAMESPACE
|
|
48
|
+
Kubernetes namespace (default: default)
|
|
49
|
+
|
|
50
|
+
-l, --selector SELECTOR
|
|
51
|
+
Label selector to find pod (e.g., app=myapp)
|
|
52
|
+
Use this instead of specifying pod name directly
|
|
53
|
+
|
|
54
|
+
--dump-type {mini,heap,triage,full}
|
|
55
|
+
Dump type (default: mini)
|
|
56
|
+
- mini: Minimal dump with stacks and exception info
|
|
57
|
+
- heap: Includes heap memory
|
|
58
|
+
- triage: Minimal info for initial diagnosis
|
|
59
|
+
- full: Complete memory dump (can be very large)
|
|
60
|
+
|
|
61
|
+
--dump-pid DUMP_PID Process ID to dump (default: 1)
|
|
62
|
+
Usually PID 1 is the main application process
|
|
63
|
+
|
|
64
|
+
--debug-image DEBUG_IMAGE
|
|
65
|
+
Debug container image to use
|
|
66
|
+
(default: mcr.microsoft.com/dotnet/sdk:latest)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
### Basic Usage - Find pod by label selector
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python3 entry.py -n production -l app=api --dump-type mini
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This will:
|
|
78
|
+
1. Find a pod with label `app=api` in namespace `production`
|
|
79
|
+
2. Create an ephemeral debug container
|
|
80
|
+
3. Download and run dotnet-dump to create a mini dump
|
|
81
|
+
4. Download the dump to `./latest_dump`
|
|
82
|
+
|
|
83
|
+
### Specify pod name directly
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python3 entry.py -n production my-app-pod-12345 --dump-type full
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Create full heap dump for memory analysis
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python3 entry.py -l app=api --dump-type heap
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Use custom debug image (e.g., specific .NET SDK version)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python3 entry.py -l app=api --debug-image mcr.microsoft.com/dotnet/sdk:8.0
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Use same-container strategy (requires root)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python3 entry.py -l app=api --strategy same-container --dump-type mini
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Analyzing Dumps
|
|
108
|
+
|
|
109
|
+
### On Linux
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
dotnet tool install -g dotnet-dump
|
|
113
|
+
dotnet-dump analyze ./latest_dump
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### On macOS (using Docker)
|
|
117
|
+
|
|
118
|
+
macOS doesn't support analyzing Linux dumps natively. Use a Docker container with the correct platform:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# For Apple Silicon (M1/M2/M3), specify linux/amd64 platform
|
|
122
|
+
docker run --rm -it --platform linux/amd64 \
|
|
123
|
+
-v $(pwd):/dumps \
|
|
124
|
+
mcr.microsoft.com/dotnet/sdk:10.0 \
|
|
125
|
+
bash
|
|
126
|
+
|
|
127
|
+
# Inside the container
|
|
128
|
+
dotnet tool install dotnet-dump
|
|
129
|
+
dotnet tool run dotnet-dump analyze /dumps/latest_dump
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Common dotnet-dump commands:
|
|
133
|
+
```
|
|
134
|
+
clrthreads # List managed threads
|
|
135
|
+
clrstack # Show managed stack trace
|
|
136
|
+
dumpheap -stat # Show heap statistics
|
|
137
|
+
sos help # Show all available commands
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
### Debug Container Strategy (Recommended)
|
|
143
|
+
|
|
144
|
+
1. **Pod Discovery**: Finds target pod using label selector or name
|
|
145
|
+
2. **Container Detection**: Identifies the default container and extracts UID/GID
|
|
146
|
+
3. **Security Context Matching**: Creates debug container with same UID/GID as target
|
|
147
|
+
4. **Shared Process Namespace**: Uses `--share-processes` to access target container's processes
|
|
148
|
+
5. **Dump Creation**: Downloads dotnet-dump and creates dump in `/proc/1/root/tmp/dumps`
|
|
149
|
+
6. **File Transfer**: Uses chunked base64 encoding to reliably download large files
|
|
150
|
+
|
|
151
|
+
### Same Container Strategy
|
|
152
|
+
|
|
153
|
+
Simpler but requires:
|
|
154
|
+
- Container running as root
|
|
155
|
+
- Writable `/tmp` directory
|
|
156
|
+
- Works by installing dotnet-dump directly in the target container
|
|
157
|
+
|
|
158
|
+
## Requirements
|
|
159
|
+
|
|
160
|
+
### Kubernetes Cluster
|
|
161
|
+
- Kubernetes 1.23+ (for ephemeral containers)
|
|
162
|
+
- Kubernetes 1.28+ recommended (for UID/GID detection from status)
|
|
163
|
+
|
|
164
|
+
### RBAC Permissions
|
|
165
|
+
For debug-container strategy, the user needs:
|
|
166
|
+
```yaml
|
|
167
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
168
|
+
kind: ClusterRole
|
|
169
|
+
metadata:
|
|
170
|
+
name: ephemeral-container-access
|
|
171
|
+
labels:
|
|
172
|
+
# This label causes the ClusterRole to be aggregated to the edit / admin role
|
|
173
|
+
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
|
174
|
+
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
|
175
|
+
rules:
|
|
176
|
+
- apiGroups: [""]
|
|
177
|
+
resources: ["pods/ephemeralcontainers"]
|
|
178
|
+
verbs: ["patch", "create", "get"]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Target Container Requirements
|
|
182
|
+
- .NET application running (tested with .NET 8.0, 9.0, 10.0)
|
|
183
|
+
- Writable `/tmp` directory OR mounted emptyDir volume
|
|
184
|
+
- For `readOnlyRootFilesystem: true`, mount emptyDir at `/tmp`
|
|
185
|
+
|
|
186
|
+
## Troubleshooting
|
|
187
|
+
|
|
188
|
+
### "Access to the path '/.dotnet' is denied"
|
|
189
|
+
This occurs in non-root containers. Use `--strategy debug-container` (default).
|
|
190
|
+
|
|
191
|
+
### "Read-only file system" errors
|
|
192
|
+
For containers with `readOnlyRootFilesystem: true`, ensure `/tmp` has an emptyDir mount:
|
|
193
|
+
```yaml
|
|
194
|
+
volumeMounts:
|
|
195
|
+
- name: tmp
|
|
196
|
+
mountPath: /tmp
|
|
197
|
+
volumes:
|
|
198
|
+
- name: tmp
|
|
199
|
+
emptyDir: {}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Large files fail with websocket errors
|
|
203
|
+
The tool automatically uses chunked transfer for reliability. If issues persist, the chunks are 10MB by default and can't be adjusted without code changes.
|
|
204
|
+
|
|
205
|
+
### "No pods found with selector"
|
|
206
|
+
Verify the label selector matches your pods:
|
|
207
|
+
```bash
|
|
208
|
+
kubectl get pods -l app=myapp -n your-namespace
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
212
|
+
|
|
213
|
+
Integration tests are available in the `tests/` directory:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install -r tests/requirements.txt
|
|
217
|
+
pytest tests/test_entry.py -v
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Tests deploy sample applications and verify dump creation across different configurations:
|
|
221
|
+
- Root vs non-root containers
|
|
222
|
+
- Debian vs Alpine vs Chiseled images
|
|
223
|
+
- With and without `readOnlyRootFilesystem`
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT
|