Skip to content

Kubernetes

The @kubernetes launch macro lets an operator edit one ConfigMap and have every consuming 10x pod converge within one poll, without an init-container, sidecar, or volume-mount reload trick. The regulator's per-container cap file is the canonical example: same write, same review, same diff history as the Git lane, applied without a Git roundtrip.

Mechanically, the 10x Engine reads ConfigMap keys into files inside its own pod and re-reads them whenever the ConfigMap changes. File-watchers on the consumer side fire on every update, the same way they would for a Git pull. The kubelet's volume-mount path doesn't behave that way: it swaps the mount via a symlink rename, which most watchers miss, so a kiwigrid/k8s-sidecar-style helper container isn't part of the design either.

The Kubernetes lane is @kubernetes, a peer to @github, sharing the same GitOps loop.

k8s

Operator checklist for the in-cluster default:

  1. Create the ConfigMap (any name; default lookup is log10x-action-intent).
  2. Grant the pod's ServiceAccount get on that ConfigMap (RBAC snippet below).
  3. Add @kubernetes={"enabled": true} to the launch (defaults to ConfigMap log10x-action-intent in the pod's own namespace). Override with "configMap": "<name>" when using a different name.

Everything else auto-resolves: namespace and the token / CA paths come from the pod's mounted service-account volume, and apiServer defaults to the in-cluster DNS name https://kubernetes.default.svc.

To learn more see deploying apps.

Config

To specify a @kubernetes launch macro via a YAML config file:

include:

tenx: run

- source: kubernetes

  options:

    # 'enabled' controls whether to enable this pull source.
    # Also overridable via the K8S_ENABLED environment variable.
    enabled: true

    # 'namespace' is the ConfigMap's namespace. If null, the engine
    # reads /var/run/secrets/kubernetes.io/serviceaccount/namespace
    # (the pod's own namespace). Overridable via K8S_NAMESPACE.
    namespace: log10x

    # 'configMap' is the ConfigMap name to read.
    # Default 'log10x-action-intent'. Overridable via K8S_CONFIGMAP.
    configMap: log10x-action-intent

    # 'keys' is a list of glob patterns selecting which ConfigMap
    # keys (from data + binaryData) to extract to disk.
    # Default ['*.csv', '*.json'].
    keys:
      - '*.csv'      # https://doc.log10x.com/api/js/#TenXLookup
      - '*.json'     # https://doc.log10x.com/config/json
      - '*.yaml'     # https://doc.log10x.com/config/yaml

    # 'syncInterval' is the poll interval for change-detection
    # against the ConfigMap's resourceVersion. Default '30s'.
    # Overridable via K8S_SYNC_INTERVAL.
    syncInterval: 30s

    # 'apiServer' is the Kubernetes API endpoint.
    # Default 'https://kubernetes.default.svc'. Overridable via K8S_API_SERVER.
    apiServer: https://kubernetes.default.svc

    # 'tokenPath' is the in-pod path to the service-account bearer token.
    # Default '/var/run/secrets/kubernetes.io/serviceaccount/token'.
    # Overridable via K8S_TOKEN_PATH.
    tokenPath: /var/run/secrets/kubernetes.io/serviceaccount/token

    # 'caPath' is the in-pod path to the API server's CA certificate.
    # Default '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'.
    # Overridable via K8S_CA_PATH.
    caPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

CLI

A @kubernetes launch macro can be specified via the CLI as a JSON object by bash-escaping its contents.

Minimal in-cluster call (defaults to ConfigMap log10x-action-intent in the pod's own namespace):

$ tenx run '@kubernetes={"enabled": true}'

Override the ConfigMap name:

$ tenx run '@kubernetes={"enabled": true, "configMap": "my-config"}'

Spelled out, all fields explicit:

$ tenx run '@kubernetes={"enabled": true, "namespace": "log10x", "configMap": "log10x-action-intent", "keys": ["*.csv", "*.json", "*.yaml"], "syncInterval": "30s"}'

RBAC

The pod's service account needs get on the target ConfigMap:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: log10x-configmap-reader
  namespace: log10x
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["log10x-action-intent"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: log10x-configmap-reader
  namespace: log10x
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: log10x-configmap-reader
subjects:
- kind: ServiceAccount
  name: log10x
  namespace: log10x

Where files land

Files land under a stable temp folder, segregated by namespace and ConfigMap:

${java.io.tmpdir}/tenx/kubernetes/<namespace>/<configMap>/<key>

The path is stable across pod restarts and across re-pulls. The path omits PID, resourceVersion, and poll-interval suffix, so downstream readers such as the rate receiver capLookup.file can hardcode the path in their config without dynamic discovery.

Change detection

Each poll issues GET /api/v1/namespaces/<ns>/configmaps/<cm> and compares the returned metadata.resourceVersion against the last seen value. If unchanged, the poll is a no-op. On change, every selected key is rewritten via temp + ATOMIC_MOVE, so file-watchers on the consumer side see one in-place modification per key. A metadata.uid change (ConfigMap deleted and recreated under the same name) also forces a re-extract, even if resourceVersion matches.

Operational notes from the API:

  • A 410 Gone response (the cached resourceVersion aged out of the API server) clears the local watermark and unconditionally accepts the next response.
  • A 413 Request Entity Too Large (the ConfigMap exceeds the API's 1 MiB body limit) is logged; the engine instructs the writer to shard the payload via a shard-manifest key in the primary ConfigMap.
  • A 404 Not Found on the first pull aborts pipeline startup with a clear error. A 404 after a successful pull keeps the stale local copy and logs.

Bytes and base64

Both ConfigMap surfaces are read:

  • data keys are decoded as UTF-8 and written verbatim.
  • binaryData keys are base64-decoded and written as raw bytes.

CSV, JSON, YAML, and JavaScript belong in data. Compiled .10x.tar symbol libraries and other binary assets belong in binaryData (remember to extend keys to match, e.g. keys: ['*.csv', '*.json', '*.10x.tar'], since the default glob only selects *.csv and *.json).

When to pick this vs @github

Pick by who edits and how fast the edit needs to propagate.

  • @github fits long-lived assets the team versions in Git: symbol libraries, scripts, geoIP tables. Edits go through pull requests; propagation takes a commit, a merge, and the configured syncInterval.
  • @kubernetes fits runtime overrides that operators or automation set against the cluster: the regulator's rate-cap.csv, lookup overrides, per-cluster mute files. An operator (or an MCP tool) writes the ConfigMap, and every consuming pod converges within roughly one syncInterval of the API server seeing the new resourceVersion. No Git roundtrip.

Both macros can run in the same launch and write to different folders under ${java.io.tmpdir}/tenx/. Downstream readers see a flat directory either way.