---
icon: material/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`](github.md), sharing the same [GitOps](https://doc.log10x.com/engine/gitops/) loop.

!!! tenx-k8sinitializer "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 the deployment procedure for your specific app under [apps](https://doc.log10x.com/apps/).

### :simple-yaml: Config

To specify a `@kubernetes` launch macro via a [YAML](https://doc.log10x.com/config/yaml/) config file:

``` yaml
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
```

### :octicons-command-palette-24: CLI

A `@kubernetes` launch macro can be specified via the [CLI](https://doc.log10x.com/config/cli/) as a JSON object by [bash-escaping](https://www.baeldung.com/linux/bash-escape-characters) its contents.

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

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

Override the ConfigMap name:

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

Spelled out, all fields explicit:

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

### :material-shield-lock: RBAC

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

``` yaml
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
```

### :material-folder-outline: 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](https://doc.log10x.com/run/receive/rate/) `capLookup.file` can hardcode the path in their config without dynamic discovery.

### :material-refresh-auto: 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.

### :material-database-search: 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`).

### :material-file-tree-outline: 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.
