Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions arthas/Plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kind: Plugin
metadata:
name: arthas
spec:
source: arthas
source: arthas-mc-plugin
version: "0.1.0"
selector:
types:
Expand All @@ -14,5 +14,4 @@ spec:
- Kubernetes::ReplicaSet
- Kubernetes::Job
- Kubernetes::CronJob
connections:
kubernetes: {}

63 changes: 63 additions & 0 deletions arthas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Arthas Mission Control Plugin

The Arthas plugin adds JVM diagnostics to Mission Control for Kubernetes workloads. It attaches [Arthas](https://arthas.aliyun.com/) to a Java process running in a selected pod and exposes the Arthas web console, HTTP API, and diagnostic operations from the Mission Control UI.

## What it does

- Adds an **Arthas** tab to Kubernetes catalog items:
- `Kubernetes::Pod`
- `Kubernetes::Deployment`
- `Kubernetes::StatefulSet`
- `Kubernetes::DaemonSet`
- `Kubernetes::ReplicaSet`
- `Kubernetes::Job`
- `Kubernetes::CronJob`
- Resolves the selected workload to a running pod and container.
- Copies/installs `arthas-boot.jar` into the target pod when needed.
- Attaches Arthas to the JVM in the target container.
- Opens Kubernetes port-forwards to the Arthas HTTP console and optional MCP endpoint.
- Proxies the Arthas UI/API through Mission Control.
- Tracks active sessions inside the plugin process.

## Operations

| Operation | Purpose |
| ---------------- | --------------------------------------------------------------- |
| `sessions-list` | List active Arthas sessions. |
| `session-create` | Attach Arthas to the selected workload or pod. |
| `session-delete` | Stop and remove a plugin session and close port-forwards. |
| `pods-list` | List ready pods that can be targeted for the selected workload. |
| `exec` | Execute an Arthas command through the Arthas HTTP API. |

## Kubernetes access

The plugin needs Kubernetes API access to resolve pods, exec into containers, and create port-forwards.

At runtime it first tries to resolve a Mission Control Kubernetes connection via:

```go
host.GetConnectionByType(ctx, sdk.ConnectionTypeKubernetes)
```

The Plugin CRD must map that connection type, for example:

```yaml
spec:
connections:
types:
kubernetes: connection://mission-control/kubernetes-vps-d1140a41
```

If no usable host connection is available, it falls back to `rest.InClusterConfig()`, using the service account of the plugin process.

## Configuration

This plugin currently does not consume `spec.properties`; `Configure()` is a no-op.

## Build

From the repository root:

```sh
task build:plugin:arthas
```
30 changes: 30 additions & 0 deletions arthas/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
Expand All @@ -14,6 +16,34 @@ import (
"github.com/flanksource/incident-commander/plugin/sdk"
)

func (p *ArthasPlugin) httpInvoke(operation string, handler func(context.Context, sdk.InvokeCtx) (any, error)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() { _ = r.Body.Close() }()
params, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(strings.TrimSpace(string(params))) == 0 {
params = []byte("{}")
}
res, err := handler(r.Context(), sdk.InvokeCtx{
Operation: operation,
ParamsJSON: params,
ConfigItemID: sdk.ConfigItemIDFromContext(r.Context()),
Host: sdk.HostClientFromContext(r.Context()),
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(res); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}

func (p *ArthasPlugin) HTTPHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/proxy/", p.httpProxyConsole)
Expand Down
11 changes: 9 additions & 2 deletions arthas/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"embed"
"io/fs"
"net/http"

pluginpb "github.com/flanksource/incident-commander/plugin/proto"
"github.com/flanksource/incident-commander/plugin/sdk"
Expand Down Expand Up @@ -79,15 +80,21 @@ func (p *ArthasPlugin) Operations() []sdk.Operation {
out := make([]sdk.Operation, 0, len(defs))
for _, d := range defs {
if h, ok := handlers[d.Name]; ok {
out = append(out, sdk.Operation{Def: d, Handler: h})
out = append(out, sdk.Operation{Def: d, Handler: h, HTTPHandler: p.httpInvoke(d.Name, h)})
}
}
return out
}

func operationDefs() []*pluginpb.OperationDef {
mk := func(name, desc string) *pluginpb.OperationDef {
return &pluginpb.OperationDef{Name: name, Description: desc, Scope: "config", ResultMime: sdk.ClickyResultMimeType}
return &pluginpb.OperationDef{
Name: name,
Description: desc,
Scope: "config",
ResultMime: sdk.ClickyResultMimeType,
Http: []*pluginpb.HTTPBinding{{Method: http.MethodPost}},
}
}
return []*pluginpb.OperationDef{
mk(OpSessionsList, "List active Arthas sessions in this plugin process."),
Expand Down
7 changes: 3 additions & 4 deletions inspektor-gadget/Plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ kind: Plugin
metadata:
name: inspektor-gadget
spec:
source: inspektor-gadget
version: "0.1.0"
source: inspektor-gadget-mc-plugin
version: v1.0.2
selector:
types:
- Kubernetes::Pod
Expand All @@ -14,5 +14,4 @@ spec:
- Kubernetes::ReplicaSet
- Kubernetes::Job
- Kubernetes::CronJob
connections:
kubernetes: {}

86 changes: 86 additions & 0 deletions inspektor-gadget/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Inspektor Gadget Mission Control Plugin

The Inspektor Gadget plugin adds eBPF-based Kubernetes workload diagnostics to Mission Control.
It can install/check the Inspektor Gadget deployment and run bounded gadget trace sessions against selected Kubernetes resources from the Mission Control UI.

## What it does

- Adds a **Gadget** tab to Kubernetes catalog items:
- `Kubernetes::Pod`
- `Kubernetes::Deployment`
- `Kubernetes::StatefulSet`
- `Kubernetes::DaemonSet`
- `Kubernetes::ReplicaSet`
- `Kubernetes::Job`
- `Kubernetes::CronJob`
- Checks whether Inspektor Gadget is deployed and ready.
- Can generate or apply the Inspektor Gadget Kubernetes manifest.
- Resolves the selected workload to pods, containers, nodes, or selectors.
- Starts eBPF gadget runs through the Inspektor Gadget gadget-service API.
- Uses Kubernetes API port-forwarding to reach gadget-service pods.
- Buffers trace events in the plugin process for UI/API retrieval.

## Supported gadget categories

The plugin exposes a curated list of Inspektor Gadget gadgets, including:

- Runtime traces: exec, signals, OOM kills, malloc, deadlock, tty snoop
- Network traces: DNS, TCP, TCP drops/retransmits, SNI, SSL, bind, tcpdump
- File traces: open, fsnotify, fsslower, mount, file top/snapshot
- Security traces: seccomp audit/advice, Linux capabilities, LSM, module load
- Profiles and snapshots: CPU, block I/O, TCP RTT, process/socket/file snapshots
- Top-style gadgets: process, TCP, file, block I/O, CPU throttling, BPF stats

## Operations

| Operation | Purpose |
| -------------- | --------------------------------------------------------------- |
| `status` | Check Inspektor Gadget deployment readiness. |
| `install-plan` | Return the Kubernetes manifest that would be applied. |
| `install` | Apply the Inspektor Gadget manifest through the Kubernetes API. |
| `traces-list` | List supported gadgets. |
| `trace-start` | Start a bounded trace session for the selected resource. |
| `trace-stop` | Stop a running trace session. |
| `trace-list` | List active and recent trace sessions. |
| `trace-events` | Return buffered events for a trace session. |

## Kubernetes access

The plugin needs Kubernetes API access to inspect/install Inspektor Gadget, resolve workload pods, port-forward to gadget-service, and run traces.

At runtime it first tries to resolve a Mission Control Kubernetes connection via:

```go
host.GetConnectionByType(ctx, sdk.ConnectionTypeKubernetes)
```

The Plugin CRD must map that connection type, for example:

```yaml
spec:
connections:
types:
kubernetes: connection://mission-control/kubernetes-vps-d1140a41
```

If no usable host connection is available, it falls back to `rest.InClusterConfig()`, using the service account of the plugin process.

## Configuration

The plugin supports these `spec.properties` settings:

| Property | Default | Description |
| ----------------- | --------- | -------------------------------------------------------------- |
| `gadgetNamespace` | `gadget` | Namespace where Inspektor Gadget is installed. |
| `gadgetTag` | `v0.52.0` | Inspektor Gadget image tag used for install and gadget images. |
| `maxDurationSec` | `900` | Maximum trace duration in seconds. |
| `maxEvents` | `10000` | Maximum buffered events per session. |
| `maxSessions` | `5` | Maximum concurrent trace sessions. |

## Build

From the repository root:

```sh
task build:plugin:inspektor-gadget
```
30 changes: 30 additions & 0 deletions inspektor-gadget/http.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/flanksource/incident-commander/plugin/sdk"
)

func (p *InspektorGadgetPlugin) httpInvoke(operation string, handler func(context.Context, sdk.InvokeCtx) (any, error)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() { _ = r.Body.Close() }()
params, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(strings.TrimSpace(string(params))) == 0 {
params = []byte("{}")
}
res, err := handler(r.Context(), sdk.InvokeCtx{
Operation: operation,
ParamsJSON: params,
ConfigItemID: sdk.ConfigItemIDFromContext(r.Context()),
Host: sdk.HostClientFromContext(r.Context()),
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(res); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}

func (p *InspektorGadgetPlugin) HTTPHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/sessions/", p.httpSession)
Expand Down
11 changes: 9 additions & 2 deletions inspektor-gadget/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"embed"
"io/fs"
"net/http"

pluginpb "github.com/flanksource/incident-commander/plugin/proto"
"github.com/flanksource/incident-commander/plugin/sdk"
Expand Down Expand Up @@ -130,15 +131,21 @@ func (p *InspektorGadgetPlugin) Operations() []sdk.Operation {
out := make([]sdk.Operation, 0, len(defs))
for _, d := range defs {
if h, ok := handlers[d.Name]; ok {
out = append(out, sdk.Operation{Def: d, Handler: h})
out = append(out, sdk.Operation{Def: d, Handler: h, HTTPHandler: p.httpInvoke(d.Name, h)})
}
}
return out
}

func operationDefs() []*pluginpb.OperationDef {
mk := func(name, desc string) *pluginpb.OperationDef {
return &pluginpb.OperationDef{Name: name, Description: desc, Scope: "config", ResultMime: sdk.ClickyResultMimeType}
return &pluginpb.OperationDef{
Name: name,
Description: desc,
Scope: "config",
ResultMime: sdk.ClickyResultMimeType,
Http: []*pluginpb.HTTPBinding{{Method: http.MethodPost}},
}
}
return []*pluginpb.OperationDef{
mk(OpStatus, "Check Inspektor Gadget deployment readiness through the Kubernetes API."),
Expand Down
2 changes: 1 addition & 1 deletion inspektor-gadget/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Inspektor Gadget</title>
<script type="module" crossorigin src="./assets/index-DEFTdG6-.js"></script>
<script type="module" crossorigin src="./assets/index-ByQx19q4.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-icX4j3sR.css">
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion inspektor-gadget/ui_checksum.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading