Skip to content

Deployment

transceiver-exporter reads NIC state directly from the host it runs on, so it is a one-instance-per-host tool: it belongs on every host whose transceivers you want visibility into, not behind a load balancer or as a horizontally-scaled service. The two natural shapes are a Kubernetes DaemonSet (one pod per node) and a systemd unit (one process per bare-metal or VM host). Neither pattern is shipped in this repository — both are derived from the same --network host + CAP_NET_ADMIN requirement covered in Permissions; adapt them to your environment.

Kubernetes: DaemonSet

A DaemonSet needs the pod on the host network namespace (the same reason docker run needs --network host) and the capability grant from Permissions:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: transceiver-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: transceiver-exporter
  template:
    metadata:
      labels:
        app: transceiver-exporter
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: transceiver-exporter
          image: ghcr.io/rknightion/transceiver-exporter:latest
          args:
            - -web.listen-address=[::]:9458
          ports:
            - containerPort: 9458
              hostPort: 9458
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
              add: ["NET_ADMIN"]

hostNetwork: true is what gives the pod visibility into the node's physical NICs, exactly like --network host does for docker run. Dropping all capabilities and adding back only NET_ADMIN mirrors the container posture in Installation — the pod still runs as root (the image sets no non-root user; see Permissions), bounded by the capability set, not the UID.

Scrape config

With hostNetwork: true the pod is reachable on the node's own address, so a node-based discovery role is the natural fit:

scrape_configs:
  - job_name: transceiver-exporter
    kubernetes_sd_configs:
      - role: pod
        selectors:
          - role: pod
            label: "app=transceiver-exporter"
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_host_ip]
        target_label: __address__
        replacement: "${1}:9458"
      - source_labels: [__meta_kubernetes_pod_node_name]
        target_label: node

A plain node-address static config works just as well if you already enumerate nodes elsewhere:

scrape_configs:
  - job_name: transceiver-exporter
    static_configs:
      - targets: ["node-a:9458", "node-b:9458"]

systemd

For bare-metal or VM hosts, run the released binary under systemd with the ambient-capability grant covered in Permissions:

[Unit]
Description=transceiver-exporter
After=network.target

[Service]
ExecStart=/usr/local/bin/transceiver-exporter -web.listen-address=[::]:9458
User=transceiver-exporter
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo useradd --system --no-create-home transceiver-exporter
sudo cp transceiver-exporter /usr/local/bin/
sudo systemctl daemon-reload
sudo systemctl enable --now transceiver-exporter

Scrape config

scrape_configs:
  - job_name: transceiver-exporter
    static_configs:
      - targets: ["<host>:9458"]

Fleet-wide filtering

On a switch or dense multi-NIC host, -collector.interface-features.enable=false and an -exclude.interfaces-regex for management/loopback-style virtual interfaces keep cardinality down across a fleet of these — see Configuration.