Version: 1.4.9This documentation is for Spitfire 1.4.9.
Connecting Kubernetes: a Tower in the cluster, no port opened
This page connects a Kubernetes cluster to Spitfire end to end: the nodes' server metrics (CPU, memory, disk, network), the kubelet's metrics (pods and containers) and the cluster's state (Deployments, DaemonSets, node conditions). The Tower runs inside the cluster and opens one outbound connection to Spitfire: no port is opened to the cluster from outside. The data source's fields and the cluster view: Infrastructure: Kubernetes.
Prerequisites
- Continuous metrics on: Towers & metrics
In Spitfire: /towers→ Collect and keep metrics (Turning metrics on). While it is off the Tower takes metrics in and drops them; the manifest applies cleanly and nothing shows up. - A Tower: this page installs it inside the cluster (A Tower in the cluster). If your network already has a Tower the cluster's pods can reach, you can use that one.
- kubectl access: a user who can create namespaces, ClusterRoles and ClusterRoleBindings
(usually
cluster-admin). Kubernetes 1.25 or later (Pod Security labels). - Outbound access: the Tower pod must reach Spitfire's address (HTTPS). Nothing else needs internet access.
- Installation admin: the Towers page is open to the installation admin only.
Why a Tower? Application APM data (OTLP with an sfotlp_ key) needs no Tower: it goes
straight to Spitfire's /otlp address, and a Tower sent traces or logs says so and turns them
away. Infrastructure metrics (the sfcol_ token) come in only through a Tower, even where there
is internet access: only the Tower accepts sfcol_. In return no port is opened into the
cluster, one outbound firewall rule is enough, the metrics carry the Tower and zone labels,
per-source limits apply and the Tower's health shows on the Towers page. Know the limit too: the
queue is in memory only (SPITFIRE_TOWER_QUEUE_MB, default 64, at most 256). Once Spitfire has
been unreachable for SPITFIRE_TOWER_DROP_AFTER_SECONDS (default 60 s) the queue is dropped,
and new metrics are dropped too until it answers: the graphs have a gap as long as the outage.
What gets installed
All in the spitfire namespace; the ClusterRole and the ClusterRoleBinding are cluster-wide.
| Object | From | What it does |
|---|---|---|
Namespace spitfire |
Tower manifest | Pod Security labels (below) |
Secrets spitfire-tower, spitfire-collector |
Secret command | The sftower_ and sfcol_ tokens |
Deployment + Service + NetworkPolicy spitfire-tower |
Tower manifest | The Tower, http://spitfire-tower.spitfire.svc:4318 |
DaemonSet spitfire-collector-host (optional) |
Tower manifest | Reads every node as a server (its root read-only) |
DaemonSet spitfire-collector-node |
Data source manifest | Every node's kubelet (kubelet_stats) |
Deployment spitfire-collector-cluster |
Data source manifest | The cluster's state (k8s_cluster) |
ServiceAccount, ClusterRole, ClusterRoleBinding spitfire-collector |
Data source manifest | Read only: nodes, pods, workloads; for the kubelet only nodes/stats |
Pod Security: with the host Collector on, the namespace is enforce: privileged, because reading
the node's root, network and processes (hostPath, hostNetwork, hostPID) exists at no other
level. warn and audit stay restricted; every pod other than the host Collector (the Tower,
the kubelet and cluster Collectors) meets restricted: a non-root user, RuntimeDefault seccomp,
a read-only root file system, every capability dropped. With the host Collector off, the namespace
is enforce: restricted.
A Tower in the cluster
Towers & metrics
In Spitfire: /towers→ Add Tower → a name (e.g.prod-eu-k8s) and an optional zone → Add Tower.The two tokens are shown only in this dialog. Keep it open through the next steps.
Choose the Kubernetes (manifest) tab.
The Also watch every node as a server box: on, the nodes show up in Servers (the namespace becomes
privileged, above). If this Tower carries the mobile push notifications, tick that box too.Copy the 1. The namespace and the Secrets command into a terminal that reaches the cluster. It asks for a token twice: first paste
sftower_…from the dialog, thensfcol_…, Enter after each.Save 2. spitfire-tower.yaml in your home folder as
spitfire-tower.yaml.Apply it:
kubectl apply --dry-run=server -f spitfire-tower.yaml && kubectl apply -f spitfire-tower.yaml--dry-run=serverasks the server first: a Pod Security or permission refusal shows up here, before anything changes.The Tower turns Connected in the list (a few seconds). The address under the tab (
http://spitfire-tower.spitfire.svc:4318) is the data source's Tower address.
The NetworkPolicy lets in 4318 only. With the host Collector off, only pods in the spitfire
namespace can send. With it on, the host Collector sends from its node's address (which no pod
selector matches), so the port is open to the cluster and the sfcol_ token guards it. A CNI that
does not enforce NetworkPolicies ignores the rule.
Secrets from a hidden prompt
The command asks for the tokens with read -rs: they are not shown and stay out of the shell
history and the manifest. They reach kubectl on standard input (--from-file=…=/dev/stdin);
--from-literal leaves the value on the command line, in the process list and in the history. The
command is one logical line (lines end in \): when a multi-line block is pasted, read takes the
next pasted line for the token. To type it yourself:
kubectl create namespace spitfire
printf 'sftower_ token: ' && read -rs SF_TOWER && echo && \
printf 'sfcol_ token: ' && read -rs SF_COL && echo && \
printf '%s' "$SF_TOWER" | kubectl -n spitfire create secret generic spitfire-tower --from-file=SPITFIRE_TOWER_TOKEN=/dev/stdin && \
printf '%s' "$SF_COL" | kubectl -n spitfire create secret generic spitfire-collector --from-file=token=/dev/stdin; \
unset SF_TOWER SF_COLThe manifest holds no token, so it can be kept in a repository (GitOps); the Secrets are managed apart.
Pinning the image to a digest
The manifest names the image by tag (usespitfire/spitfire:<version>, and <version>-collector
for the host Collector); the version must match Spitfire's. For a digest instead of the tag:
docker buildx imagetools inspect usespitfire/spitfire:<version> --format '{{json .Manifest.Digest}}'
docker buildx imagetools inspect usespitfire/spitfire:<version>-collector --format '{{json .Manifest.Digest}}'and change the manifest's image: lines to usespitfire/spitfire:<version>@sha256:<digest>. When
upgrading, take the new manifest from the dialog and compare with
kubectl diff -f spitfire-tower.yaml.
The Kubernetes data source
Infrastructure sources → Add a source → Kubernetes. If a prerequisite is missing the dialog says so at the top.
The Manual install tab: Cluster name (e.g.
prod-eu; the cluster's name in Spitfire) and Tower addresshttp://spitfire-tower.spitfire.svc:4318→ Save.Skip the Secret command: the
spitfire-collectorSecret was made in the Tower step. (If you send to another Tower in your network, run it; it asks for that Tower'ssfcol_token.)Copy the manifest command with Copy and run it in the same terminal. It is one command (
kubectl apply -f - <<'EOF' … EOF); no file is saved. Then wait for the rollout:kubectl -n spitfire rollout status daemonset/spitfire-collector-node --timeout=120sTest: if it says "Collected from inside the cluster", wait; the first data comes in 1-2 minutes.
The Quick install tab does the same: Create the command gives a command with a one-hour,
single-use sfsetup_ token; the script asks for the kubectl context, the cluster name, the Tower
address and the sfcol_ token, updates an existing Secret and applies the manifest. The token is
made only when you press the button; opening and closing the dialog leaves no valid token behind.
Verification
The pods are
Running: onespitfire-tower, onespitfire-collector-nodeon every node (and onespitfire-collector-hostwith the host Collector on), onespitfire-collector-cluster.kubectl -n spitfire get pods -o wideThe Tower is connected: its log has
connected to controller.kubectl -n spitfire logs deploy/spitfire-tower | grep -E 'connected|unreachable|not kept'No
no such host,connection refused,forbiddenor401in the Collectors' logs:kubectl -n spitfire logs ds/spitfire-collector-node --tail=50 kubectl -n spitfire logs deploy/spitfire-collector-cluster --tail=50In Spitfire: in Towers & metrics the Tower is Connected and its traffic grows; in Infrastructure sources the Kubernetes source is green; APM → Kubernetes shows the nodes and namespaces; with the host Collector on, Servers shows the node names.
Common errors
| Seen | Cause | Fix |
|---|---|---|
Everything Running, no data in Spitfire; Tower log metrics are not kept on the controller |
Continuous metrics are off | Prerequisites: Collect and keep metrics |
Tower log controller unreachable |
The Tower pod cannot reach Spitfire's address (DNS, proxy, an egress NetworkPolicy) | Try the address from the pod; add Spitfire's address to the cluster's egress rule |
would violate PodSecurity "restricted:latest": hostNetwork, hostPID, hostPath |
The host Collector; an expected warning (warn: restricted) |
Nothing to do; the namespace enforces privileged |
| The same warning for a pod other than the host Collector | An old manifest | Take the manifest again from this version's dialog |
secrets "spitfire-collector" already exists |
The Secret was made in the Tower step | Skip the Secret command in the data source step; to change it, kubectl -n spitfire delete secret spitfire-collector and run the command again |
namespaces "spitfire" already exists |
The namespace is already there | Harmless; run the rest of the command |
Warning: resource namespaces/spitfire is missing the kubectl.kubernetes.io/last-applied-configuration annotation |
The namespace was made with kubectl create first |
Harmless, kubectl adds it |
Collector log dial tcp <node IP>:10250: i/o timeout |
The kubelet is reached at the node's IP (status.hostIP, from 1.4.8) and the pod network cannot reach it |
Allow the pod network to the node's 10250/TCP; a manifest from before 1.4.8 reached the node by name (no such host): apply the manifest again |
Collector log connection refused (10250) |
The kubelet does not listen on 10250, or the node's firewall blocks it | Allow the pod network to the node's 10250/TCP |
Collector log forbidden |
The ClusterRole or the binding is missing | Apply the data source manifest again |
Collector log 401 / the Tower counts unauthorized |
The sfcol_ token is another Tower's, or the Tower was revoked |
Make the Secret again with this Tower's token |
Tower log SPITFIRE_TOWER_BUFFER_MB and _HOURS are no longer used |
An old template; there is no disk buffer | Use SPITFIRE_TOWER_QUEUE_MB; take the manifest again |
| The host Collector's ConfigMap changed, the behaviour did not | A hand-edited ConfigMap; the pod template did not change, so nothing restarts | kubectl -n spitfire rollout restart ds/spitfire-collector-host (in the generated manifest a config digest restarts the pods itself) |
Undoing it
In Spitfire: delete the Kubernetes source in Infrastructure sources, and Revoke the Tower in Towers & metrics. Both tokens stop working.
In the cluster, delete the manifests (the ClusterRole, the ClusterRoleBinding and the namespace too; the Secrets go with the namespace):
kubectl delete namespace spitfire kubectl delete clusterrolebinding spitfire-collector --ignore-not-found kubectl delete clusterrole spitfire-collector --ignore-not-foundExpected output:
namespace "spitfire" deleted, thendeletedfor the ClusterRole and the ClusterRoleBinding. If you installed into another namespace, write it instead ofspitfire.
Past metrics are deleted when their retention runs out.