> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vantage.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes Network Cost Attribution

> Allocate pod-level network costs from the Vantage Kubernetes agent and view them in Cost Reports.

Network Cost Attribution for Kubernetes allocates network spend to the individual pods that drive it. When enabled, the [Vantage Kubernetes agent](/kubernetes_agent) attributes network costs down to the pod level, so they appear in [Cost Reports](/cost_reports) alongside compute, memory, GPU, and storage costs. Each pod's network cost is broken out by destination type, such as intra-zone, intra-region (cross-AZ), cross-region, and internet egress, so you can see not just how much a workload spent on the network, but what type of traffic drove the cost. You can then use [Virtual Tags](/tagging) to roll these costs up to the teams, applications, or business units that generated them.

## How It Works

When network cost collection is enabled, the Vantage Kubernetes agent Helm chart deploys a per-node network collector that runs as a DaemonSet. The collector reads Linux connection-tracking (`conntrack`) data on each node, attributes each pod's traffic, and classifies the transmitted bytes into mutually exclusive [billing buckets](/kubernetes_network_costs#billing-buckets) by destination. The agent pulls these per-pod summaries each report cycle and joins them with your provider's data transfer rates to produce a per-pod network cost.

<Accordion title="Click to view the network cost pipeline">
  ```mermaid actions={true} placement="top-right" theme={null}
  flowchart TB
    CT["conntrack (per node)"] -->|"pod byte counts"| COL["Network collector DaemonSet"]
    COL -->|"classify by destination"| CLASS["Billing buckets (intra-zone, intra-region, cross-region, internet, S3)"]
    CLASS -->|"pulled each cycle"| AGENT["Vantage Kubernetes agent"]
    AGENT -->|"priced per pod"| CR["Cost Reports"]
  ```
</Accordion>

The collector does not retain full flow records; it summarizes traffic into cost buckets and reports running totals. There is no flow log storage or query infrastructure to operate, and enabling this feature does not require VPC Flow Logs.

## Network Cost Attribution vs. Network Flow Reports

Network Cost Attribution is different from [Network Flow Reports](/network_flow_reports). Network Flow Reports use VPC Flow Log data to analyze source/destination flows and route classification at the cloud-provider level (answering *"where is my traffic going?"*). Kubernetes Network Cost Attribution allocates pod-level network *cost* using connection-tracking data observed inside the cluster (answering *"which workload owns this network spend?"*). It classifies traffic by destination type (intra-zone, intra-region, cross-region, internet, and more) rather than showing individual destination addresses.

The two features are complementary: use Network Flow Reports when you need to investigate where specific traffic is going, and use Kubernetes Network Cost Attribution when you need to put a per-pod dollar amount on network spend and charge it back to a team.

## Supported Providers

Network Cost Attribution supports the following providers at launch:

* Amazon Elastic Kubernetes Service (EKS)
* Azure Kubernetes Service (AKS)
* Google Kubernetes Engine (GKE)

Collection works on any node where the collector can run within these providers, including IPv6 and dual-stack (IPv4/IPv6) clusters.

<Warning>
  The collector reads traffic from Linux connection tracking (conntrack). On clusters where the CNI datapath bypasses kernel netfilter, for example, Cilium with kube-proxy replacement, conntrack sees little or no traffic, so network costs are not attributed. These configurations are not supported at launch.
</Warning>

<Note>
  Oracle Cloud (OKE) and on-premises clusters are not supported at launch. AWS network discovery and S3 path-aware pricing are available on EKS only; other supported providers use manual subnet configuration.
</Note>

## Prerequisites

Before you enable network cost collection, ensure you meet the following requirements:

* A working [Vantage Kubernetes agent](/kubernetes_agent) integration.
* Vantage Kubernetes [agent version 1.3.3 or later](/kubernetes_agent#upgrade-agent), available as part of Helm chart version 1.9.5 or later. This version includes the network collector, AWS network discovery, and the collector metrics endpoint.
* **Connection-tracking byte accounting must be enabled on every node.** Set the `net.netfilter.nf_conntrack_acct=1` sysctl cluster-wide *before* traffic is generated. When this is off (the default on many node images), the kernel leaves conntrack byte counters at zero, so the collector cannot attribute traffic. Only connections opened after the sysctl is set receive counters.

  <Note>
    `nf_conntrack_acct` is a node-level kernel setting that Vantage and the Helm chart cannot enable for you. It's applied through your node configuration, so it's typically handled by whoever manages your cluster's nodes. To check whether it is already on for a node (a value of `1` means it is):

    ```sh theme={null}
    kubectl debug node/<node-name> -it --image=busybox -- cat /host/proc/sys/net/netfilter/nf_conntrack_acct
    ```

    After it's enabled, confirm it took effect with the `vantage_collector_conntrack_acct_enabled` metric (see [Monitor the Collector](/kubernetes_network_costs#monitor-the-collector)); the collector also logs a warning at startup when byte accounting is disabled.
  </Note>
* The collector runs as a DaemonSet with host network access and an elevated security context (the `NET_ADMIN` capability) so it can read conntrack for local pods. The Helm chart configures this for you (`hostNetwork: true` and `capabilities.add: ["NET_ADMIN"]`). Its ServiceAccount needs read-only (`get`/`list`/`watch`) access to pods, nodes, and ConfigMaps.
  <Warning>
    The network collector is opt-in and can be disabled per cluster if your security policy does not permit a privileged, host-network DaemonSet. Nodes where the collector cannot run (for example, Fargate or hybrid nodes without conntrack access) will not report network costs.
  </Warning>

## Enable Network Cost Collection

Enable the collector and provide your subnet configuration through the Vantage Kubernetes agent [Helm chart](https://github.com/vantage-sh/helm-charts/blob/main/charts/vantage-kubernetes-agent/values.yaml).

<Steps>
  <Step>
    Enable `net.netfilter.nf_conntrack_acct=1` on every node (see [Prerequisites](/kubernetes_network_costs#prerequisites)).
  </Step>

  <Step>
    Set `agent.networkCost.enabled=true` and populate `agent.networkCost.subnets` with your cluster's destination CIDRs. See [Configure Subnets](/kubernetes_network_costs#configure-subnets) for how to inventory them. For example:

    ```yaml theme={null}
    agent:
      networkCost:
        enabled: true
        subnets:
          - cidr: "10.0.0.0/20"
            zone: "us-east-1a"
          - cidr: "10.0.16.0/20"
            zone: "us-east-1b"
    ```
  </Step>

  <Step>
    Upgrade the agent to apply the changes:

    ```bash theme={null}
    helm repo update && helm upgrade -n vantage vka vantage/vantage-kubernetes-agent --reuse-values \
      --set agent.networkCost.enabled=true
    ```
  </Step>
</Steps>

Allocated network costs begin appearing in your Kubernetes Cost Reports within one full collection cycle (approximately 24 hours).

## Configure Subnets

The collector compares each flow's remote IP with your subnet map to classify it as `in_zone`, `in_region`, or `cross_region`. Populate `agent.networkCost.subnets` with the private destination CIDRs your workloads can reach: cluster subnets, peered networks, private managed services, and overlay pod CIDRs where applicable.

Keep the following guidelines in mind when defining your subnets:

* A `zone` enables same-zone classification. When only a `region` is supplied, all same-region matches are reported as `in_region`. The region is derived from the zone when omitted (for example, `us-east-1a` resolves to `us-east-1`).
* On a dual-stack cluster, add both IPv4 and IPv6 ranges. IP families are matched independently.
* The collector does not assume that an unmatched private address is internet traffic. It reports the traffic as `unknown` until you add the CIDR or an [override](/kubernetes_network_costs#nat-peering-and-overrides).

Use the tabs below to inventory the cluster network for your provider, then add the output to your Helm values.

<Tabs>
  <Tab title="AWS / EKS">
    AWS subnets are Availability Zone scoped. List every IPv4 subnet in a VPC in the same `cidr`/`zone` shape used by the Helm value:

    ```sh theme={null}
    aws ec2 describe-subnets \
      --filters "Name=vpc-id,Values=${VPC_ID}" \
      --query 'Subnets[].{cidr:CidrBlock,zone:AvailabilityZone}' \
      --output yaml
    ```

    Add the results to your Helm values:

    ```yaml theme={null}
    agent:
      networkCost:
        subnets:
          - cidr: "10.0.0.0/20"
            zone: "us-east-1a"
          - cidr: "10.0.16.0/20"
            zone: "us-east-1b"
    ```

    <Tip>
      For an EKS cluster that can grant the agent EC2 read permissions, prefer [AWS network discovery](/kubernetes_network_costs#optional-aws-network-discovery-eks). It discovers VPC subnets automatically and also enables S3 egress-path classification, so you don't have to maintain the subnet list by hand.
    </Tip>
  </Tab>

  <Tab title="Azure / AKS">
    Azure VNets and their subnets are regional. List a VNet's subnet address prefixes:

    ```sh theme={null}
    az network vnet subnet list \
      --resource-group "${RESOURCE_GROUP}" \
      --vnet-name "${VNET_NAME}" \
      --query '[].{cidr:addressPrefix}' \
      --output yaml
    ```

    Obtain the VNet location once and use it for all of its subnets:

    ```sh theme={null}
    az network vnet show \
      --resource-group "${RESOURCE_GROUP}" \
      --name "${VNET_NAME}" \
      --query location \
      --output tsv
    ```

    Configure the subnets with the VNet's region, and do not set a `zone`:

    ```yaml theme={null}
    agent:
      networkCost:
        subnets:
          - cidr: "10.30.0.0/20"
            region: "eastus"
          - cidr: "10.30.16.0/20"
            region: "eastus"
    ```

    <Warning>
      Do not set a `zone` from an AKS node pool on a subnet entry. Azure availability zones apply to resources, while a VNet subnet is regional; a guessed zone can incorrectly classify same-region traffic as `cross_region`.
    </Warning>

    For Azure CNI, pod addresses may come from the node subnet or from a separate pod subnet. Add every pod subnet. For kubenet and overlay modes, obtain the pod CIDR from the AKS cluster network profile and add it when workloads can reach remote pod IPs:

    ```sh theme={null}
    az aks show \
      --resource-group "${RESOURCE_GROUP}" \
      --name "${AKS_CLUSTER}" \
      --query 'networkProfile.{podCidrs:podCidrs,podCidr:podCidr}' \
      --output yaml
    ```
  </Tab>

  <Tab title="Google Cloud / GKE">
    GCP subnets are regional, not zonal. List the primary CIDR and its region:

    ```sh theme={null}
    gcloud compute networks subnets list \
      --network="${VPC_NETWORK}" \
      --format='table(ipCidrRange, region)'
    ```

    Use the regional location (without its URL prefix) in your Helm values, and do not set a `zone`:

    ```yaml theme={null}
    agent:
      networkCost:
        subnets:
          - cidr: "10.20.0.0/20"
            region: "us-central1"
          - cidr: "10.20.16.0/20"
            region: "us-central1"
    ```

    For VPC-native GKE, pod and Service CIDRs are usually secondary IP ranges. Inspect each subnet's secondary ranges and add the pod ranges that are reachable from your workloads:

    ```sh theme={null}
    gcloud compute networks subnets describe "${SUBNET}" \
      --region="${REGION}" \
      --format='yaml(ipCidrRange, secondaryIpRanges)'
    ```

    Because GCP does not assign a subnet to a single zone, traffic to a configured subnet in the node's region is reported as `in_region`, even if both endpoints happen to be in the same zone.
  </Tab>
</Tabs>

### Overlay Networks and Pod CIDRs

Overlay networking (for example, Calico, Cilium, kubenet, or GKE VPC-native networking) can allocate pod addresses outside the node's primary VPC/VNet subnet. When a remote pod address does not match an entry in `subnets`, it is classified as `unknown`. Add the relevant pod CIDR with the cluster's zone or region:

```yaml theme={null}
agent:
  networkCost:
    subnets:
      - cidr: "172.20.0.0/16"
        region: "us-central1"
```

### NAT, Peering, and Overrides

Use `overrides` to force a bucket for a destination CIDR. Overrides take precedence over subnet entries, and the most-specific CIDR wins. Use them when destination ownership or topology cannot be represented accurately by a zone/region map.

```yaml theme={null}
agent:
  networkCost:
    overrides:
      # A NAT/proxy appliance range that is a real remote destination for pods.
      - cidr: "10.40.8.0/24"
        bucket: "in_region"
      # A CIDR in a peered VPC in another region.
      - cidr: "10.80.0.0/16"
        bucket: "cross_region"
```

<Note>
  A managed NAT gateway normally source-NATs traffic on its way to a public destination; it is not the remote IP visible in the pod's conntrack flow. Do not add its private address merely to classify ordinary internet egress. The public destination already falls into `internet`. Reserve NAT overrides for cases where a NAT or proxy appliance is itself the remote endpoint. For a same-region peered VPC, prefer a normal `subnets` entry with its actual region rather than a `cross_region` override.
</Note>

### Validate the Rendered Configuration

After you apply the Helm values, inspect the ConfigMap mounted by the network collector to confirm your configuration rendered correctly:

```sh theme={null}
kubectl -n "${NAMESPACE}" get configmap vantage-network-subnets \
  -o jsonpath='{.data.subnets\.yaml}'
```

The rendered document should include both `subnets:` and `overrides:` (the latter may be an empty list).

## Optional AWS Network Discovery (EKS)

On EKS, you can let the agent discover your network topology automatically instead of maintaining subnet lists by hand. Enable it with `agent.networkCost.discovery.enabled=true`.

Discovery has two benefits:

* **Turnkey subnet inventory:** Classification depends on knowing every subnet CIDR in the cluster's VPC(s) and each subnet's zone/region. Maintaining that list by hand is error-prone and drifts whenever someone adds a subnet, AZ, or VPC. Any missed subnet classifies traffic as `unknown` or `internet` instead of the correct bucket. Discovery enumerates all subnets in the tagged VPC(s) directly from the EC2 APIs, so the inventory stays complete and current. With discovery on, the manual subnet list can be as small as `subnets: []` plus any human-only overrides.
* **Accurate S3 egress pricing:** The S3 path-aware buckets exist because the cost of reaching S3 depends on how each source subnet egresses (a gateway VPC endpoint is free; a NAT gateway is not). Discovery resolves each subnet's S3 egress path and fetches the regional S3 prefix lists automatically, so S3 traffic is priced correctly without hand-authored fields.

Discovery requires the following read-only EC2 permissions on the agent's ServiceAccount (via IRSA or EKS Pod Identity):

* `ec2:DescribeSubnets`
* `ec2:DescribeRouteTables`
* `ec2:DescribeManagedPrefixLists`
* `ec2:GetManagedPrefixListEntries`

<Accordion title="Grant these permissions with Terraform">
  ```hcl theme={null}
  # Read-only EC2 permissions the Vantage Kubernetes agent needs for AWS
  # network discovery. Attach to the IAM role assumed by the agent's
  # ServiceAccount (via IRSA or EKS Pod Identity).
  resource "aws_iam_policy" "vantage_network_discovery" {
    name        = "vantage-k8s-network-discovery"
    description = "Read-only EC2 access for Vantage Kubernetes network cost discovery"

    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Sid    = "VantageNetworkDiscovery"
          Effect = "Allow"
          Action = [
            "ec2:DescribeSubnets",
            "ec2:DescribeRouteTables",
            "ec2:DescribeManagedPrefixLists",
            "ec2:GetManagedPrefixListEntries",
          ]
          Resource = "*"
        }
      ]
    })
  }

  # Attach to your existing agent IRSA role.
  resource "aws_iam_role_policy_attachment" "vantage_network_discovery" {
    role       = aws_iam_role.vantage_agent_irsa.name
    policy_arn = aws_iam_policy.vantage_network_discovery.arn
  }
  ```
</Accordion>

<Info>
  Discovery is fail-closed. If the region cannot be resolved, credentials are missing, or the permission probe fails, the agent logs a warning and leaves discovery off. It does not crash-loop, and classification continues on the manual subnet configuration.
</Info>

## Billing Buckets

Transmitted bytes are grouped into mutually exclusive buckets. Each bucket is priced by the destination and, for S3-bound traffic, by how the source subnet egresses.

The label in the first column is what appears in Cost Reports. The configuration key is the value you use in a subnet [override](/kubernetes_network_costs#nat-peering-and-overrides).

| Cost Reports Label         | Configuration Key | Meaning                                                                                                                  |
| -------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Intra-Zone Egress          | `in_zone`         | The destination is in the same Availability Zone as the node.                                                            |
| Intra-Region Egress        | `in_region`       | The destination is in the same region (including same-region, cross-AZ traffic).                                         |
| Cross-Region Egress        | `cross_region`    | The destination is in a different region.                                                                                |
| Internet Egress            | `internet`        | Public internet egress.                                                                                                  |
| Unclassified Egress        | `unknown`         | A private destination that does not match any configured subnet or override. Add the missing CIDR to price it correctly. |
| S3 Gateway Endpoint (Free) | `s3_endpoint`     | S3 reached through a gateway VPC endpoint. No data-processing or egress charge (free).                                   |
| S3 via NAT Gateway         | `s3_nat_gateway`  | S3 reached through a managed NAT gateway. Priced as NAT processing plus internet egress.                                 |
| S3 via NAT Instance        | `s3_nat_instance` | S3 reached through an EC2 NAT instance. Priced as standard internet egress.                                              |
| S3 via Internet Gateway    | `s3_igw`          | S3 reached directly through an internet gateway. Priced as standard internet egress.                                     |

<Note>
  The S3 path-aware buckets require [AWS network discovery](/kubernetes_network_costs#optional-aws-network-discovery-eks). Without it, S3 traffic falls through to standard destination matching (often Internet Egress). Rates are blended per-GB rates derived from your provider's actual data transfer charges over the month.
</Note>

## View Network Costs in Cost Reports

Pod-level network costs appear in [Cost Reports](/cost_reports) under the **Kubernetes** provider, alongside compute, memory, GPU, and storage costs. To isolate network costs, filter the **Kubernetes** provider by **Category** and select **network**. The `vntg:category` tag also works and is the same pattern used for CPU, RAM, GPU, and storage (see [Filter by CPU, RAM, Storage, and GPU](/kubernetes#filter-by-cpu-ram-storage-and-gpu)).

<Info>
  Network costs are not part of [Kubernetes Efficiency Reports](/kubernetes#kubernetes-efficiency-metrics-and-reports). Efficiency Reports measure utilization of provisioned capacity, whereas network traffic is charged per byte and has no notion of idle reserved capacity. Network costs can still surface optimization opportunities, such as cross-AZ traffic, NAT gateway processing, internet egress, or inefficient S3 routing, through Cost Reports.
</Info>

<Steps>
  <Step>
    Create or open a Kubernetes [Cost Report](/cost_reports).
  </Step>

  <Step>
    Add a filter: **All costs** from **Kubernetes** where **Category** is **network**. (Filtering by **Tag** **vntg:category** is **network** works too.)
  </Step>

  <Step>
    Use the report's [**Group By**](/cost_reports#configure-report-groups) menu to break the network costs down. Group by **Subcategory** to see the destination breakdown (Intra-Zone Egress, Intra-Region Egress, Cross-Region Egress, Internet Egress, and the S3 paths), which shows how a workload's traffic is classified. To roll network costs up to the owning team or application, group by **Tag** and choose the `namespace` tag, a workload label, or a [Virtual Tag](/tagging). On Kubernetes Cost Reports, namespace and labels are surfaced as tags.
  </Step>
</Steps>

Because network costs carry the same pod, namespace, label, and annotation tags as the pod's compute costs, any [Virtual Tag](/tagging) you already use for chargeback will allocate network costs alongside compute, memory, and GPU. For step-by-step chargeback setups—such as mapping costs to a `team` tag or allocating shared costs by usage—see [Virtual Tagging Examples](/tagging_examples).

<Warning>
  Kubernetes network costs are re-derived from your cloud provider's data transfer charges (for AWS, the **Data Transfer** and **NAT Gateway** charges already in your Cost and Usage Report). As with compute, reporting on both your primary provider and Kubernetes in the same Cost Report double counts these charges. Removing the overlap is only approximate: data transfer and NAT gateway line items usually aren't tagged with the cluster, so there is no clean way to exclude only the cluster's share. Excluding all of a provider's data transfer and NAT gateway charges also removes network spend from non-Kubernetes workloads, while excluding none leaves the double count in place. Choose whichever approximation better fits your reporting, and see [Avoid Double Counting Costs in Cost Reports](/kubernetes#avoid-double-counting-costs-in-cost-reports) for the compute equivalent.
</Warning>

### Example: Charge Network Costs Back to a Team

Suppose unallocated network spend currently lands on your platform team, and you want each application team to own its network costs alongside compute. If your workloads are labeled with a team identifier (for example, a `team` pod label or the `app.kubernetes.io/part-of` label), you can roll network costs up to that team.

<Steps>
  <Step>
    Confirm the label is collected by the agent. Labels matching `app.kubernetes.io/*` are collected by default; to collect a custom label such as `team`, add it to the agent's [allowed labels](/kubernetes_agent#enable-collection-of-annotations-and-namespace-labels). Collected labels appear as tags on Kubernetes cost rows, including network rows.
  </Step>

  <Step>
    (Optional) If team names vary across clusters or providers, create a `team` [Custom Value Virtual Tag](/tagging_examples#example-1-cross-provider-tagging) that maps the underlying labels and namespaces to standardized team values.
  </Step>

  <Step>
    Open a Kubernetes [Cost Report](/cost_reports) and group by your `team` tag. To view only the network portion, add the filter **where Category is network**; to show each team's full Kubernetes bill, leave the category filter off so network rolls up alongside compute, memory, GPU, and storage.
  </Step>
</Steps>

Each team's row now includes the network costs their workloads drove, so the platform team no longer absorbs unallocated network spend.

## Troubleshooting

If network reporting looks empty or incomplete, check the following in order. The agent and collector expose Prometheus metrics (see [Monitor the Collector](/kubernetes_network_costs#monitor-the-collector)) that correspond to each check.

<Steps>
  <Step title="Collector coverage gaps">
    Confirm a collector is running on every node. A missing collector (from scheduling, a crash loop, or a taint without a matching toleration) means those nodes report no network costs. Add tolerations for any tainted nodes you want network costs from.
  </Step>

  <Step title="Connection-tracking accounting disabled">
    If `net.netfilter.nf_conntrack_acct` is not enabled, byte counters stay at zero and no traffic is attributed, even when the collector is healthy. Enable the sysctl cluster-wide, and remember that only connections opened afterward receive counters.
  </Step>

  <Step title="Agent cannot pull from a collector">
    If the agent cannot retrieve reports from a collector, or has not succeeded within your report interval, network data will be stale or missing for that node's pods.
  </Step>

  <Step title="Traffic shown as Unclassified Egress">
    Costs appearing under **Unclassified Egress** (the `unknown` bucket) mean a private destination CIDR is missing from your configuration. Add the subnet (or an [override](/kubernetes_network_costs#nat-peering-and-overrides)), or enable [AWS network discovery](/kubernetes_network_costs#optional-aws-network-discovery-eks) on EKS.
  </Step>

  <Step title="Short-lived pods">
    Very short-lived pods may not be captured if your collection interval is coarse. Tune the [Kubernetes polling interval](/kubernetes_agent#configure-polling-interval) based on the shortest-lived workload you need to attribute.
  </Step>
</Steps>

### Monitor the Collector

The agent exposes network metrics on its existing metrics endpoint (default port `9010`), and the network collector exposes its own metrics endpoint (default port `8883`). Point your Prometheus scrape configuration at both. The following series are the most useful for catching silent failures and undercounting in cases where pods look healthy but network data is missing, stale, or incomplete.

| Metric                                                                          | Component | Type    | Labels                | Meaning                                                                                                                                                                            |
| ------------------------------------------------------------------------------- | --------- | ------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vantage_network_collectors_discovered` / `vantage_network_collectors_expected` | Agent     | gauge   | —                     | Collector coverage. Alert when discovered is less than expected.                                                                                                                   |
| `vantage_network_pull_failures_total`                                           | Agent     | counter | `collector`, `reason` | Failed agent pulls from a collector, by reason.                                                                                                                                    |
| `vantage_network_last_pull_timestamp_seconds`                                   | Agent     | gauge   | `collector`           | Last successful pull from each collector. Use to catch stale pulls.                                                                                                                |
| `vantage_network_pods_rejected_total`                                           | Agent     | counter | `reason`              | Pod summaries dropped instead of attributed (for example, the pod was no longer in the agent's store). Rising values mean some network bytes aren't being allocated to a workload. |
| `vantage_network_unattributed_bytes_total`                                      | Agent     | counter | —                     | Total bytes that could not be attributed to a known pod. Rising values indicate network cost that won't roll up to a workload.                                                     |
| `vantage_collector_conntrack_acct_enabled`                                      | Collector | gauge   | —                     | `1` when conntrack byte accounting is enabled, `0` otherwise.                                                                                                                      |
| `vantage_collector_last_successful_poll_timestamp_seconds`                      | Collector | gauge   | —                     | Last successful conntrack poll (collector heartbeat).                                                                                                                              |
| `vantage_collector_subnet_config_load_failure_total`                            | Collector | counter | `reason`              | Failed subnet ConfigMap reloads, by reason.                                                                                                                                        |
| `vantage_collector_conntrack_table_fullness_ratio`                              | Collector | gauge   | —                     | Conntrack table utilization (current entries ÷ `nf_conntrack_max`). As it approaches `1`, the kernel can drop flows before the collector sees them, undercounting traffic.         |
| `vantage_collector_bytes_classified_total`                                      | Collector | counter | `bucket`              | Transmitted bytes routed into each billing bucket.                                                                                                                                 |

The following Prometheus alert rules are useful starting points. Tune the stale-pull threshold to match your deployed report interval (the example assumes the default \~1 hour interval).

```yaml theme={null}
groups:
  - name: vantage-network-collector
    rules:
      - alert: VantageNetworkCollectorCoverageGap
        expr: vantage_network_collectors_discovered < vantage_network_collectors_expected
        for: 10m
        annotations:
          summary: Network collectors are missing from one or more nodes
      - alert: VantageNetworkCollectorConntrackAccountingDisabled
        expr: vantage_collector_conntrack_acct_enabled == 0
        for: 5m
        annotations:
          summary: Conntrack byte accounting is disabled on {{ $labels.instance }}
      - alert: VantageNetworkCollectorPullStale
        expr: time() - vantage_network_last_pull_timestamp_seconds > 5400
        for: 15m
        annotations:
          summary: Agent has not recently pulled from network collector {{ $labels.collector }}
```
