Network policies and Service
The operator lays down every concrete Kubernetes object for the Platform Agent — the Deployment, ConfigMaps, RBAC, the Service and the NetworkPolicy — when it reconciles a PlatformAgent CR. The one object over that Pod it does not render is the PodMonitoring that scrapes the event watcher’s metrics, which the chart renders behind platformAgent.podMonitoring (Telemetry). No static copy of the Service or the network policies ships in the repository: the objects below exist only as the operator renders them, and each carries an owner reference to the CR that produced it. The remote Kustomize base that used to ship at deploy/kustomize/platform is gone; an overlay that still lists it as a resource fails to build, and the fix is to drop that resource, because the gateway policy and Service the operator renders already cover what it applied.
The gateway NetworkPolicy
Section titled “The gateway NetworkPolicy”The operator renders one NetworkPolicy over the agent Pod, <agent-name>-gateway-netpol, covering:
- Ingress — the Hermes API (
8642), the credential proxy (8643) and, whenharness.hermes.dashboardEnabledis set, the dashboard (9119), from Pods in the agent’s own namespace; and the event watcher’s metrics port (9095) and the chat plugin’s (9097) from thegke-gmp-systemnamespace, where GKE Managed Prometheus’s collectors run, and from the operator’s own pods, which read the watcher’s counter and cluster gauge and the chat plugin’s counter intostatus.usage. - DNS egress — CoreDNS and NodeLocal DNSCache, the cluster’s DNS ClusterIP, and the metadata address
169.254.169.254/32on port53alone, because it is the resolver on a Cloud DNS for GKE cluster. The policy opens no metadata credential port: the gateway Pod holds no Google identity. - In-cluster egress — LiteLLM, vLLM, the GitHub token minter, Hindsight and the managed OTel collector.
- Control-plane egress — the Kubernetes API server, at the endpoints the operator discovers.
- External egress — HTTPS (
443) to0.0.0.0/0minus the private ranges, unless FQDN filtering is on.
Security and IAM is canonical for the full rule set and why each peer is there. spec.networkPolicy.enabled: false stops policy generation and deletes the policies the operator manages; PlatformAgent CRD is canonical for that field.
GKE Dataplane V2 & FQDN Network Policies
Section titled “GKE Dataplane V2 & FQDN Network Policies”[!IMPORTANT] GKE Dataplane V2 Requirement: Setting the annotation
kubeagents.x-k8s.io/enable-fqdn-network-policy: "true"on thePlatformAgentCR makes the operator render a companionFQDNNetworkPolicy(networking.gke.io/v1alpha1) and omit the blanket0.0.0.0/0:443rule. That custom resource requires GKE Dataplane V2 (--enable-dataplane-v2) and FQDN Network Policy enabled (--enable-fqdn-network-policy) on your Google Kubernetes Engine (GKE) cluster (running GKE 1.26.4-gke.500 or 1.27.1-gke.400 or later). Standard clusters running kube-proxy without Dataplane V2 will not enforce or supportFQDNNetworkPolicyobjects. The policy selects only the gateway pod, and its forge hosts are derived from the CR’sspec.integration.forgesdeclaration; PlatformAgent CRD is canonical for that field.
Configuring NetworkPolicy for GKE Private Clusters, Dataplane V2, & Custom CIDRs
Section titled “Configuring NetworkPolicy for GKE Private Clusters, Dataplane V2, & Custom CIDRs”[!IMPORTANT] Kubernetes API Server Egress on GKE Dataplane V2: On GKE Dataplane V2, eBPF performs Destination NAT (DNAT) on
kubernetes.default.svcClusterIP traffic to the control plane’s internal endpoint beforeNetworkPolicyevaluation. Because Kubernetes NetworkPolicyipBlockevaluates the post-DNAT destination address, a rule naming only the ClusterIP (10.96.0.1/32on a classic service range) will not match.The operator discovers the real control plane endpoint IPs (from
default/kubernetesEndpoints,KUBERNETES_SERVICE_HOST, and the Service ClusterIP). You can supply custom CIDRs (including private fleet cluster control plane subnets like172.16.0.0/28and Private Service Connect VIPs) via thekubeagents.x-k8s.io/apiserver-cidrorkubeagents.x-k8s.io/custom-egress-cidrsannotation on thePlatformAgentCR, or theKUBERNETES_API_SERVER_CIDRenvironment variable on the operator deployment.
[!IMPORTANT] Workload Identity metadata egress: On GKE Dataplane V1 (iptables), the node DNATs
169.254.169.254:80to the node-local metadata daemon at169.254.169.252:988innat PREROUTINGbeforeNetworkPolicyis evaluated. Dataplane V2 (eBPF) evaluates policy pre-NAT at the socket layer, where the169.254.169.254/32rule on port80satisfies it directly. Ports8080and987(ALTS DirectPath) are intentionally omitted under least privilege since agent components authenticate over standard REST ADC. That deviates from Google’s guidance, which recommends allowing both and warns that workloads omitting them “might experience disruptions during auto-upgrades” — if a token fetch starts failing during a node auto-upgrade, check the drop’s destination port before looking elsewhere.The operator generates both rules (
169.254.169.254/32on port80and169.254.169.252/32on port988) inlitellm-policy, covering both dataplanes out of the box. The agent’s<name>-gateway-netpolcarries neither: the gateway Pod holds no Google identity, and its Google calls go through the credential broker. The cluster DNS ClusterIP is discovered from thekube-system/kube-dnsService; the metadata daemon container port is discovered from thekube-system/gke-metadata-serverDaemonSet (falling back to port988and IP169.254.169.252if undiscoverable). Either can be overridden via thekubeagents.x-k8s.io/dns-cluster-ip/kubeagents.x-k8s.io/metadata-daemon-ipannotations, the typedspec.networkPolicyblock on the CR, or theKUBERNETES_DNS_CLUSTER_IP/KUBERNETES_METADATA_DAEMON_IPoperator environment variables — in that precedence order, ahead of discovery. PlatformAgent CRD is canonical for the typed field, includingenabled: false, which stops policy generation and deletes the policies the operator manages — the gatewayNetworkPolicy, theFQDNNetworkPolicythatkubeagents.x-k8s.io/enable-fqdn-network-policyturns on, and the sharedlitellm-policy(if LiteLLM is present).
Do not edit the rendered policy in the cluster: the operator applies it with server-side apply on every reconcile and reverts a hand edit. Change the annotations or the spec.networkPolicy block on the CR instead.
The Service
Section titled “The Service”The operator applies a ClusterIP Service named after the CR (platform-agent on a stock install) whose selector matches what it labels its gateway pods, <agent-name>-gateway, plus kubeagents.io/is-leader: "true" when the agent runs more than one replica, which the operator does not render today: it runs the gateway as a single replica whatever spec.deployment.availability.replicas asks for. Its app.kubernetes.io/* labels follow the project-wide contract that makes the whole kube-agents footprint selectable in one query — Resource labels is canonical for what each key means and why component and version are absent.
The exposed ports:
8642— the Platform Agent API. Chat integrations hit this. It targets8643on the pod, the credential proxy’s authenticated listener: Hermes itself binds8642on loopback only and validates a different key, so a caller never reaches it directly. Credential isolation is canonical for that topology. The operator’s health probes do not use this port — theyexeccurlagainst127.0.0.1:8642inside the container.9119— Hermes dashboard. Behindharness.hermes.dashboardEnabledin the CR. Nothing answers on the pod network; the listener is loopback-only — seePlatformAgentCRD for how to reach it.
Kustomize for operator integrations
Section titled “Kustomize for operator integrations”k8s-operator/config/ holds larger Kustomize bases the operator manager uses. Notable subtrees:
config/crd/— thePlatformAgent,AgentPluginandAgentProfileCRDs.config/rbac/— ClusterRoles + bindings for the manager.config/webhook/— admission webhook config (validating + mutating). The Service targets port10250on the manager pod for the GKE firewall reason in Admission webhooks.config/manager/— Deployment for the controller manager, plus itsPodDisruptionBudget.config/integrations/github/— Minty deployment and itsPodDisruptionBudget.config/integrations/litellm/— LiteLLM Deployment + Service (plusPodDisruptionBudget,NetworkPolicy,PodMonitoring, andvertex_ai,anthropicandchatgptoverlays).config/integrations/inference-replay/— replay proxy Deployment, Service, PVC, andPodDisruptionBudget.config/integrations/hindsight/— the Planning Agent’s memory store: API Deployment, Postgres/pgvector StatefulSet, and their Service,PodDisruptionBudgets,NetworkPolicy, andPodMonitoring.
Each is built and applied on its own; there is no aggregate kustomization over
config/integrations/, because every one of them needs envsubst over the built
output before it can be applied — each carries its image as a ${…} variable so
a mirrored install can redirect it, and most need other substitutions besides.
These copies are the development path: a stock install gets the same
components rendered by the kube-agents Helm chart
(via the Terraform engine the installer drives), while k8s-operator/config/
remains the source of truth for the CRDs, operator RBAC, admission policy and
webhook configuration the chart copies or mirrors (make chart-check enforces
that). Deploy the dev copies via make deploy-*
from k8s-operator/:
make deploy IMG=<registry>/k8s-operator:$(git rev-parse HEAD) # operator; refuses a floating tagmake deploy-litellm # inference gatewaymake deploy-github # Mintymake deploy-inference-replay # replay proxymake deploy-hindsight # memory store