Agent on Kubernetes
Layout
Section titled “Layout”kubernetes/apps/network/towonel-agent/├── ks.yaml└── app/ ├── kustomization.yaml ├── ocirepository.yaml ├── helmrelease.yaml ├── rbac.yaml ├── externalsecret.yaml └── dnsendpoint.yamlHelmRelease
Section titled “HelmRelease”apiVersion: helm.toolkit.fluxcd.io/v2kind: HelmReleasemetadata: name: towonel-agentspec: chartRef: kind: OCIRepository name: towonel-agent interval: 1h values: defaultPodOptions: automountServiceAccountToken: true securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 fsGroup: 10001 seccompProfile: { type: RuntimeDefault } controllers: towonel-agent: type: deployment # NodePort UDP load-balances per packet; iroh state is per-pod. replicas: 1 annotations: reloader.stakater.com/auto: "true" serviceAccount: name: towonel-agent containers: app: image: repository: codeberg.org/towonel/towonel-agent tag: latest env: RUST_LOG: info TOWONEL_AGENT_HEALTH_LISTEN_ADDR: 0.0.0.0:9090 TOWONEL_AGENT_IROH_PORT: "51820" TOWONEL_AGENT_K8S_AUTODISCOVER: "true" TOWONEL_AGENT_K8S_SERVICE: towonel-agent TOWONEL_AGENT_SERVICES: | [{"hostname":"*.example.dev","origin":"envoy.network.svc:443"}] TOWONEL_AGENT_TCP_SERVICES: | [{"name":"ssh","origin":"forgejo-ssh.network.svc:2222","listen_port":2222}] TOWONEL_AGENT_UDP_SERVICES: | [{"name":"turn","origin":"coturn.network.svc:3478","listen_port":3478}] NODE_NAME: valueFrom: { fieldRef: { fieldPath: spec.nodeName } } POD_NAMESPACE: valueFrom: { fieldRef: { fieldPath: metadata.namespace } } TOWONEL_INVITE_TOKEN: valueFrom: secretKeyRef: name: towonel-agent-secret key: TOWONEL_INVITE_TOKEN probes: liveness: { enabled: true, custom: true, spec: { httpGet: { path: /healthz, port: 9090 } } } readiness: { enabled: true, custom: true, spec: { httpGet: { path: /healthz, port: 9090 } } } securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: ["ALL"] } resources: requests: { cpu: 50m, memory: 64Mi } limits: { cpu: 500m, memory: 64Mi } service: app: ports: http: { port: 9090 } iroh: controller: towonel-agent type: NodePort ipFamilyPolicy: RequireDualStack ipFamilies: [IPv4, IPv6] ports: iroh: port: 51820 targetPort: 51820 nodePort: 31820 protocol: UDP serviceMonitor: app: endpoints: - { port: http, interval: 1m }TOWONEL_AGENT_SERVICES is SNI-routed TLS passthrough. _TCP_SERVICES
and _UDP_SERVICES are opaque; listen_port is the public edge port.
Reachability
Section titled “Reachability”The edge reverse-dials the agent over iroh QUIC. Pod-local IPs are not reachable, so advertise something that is — a NodePort UDP service, an external relay, or both.
TOWONEL_AGENT_IROH_PORTpins the iroh UDP bind to the Service’stargetPort(required for NodePort).TOWONEL_AGENT_K8S_AUTODISCOVER=truequeries the API for the node’sExternalIP/InternalIPand the Service’s UDPnodePort, then advertises them as iroh external addresses. NeedsNODE_NAMEandPOD_NAMESPACEfrom the downward API.TOWONEL_AGENT_K8S_SERVICE(defaulttowonel-agent) names the NodePort Service to read.TOWONEL_AGENT_RELAY_URLoverrides the hub-served relay (setTOWONEL_HUB_RELAY_URLon the hub for the default).TOWONEL_AGENT_EXTRA_LOCAL_ADDRSis a CSV of externally-reachablehost:port(IPv6 in brackets) when autodiscovery doesn’t apply.
apiVersion: v1kind: ServiceAccountmetadata: name: towonel-agent namespace: network---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: towonel-agent-discover-nodesrules: - { apiGroups: [""], resources: [nodes], verbs: [get] }---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: towonel-agent-discover-nodesroleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: towonel-agent-discover-nodes }subjects: - { kind: ServiceAccount, name: towonel-agent, namespace: network }---apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: towonel-agent-discover-services namespace: networkrules: - { apiGroups: [""], resources: [services], verbs: [get] }---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: towonel-agent-discover-services namespace: networkroleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: towonel-agent-discover-services }subjects: - { kind: ServiceAccount, name: towonel-agent, namespace: network }Nodes are cluster-scoped; services are namespaced.
Secret
Section titled “Secret”apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: towonel-agent-secretspec: secretStoreRef: kind: ClusterSecretStore name: onepassword target: name: towonel-agent-secret creationPolicy: Owner data: - secretKey: TOWONEL_INVITE_TOKEN remoteRef: key: towonel-tunnel property: invite_tokenReloader rolls the pods on secret change.
apiVersion: externaldns.k8s.io/v1alpha1kind: DNSEndpointmetadata: name: towonel-agentspec: endpoints: - dnsName: app.example.dev recordType: A targets: ["203.0.113.10"]