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
6 changes: 4 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ jobs:
--charts charts/api7 \
--charts charts/gateway \
--charts charts/ingress-controller \
--charts charts/aisix-cp'
--charts charts/aisix-cp \
--charts charts/ngxdig'

- name: Verify Chart.lock files
run: |
Expand Down Expand Up @@ -76,7 +77,8 @@ jobs:
&& helm repo add jaegertracing https://jaegertracing.github.io/helm-charts \
&& ct install \
--charts charts/api7 \
--charts charts/ingress-controller'
--charts charts/ingress-controller \
--charts charts/ngxdig'
Comment thread
jarvis9443 marked this conversation as resolved.

- name: Test gateway charts
run: |
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This repo (`api7/api7-helm-chart`) holds the Helm charts for API7 EE components
| `api7-ingress-controller` | `charts/ingress-controller` | no — independent product version |
| `developer-portal-fe` | `charts/developer-portal-fe` | no — independent product version |
| `aisix-cp` (AISIX private-deployment control plane) | `charts/aisix-cp` | no — independent product version (source of truth: `api7/AISIX-Cloud` `helm/aisix-cp`) |
| `ngxdig` (eBPF flame-graph / diagnosis agent, DaemonSet) | `charts/ngxdig` | no — independent product version (source of truth: `api7/ngx-flame`) |
Comment thread
jarvis9443 marked this conversation as resolved.

## Multi-line maintenance model

Expand Down
23 changes: 23 additions & 0 deletions charts/ngxdig/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
21 changes: 21 additions & 0 deletions charts/ngxdig/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: v2
name: ngxdig
description: eBPF on-CPU flame graphs and guided CPU/memory/latency diagnosis for OpenResty/Nginx, deployed as a per-node collector
type: application
version: 0.1.0
appVersion: "0.1.0"
keywords:
- ebpf
- profiling
- flamegraph
- openresty
- nginx
- apisix
- diagnostics
home: https://github.com/api7/ngx-flame
sources:
- https://github.com/api7/ngx-flame
maintainers:
- name: API7
email: support@api7.ai
url: https://api7.ai
138 changes: 138 additions & 0 deletions charts/ngxdig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# ngxdig

![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square)

eBPF on-CPU flame graphs and guided CPU/memory/latency diagnosis for OpenResty/Nginx, deployed as a per-node collector

**Homepage:** <https://github.com/api7/ngx-flame>

## Maintainers

| Name | Email | Url |
| ---- | ------ | --- |
| API7 | <support@api7.ai> | <https://api7.ai> |

## Source Code

* <https://github.com/api7/ngx-flame>

## Install

```sh
helm repo add api7 https://charts.api7.ai
helm repo update

helm install ngxdig api7/ngxdig --namespace ngxdig --create-namespace
```

ngxdig is deployed as a DaemonSet — one collector Pod per node — so it can
profile the OpenResty/Nginx (and APISIX) worker processes running on that node.

## Accessing the debug UI

The chart always creates a Service for the UI. Most deployments run a single
collector — one node, or the DaemonSet pinned to one node (see below) — so the
`ClusterIP` points at that one Pod.

Quick local access — port-forward to the collector Pod. Scope the selector to
this release, and on a multi-node install pick the Pod on the node you want with
a `spec.nodeName` field selector:

```sh
NODE=<node-name> # from `kubectl get nodes`; omit the field selector if single-node
POD=$(kubectl -n ngxdig get pod \
-l app.kubernetes.io/name=ngxdig,app.kubernetes.io/instance=ngxdig \
--field-selector spec.nodeName=$NODE \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n ngxdig port-forward $POD 8080:8080
# then open http://127.0.0.1:8080/
```

Node-IP access — set the Service to `NodePort`; with `externalTrafficPolicy:
Local`, `<nodeIP>:<nodePort>` reaches the collector on that same node:

```yaml
# values.yaml
service:
type: NodePort
externalTrafficPolicy: Local
# nodePort: 30080 # optional; auto-assigned when omitted
```

The UI is not a hardened multi-user service — keep it on a trusted network and
prefer a port-forward over a public ingress. Note the `ClusterIP` Service is not
node-specific: on a single-node or node-pinned install it points at the one
collector, but when the DaemonSet spans several nodes it load-balances across
collectors and each collector only sees its own node's processes — so target a
specific node's Pod (the `spec.nodeName` selector above) rather than the Service.

## Run on specific node(s) only

Usually you only want a collector on the node running the workload you are
debugging, not every node. Restrict the DaemonSet with a `nodeSelector`; the
controller then creates a Pod only on matching nodes.

Pin to a single node by its built-in `kubernetes.io/hostname` label (the value
is the node name shown by `kubectl get nodes`):

```sh
helm install ngxdig api7/ngxdig --namespace ngxdig --create-namespace \
--set nodeSelector."kubernetes\.io/hostname"=<node-name>
```

To target a group of nodes, label them first and select by that label:

```sh
kubectl label node <node-a> <node-b> ngxdig=enabled
```

```yaml
# values.yaml
nodeSelector:
ngxdig: enabled
```

For rules that `nodeSelector` cannot express, use `affinity` instead. Note that
for a DaemonSet you should not set the Pod's `.spec.nodeName` directly — node
placement is owned by the DaemonSet controller, and `nodeSelector`/`affinity`
is the supported way to constrain it.

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | Affinity rules for the DaemonSet Pods |
| containerPort | int | `8080` | Port the ngxdig UI listens on inside the container |
| extraArgs | list | `[]` | Extra arguments appended to the `ngxdig ui` command |
| extraEnvVars | list | `[]` | Extra environment variables for the collector container |
| extraVolumeMounts | list | `[]` | Extra volume mounts for the collector container |
| extraVolumes | list | `[]` | Extra volumes for the collector Pod |
| fullnameOverride | string | `""` | Override the fully qualified release name |
| hostBtf.enabled | bool | `true` | Mount the node's kernel BTF read-only. Required by the eBPF collector to resolve kernel types without DWARF |
| hostBtf.path | string | `"/sys/kernel/btf"` | Host path to the kernel BTF directory |
| hostPID | bool | `true` | Share the host PID namespace so the collector can see Nginx/OpenResty worker processes elsewhere on the node. Required to profile targets outside the collector's own Pod |
| image.pullPolicy | string | `"Always"` | Image pull policy. Defaults to Always because the image tag is the moving `latest`, so nodes must re-pull to pick up newer builds |
| image.repository | string | `"api7/ngxdig"` | ngxdig image repository |
| image.tag | string | `"latest"` | Image tag. Defaults to the chart appVersion when left empty |
| imagePullSecrets | list | `[]` | Image pull secrets for a private registry |
| livenessProbe | object | `{"httpGet":{"path":"/","port":"http"},"initialDelaySeconds":10,"periodSeconds":15}` | Liveness probe for the collector container |
| nameOverride | string | `""` | Override the chart name |
| nodeSelector | object | `{}` | Node selector for the DaemonSet Pods |
| output.path | string | `"/out"` | Directory inside the container where run artifacts are written |
| output.sizeLimit | string | `""` | sizeLimit for the artifact emptyDir volume. No limit when empty |
| podAnnotations | object | `{}` | Additional annotations for the Pods |
| podLabels | object | `{}` | Additional labels for the Pods |
| podSecurityContext | object | `{}` | Pod-level security context |
| readinessProbe | object | `{"httpGet":{"path":"/","port":"http"},"initialDelaySeconds":5,"periodSeconds":10}` | Readiness probe for the collector container |
| resources | object | `{}` | Resource requests and limits for the collector container |
| securityContext | object | `{"appArmorProfile":{"type":"Unconfined"},"capabilities":{"add":["BPF","PERFMON","SYS_PTRACE","SYS_ADMIN"]},"runAsUser":0,"seccompProfile":{"type":"Unconfined"}}` | Container security context. The defaults grant exactly what the eBPF collector needs: run as root, and the CAP_BPF / CAP_PERFMON / CAP_SYS_PTRACE / CAP_SYS_ADMIN capabilities with unconfined seccomp/AppArmor so BPF, perf events, ptrace, and the perf_uprobe PMU attachments used by `cpu-on` and `latency` are all permitted. CAP_SYS_ADMIN is the smallest addition that satisfies the uprobe perf_event check — `--privileged` is not required |
| service.annotations | object | `{}` | Service annotations |
| service.externalTrafficPolicy | string | `"Local"` | externalTrafficPolicy for NodePort/LoadBalancer. Local keeps traffic on the receiving node, so <nodeIP>:<nodePort> reaches that node's own collector instead of being load-balanced to another node |
| service.nodePort | string | `""` | Static node port for type NodePort. Auto-assigned when empty |
| service.port | int | `8080` | Service port |
| service.type | string | `"ClusterIP"` | Service type (ClusterIP, NodePort, or LoadBalancer) |
| serviceAccount.annotations | object | `{}` | Annotations to add to the ServiceAccount |
| serviceAccount.create | bool | `true` | Create a ServiceAccount for the collector |
| serviceAccount.name | string | `""` | Name of the ServiceAccount to use. Generated from the fullname when empty |
| tolerations | list | `[]` | Tolerations for the DaemonSet Pods (e.g. to also run on control-plane nodes) |
| updateStrategy | object | `{}` | Update strategy for the DaemonSet |
94 changes: 94 additions & 0 deletions charts/ngxdig/README.md.gotmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{{ template "chart.header" . }}

{{ template "chart.badgesSection" . }}

{{ template "chart.description" . }}

{{ template "chart.homepageLine" . }}

{{ template "chart.maintainersSection" . }}

{{ template "chart.sourcesSection" . }}

## Install

```sh
helm repo add api7 https://charts.api7.ai
helm repo update

helm install ngxdig api7/ngxdig --namespace ngxdig --create-namespace
```

ngxdig is deployed as a DaemonSet — one collector Pod per node — so it can
profile the OpenResty/Nginx (and APISIX) worker processes running on that node.

## Accessing the debug UI

The chart always creates a Service for the UI. Most deployments run a single
collector — one node, or the DaemonSet pinned to one node (see below) — so the
`ClusterIP` points at that one Pod.

Quick local access — port-forward to the collector Pod. Scope the selector to
this release, and on a multi-node install pick the Pod on the node you want with
a `spec.nodeName` field selector:

```sh
NODE=<node-name> # from `kubectl get nodes`; omit the field selector if single-node
POD=$(kubectl -n ngxdig get pod \
-l app.kubernetes.io/name=ngxdig,app.kubernetes.io/instance=ngxdig \
--field-selector spec.nodeName=$NODE \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n ngxdig port-forward $POD 8080:8080
# then open http://127.0.0.1:8080/
```

Node-IP access — set the Service to `NodePort`; with `externalTrafficPolicy:
Local`, `<nodeIP>:<nodePort>` reaches the collector on that same node:

```yaml
# values.yaml
service:
type: NodePort
externalTrafficPolicy: Local
# nodePort: 30080 # optional; auto-assigned when omitted
```

The UI is not a hardened multi-user service — keep it on a trusted network and
prefer a port-forward over a public ingress. Note the `ClusterIP` Service is not
node-specific: on a single-node or node-pinned install it points at the one
collector, but when the DaemonSet spans several nodes it load-balances across
collectors and each collector only sees its own node's processes — so target a
specific node's Pod (the `spec.nodeName` selector above) rather than the Service.

## Run on specific node(s) only

Usually you only want a collector on the node running the workload you are
debugging, not every node. Restrict the DaemonSet with a `nodeSelector`; the
controller then creates a Pod only on matching nodes.

Pin to a single node by its built-in `kubernetes.io/hostname` label (the value
is the node name shown by `kubectl get nodes`):

```sh
helm install ngxdig api7/ngxdig --namespace ngxdig --create-namespace \
--set nodeSelector."kubernetes\.io/hostname"=<node-name>
```

To target a group of nodes, label them first and select by that label:

```sh
kubectl label node <node-a> <node-b> ngxdig=enabled
```

```yaml
# values.yaml
nodeSelector:
ngxdig: enabled
```

For rules that `nodeSelector` cannot express, use `affinity` instead. Note that
for a DaemonSet you should not set the Pod's `.spec.nodeName` directly — node
placement is owned by the DaemonSet controller, and `nodeSelector`/`affinity`
is the supported way to constrain it.

{{ template "chart.valuesSection" . }}
5 changes: 5 additions & 0 deletions charts/ngxdig/ci/ct-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Values used by chart-testing (`ct install`) to validate the chart on a
# single-node kind cluster. Tolerate any node taint so the DaemonSet Pod is
# guaranteed to schedule on the CI node.
tolerations:
- operator: Exists
45 changes: 45 additions & 0 deletions charts/ngxdig/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
ngxdig is installed as a DaemonSet — one collector Pod per node.

Each Pod runs the ngxdig debug UI and can profile the OpenResty/Nginx (and
APISIX) worker processes running on its own node.

1. List the collector Pods and the node each one runs on:

kubectl get pods --namespace {{ .Release.Namespace }} \
-l "app.kubernetes.io/name={{ include "ngxdig.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" \
-o wide

2. Open the debug UI for the node whose workload you want to inspect. The UI is
not a hardened multi-user service, so the recommended access is a
port-forward to the collector Pod on that exact node — no Service, nothing
exposed on the network:

export NODE=<node-name> # a node from step 1 / `kubectl get nodes`
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} \
-l "app.kubernetes.io/name={{ include "ngxdig.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" \
--field-selector spec.nodeName=$NODE \
-o jsonpath="{.items[0].metadata.name}")
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:{{ .Values.containerPort }}

Then browse to http://127.0.0.1:8080/ , pick a worker PID, and run a
diagnostic action (profile / cpu-on / memsnap / latency).

The UI is also reachable through the Service
{{ include "ngxdig.fullname" . }}:{{ .Values.service.port }} (type {{ .Values.service.type }}).
{{- if eq .Values.service.type "NodePort" }}
With type NodePort{{ if eq (toString .Values.service.externalTrafficPolicy) "Local" }} and externalTrafficPolicy Local{{ end }}, reach a
node's own collector at <that-node-IP>:<nodePort>:

kubectl get svc --namespace {{ .Release.Namespace }} {{ include "ngxdig.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}"
{{- end }}

3. Check host readiness for eBPF collection on a node:

kubectl --namespace {{ .Release.Namespace }} exec $POD_NAME -- ngxdig doctor

Requirements: eBPF collection needs the node to expose kernel BTF
({{ .Values.hostBtf.path }}){{ if not .Values.hostBtf.enabled }} — currently disabled via hostBtf.enabled=false{{ end }}, and the
collector must keep CAP_BPF, CAP_PERFMON, CAP_SYS_PTRACE, and CAP_SYS_ADMIN with
unconfined seccomp/AppArmor (the chart defaults grant these; CAP_SYS_ADMIN is
needed for the perf_uprobe attachments in `cpu-on`/`latency`). If a diagnostic
fails before collecting, `ngxdig doctor` reports the exact fix.
Loading
Loading