Deployment¶
This repository publishes container images and, for each service, the environment contract that configures it. The KELT repository renders the Kubernetes manifests and pins the image versions that run on the testbed. This page covers what an image expects from its deployment.
Images¶
| Image | Port | Published |
|---|---|---|
camara-gateway |
8080 | yes |
positioning-engine |
8080 | yes |
wifi-adapter |
8080 | yes |
vendor-adapter |
8080 | yes |
synthetic-adapter |
8080 | yes |
placement-editor |
8080 | yes |
location-app |
80 | yes |
mock-vendor |
8080 | no, built from source by make demo |
A published image is ghcr.io/jacobbista/5g-northbound/<image>:<version>. The
Python images run as the non-root user app, uid 1001. location-app is an
nginx image serving the built bundle.
Everything in an image has a fixed version. Each FROM names its base image
by digest. A Python image installs with services/<image>/constraints.txt,
which pins every package it adds to the base, and the tests in CI install with
the same file. The JavaScript bundles install from package-lock.json with
npm ci. make lock moves the base images to the digests their tags point at
now and regenerates the constraints. It is run on purpose, after a dependency
changes or to take upstream fixes, and followed by make test.
An adapter that carries vendor SDKs or material under NDA is built in a private
repository and implements the same adapter contract. The cluster runs it like
a published adapter, with an imagePullSecret.
Releases¶
A tag v* triggers
build.yml.
It builds the images whose services/<image> directory changed since the
previous tag, pushes each as <version> and latest, and creates a GitHub
Release that lists them. Image versions therefore differ between services.
The GitHub Release names what a tag published. The KELT pins name what runs.
GHCR creates a new package as private. It is made public once, by hand, in the package settings.
Every push runs
test.yml
(the Python and JavaScript suites) and
checks.yml
(environment contracts and sensitivity manifest, positioning fabric, compose
file, profiled specification freshness, leak scan).
Configuration¶
A service is configured only through environment variables. Each service
declares them in env.contract.yaml beside its code, in the format described in
deploy/contracts,
and a running pod serves the same declaration on GET /contract. The contract
is the reference for names, defaults and meaning.
A variable with sensitive: true goes into a Secret, every other one into a
ConfigMap, and both reach the pod through envFrom.
deploy/contracts/sensitivity-manifest.json, generated by make contracts,
lists every variable with its routing for KELT.
location-app and placement-editor run in the browser. At start their
entrypoint.sh writes the variables marked runtime_layer: window.__ENV__
into env-config.js, so these too are set as pod environment. A changed Secret
or ConfigMap takes effect after kubectl rollout restart.
make env-check # compose environment against the contracts
python3 deploy/tools/contracts.py render-k8s <image> # ConfigMap and Secret skeleton for one service
Wiring¶
| Service | Variable | Points to |
|---|---|---|
| camara-gateway | POSITIONING_ENGINE_URL |
positioning-engine |
| camara-gateway | WIFI_ADAPTER_URL |
wifi-adapter, for GET /anchors/calibration. Empty disables that route |
| camara-gateway | KEYCLOAK_URL, KEYCLOAK_REALM |
the identity provider that signs the tokens |
| each adapter | POSITIONING_ENGINE_URL |
positioning-engine, to register and to read the blueprint |
| each adapter | ADAPTER_BASE_URL |
the adapter's own Service, which the engine polls |
| placement-editor | POSITIONING_ENGINE_URL, WIFI_ADAPTER_URL, VENDOR_ADAPTER_URL |
the services its proxy routes reach |
| location-app | VITE_CAMARA_API_BASE, VITE_KEYCLOAK_URL |
the gateway and the identity provider, as the browser reaches them |
The engine needs no adapter list: each adapter registers itself and sends a
heartbeat (adapter registry). ADAPTER_URLS on the
engine only seeds an empty registry.
Every service runs behind a ClusterIP Service. Only camara-gateway,
location-app and placement-editor are reached from outside, and each of their
contracts names the variable that holds its public origin
(external_origin). The edge wifi-scanner posts to wifi-adapter from outside
the cluster.
Storage¶
| Service | Variable | Content | Volume |
|---|---|---|---|
| positioning-engine | BLUEPRINT_PATH, ADAPTER_REGISTRY_PATH |
the blueprint, the seeded adapter registrations | persistent volume at /app/data |
| camara-gateway | ASSET_STORE_FILE |
the asset map | persistent volume at /app/data |
| wifi-adapter | WIFI_CONFIG_PATH |
the bindings and the calibration survey | persistent volume |
| vendor-adapter | SCHEMA_FILE |
the vendor schema | ConfigMap, read-only |
Each persistent volume is written by one pod, so ReadWriteOnce suffices. The
pod needs fsGroup: 1001 to write it. A volume starts empty.
BLUEPRINT_SEED_PATH and ASSET_SEED_FILE name a read-only file that is
copied in once when the store is absent. The bindings are seeded with
PUT /bindings, through the editor's ⇪ import bindings.
The real blueprint, bindings and asset map, and the vendor credentials, never
enter this repository. The committed files under dev/ and
services/location-app/public/layout.example.json are placeholders
(blueprint and bindings,
asset registry).
Probes¶
| Service | Liveness | Readiness |
|---|---|---|
| wifi-adapter | /health |
/ready: 503 until the blueprint is loaded |
| vendor-adapter | /health |
/ready: 503 without a schema, or with one that contradicts ADAPTER_CAPABILITIES |
| synthetic-adapter | /health |
/ready |
| camara-gateway, positioning-engine, placement-editor | /health |
/health |
| location-app | / |
/ |
A pod that is not ready still answers /health and /contract, so its
configuration can be read and corrected.
Adding an adapter¶
- Deploy the adapter with a
ClusterIPService in the engine's namespace. - Set
POSITIONING_ENGINE_URL,ADAPTER_NAME,ADAPTER_BASE_URLandADAPTER_CAPABILITIES(what an adapter declares). - The adapter registers at start and sends a heartbeat. The engine drops it
after
ADAPTER_TTL_S(45 s) without one. - Assets reach it through capabilities whose
sourceis itsADAPTER_NAME.
The engine and the gateway are not restarted.