Skip to main content
Version: v3.9.0

Local control plane

This page is the engine manifest: apiVersion: edgelet.iofog.org/v1 and kind: ControlPlane. The user file for a local trial is kind: LocalControlPlane. See Local control plane.

Edgelet can host one Controller container per node when you apply a kind: ControlPlane manifest. There is no default controller on install.

Core rules​

  • At most one controller deployment per Edgelet (SQLite singleton).
  • Edgelet may run with controllerUrl pointing to a remote cluster; local ControlPlane is optional.
  • Reconcile runs before managed microservices; accidental docker rm recreates the container while the DB row exists.
  • Only edgelet controlplane delete (or DELETE /v1/system/controlplane) removes the deployment and its private DB/log volumes under {diskDirectory}/volumes/data/{controller-uuid}/. Restart keeps those directories. Control-plane volumes are never scope: shared and are never dropped by --purge-volumes.

Use this order when the Controller runs on the same Edgelet node via ControlPlane:

  1. Deploy ControlPlane on Edgelet (edgelet deploy -f controlplane.yaml). Wait until edgelet controlplane get shows runtimeState: running. Edgelet does not auto-update controllerUrl in config.
  2. Confirm the controller process listens on the host API port (51121 by default) and that Console is reachable on host 80 → container console port (default 8008).
  3. On Controller (Console or API): create an agent/node (fog) with isSystem: true, generate a provision key for that fog.
  4. On Edgelet: set controllerUrl in config to the Controller API base the agent should use (must match TLS/trust settings), then edgelet provision <key>.

Registration of the controller workload with Controller (see below) runs after provision and only when the local controller container is running. The fog must be a system fog; registration uses the agent fog token and fails with 403 on non-system fogs.

Remote Controller (controllerUrl points elsewhere) without local ControlPlane is valid; registration and microservice-list merge sections apply only when a local ControlPlane row exists.

ControlPlane manifest (kind overview)​

FieldValue
apiVersionedgelet.iofog.org/v1 (required)
kindControlPlane (required)
metadata.nameRequired DNS-1123 label (≤63 chars). Drives CONTROLLER_NAME and bridge DNS; not the Controller microservice list name (see Registration).
metadata.namespaceOptional; empty → default. Drives CONTROLLER_NAMESPACE and bridge DNS.
metadata.labelsForbidden: validation rejects any labels.

Singleton: at most one ControlPlane deployment per Edgelet (system_control_plane in SQLite). Re-apply updates image, env, and related spec on the same controllerUuid. To change metadata.name or metadata.namespace, delete the deployment first.

Required spec (validation):

  • spec.controller.image
  • spec.auth.mode: embedded or external
  • embedded: spec.auth.bootstrap.username and spec.auth.bootstrap.password (password rules: ≥12 chars, 1 uppercase, 1 special)
  • external: spec.auth.issuerUrl, spec.auth.client.id, spec.auth.client.secret

Forbidden YAML​

Forbidden in YAML: spec.siteCA, spec.localCA, legacy keys (auth.url, ecnViewer*, spec.https, old Keycloak fields). Import site/local CA via Controller REST after deploy.

Annotated example: examples/controlplane.yaml.

How Edgelet applies the manifest​

Create vs patch

  • Create: new random controllerUuid, generation = 1.
  • Patch: same controllerUuid; generation increments; metadata.name / metadata.namespace must match the existing row or apply fails.

Async apply: CLI returns 202, polls until terminal status (default 15 minute budget; override with edgelet --timeout). If the CLI times out, the daemon may still be pulling or starting. Use edgelet controlplane get or poll GET /v1/deploy/controlplane:apply/{operationId}. Avoid immediate duplicate deploy after timeout.

Reconcile (steady state): the process manager runs reconcileControlPlane() before managed microservices when that workload is due. If the SQLite row says the controller should run but the container is missing (for example after manual docker rm), Edgelet recreates it. Only edgelet controlplane delete or DELETE /v1/system/controlplane removes the row and private DB/log volumes.

YAML → runtime (ports, volumes, labels)​

Edgelet builds a local microservice launch spec from the manifest. This is separate from the Controller microservice row created at registration.

Manifest / constantRuntime behavior
spec.controller.imageContainer image; stored on deployment row
spec.controller.registryOptional local registry id for pull; if unset, default registry id 2 (from_cache)
spec.controller.portContainer API port; default 51121. Host port is always 51121 → that container port
spec.console.portContainer console port; default 8008. Host port is always 80 → that container port
(volumes)Named volumes iofog-controller-db → /home/runner/.npm-global/lib/node_modules/controller/src/data/sqlite_files/, iofog-controller-log → /var/log/iofog-controller under {diskDirectory}/volumes/data/{controllerUuid}/ (private; never shared scope)
spec.tls.pathRead-only bind mount of host dir to /etc/iofog/controller-cert/ (tls.crt, tls.key, optional ca.crt)
spec.tls.base64No bind mount; cert material passed via env (see environment table)
CapabilitiesNET_RAW always added; container is not privileged
NetworkBridge (not host network)
Labelsedgelet.iofog.org/system=true, role=controller, plus workload identity labels; scope local
Container envCONTROL_PLANE=Remote always; see next section

Not driven by Controller microservice reconcile for the controller UUID: the process manager skips ADD/UPDATE/DELETE for the microservice whose UUID equals the ControlPlane controllerUuid. Lifecycle for that container is owned by the ControlPlane reconciler and dedicated APIs (controlplane restart, redeploy YAML, controlplane delete).

YAML → controller container environment​

Edgelet sets container environment with BuildControllerEnv. Rules:

  • Always set: CONTROL_PLANE=Remote, CONTROLLER_UUID, CONTROLLER_NAMESPACE, CONTROLLER_NAME.
  • Optional blocks: if a YAML block or field is omitted, Edgelet emits no corresponding env vars (except the four identity/plane vars above).
  • Ports: if spec.controller.port / spec.console.port are omitted, Edgelet does not set API_PORT / CONSOLE_PORT; defaults 51121 / 8008 still apply via port mappings, not env.
  • spec.console.url: set only when non-empty in YAML. Edgelet does not copy spec.controller.publicUrl into CONSOLE_URL.
  • spec.vault.basePath: passed literally; Edgelet does not expand $namespace. Use the real path or namespace string in YAML.

Identity (always)​

YAML sourceContainer env
(generated stable uuid)CONTROLLER_UUID
metadata.namespace (default default)CONTROLLER_NAMESPACE
metadata.nameCONTROLLER_NAME
(fixed)CONTROL_PLANE = Remote

spec.controller​

YAML fieldContainer envWhen omitted
publicUrlCONTROLLER_PUBLIC_URLnot set
trustProxyTRUST_PROXY (true/false)not set
portAPI_PORTnot set; host/container mapping still defaults to 51121

spec.console​

YAML fieldContainer envWhen omitted
portCONSOLE_PORTnot set; host 80 maps to default 8008
urlCONSOLE_URLnot set

spec.auth​

YAML fieldContainer env
modeAUTH_MODE
insecureAllowHttpAUTH_INSECURE_ALLOW_HTTP
insecureAllowBootstrapLogAUTH_INSECURE_ALLOW_BOOTSTRAP_LOG
bootstrap.usernameOIDC_BOOTSTRAP_ADMIN_USERNAME
bootstrap.passwordOIDC_BOOTSTRAP_ADMIN_PASSWORD
issuerUrlOIDC_ISSUER_URL
client.idOIDC_CLIENT_ID
client.secretOIDC_CLIENT_SECRET
rateLimit.enabledAUTH_RATE_LIMIT_ENABLED
rateLimit.maxRequestsPerWindowAUTH_RATE_LIMIT_MAX_REQUESTS
rateLimit.windowMsAUTH_RATE_LIMIT_WINDOW_MS
sessionStore.typeAUTH_SESSION_STORE_TYPE
sessionStore.ttlMsAUTH_SESSION_STORE_TTL_MS
sessionStore.secretAUTH_SESSION_SECRET
tokenTtl.accessTokenTtlSecondsAUTH_ACCESS_TOKEN_TTL_SECONDS
tokenTtl.refreshTokenTtlSecondsAUTH_REFRESH_TOKEN_TTL_SECONDS
oidcTtl.interactionTtlSecondsAUTH_OIDC_INTERACTION_TTL_SECONDS
oidcTtl.grantTtlSecondsAUTH_OIDC_GRANT_TTL_SECONDS
oidcTtl.sessionTtlSecondsAUTH_OIDC_SESSION_TTL_SECONDS
oidcTtl.idTokenTtlSecondsAUTH_OIDC_ID_TOKEN_TTL_SECONDS

spec.database​

YAML fieldContainer env
providerDB_PROVIDER
userDB_USERNAME
hostDB_HOST
portDB_PORT
passwordDB_PASSWORD
databaseNameDB_NAME
sslDB_USE_SSL
caDB_SSL_CA

spec.events​

YAML fieldContainer env
auditEnabledEVENT_AUDIT_ENABLED
retentionDaysEVENT_RETENTION_DAYS
cleanupIntervalEVENT_CLEANUP_INTERVAL
captureIpAddressEVENT_CAPTURE_IP_ADDRESS

spec.systemMicroservices (arch → image slot)​

YAML map keyContainer env
router.amd64ROUTER_IMAGE_1
router.arm64ROUTER_IMAGE_2
router.riscv64ROUTER_IMAGE_3
router.armROUTER_IMAGE_4
nats.amd64NATS_IMAGE_1
nats.arm64NATS_IMAGE_2
nats.riscv64NATS_IMAGE_3
nats.armNATS_IMAGE_4

spec.nats​

YAML fieldContainer env
enabledNATS_ENABLED

spec.logLevel​

YAML fieldContainer env
logLevelLOG_LEVEL

spec.tls​

Host path (spec.tls.path): directory must exist on the Edgelet host (absolute path). Edgelet mounts it read-only and sets:

ConditionContainer env
path modeSERVER_DEV_MODE=false, TLS_PATH_CERT=tls.crt, TLS_PATH_KEY=tls.key
ca.crt present in diralso TLS_PATH_INTERMEDIATE_CERT=ca.crt, INTERMEDIATE_CERT=/etc/iofog/controller-cert/ca.crt

Inline base64 (spec.tls.base64):

YAML fieldContainer env
(block present)SERVER_DEV_MODE=false
certTLS_BASE64_CERT
keyTLS_BASE64_KEY
caTLS_BASE64_INTERMEDIATE_CERT

spec.vault​

YAML fieldContainer env
enabledVAULT_ENABLED
providerVAULT_PROVIDER
basePathVAULT_BASE_PATH
hashicorp.addressVAULT_HASHICORP_ADDRESS
hashicorp.tokenVAULT_HASHICORP_TOKEN
hashicorp.mountVAULT_HASHICORP_MOUNT
aws.regionVAULT_AWS_REGION
aws.accessKeyIdVAULT_AWS_ACCESS_KEY_ID
aws.accessKeyVAULT_AWS_ACCESS_KEY
azure.urlVAULT_AZURE_URL
azure.tenantIdVAULT_AZURE_TENANT_ID
azure.clientIdVAULT_AZURE_CLIENT_ID
azure.clientSecretVAULT_AZURE_CLIENT_SECRET
google.projectIdVAULT_GOOGLE_PROJECT_ID
google.credentialsVAULT_GOOGLE_CREDENTIALS

Secrets in CLI/API output​

edgelet controlplane get --manifest and GET /v1/system/controlplane/manifest return YAML with secrets replaced by ***: auth bootstrap password, OIDC client secret, session store secret, database password/CA, TLS base64 blobs, vault tokens/keys/credentials. Non-secret fields remain visible.

Controller microservice registration​

After provision, Edgelet registers the local controller workload with Controller so it appears in the agent microservice list with isController: true.

Preconditions (all required):

  • Agent provisioned and connected to controllerUrl.
  • Local ControlPlane row exists; desiredState and runtimeState are running.
  • Controller container exists for controllerUuid.
  • Fog is system (isSystem: true); otherwise Controller returns 403.

Request (Edgelet-built): POST /api/v3/agent/controller/register with agent auth. Body includes:

FieldEdgelet source
uuidControlPlane controllerUuid (stable)
nameAlways controller (Controller schema requirement)
images[{ containerImage, archId }] from manifest image + host arch
registryIdFrom manifest registry or default 2
portsSame mappings as launch (51121 / 80 → console)
volumeMappingsDB + log volumes (+ TLS bind if used)
envSame map as container env table above
capAddIncludes NET_RAW

Once per UUID: after a successful 200, Edgelet sets controllerRegistered on the deployment row and stops calling register on the 30s worker until state is reset (deprovision clears persisted register flags).

Forced re-register: when you patch ControlPlane YAML while provisioned and apply completes with runtimeState: running, Edgelet calls register again with force so Controller upserts env/image/ports.

Restart: edgelet controlplane restart does not re-register; UUID, volumes, and register state are preserved.

Identity summary (three names):

ConceptValue
Bridge DNS / CONTROLLER_NAME / CONTROLLER_NAMESPACEmetadata.name / metadata.namespace
Controller microservice name in list/APIcontroller
Controller application for system MSController assigns system-{fogName} from the provisioned fog (not the YAML namespace)

edgelet ms ls --source controlplane shows application/name from metadata for display; the Controller's stored application for the registered row follows Controller rules above.

How Edgelet handles Controller microservice list updates​

When getChanges includes microserviceList, the field agent loads microservices from Controller into SQLite, then runs controller-specific merge if a local ControlPlane row exists.

For the row whose UUID matches controllerUuid:

Controller signalEdgelet behavior
delete: trueIgnored while ControlPlane deployment exists; warning logged; delete flag cleared in memory
rebuild: trueFirst rebuild after successful register: skipped once (initialRebuildSkipped) to avoid redundant recreate right after registration. Later rebuilds: bump ControlPlane generation, mark reconcile dirty, optional pull on recreate
Image / registryId drift vs local manifestUpdate spec.controller.image and/or spec.controller.registry in stored manifest YAML, bump generation, trigger ControlPlane recreate via process manager
Other fields (env, ports, config from Controller)Not merged into ControlPlane manifest by this path: change local YAML and edgelet deploy -f

After merge, the process manager reconciles ControlPlane from the SQLite manifest, not from Controller ADD/DELETE for that UUID.

Provisioned guards: while provisioned, ms rm|stop|kill|start|restart on the controller UUID is rejected; use edgelet controlplane restart or redeploy YAML. controlplane delete is rejected until deprovision.

Status, logs, exec: Controller still drives status/log/exec session polling for the registered controller microservice like any other managed MS; only container lifecycle ownership is special as above.

Fixtures​

Pathmetadata.namespacemetadata.nameUse
test/deployment-yamls/controlplane.yamlbarfooDev smoke / manual apply
IT fixturedefaultpottest/control-plane/fixtures/controlplane-it.yaml: DNS: edgelet.controller..., controller.default..., default.pot...

Deploy​

edgelet deploy -f controlplane.yaml
edgelet deploy -f controlplane.yaml --timeout=20m # optional: override default poll budget
  • apiVersion: edgelet.iofog.org/v1
  • kind: ControlPlane
  • Re-apply updates image/env (same controller UUID). To change metadata.name or metadata.namespace, delete first.

Long deploys (async apply)​

Control plane apply is asynchronous: the CLI returns 202 immediately, polls apply status, then confirms edgelet controlplane get shows runtimeState: running.

DefaultValue
Poll budget (CP deploy)15 minutes
Per status request60 seconds
Overrideedgelet --timeout on poll total

If the CLI times out, work may still continue on the daemon. Check:

edgelet controlplane get
# or poll: GET /v1/deploy/controlplane:apply/<operationId>

Do not run a second deploy immediately after a timeout (avoids duplicate apply). Registry manifests remain synchronous (fast).

Inspect​

edgelet controlplane get
edgelet controlplane get --manifest # secrets masked
edgelet ms ls --source controlplane
edgelet ms inspect <uuid|namespace.name> # engine container inspect (raw.engineInspect)

Use controlplane get for deployment status and manifest; ms inspect returns the runtime container inspect (same as other microservices), not the SQLite manifest row.

Restart​

Bounce the controller container without removing the deployment or volumes. Allowed while the agent is provisioned (unlike controlplane delete).

edgelet controlplane restart
edgelet controlplane restart --pull # recreate and pull image
edgelet controlplane get
  • Restart. Process/container bounce; same UUID, volumes, and manifest row.
  • Deploy. Spec/image/env changes: edgelet deploy -f controlplane.yaml
  • ms restart on the controller UUID is rejected when provisioned; use controlplane restart.

Delete​

edgelet controlplane delete

This is the only supported way to remove the controller deployment and its private DB/log volumes. It remains provisioned-guarded: rejected while the agent is provisioned. Deprovision the agent first. While unprovisioned, edgelet ms rm on the controller UUID is also rejected; Edgelet will reconcile the container back if the ControlPlane record still exists. edgelet deprovision --purge-volumes does not drop these volumes.

Use Restart (above) to bounce the container without deleting the deployment.

Controller registration (system fog)​

See Recommended rollout and Controller microservice registration above. Operator requirement: system fog with isSystem: true. Spec updates from Controller merge image/registry/rebuild only; redeploy YAML for env changes.

Ports​

HostContainerService
51121spec.controller.port (default 51121)Controller API
80spec.console.port (default 8008)EdgeOps Console

DNS (embedded)​

Controller microservice identity matches other workloads: application = metadata.namespace, name = metadata.name.

From workloads on the bridge network, three names resolve to the controller IP:

  1. edgelet.controller.svc.bridge.local. Stable alias
  2. controller.<metadata.namespace>.svc.bridge.local. Namespace alias
  3. <metadata.namespace>.<metadata.name>.svc.bridge.local. Standard appName.microserviceName pattern

(edgelet ms ls --source controlplane shows the same application and name fields.)

Set controllerUrl in Edgelet config separately (Edgelet does not auto-update it on deploy).

Router CA (siteCA / localCA)​

Not in the Edgelet ControlPlane manifest. Use potctl to import site/local CA material via the Controller REST API after the controller is up.

Watchdog​

Edgelet does not delete the controller container when labels match the DB row (edgelet.iofog.org/system=true, role=controller, UUID, container ID, image ref). Manual docker run without matching identity may be removed by the watchdog.

Image load (global CLI)​

Large archives use async load (same pattern as edgelet image pull):

edgelet image load /path/to/archive.tar

Default poll budget 30 minutes; use edgelet --timeout to override.

Integration tests​

./test/control-plane/run-all.sh

See test/control-plane/README.md.

Group 3See anything wrong with the document? Help us improve it!