From fffe043ac7873819da6b37848a27ff9b73ffd06f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:29:09 +0000 Subject: [PATCH 1/4] Initial plan From 5294d68bf6531e3ba225479332c732cc22094113 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:36:48 +0000 Subject: [PATCH 2/4] feat: integrate EdgeX Foundry 4.0 (Odessa) into EdgeKit - Add docker-compose.edgex-client.yml: minimal no-security EdgeX stack for edge nodes (Core Services + Supporting Services + device-modbus) - Add docker-compose.edgex-server.yml: EdgeX Security Services (OpenBao, nginx gateway, bootstrapper, proxy-auth) for central server - Add helm/edgekit/values-client.yaml: Helm overrides for edge clusters - Add helm/edgekit/values-server.yaml: Helm overrides for central server - Add fleet.yaml: Rancher Fleet GitOps multi-cluster config - Add scripts: start/stop helpers for both EdgeX stacks - Add docs/edgex.md: full EdgeX integration guide - Update docs/architecture.md and README.md --- README.md | 111 +++++++---- docker-compose.edgex-client.yml | 338 ++++++++++++++++++++++++++++++++ docker-compose.edgex-server.yml | 270 +++++++++++++++++++++++++ docs/architecture.md | 47 +++++ docs/edgex.md | 203 +++++++++++++++++++ fleet.yaml | 37 ++++ helm/edgekit/values-client.yaml | 32 +++ helm/edgekit/values-server.yaml | 47 +++++ scripts/start-edgex-client.sh | 26 +++ scripts/start-edgex-server.sh | 22 +++ scripts/stop-edgex-client.sh | 16 ++ scripts/stop-edgex-server.sh | 16 ++ 12 files changed, 1132 insertions(+), 33 deletions(-) create mode 100644 docker-compose.edgex-client.yml create mode 100644 docker-compose.edgex-server.yml create mode 100644 docs/edgex.md create mode 100644 fleet.yaml create mode 100644 helm/edgekit/values-client.yaml create mode 100644 helm/edgekit/values-server.yaml create mode 100755 scripts/start-edgex-client.sh create mode 100755 scripts/start-edgex-server.sh create mode 100755 scripts/stop-edgex-client.sh create mode 100755 scripts/stop-edgex-server.sh diff --git a/README.md b/README.md index 6832223..b206432 100644 --- a/README.md +++ b/README.md @@ -3,38 +3,51 @@ **EdgeKit** is an open-source client/server IoT edge platform designed to run on [k3s](https://k3s.io/). It provides a central MQTT broker (server) and lightweight edge agents (clients) that collect telemetry and stream it to the server via MQTT over WebSocket. +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to add full IoT device management, Modbus connectivity, and centrally-managed security services. + --- ## Architecture ``` -┌─────────────────────────────────────────┐ -│ k3s / Kubernetes │ -│ │ -│ ┌───────────────────────────────────┐ │ -│ │ edgekit-server │ │ -│ │ Eclipse Mosquitto MQTT broker │ │ -│ │ • port 1883 – plain MQTT │ │ -│ │ • port 9001 – MQTT over WS │ │ -│ └───────────────────────────────────┘ │ -│ ▲ ▲ │ -│ │ WebSocket │ │ -│ ┌─────────┴──┐ ┌──────┴─────────┐ │ -│ │ client 1 │ │ client N │ │ -│ │ edge agent │ │ edge agent │ │ -│ └────────────┘ └────────────────┘ │ -└─────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────┐ +│ Central Server │ +│ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ edgekit-server (Eclipse Mosquitto MQTT broker) │ │ +│ │ • port 1883 – plain MQTT │ │ +│ │ • port 9001 – MQTT over WS │ │ +│ └───────────────────────────────────────────────────┘ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ EdgeX Security Services │ │ +│ │ • secret-store (OpenBao) :8200 │ │ +│ │ • nginx API gateway :8443 │ │ +│ └───────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ + ▲ WebSocket / MQTT ▲ WebSocket / MQTT + │ │ +┌────────┴───────┐ ┌────────┴────────┐ +│ Edge Node 1 │ │ Edge Node N │ +│ │ │ │ +│ edgekit-client │ │ edgekit-client │ +│ EdgeX Core + │ │ EdgeX Core + │ +│ device-modbus │ │ device-modbus │ +└────────────────┘ └─────────────────┘ ``` -Each **client** is a single Docker container that: +Each **edge node** runs: + +- `edgekit-client` – collects system metrics and publishes them to the central MQTT broker +- EdgeX Core Services (`core-data`, `core-metadata`, `core-command`) +- EdgeX Supporting Services (`support-notifications`, `support-scheduler`) +- `device-modbus` – reads from Modbus TCP/RTU field devices -- Collects system metrics (CPU, memory, disk, network) -- Publishes JSON payloads to `edgekit//metrics` every 5 seconds (configurable) -- Automatically reconnects to the broker on failure +The **central server** runs: -The **server** is a single Eclipse Mosquitto container with both plain MQTT (1883) and WebSocket (9001) listeners. +- `edgekit-server` – Eclipse Mosquitto MQTT broker for telemetry +- EdgeX Security Services (OpenBao secret store, nginx API gateway, authentication) -For a deeper dive, see [docs/architecture.md](docs/architecture.md). +For a deeper dive, see [docs/architecture.md](docs/architecture.md) and [docs/edgex.md](docs/edgex.md). --- @@ -135,36 +148,68 @@ See [helm/edgekit/values.yaml](helm/edgekit/values.yaml) for the full Helm confi --- -## Repository Layout +## EdgeX Foundry integration + +EdgeKit embeds [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) for full IoT device management and Modbus connectivity. + +### Edge node – start the EdgeX core stack + +```bash +./scripts/start-edgex-client.sh +``` + +Starts: MQTT message bus, PostgreSQL, core-keeper, core-data, core-metadata, core-command, support-notifications, support-scheduler, device-modbus. + +### Central server – start the EdgeX security stack + +```bash +./scripts/start-edgex-server.sh +``` + +Starts: OpenBao (secret store), security-bootstrapper, security-secretstore-setup, security-proxy-auth, security-proxy-setup, nginx (TLS API gateway on :8443). + +See [docs/edgex.md](docs/edgex.md) for the full integration guide. + +--- ``` edgekit/ -├── server/ # MQTT broker container +├── server/ # MQTT broker container │ ├── Dockerfile │ ├── mosquitto.conf │ └── entrypoint.sh -├── client/ # Edge agent container +├── client/ # Edge agent container │ ├── Dockerfile │ ├── package.json │ └── src/ │ └── index.js ├── helm/ -│ └── edgekit/ # Root Helm chart +│ └── edgekit/ # Root Helm chart │ ├── Chart.yaml -│ ├── values.yaml +│ ├── values.yaml # Default values +│ ├── values-client.yaml # Edge node overrides +│ ├── values-server.yaml # Central server overrides │ └── templates/ ├── scripts/ -│ ├── build.sh # Build Docker images -│ ├── start-local.sh # Start via docker compose -│ └── stop-local.sh # Stop local stack +│ ├── build.sh # Build Docker images +│ ├── start-local.sh # Start base stack via docker compose +│ ├── stop-local.sh # Stop base stack +│ ├── start-edgex-client.sh # Start EdgeX edge stack +│ ├── stop-edgex-client.sh # Stop EdgeX edge stack +│ ├── start-edgex-server.sh # Start EdgeX security stack +│ └── stop-edgex-server.sh # Stop EdgeX security stack ├── .github/ │ └── workflows/ -│ ├── ci.yml # CI: lint + build on PR/push to develop|main -│ └── release.yml # Release: push images + Helm chart on tag +│ ├── ci.yml # CI: lint + build on PR/push to develop|main +│ └── release.yml # Release: push images + Helm chart on tag ├── docs/ │ ├── architecture.md +│ ├── edgex.md # EdgeX integration guide │ └── quickstart.md -└── docker-compose.yml # Local development +├── docker-compose.yml # Base local stack (MQTT broker + edge agent) +├── docker-compose.edgex-client.yml # EdgeX edge stack (no security) +├── docker-compose.edgex-server.yml # EdgeX security services +└── fleet.yaml # Rancher Fleet multi-cluster GitOps ``` --- diff --git a/docker-compose.edgex-client.yml b/docker-compose.edgex-client.yml new file mode 100644 index 0000000..fb8c050 --- /dev/null +++ b/docker-compose.edgex-client.yml @@ -0,0 +1,338 @@ +# ============================================================================= +# EdgeKit – EdgeX Foundry 4.0 (Odessa) – Client / Edge node stack +# +# Runs a minimal, no-security EdgeX platform on the edge device: +# • Core Services – core-keeper, core-common-config-bootstrapper, +# core-data, core-metadata, core-command +# • Supporting – support-notifications, support-scheduler +# • Device Services – device-modbus +# • Infrastructure – MQTT message bus, PostgreSQL +# +# Usage: +# docker compose -f docker-compose.edgex-client.yml up -d +# +# This compose file is intended for edge (client) nodes only. +# The central server runs docker-compose.edgex-server.yml instead. +# +# References: +# https://docs.edgexfoundry.org/4.0/ +# https://github.com/edgexfoundry/edgex-compose/blob/v4.0.0/docker-compose-no-secty.yml +# ============================================================================= +name: edgex-client + +services: + + # --------------------------------------------------------------------------- + # MQTT broker – internal EdgeX message bus + # --------------------------------------------------------------------------- + mqtt-broker: + image: eclipse-mosquitto:2.0.21 + container_name: edgex-mqtt-broker + hostname: edgex-mqtt-broker + command: + - /usr/sbin/mosquitto + - -c + - /mosquitto-no-auth.conf + ports: + - "127.0.0.1:1883:1883" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + + # --------------------------------------------------------------------------- + # PostgreSQL – data store for core-data, core-metadata, core-keeper + # --------------------------------------------------------------------------- + database: + image: postgres:16.3-alpine3.20 + container_name: edgex-postgres + hostname: edgex-postgres + environment: + POSTGRES_DB: edgex_db + POSTGRES_PASSWORD: postgres + ports: + - "127.0.0.1:5432:5432" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /run + volumes: + - /etc/localtime:/etc/localtime:ro + - db-data:/var/lib/postgresql/data + + # --------------------------------------------------------------------------- + # core-keeper – service registry (replaces Consul in EdgeX 4.0) + # --------------------------------------------------------------------------- + core-keeper: + image: edgexfoundry/core-keeper:4.0.0 + container_name: edgex-core-keeper + hostname: edgex-core-keeper + depends_on: + database: + condition: service_started + mqtt-broker: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-keeper + DATABASE_HOST: edgex-postgres + MESSAGEBUS_TYPE: mqtt + MESSAGEBUS_PROTOCOL: tcp + MESSAGEBUS_HOST: edgex-mqtt-broker + MESSAGEBUS_PORT: "1883" + MESSAGEBUS_AUTHMODE: none + MESSAGEBUS_SECRETNAME: "" + ports: + - "127.0.0.1:59890:59890" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-common-config-bootstrapper – pushes shared config to core-keeper + # --------------------------------------------------------------------------- + core-common-config-bootstrapper: + image: edgexfoundry/core-common-config-bootstrapper:4.0.0 + container_name: edgex-core-common-config-bootstrapper + hostname: edgex-core-common-config-bootstrapper + command: + - /core-common-config-bootstrapper + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + ALL_SERVICES_DATABASE_HOST: edgex-postgres + ALL_SERVICES_MESSAGEBUS_HOST: edgex-mqtt-broker + ALL_SERVICES_REGISTRY_HOST: edgex-core-keeper + APP_SERVICES_CLIENTS_CORE_METADATA_HOST: edgex-core-metadata + DEVICE_SERVICES_CLIENTS_CORE_METADATA_HOST: edgex-core-metadata + networks: + - edgex-network + read_only: true + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-metadata – device / profile / service registry + # --------------------------------------------------------------------------- + core-metadata: + image: edgexfoundry/core-metadata:4.0.0 + container_name: edgex-core-metadata + hostname: edgex-core-metadata + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-metadata + ports: + - "127.0.0.1:59881:59881" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-data – event / reading persistence and forwarding + # --------------------------------------------------------------------------- + core-data: + image: edgexfoundry/core-data:4.0.0 + container_name: edgex-core-data + hostname: edgex-core-data + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-data + ports: + - "127.0.0.1:59880:59880" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # core-command – REST / MQTT command interface for devices + # --------------------------------------------------------------------------- + core-command: + image: edgexfoundry/core-command:4.0.0 + container_name: edgex-core-command + hostname: edgex-core-command + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + core-metadata: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-core-command + EXTERNALMQTT_URL: tcp://edgex-mqtt-broker:1883 + ports: + - "127.0.0.1:59882:59882" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # support-notifications – alerting and notification service + # --------------------------------------------------------------------------- + support-notifications: + image: edgexfoundry/support-notifications:4.0.0 + container_name: edgex-support-notifications + hostname: edgex-support-notifications + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-support-notifications + ports: + - "127.0.0.1:59860:59860" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # support-scheduler – job scheduling service + # --------------------------------------------------------------------------- + support-scheduler: + image: edgexfoundry/support-scheduler:4.0.0 + container_name: edgex-support-scheduler + hostname: edgex-support-scheduler + command: + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + database: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-support-scheduler + ports: + - "127.0.0.1:59863:59863" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + + # --------------------------------------------------------------------------- + # device-modbus – Modbus (TCP/RTU) device service + # --------------------------------------------------------------------------- + device-modbus: + image: edgexfoundry/device-modbus:4.0.0 + container_name: edgex-device-modbus + hostname: edgex-device-modbus + command: + - -cp=keeper.http://edgex-core-keeper:59890 + - --registry + depends_on: + core-keeper: + condition: service_started + core-common-config-bootstrapper: + condition: service_started + core-data: + condition: service_started + core-metadata: + condition: service_started + environment: + EDGEX_SECURITY_SECRET_STORE: "false" + SERVICE_HOST: edgex-device-modbus + ports: + - "127.0.0.1:59901:59901" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + +networks: + edgex-network: + name: edgex_edgex-network + driver: bridge + +volumes: + db-data: + name: edgex_db-data diff --git a/docker-compose.edgex-server.yml b/docker-compose.edgex-server.yml new file mode 100644 index 0000000..3b6f1ff --- /dev/null +++ b/docker-compose.edgex-server.yml @@ -0,0 +1,270 @@ +# ============================================================================= +# EdgeKit – EdgeX Foundry 4.0 (Odessa) – Server / Central node security stack +# +# Runs the EdgeX Security Services on the central server: +# • secret-store – OpenBao (secret store, open-source Vault fork) +# • security-bootstrapper – orchestrates the secure startup sequence +# • security-secretstore-setup – initialises OpenBao and mints service tokens +# • security-proxy-setup – configures the nginx API gateway +# • security-proxy-auth – JWT / token-based API authentication +# • nginx – TLS-terminating reverse proxy (API gateway) +# +# Usage: +# docker compose -f docker-compose.edgex-server.yml up -d +# +# This compose file is intended for the central (server) node only. +# Edge nodes run docker-compose.edgex-client.yml instead. +# +# References: +# https://docs.edgexfoundry.org/4.0/security/Ch-Security/ +# https://github.com/edgexfoundry/edgex-compose/blob/v4.0.0/docker-compose.yml +# ============================================================================= +name: edgex-server + +# Shared environment block – stage-gate coordinates the secure boot sequence +x-stagegate-env: &stagegate-env + STAGEGATE_BOOTSTRAPPER_HOST: edgex-security-bootstrapper + STAGEGATE_BOOTSTRAPPER_STARTPORT: "54321" + STAGEGATE_DATABASE_HOST: edgex-postgres + STAGEGATE_DATABASE_PORT: "5432" + STAGEGATE_DATABASE_READYPORT: "5432" + STAGEGATE_PROXYSETUP_READYPORT: "54325" + STAGEGATE_READY_TORUNPORT: "54329" + STAGEGATE_REGISTRY_HOST: edgex-core-keeper + STAGEGATE_REGISTRY_PORT: "59890" + STAGEGATE_REGISTRY_READYPORT: "54324" + STAGEGATE_SECRETSTORESETUP_HOST: edgex-security-secretstore-setup + STAGEGATE_SECRETSTORESETUP_TOKENS_READYPORT: "54322" + STAGEGATE_WAITFOR_TIMEOUT: 60s + PROXY_SETUP_HOST: edgex-security-proxy-setup + SECRETSTORE_HOST: edgex-secret-store + CLIENTS_SECURITY_SECRETSTORE_SETUP_HOST: edgex-security-secretstore-setup + +services: + + # --------------------------------------------------------------------------- + # security-bootstrapper – creates /edgex-init volume, seeds network keys, + # and coordinates the multi-stage secure startup sequence + # --------------------------------------------------------------------------- + security-bootstrapper: + image: edgexfoundry/security-bootstrapper:4.0.0 + container_name: edgex-security-bootstrapper + hostname: edgex-security-bootstrapper + environment: + <<: *stagegate-env + EDGEX_USER: "2002" + EDGEX_GROUP: "2001" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init + + # --------------------------------------------------------------------------- + # secret-store – OpenBao (open-source HashiCorp Vault fork) + # Stores all service secrets and credentials + # --------------------------------------------------------------------------- + secret-store: + image: openbao/openbao:2.1.1 + container_name: edgex-secret-store + hostname: edgex-secret-store + command: + - server + entrypoint: + - /edgex-init/secretstore_wait_install.sh + depends_on: + security-bootstrapper: + condition: service_started + environment: + <<: *stagegate-env + SKIP_SETCAP: "true" + BAO_ADDR: http://edgex-secret-store:8200 + BAO_CONFIG_DIR: /openbao/config + BAO_LOCAL_CONFIG: | + listener "tcp" { + address = "edgex-secret-store:8200" + tls_disable = "1" + cluster_address = "edgex-secret-store:8201" + } + backend "file" { + path = "/openbao/file" + } + default_lease_ttl = "168h" + max_lease_ttl = "720h" + disable_mlock = true + ports: + - "127.0.0.1:8200:8200" + networks: + - edgex-network + restart: always + tmpfs: + - /openbao/config + user: "root:root" + volumes: + - edgex-init:/edgex-init:ro + - secret-store-file:/openbao/file + - secret-store-logs:/openbao/logs + + # --------------------------------------------------------------------------- + # security-secretstore-setup – seeds OpenBao with all EdgeX service tokens + # --------------------------------------------------------------------------- + security-secretstore-setup: + image: edgexfoundry/security-secretstore-setup:4.0.0 + container_name: edgex-security-secretstore-setup + hostname: edgex-security-secretstore-setup + entrypoint: + - /edgex-init/secretstore_setup_wait_install.sh + depends_on: + secret-store: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + EDGEX_USER: "2002" + EDGEX_GROUP: "2001" + SECRETSTORE_HOST: edgex-secret-store + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets:/tmp/edgex/secrets:z + + # --------------------------------------------------------------------------- + # security-proxy-auth – JWT / API-token authentication service + # --------------------------------------------------------------------------- + security-proxy-auth: + image: edgexfoundry/security-proxy-auth:4.0.0 + container_name: edgex-proxy-auth + hostname: edgex-proxy-auth + command: + - entrypoint.sh + - /security-proxy-auth + - --registry + - -cp=keeper.http://edgex-core-keeper:59890 + entrypoint: + - /bin/sh + - /edgex-init/ready_to_run_wait_install.sh + depends_on: + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + SERVICE_HOST: edgex-proxy-auth + ports: + - "127.0.0.1:59842:59842" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets/security-proxy-auth:/tmp/edgex/secrets/security-proxy-auth:ro,z + + # --------------------------------------------------------------------------- + # security-proxy-setup – configures the nginx API gateway routes + # --------------------------------------------------------------------------- + security-proxy-setup: + image: edgexfoundry/security-proxy-setup:4.0.0 + container_name: edgex-security-proxy-setup + hostname: edgex-security-proxy-setup + entrypoint: + - /edgex-init/proxy_setup_wait_install.sh + depends_on: + security-bootstrapper: + condition: service_started + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + # Add routes for the EdgeX core services exposed by this proxy + EDGEX_ADD_PROXY_ROUTE: >- + core-data.http://edgex-core-data:59880, + core-metadata.http://edgex-core-metadata:59881, + core-command.http://edgex-core-command:59882, + support-notifications.http://edgex-support-notifications:59860, + support-scheduler.http://edgex-support-scheduler:59863, + device-modbus.http://edgex-device-modbus:59901 + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "root:root" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - nginx-tls:/etc/ssl/nginx + - nginx-templates:/etc/nginx/templates + + # --------------------------------------------------------------------------- + # nginx – TLS-terminating reverse proxy / API gateway + # Exposes EdgeX APIs externally on port 8443 (HTTPS) + # --------------------------------------------------------------------------- + nginx: + image: nginx:1.25.5-alpine-slim + container_name: edgex-nginx + hostname: edgex-nginx + command: + - /docker-entrypoint.sh + - nginx + - -g + - "daemon off;" + entrypoint: + - /bin/sh + - /edgex-init/nginx_wait_install.sh + depends_on: + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + ports: + - "8443:8443" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /etc/nginx/conf.d + - /var/cache/nginx + - /var/log/nginx + - /var/run + volumes: + - edgex-init:/edgex-init:ro + - nginx-templates:/etc/nginx/templates + - nginx-tls:/etc/ssl/nginx + +networks: + edgex-network: + name: edgex_edgex-network + driver: bridge + +volumes: + edgex-init: + name: edgex_edgex-init + secret-store-file: + name: edgex_secret-store-file + secret-store-logs: + name: edgex_secret-store-logs + nginx-tls: + name: edgex_nginx-tls + nginx-templates: + name: edgex_nginx-templates diff --git a/docs/architecture.md b/docs/architecture.md index 4da2036..4e4bafd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -112,6 +112,52 @@ The Helm chart at `helm/edgekit/` provisions all of the above resources. The cli --- +## EdgeX Foundry integration + +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to provide a full IoT edge computing platform. + +### Edge node (client) + +The `docker-compose.edgex-client.yml` file adds a minimal, no-security EdgeX stack to the edge node: + +``` +Edge node +│ +├── edgekit-client – Edge agent (MQTT telemetry to central server) +└── EdgeX (no-security) + ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) + ├── database – PostgreSQL + ├── core-keeper – Service registry + ├── core-common-config-bootstrapper + ├── core-data – Event / reading persistence :59880 + ├── core-metadata – Device / profile registry :59881 + ├── core-command – Device command service :59882 + ├── support-notifications – Alerting :59860 + ├── support-scheduler – Job scheduling :59863 + └── device-modbus – Modbus TCP/RTU device service :59901 +``` + +### Central server + +The `docker-compose.edgex-server.yml` file adds the EdgeX Security Services to the central server: + +``` +Central server +│ +├── edgekit-server – Eclipse Mosquitto MQTT broker :1883 / :9001 +└── EdgeX Security Services + ├── secret-store – OpenBao (Vault-compatible) :8200 + ├── security-bootstrapper + ├── security-secretstore-setup + ├── security-proxy-auth – API authentication :59842 + ├── security-proxy-setup + └── nginx – TLS API gateway :8443 +``` + +See [docs/edgex.md](edgex.md) for a full guide including Helm and Rancher Fleet deployment. + +--- + ## Extending EdgeKit The architecture is intentionally minimal. Common extensions: @@ -120,3 +166,4 @@ The architecture is intentionally minimal. Common extensions: - **Add a dashboard**: Deploy [MQTT Explorer](https://mqtt-explorer.com/) or [Grafana + EMQX](https://docs.emqx.com/en/emqx/latest/dashboard/introduction.html) as additional pods. - **Custom metrics**: Extend `client/src/index.js` to publish additional data (GPIO readings, custom sensors, application logs, etc.). - **Multiple namespaces**: Deploy the chart multiple times with different `MQTT_TOPIC_PREFIX` values. +- **EdgeX rules engine**: Add `app-rules-engine` (eKuiper) to the edge stack to filter and transform device readings before forwarding. diff --git a/docs/edgex.md b/docs/edgex.md new file mode 100644 index 0000000..f8211ec --- /dev/null +++ b/docs/edgex.md @@ -0,0 +1,203 @@ +# EdgeX Foundry Integration + +EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to provide a full IoT edge computing platform alongside the existing MQTT telemetry pipeline. + +--- + +## Architecture overview + +``` +Central Server (edgekit/role=central) +│ +├── edgekit-server – Eclipse Mosquitto MQTT broker (telemetry) +│ └── ports 1883, 9001 +│ +└── EdgeX Security Services + ├── secret-store – OpenBao (secret store, Vault-compatible) + ├── security-bootstrapper + ├── security-secretstore-setup + ├── security-proxy-auth – JWT / API-token authentication + ├── security-proxy-setup + └── nginx – TLS API gateway (port 8443) + + +Edge Node (edgekit/role=edge) +│ +├── edgekit-client – Edge agent (publishes metrics to central MQTT) +│ +└── EdgeX Core + Device Services (no-security mode) + ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) + ├── database – PostgreSQL (data + metadata store) + ├── core-keeper – Service registry (replaces Consul in 4.0) + ├── core-common-config-bootstrapper + ├── core-data – Event / reading persistence (port 59880) + ├── core-metadata – Device / profile registry (port 59881) + ├── core-command – REST + MQTT device command (port 59882) + ├── support-notifications – Alerting (port 59860) + ├── support-scheduler – Job scheduler (port 59863) + └── device-modbus – Modbus TCP/RTU device service (port 59901) +``` + +--- + +## EdgeX service roles + +### Core Services + +| Service | Port | Description | +|---------|------|-------------| +| `core-keeper` | 59890 | Service registry; replaces Consul in EdgeX 4.0 | +| `core-common-config-bootstrapper` | – | Pushes shared configuration to core-keeper | +| `core-data` | 59880 | Receives, stores, and forwards device events/readings | +| `core-metadata` | 59881 | Registry for device profiles, devices, and device services | +| `core-command` | 59882 | Issues commands to devices via REST or external MQTT | + +### Supporting Services + +| Service | Port | Description | +|---------|------|-------------| +| `support-notifications` | 59860 | Sends alerts via email, Slack, webhook, etc. | +| `support-scheduler` | 59863 | Schedules recurring actions (e.g. periodic reads) | + +### Device Services + +| Service | Port | Description | +|---------|------|-------------| +| `device-modbus` | 59901 | Reads from and writes to Modbus TCP/RTU devices | + +### Security Services (central server only) + +| Service | Port | Description | +|---------|------|-------------| +| `secret-store` (OpenBao) | 8200 | Secret store – holds all service credentials and tokens | +| `security-bootstrapper` | – | Orchestrates the secure startup sequence | +| `security-secretstore-setup` | – | Initialises OpenBao; mints per-service tokens | +| `security-proxy-auth` | 59842 | Validates JWT and API tokens for API requests | +| `security-proxy-setup` | – | Configures nginx routes for all EdgeX services | +| `nginx` | 8443 | TLS-terminating reverse proxy; only entry point to EdgeX APIs | + +--- + +## Running locally with Docker Compose + +### Edge node stack (no security) + +```bash +# Start EdgeX core + device services +./scripts/start-edgex-client.sh + +# Verify all services are healthy +curl http://localhost:59880/api/v3/ping # core-data +curl http://localhost:59881/api/v3/ping # core-metadata +curl http://localhost:59882/api/v3/ping # core-command +curl http://localhost:59901/api/v3/ping # device-modbus + +# Stop +./scripts/stop-edgex-client.sh +``` + +### Central server stack (security services) + +```bash +# Start EdgeX security services +./scripts/start-edgex-server.sh + +# Verify the secret store is running +curl http://localhost:8200/v1/sys/health + +# Stop +./scripts/stop-edgex-server.sh +``` + +--- + +## Kubernetes / Helm deployment + +### Edge cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-client.yaml +``` + +This deploys only the **edge agent** (`edgekit-client`) and disables the broker. The agent connects to the central MQTT broker. + +### Central cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-server.yaml +``` + +This deploys only the **MQTT broker** (`edgekit-server`) with a `LoadBalancer` service so edge nodes can connect. + +--- + +## Rancher Fleet (GitOps) + +The `fleet.yaml` at the repository root enables GitOps-driven multi-cluster deployment with [Rancher Fleet](https://fleet.rancher.io/). + +Label your clusters before importing: + +```bash +# Central server cluster +kubectl label cluster edgekit/role=central + +# Edge node clusters +kubectl label cluster edgekit/role=edge +kubectl label cluster edgekit/role=edge +``` + +Fleet will automatically apply `values-server.yaml` to central clusters and `values-client.yaml` to edge clusters. + +--- + +## Modbus device configuration + +The `device-modbus` service discovers its device profiles and device configuration from `core-metadata`. To add a Modbus device: + +1. Create a device profile (YAML) describing the device's resources and commands. +2. POST the profile to `core-metadata`: + +```bash +curl -X POST http://localhost:59881/api/v3/deviceprofile/uploadfile \ + -F "file=@my-modbus-profile.yaml" +``` + +3. Register the device: + +```bash +curl -X POST http://localhost:59881/api/v3/device \ + -H "Content-Type: application/json" \ + -d '{ + "apiVersion": "v3", + "device": { + "name": "my-modbus-device", + "profileName": "my-modbus-profile", + "serviceName": "device-modbus", + "protocols": { + "modbus-tcp": { + "Address": "192.168.1.100", + "Port": "502", + "UnitID": "1" + } + } + } + }' +``` + +See the [EdgeX device-modbus documentation](https://docs.edgexfoundry.org/4.0/microservices/device/services/device-modbus/General/) for full details. + +--- + +## References + +- [EdgeX Foundry 4.0 documentation](https://docs.edgexfoundry.org/4.0/) +- [EdgeX compose files (v4.0.0)](https://github.com/edgexfoundry/edgex-compose/tree/v4.0.0) +- [device-modbus documentation](https://docs.edgexfoundry.org/4.0/microservices/device/services/device-modbus/General/) +- [EdgeX security architecture](https://docs.edgexfoundry.org/4.0/security/Ch-Security/) +- [OpenBao (secret store)](https://openbao.org/) diff --git a/fleet.yaml b/fleet.yaml new file mode 100644 index 0000000..83f0dc0 --- /dev/null +++ b/fleet.yaml @@ -0,0 +1,37 @@ +# ============================================================================= +# Rancher Fleet – EdgeKit multi-cluster GitOps +# +# Clusters with label edgekit/role=central receive the MQTT broker (server). +# Clusters with label edgekit/role=edge receive the edge agent (client). +# +# See: https://fleet.rancher.io/ref-fleet-yaml +# ============================================================================= + +namespace: edgekit + +helm: + chart: helm/edgekit + releaseName: edgekit + values: + global: + imageRegistry: "" + +targets: + + # Central server – runs the MQTT broker; no edge agent + - name: central + clusterSelector: + matchLabels: + edgekit/role: central + helm: + valuesFiles: + - helm/edgekit/values-server.yaml + + # Edge nodes – run the edge agent; broker is hosted centrally + - name: edge + clusterSelector: + matchLabels: + edgekit/role: edge + helm: + valuesFiles: + - helm/edgekit/values-client.yaml diff --git a/helm/edgekit/values-client.yaml b/helm/edgekit/values-client.yaml new file mode 100644 index 0000000..2aa92dd --- /dev/null +++ b/helm/edgekit/values-client.yaml @@ -0,0 +1,32 @@ +# ============================================================================= +# EdgeKit Helm – Client (edge node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-client.yaml +# +# On an edge cluster the MQTT broker runs remotely (central server), so the +# server component is disabled here. The client edge-agent connects to the +# central broker URL supplied below. +# ============================================================================= + +# Disable the in-cluster MQTT broker – edge nodes connect to the central one +server: + enabled: false + +client: + enabled: true + replicaCount: 1 + + # Point the edge agent at the central MQTT broker + mqttBrokerUrl: "ws://edgekit-server.edgekit.svc.cluster.local:9001" + topicPrefix: "edgekit" + publishIntervalMs: 5000 + + podAnnotations: + edgekit/role: edge + + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/helm/edgekit/values-server.yaml b/helm/edgekit/values-server.yaml new file mode 100644 index 0000000..168dc2b --- /dev/null +++ b/helm/edgekit/values-server.yaml @@ -0,0 +1,47 @@ +# ============================================================================= +# EdgeKit Helm – Server (central node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-server.yaml +# +# On the central server the MQTT broker is enabled and exposed so that edge +# agents can connect. The client agent is disabled – the server does not +# collect metrics itself. +# ============================================================================= + +# Enable the MQTT broker on the central server +server: + enabled: true + replicaCount: 1 + + service: + type: LoadBalancer # expose externally so edge nodes can connect + mqttPort: 1883 + wsPort: 9001 + + persistence: + enabled: true + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + + resources: + requests: + cpu: 200m + memory: 128Mi + limits: + cpu: 1000m + memory: 512Mi + + podAnnotations: + edgekit/role: central + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Disable the edge agent on the central server +client: + enabled: false diff --git a/scripts/start-edgex-client.sh b/scripts/start-edgex-client.sh new file mode 100755 index 0000000..0205b18 --- /dev/null +++ b/scripts/start-edgex-client.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/start-edgex-client.sh +# Start the EdgeX Foundry 4.0 (no-security) stack on an edge / client node +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-client.yml" + +echo "==> Starting EdgeX client stack (edge node)…" +docker compose -f "${COMPOSE_FILE}" up --pull missing -d + +echo "" +echo "✅ EdgeX client stack is running!" +echo "" +echo " core-data: http://localhost:59880/api/v3/ping" +echo " core-metadata: http://localhost:59881/api/v3/ping" +echo " core-command: http://localhost:59882/api/v3/ping" +echo " support-notifications: http://localhost:59860/api/v3/ping" +echo " support-scheduler: http://localhost:59863/api/v3/ping" +echo " device-modbus: http://localhost:59901/api/v3/ping" +echo " core-keeper (registry): http://localhost:59890/api/v3/ping" +echo "" +echo " View logs: docker compose -f docker-compose.edgex-client.yml logs -f" +echo " Stop: ./scripts/stop-edgex-client.sh" diff --git a/scripts/start-edgex-server.sh b/scripts/start-edgex-server.sh new file mode 100755 index 0000000..9d62047 --- /dev/null +++ b/scripts/start-edgex-server.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/start-edgex-server.sh +# Start the EdgeX Foundry 4.0 Security Services stack on the central server +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-server.yml" + +echo "==> Starting EdgeX server stack (security services)…" +docker compose -f "${COMPOSE_FILE}" up --pull missing -d + +echo "" +echo "✅ EdgeX server (security) stack is running!" +echo "" +echo " secret-store (OpenBao): http://localhost:8200/v1/sys/health" +echo " API gateway (nginx): https://localhost:8443" +echo " proxy-auth: http://localhost:59842/api/v3/ping" +echo "" +echo " View logs: docker compose -f docker-compose.edgex-server.yml logs -f" +echo " Stop: ./scripts/stop-edgex-server.sh" diff --git a/scripts/stop-edgex-client.sh b/scripts/stop-edgex-client.sh new file mode 100755 index 0000000..aa1d260 --- /dev/null +++ b/scripts/stop-edgex-client.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/stop-edgex-client.sh +# Stop the EdgeX Foundry 4.0 (no-security) stack on an edge / client node +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-client.yml" + +echo "==> Stopping EdgeX client stack…" +docker compose -f "${COMPOSE_FILE}" down + +echo "" +echo "✅ EdgeX client stack stopped." +echo " To also remove volumes: docker compose -f docker-compose.edgex-client.yml down -v" diff --git a/scripts/stop-edgex-server.sh b/scripts/stop-edgex-server.sh new file mode 100755 index 0000000..2b98521 --- /dev/null +++ b/scripts/stop-edgex-server.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# ============================================================================= +# scripts/stop-edgex-server.sh +# Stop the EdgeX Foundry 4.0 Security Services stack on the central server +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +COMPOSE_FILE="${REPO_ROOT}/docker-compose.edgex-server.yml" + +echo "==> Stopping EdgeX server stack…" +docker compose -f "${COMPOSE_FILE}" down + +echo "" +echo "✅ EdgeX server stack stopped." +echo " To also remove volumes: docker compose -f docker-compose.edgex-server.yml down -v" From add6f846bbf205e216db8cff83eac8983eb3ab3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 07:04:14 +0000 Subject: [PATCH 3/4] feat: add thin single-container EdgeX client Introduces a standalone, hardened single-container EdgeX client (edgekit-thin-client) alongside the existing full k3s multi-container client, as requested. Thin client bundles all EdgeX Core Services, Supporting Services, device-modbus, embedded PostgreSQL 16, and Mosquitto MQTT broker into one Alpine-based Docker image managed by supervisord. Key properties: - Standalone: no external service dependencies - Hardened: read-only root FS, no-new-privileges, minimal Alpine base, internal MQTT not published - Fixed service set: not extendable without rebuilding (contrast with full k3s client which uses separate containers and compose overrides) - Same EdgeX API ports as full client (59880/59881/59882/59860/59863/59901) New files: - edgex-thin-client/Dockerfile (multi-stage build) - edgex-thin-client/supervisord.conf - edgex-thin-client/mosquitto.conf - edgex-thin-client/entrypoint.sh - docker-compose.edgex-thin-client.yml - helm/edgekit/values-thin-client.yaml - scripts/start-edgex-thin-client.sh - scripts/stop-edgex-thin-client.sh Updated: - fleet.yaml: add edge-thin target (edgekit/role=edge-thin) - docs/edgex.md: document both client variants with comparison table - docs/architecture.md: add thin client architecture section - README.md: update architecture diagram and EdgeX section --- README.md | 58 ++++--- docker-compose.edgex-thin-client.yml | 79 +++++++++ docs/architecture.md | 31 +++- docs/edgex.md | 79 ++++++++- edgex-thin-client/Dockerfile | 114 +++++++++++++ edgex-thin-client/entrypoint.sh | 45 ++++++ edgex-thin-client/mosquitto.conf | 27 ++++ edgex-thin-client/supervisord.conf | 231 +++++++++++++++++++++++++++ fleet.yaml | 14 +- helm/edgekit/values-thin-client.yaml | 80 ++++++++++ scripts/start-edgex-thin-client.sh | 45 ++++++ scripts/stop-edgex-thin-client.sh | 26 +++ 12 files changed, 796 insertions(+), 33 deletions(-) create mode 100644 docker-compose.edgex-thin-client.yml create mode 100644 edgex-thin-client/Dockerfile create mode 100755 edgex-thin-client/entrypoint.sh create mode 100644 edgex-thin-client/mosquitto.conf create mode 100644 edgex-thin-client/supervisord.conf create mode 100644 helm/edgekit/values-thin-client.yaml create mode 100755 scripts/start-edgex-thin-client.sh create mode 100755 scripts/stop-edgex-thin-client.sh diff --git a/README.md b/README.md index b206432..70019c4 100644 --- a/README.md +++ b/README.md @@ -26,26 +26,24 @@ EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4. └──────────────────────────────────────────────────────────┘ ▲ WebSocket / MQTT ▲ WebSocket / MQTT │ │ -┌────────┴───────┐ ┌────────┴────────┐ -│ Edge Node 1 │ │ Edge Node N │ -│ │ │ │ -│ edgekit-client │ │ edgekit-client │ -│ EdgeX Core + │ │ EdgeX Core + │ -│ device-modbus │ │ device-modbus │ -└────────────────┘ └─────────────────┘ +┌────────┴───────┐ ┌────────┴────────────────┐ +│ Full k3s Node │ │ Thin Edge Node │ +│ │ │ │ +│ edgekit-client │ │ edgekit-thin-client │ +│ EdgeX Core + │ │ (single container: │ +│ device-modbus │ │ EdgeX + PG + MQTT) │ +│ (10 containers)│ │ │ +└────────────────┘ └─────────────────────────┘ ``` -Each **edge node** runs: +Each **full k3s edge node** runs: - `edgekit-client` – collects system metrics and publishes them to the central MQTT broker -- EdgeX Core Services (`core-data`, `core-metadata`, `core-command`) +- EdgeX Core Services (`core-data`, `core-metadata`, `core-command`) in separate containers - EdgeX Supporting Services (`support-notifications`, `support-scheduler`) - `device-modbus` – reads from Modbus TCP/RTU field devices -The **central server** runs: - -- `edgekit-server` – Eclipse Mosquitto MQTT broker for telemetry -- EdgeX Security Services (OpenBao secret store, nginx API gateway, authentication) +Each **thin edge node** runs a single `edgekit-thin-client` container that bundles all of the above (EdgeX + PostgreSQL + Mosquitto, managed by supervisord). It is hardened, standalone, and not extendable without rebuilding the image. For a deeper dive, see [docs/architecture.md](docs/architecture.md) and [docs/edgex.md](docs/edgex.md). @@ -150,15 +148,24 @@ See [helm/edgekit/values.yaml](helm/edgekit/values.yaml) for the full Helm confi ## EdgeX Foundry integration -EdgeKit embeds [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) for full IoT device management and Modbus connectivity. +EdgeKit embeds [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) for full IoT device management and Modbus connectivity. There are **two edge client variants**: -### Edge node – start the EdgeX core stack +### Full k3s edge node – start the EdgeX core stack ```bash ./scripts/start-edgex-client.sh ``` -Starts: MQTT message bus, PostgreSQL, core-keeper, core-data, core-metadata, core-command, support-notifications, support-scheduler, device-modbus. +Starts 10 separate containers: MQTT message bus, PostgreSQL, core-keeper, core-data, core-metadata, core-command, support-notifications, support-scheduler, device-modbus. Best for k3s clusters or development environments where services need to be extended. + +### Thin edge node – single-container EdgeX stack + +```bash +docker build -t edgekit-thin-client edgex-thin-client/ +./scripts/start-edgex-thin-client.sh --no-build +``` + +Starts **one hardened container** with all EdgeX services embedded (PostgreSQL + Mosquitto + all Core/Supporting/Device services, managed by supervisord). Standalone, not extendable without rebuilding. Best for resource-constrained or locked-down production devices. ### Central server – start the EdgeX security stack @@ -183,21 +190,29 @@ edgekit/ │ ├── package.json │ └── src/ │ └── index.js +├── edgex-thin-client/ # Single-container thin EdgeX client image +│ ├── Dockerfile # Multi-stage: extracts EdgeX binaries + Alpine base +│ ├── supervisord.conf # Manages all internal processes +│ ├── mosquitto.conf # Embedded MQTT (localhost only) +│ └── entrypoint.sh # PostgreSQL initdb + supervisord ├── helm/ │ └── edgekit/ # Root Helm chart │ ├── Chart.yaml │ ├── values.yaml # Default values -│ ├── values-client.yaml # Edge node overrides +│ ├── values-client.yaml # Full k3s edge node overrides │ ├── values-server.yaml # Central server overrides +│ ├── values-thin-client.yaml # Thin edge node overrides │ └── templates/ ├── scripts/ │ ├── build.sh # Build Docker images │ ├── start-local.sh # Start base stack via docker compose │ ├── stop-local.sh # Stop base stack -│ ├── start-edgex-client.sh # Start EdgeX edge stack -│ ├── stop-edgex-client.sh # Stop EdgeX edge stack +│ ├── start-edgex-client.sh # Start full k3s EdgeX edge stack +│ ├── stop-edgex-client.sh # Stop full k3s EdgeX edge stack │ ├── start-edgex-server.sh # Start EdgeX security stack -│ └── stop-edgex-server.sh # Stop EdgeX security stack +│ ├── stop-edgex-server.sh # Stop EdgeX security stack +│ ├── start-edgex-thin-client.sh # Build + start thin EdgeX client +│ └── stop-edgex-thin-client.sh # Stop thin EdgeX client ├── .github/ │ └── workflows/ │ ├── ci.yml # CI: lint + build on PR/push to develop|main @@ -207,7 +222,8 @@ edgekit/ │ ├── edgex.md # EdgeX integration guide │ └── quickstart.md ├── docker-compose.yml # Base local stack (MQTT broker + edge agent) -├── docker-compose.edgex-client.yml # EdgeX edge stack (no security) +├── docker-compose.edgex-client.yml # Full k3s EdgeX edge stack (no security) +├── docker-compose.edgex-thin-client.yml # Thin EdgeX single-container stack ├── docker-compose.edgex-server.yml # EdgeX security services └── fleet.yaml # Rancher Fleet multi-cluster GitOps ``` diff --git a/docker-compose.edgex-thin-client.yml b/docker-compose.edgex-thin-client.yml new file mode 100644 index 0000000..3e2f271 --- /dev/null +++ b/docker-compose.edgex-thin-client.yml @@ -0,0 +1,79 @@ +# ============================================================================= +# docker-compose.edgex-thin-client.yml +# +# EdgeKit Thin Client – single-container EdgeX Foundry 4.0 edge node +# +# All EdgeX Core Services, Supporting Services, device-modbus, an embedded +# PostgreSQL database, and an Eclipse Mosquitto MQTT message bus run inside +# ONE container. This configuration is: +# +# • Standalone – no external dependencies; works anywhere Docker is present +# • Hardened – read-only root FS; data on named volume; no new privileges +# • Fixed – service set baked into the image; not extendable without +# rebuilding (contrast: docker-compose.edgex-client.yml) +# +# Prerequisites: +# Build the image first: +# docker build -t edgekit-thin-client edgex-thin-client/ +# +# Usage: +# docker compose -f docker-compose.edgex-thin-client.yml up -d +# # smoke tests: +# curl http://localhost:59890/api/v3/ping # core-keeper +# curl http://localhost:59880/api/v3/ping # core-data +# curl http://localhost:59881/api/v3/ping # core-metadata +# curl http://localhost:59882/api/v3/ping # core-command +# curl http://localhost:59860/api/v3/ping # support-notifications +# curl http://localhost:59863/api/v3/ping # support-scheduler +# curl http://localhost:59901/api/v3/ping # device-modbus +# ============================================================================= + +services: + edgex-thin: + image: edgekit-thin-client:latest + container_name: edgex-thin + restart: unless-stopped + + # Run as root so entrypoint.sh can chown the PostgreSQL data directory; + # supervisord then drops individual services to the edgex user (uid 2002) + # or postgres user as appropriate. + user: "0:0" + + # Hardening + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + # Writable scratch space for sockets, PIDs, and runtime temp files + - /tmp:mode=1777,size=64m + - /run:mode=0755,size=16m + - /run/postgresql:mode=0755,uid=70,size=4m + + # Persistent data (PostgreSQL + optional Mosquitto persistence) + volumes: + - edgex-thin-db:/data/postgresql + - edgex-thin-mosquitto:/data/mosquitto + + # Expose EdgeX REST API ports (bind to localhost for added security) + # Internal MQTT (1883) is intentionally NOT published. + ports: + - "127.0.0.1:59890:59890" # core-keeper + - "127.0.0.1:59880:59880" # core-data + - "127.0.0.1:59881:59881" # core-metadata + - "127.0.0.1:59882:59882" # core-command + - "127.0.0.1:59860:59860" # support-notifications + - "127.0.0.1:59863:59863" # support-scheduler + - "127.0.0.1:59901:59901" # device-modbus + + # Resource limits – appropriate for a constrained edge device + mem_limit: 512m + cpus: "1.0" + + environment: + TZ: UTC + +volumes: + edgex-thin-db: + driver: local + edgex-thin-mosquitto: + driver: local diff --git a/docs/architecture.md b/docs/architecture.md index 4e4bafd..633d844 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -116,15 +116,15 @@ The Helm chart at `helm/edgekit/` provisions all of the above resources. The cli EdgeKit integrates [EdgeX Foundry 4.0 (Odessa)](https://docs.edgexfoundry.org/4.0/) to provide a full IoT edge computing platform. -### Edge node (client) +### Full k3s edge node (`edgekit/role=edge`) -The `docker-compose.edgex-client.yml` file adds a minimal, no-security EdgeX stack to the edge node: +The `docker-compose.edgex-client.yml` file adds a minimal, no-security EdgeX stack to the edge node using separate containers — one per service — suitable for k3s or development use: ``` -Edge node +Full k3s edge node │ ├── edgekit-client – Edge agent (MQTT telemetry to central server) -└── EdgeX (no-security) +└── EdgeX (no-security, separate containers) ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) ├── database – PostgreSQL ├── core-keeper – Service registry @@ -137,6 +137,29 @@ Edge node └── device-modbus – Modbus TCP/RTU device service :59901 ``` +### Thin edge node (`edgekit/role=edge-thin`) + +The thin client bundles all of the above into **one hardened, standalone container** (`edgekit-thin-client`). It exposes the same EdgeX API ports but is not extendable without rebuilding the image: + +``` +Thin edge node +│ +└── edgekit-thin-client (ONE container) + └── supervisord manages internally: + ├── PostgreSQL – embedded database (localhost:5432) + ├── Mosquitto – embedded message bus (localhost:1883, not published) + ├── core-keeper :59890 + ├── core-common-config-bootstrapper (one-shot) + ├── core-data :59880 + ├── core-metadata :59881 + ├── core-command :59882 + ├── support-notifications :59860 + ├── support-scheduler :59863 + └── device-modbus :59901 +``` + +See [`edgex-thin-client/Dockerfile`](../edgex-thin-client/Dockerfile) for the multi-stage build. + ### Central server The `docker-compose.edgex-server.yml` file adds the EdgeX Security Services to the central server: diff --git a/docs/edgex.md b/docs/edgex.md index f8211ec..c4d40f5 100644 --- a/docs/edgex.md +++ b/docs/edgex.md @@ -21,11 +21,11 @@ Central Server (edgekit/role=central) └── nginx – TLS API gateway (port 8443) -Edge Node (edgekit/role=edge) +Full k3s Edge Node (edgekit/role=edge) │ ├── edgekit-client – Edge agent (publishes metrics to central MQTT) │ -└── EdgeX Core + Device Services (no-security mode) +└── EdgeX Core + Device Services – separate containers, no-security mode ├── mqtt-broker – Eclipse Mosquitto (EdgeX internal message bus) ├── database – PostgreSQL (data + metadata store) ├── core-keeper – Service registry (replaces Consul in 4.0) @@ -36,10 +36,37 @@ Edge Node (edgekit/role=edge) ├── support-notifications – Alerting (port 59860) ├── support-scheduler – Job scheduler (port 59863) └── device-modbus – Modbus TCP/RTU device service (port 59901) + + +Thin Edge Node (edgekit/role=edge-thin) +│ +└── edgekit-thin-client – ONE container; all of the above bundled inside + (PostgreSQL + Mosquitto + all EdgeX services + managed by supervisord; hardened, standalone) ``` --- +## Edge client variants + +EdgeKit provides **two EdgeX client variants**. Both expose identical EdgeX APIs on the same ports and carry the same functional scope. They differ in architecture and deployment characteristics. + +| | Full k3s client | Thin client | +|---|---|---| +| **Compose file** | `docker-compose.edgex-client.yml` | `docker-compose.edgex-thin-client.yml` | +| **Image** | Official EdgeX images (one per service) | Single `edgekit-thin-client` image | +| **Containers** | 10 containers | 1 container | +| **Process manager** | Docker Compose / k3s | supervisord (internal) | +| **Database** | External `postgres:16` container | Embedded PostgreSQL 16 | +| **Message bus** | External Mosquitto container | Embedded Mosquitto (localhost) | +| **Extendable** | ✅ Add services via compose overrides | ❌ Fixed service set; rebuild to extend | +| **Hardened** | Standard Docker security defaults | `read_only`, `no-new-privileges`, minimal Alpine | +| **Fleet label** | `edgekit/role=edge` | `edgekit/role=edge-thin` | +| **Helm values** | `values-client.yaml` | `values-thin-client.yaml` | +| **Best for** | Dev, k3s clusters, extensible deployments | Resource-constrained devices, locked-down production | + +--- + ## EdgeX service roles ### Core Services @@ -80,7 +107,7 @@ Edge Node (edgekit/role=edge) ## Running locally with Docker Compose -### Edge node stack (no security) +### Full k3s edge node stack (no security, multi-container) ```bash # Start EdgeX core + device services @@ -96,6 +123,29 @@ curl http://localhost:59901/api/v3/ping # device-modbus ./scripts/stop-edgex-client.sh ``` +### Thin edge node (single container, hardened) + +```bash +# Build the image once +docker build -t edgekit-thin-client edgex-thin-client/ + +# Start the thin client +./scripts/start-edgex-thin-client.sh --no-build # skip build if already built + +# Verify all services are healthy (same ports as full client) +curl http://localhost:59890/api/v3/ping # core-keeper +curl http://localhost:59880/api/v3/ping # core-data +curl http://localhost:59881/api/v3/ping # core-metadata +curl http://localhost:59882/api/v3/ping # core-command +curl http://localhost:59901/api/v3/ping # device-modbus + +# Stop +./scripts/stop-edgex-thin-client.sh + +# Stop and wipe persistent data +./scripts/stop-edgex-thin-client.sh --purge +``` + ### Central server stack (security services) ```bash @@ -113,7 +163,7 @@ curl http://localhost:8200/v1/sys/health ## Kubernetes / Helm deployment -### Edge cluster +### Full k3s edge cluster ```bash helm install edgekit ./helm/edgekit \ @@ -124,6 +174,17 @@ helm install edgekit ./helm/edgekit \ This deploys only the **edge agent** (`edgekit-client`) and disables the broker. The agent connects to the central MQTT broker. +### Thin edge cluster + +```bash +helm install edgekit ./helm/edgekit \ + --namespace edgekit \ + --create-namespace \ + -f helm/edgekit/values-thin-client.yaml +``` + +This deploys the **single-container thin EdgeX client** (`edgekit-thin-client`) with a dedicated PersistentVolumeClaim for PostgreSQL data. + ### Central cluster ```bash @@ -147,12 +208,18 @@ Label your clusters before importing: # Central server cluster kubectl label cluster edgekit/role=central -# Edge node clusters +# Full k3s edge node clusters kubectl label cluster edgekit/role=edge kubectl label cluster edgekit/role=edge + +# Thin edge node clusters (single-container, locked-down) +kubectl label cluster edgekit/role=edge-thin ``` -Fleet will automatically apply `values-server.yaml` to central clusters and `values-client.yaml` to edge clusters. +Fleet will automatically apply: +- `values-server.yaml` to `edgekit/role=central` clusters +- `values-client.yaml` to `edgekit/role=edge` clusters +- `values-thin-client.yaml` to `edgekit/role=edge-thin` clusters --- diff --git a/edgex-thin-client/Dockerfile b/edgex-thin-client/Dockerfile new file mode 100644 index 0000000..a1b900f --- /dev/null +++ b/edgex-thin-client/Dockerfile @@ -0,0 +1,114 @@ +# ============================================================================= +# EdgeKit Thin Client – Single-container EdgeX Foundry 4.0 +# +# Bundles all EdgeX Core Services, Supporting Services, and device-modbus +# into ONE hardened Docker image, together with an embedded PostgreSQL 16 +# database and an Eclipse Mosquitto MQTT message bus. +# +# Key characteristics: +# • Standalone – no external service dependencies; run with a single +# `docker run` or one-line docker compose service +# • Hardened – read-only root FS (data on tmpfs/volume), non-root +# EdgeX processes, no-new-privileges, minimal Alpine base +# • Fixed – service set is baked in; not extendable at runtime; +# add new device services by rebuilding the image +# +# Contrast with the full k3s client (docker-compose.edgex-client.yml) which +# uses separate containers per service and is extendable via compose overrides. +# +# Build: +# docker build -t edgekit-thin-client edgex-thin-client/ +# +# References: +# https://docs.edgexfoundry.org/4.0/ +# ============================================================================= + +# ── Stage 1: extract EdgeX Go binaries and default configs ─────────────────── +FROM edgexfoundry/core-keeper:4.0.0 AS core-keeper +FROM edgexfoundry/core-common-config-bootstrapper:4.0.0 AS core-ccb +FROM edgexfoundry/core-data:4.0.0 AS core-data +FROM edgexfoundry/core-metadata:4.0.0 AS core-metadata +FROM edgexfoundry/core-command:4.0.0 AS core-command +FROM edgexfoundry/support-notifications:4.0.0 AS support-notifications +FROM edgexfoundry/support-scheduler:4.0.0 AS support-scheduler +FROM edgexfoundry/device-modbus:4.0.0 AS device-modbus + +# ── Stage 2: assemble the thin client image ─────────────────────────────────── +FROM alpine:3.20 + +LABEL org.opencontainers.image.title="edgekit-thin-client" +LABEL org.opencontainers.image.description="Standalone, hardened single-container EdgeX Foundry 4.0 edge node (Core Services + Supporting Services + device-modbus)" +LABEL org.opencontainers.image.source="https://github.com/perspikapps/edgekit" + +# Install runtime deps: +# • postgresql16 – embedded database for core-data / core-metadata / core-keeper +# • mosquitto – embedded MQTT message bus (EdgeX internal bus) +# • py3-supervisor – supervisord process manager (manages all EdgeX processes) +# • su-exec – privilege-drop helper used by entrypoint +# • tzdata – timezone data +RUN apk add --no-cache \ + postgresql16 \ + mosquitto \ + py3-supervisor \ + su-exec \ + tzdata \ + && mkdir -p \ + /edgex/bin \ + /edgex/services/core-keeper/res \ + /edgex/services/core-ccb/res \ + /edgex/services/core-data/res \ + /edgex/services/core-metadata/res \ + /edgex/services/core-command/res \ + /edgex/services/support-notifications/res \ + /edgex/services/support-scheduler/res \ + /edgex/services/device-modbus/res \ + /run/postgresql \ + /tmp/edgex/logs \ + && chown postgres:postgres /run/postgresql + +# ── Copy EdgeX binaries (statically compiled Go, run fine on Alpine/musl) ──── +COPY --from=core-keeper /core-keeper /edgex/bin/core-keeper +COPY --from=core-ccb /core-common-config-bootstrapper /edgex/bin/core-common-config-bootstrapper +COPY --from=core-data /core-data /edgex/bin/core-data +COPY --from=core-metadata /core-metadata /edgex/bin/core-metadata +COPY --from=core-command /core-command /edgex/bin/core-command +COPY --from=support-notifications /support-notifications /edgex/bin/support-notifications +COPY --from=support-scheduler /support-scheduler /edgex/bin/support-scheduler +COPY --from=device-modbus /device-modbus /edgex/bin/device-modbus + +# ── Copy EdgeX default resource / config directories ───────────────────────── +# Each service reads its configuration template from ./res/ relative to its CWD. +# The common-config-bootstrapper pushes shared config to core-keeper from its /res. +COPY --from=core-keeper /res/ /edgex/services/core-keeper/res/ +COPY --from=core-ccb /res/ /edgex/services/core-ccb/res/ +COPY --from=core-data /res/ /edgex/services/core-data/res/ +COPY --from=core-metadata /res/ /edgex/services/core-metadata/res/ +COPY --from=core-command /res/ /edgex/services/core-command/res/ +COPY --from=support-notifications /res/ /edgex/services/support-notifications/res/ +COPY --from=support-scheduler /res/ /edgex/services/support-scheduler/res/ +COPY --from=device-modbus /res/ /edgex/services/device-modbus/res/ + +# ── Copy thin-client configuration files ───────────────────────────────────── +COPY mosquitto.conf /etc/mosquitto/mosquitto.conf +COPY supervisord.conf /etc/supervisord.conf +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh /edgex/bin/* + +# Create the dedicated EdgeX user/group (uid/gid 2002/2001 matches EdgeX convention) +RUN addgroup -g 2001 edgex \ + && adduser -u 2002 -G edgex -H -D edgex \ + && chown -R edgex:edgex /edgex /tmp/edgex + +# ── Volumes ─────────────────────────────────────────────────────────────────── +# /data/postgresql – persistent PostgreSQL data (survives container restarts) +# /data/mosquitto – optional Mosquitto persistence +VOLUME ["/data/postgresql", "/data/mosquitto"] + +# ── Exposed ports ───────────────────────────────────────────────────────────── +# These are the EdgeX REST API ports. Bind only the ones you need externally. +# Internal MQTT (1883) is NOT exposed – it is a localhost-only bus inside the +# container, preventing external interference (hardening). +EXPOSE 59880 59881 59882 59860 59863 59890 59901 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/edgex-thin-client/entrypoint.sh b/edgex-thin-client/entrypoint.sh new file mode 100755 index 0000000..a6fabb4 --- /dev/null +++ b/edgex-thin-client/entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# ============================================================================= +# entrypoint.sh – EdgeKit Thin Client container entry point +# +# Responsibilities: +# 1. Initialize the PostgreSQL data directory on first run (if empty). +# 2. Set correct ownership on the persistent data volume. +# 3. Hand off to supervisord which manages all EdgeX processes. +# +# The container should be started as root so that: +# • We can chown /data/postgresql to the postgres user. +# • supervisord can drop privileges per-program (postgres runs as 'postgres'). +# ============================================================================= +set -e + +PGDATA="/data/postgresql" +PGUSER="postgres" + +# ── 1. Ensure the PostgreSQL data directory exists and is owned correctly ───── +mkdir -p "${PGDATA}" +chown -R "${PGUSER}:${PGUSER}" "${PGDATA}" + +# ── 2. Initialize the PostgreSQL cluster on first run ──────────────────────── +if [ ! -f "${PGDATA}/PG_VERSION" ]; then + echo "[entrypoint] Initialising PostgreSQL data directory at ${PGDATA} ..." + su-exec "${PGUSER}" initdb \ + --pgdata="${PGDATA}" \ + --username="${PGUSER}" \ + --encoding=UTF8 \ + --locale=C + + # Allow local connections without password (internal use only) + echo "host all all 127.0.0.1/32 trust" >> "${PGDATA}/pg_hba.conf" + echo "host all all ::1/128 trust" >> "${PGDATA}/pg_hba.conf" + + echo "[entrypoint] PostgreSQL initialised." +fi + +# ── 3. Ensure Mosquitto data directory exists (for optional persistence) ────── +mkdir -p /data/mosquitto +chown mosquitto:mosquitto /data/mosquitto 2>/dev/null || true + +# ── 4. Start supervisord (manages all services) ─────────────────────────────── +echo "[entrypoint] Starting supervisord ..." +exec supervisord -c /etc/supervisord.conf diff --git a/edgex-thin-client/mosquitto.conf b/edgex-thin-client/mosquitto.conf new file mode 100644 index 0000000..11757c4 --- /dev/null +++ b/edgex-thin-client/mosquitto.conf @@ -0,0 +1,27 @@ +# ============================================================================= +# mosquitto.conf – EdgeKit Thin Client internal MQTT message bus +# +# Mosquitto listens on localhost:1883 ONLY. +# This is intentional: the MQTT bus is an internal EdgeX message bus and must +# not be accessible from outside the container (hardening requirement). +# +# Anonymous connections are allowed because this is a single-container +# deployment with no external surface – all clients are EdgeX services running +# inside the same container. +# ============================================================================= + +# Listen on localhost only (internal EdgeX message bus) +listener 1883 127.0.0.1 + +# Allow anonymous connections (all clients are internal EdgeX services) +allow_anonymous true + +# Disable persistent session storage (stateless; edge nodes may restart often) +persistence false + +# Log to stdout so supervisord can capture it +log_dest stdout +log_type error +log_type warning +log_type notice +log_type information diff --git a/edgex-thin-client/supervisord.conf b/edgex-thin-client/supervisord.conf new file mode 100644 index 0000000..ea501e8 --- /dev/null +++ b/edgex-thin-client/supervisord.conf @@ -0,0 +1,231 @@ +; ============================================================================= +; supervisord.conf – EdgeKit Thin Client process supervisor +; +; Manages all EdgeX Foundry 4.0 services inside the single container, +; together with the embedded PostgreSQL database and Mosquitto MQTT broker. +; +; Startup order is enforced via the `priority` field and `startretries`/ +; `autorestart` settings – higher-priority processes start first; services +; that depend on them will fail-and-retry until their dependencies are ready. +; +; Priority bands: +; 10 – infrastructure (PostgreSQL, Mosquitto) +; 20 – core-keeper (service registry; depends on DB + MQTT) +; 30 – config boot (core-common-config-bootstrapper; one-shot) +; 40 – core-data, core-metadata (depend on keeper + DB) +; 50 – core-command (depends on core-metadata) +; 60 – support services (notifications, scheduler) +; 70 – device-modbus (depends on core-metadata + core-data) +; ============================================================================= + +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +loglevel=info +pidfile=/tmp/supervisord.pid + +[unix_http_server] +file=/tmp/supervisor.sock + +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +; ── Infrastructure ──────────────────────────────────────────────────────────── + +[program:postgresql] +; Managed by entrypoint.sh (initdb + start); supervisord keeps it alive. +command=/usr/bin/postgres -D /data/postgresql +user=postgres +priority=10 +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:mosquitto] +; Embedded MQTT broker – internal EdgeX message bus (localhost only). +command=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf +priority=10 +autostart=true +autorestart=true +startsecs=3 +startretries=3 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── core-keeper (service registry) ─────────────────────────────────────────── + +[program:core-keeper] +command=/edgex/bin/core-keeper +directory=/edgex/services/core-keeper +priority=20 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost", + DATABASE_HOST="localhost", + MESSAGEBUS_TYPE="mqtt", + MESSAGEBUS_PROTOCOL="tcp", + MESSAGEBUS_HOST="localhost", + MESSAGEBUS_PORT="1883", + MESSAGEBUS_AUTHMODE="none", + MESSAGEBUS_SECRETNAME="" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── core-common-config-bootstrapper (one-shot; exits 0 on success) ─────────── + +[program:core-ccb] +command=/edgex/bin/core-common-config-bootstrapper + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-ccb +priority=30 +autostart=true +; One-shot service: do not restart after clean exit. +autorestart=unexpected +exitcodes=0 +startretries=10 +startsecs=3 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + ALL_SERVICES_DATABASE_HOST="localhost", + ALL_SERVICES_MESSAGEBUS_HOST="localhost", + ALL_SERVICES_REGISTRY_HOST="localhost", + APP_SERVICES_CLIENTS_CORE_METADATA_HOST="localhost", + DEVICE_SERVICES_CLIENTS_CORE_METADATA_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Core Services ───────────────────────────────────────────────────────────── + +[program:core-data] +command=/edgex/bin/core-data + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-data +priority=40 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:core-metadata] +command=/edgex/bin/core-metadata + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-metadata +priority=40 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:core-command] +command=/edgex/bin/core-command + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/core-command +priority=50 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost", + EXTERNALMQTT_URL="tcp://localhost:1883" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Supporting Services ─────────────────────────────────────────────────────── + +[program:support-notifications] +command=/edgex/bin/support-notifications + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/support-notifications +priority=60 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:support-scheduler] +command=/edgex/bin/support-scheduler + --registry + -cp=keeper.http://localhost:59890 +directory=/edgex/services/support-scheduler +priority=60 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Device Services ─────────────────────────────────────────────────────────── + +[program:device-modbus] +command=/edgex/bin/device-modbus + -cp=keeper.http://localhost:59890 + --registry +directory=/edgex/services/device-modbus +priority=70 +autostart=true +autorestart=true +startsecs=5 +startretries=10 +environment= + EDGEX_SECURITY_SECRET_STORE="false", + SERVICE_HOST="localhost" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/fleet.yaml b/fleet.yaml index 83f0dc0..60f1f9d 100644 --- a/fleet.yaml +++ b/fleet.yaml @@ -1,8 +1,9 @@ # ============================================================================= # Rancher Fleet – EdgeKit multi-cluster GitOps # -# Clusters with label edgekit/role=central receive the MQTT broker (server). -# Clusters with label edgekit/role=edge receive the edge agent (client). +# Clusters with label edgekit/role=central receive the MQTT broker (server). +# Clusters with label edgekit/role=edge receive the full k3s edge agent. +# Clusters with label edgekit/role=edge-thin receive the thin edge agent. # # See: https://fleet.rancher.io/ref-fleet-yaml # ============================================================================= @@ -35,3 +36,12 @@ targets: helm: valuesFiles: - helm/edgekit/values-client.yaml + + # Thin edge nodes – single-container EdgeX; no k3s orchestration needed + - name: edge-thin + clusterSelector: + matchLabels: + edgekit/role: edge-thin + helm: + valuesFiles: + - helm/edgekit/values-thin-client.yaml diff --git a/helm/edgekit/values-thin-client.yaml b/helm/edgekit/values-thin-client.yaml new file mode 100644 index 0000000..97ba6b6 --- /dev/null +++ b/helm/edgekit/values-thin-client.yaml @@ -0,0 +1,80 @@ +# ============================================================================= +# EdgeKit Helm – Thin Client (edge-thin node) values override +# +# Apply with: +# helm install edgekit ./helm/edgekit \ +# --namespace edgekit --create-namespace \ +# -f helm/edgekit/values-thin-client.yaml +# +# Thin edge nodes run the single-container EdgeX stack (edgekit-thin-client +# image) via a minimal Kubernetes Deployment. The full multi-container k3s +# EdgeX setup is NOT deployed here. +# +# The thin client has the same functional role as the full k3s edge client but +# is deliberately standalone and not extendable with additional services. +# ============================================================================= + +# Disable the in-cluster MQTT broker – edge nodes connect to the central one +server: + enabled: false + +client: + enabled: true + replicaCount: 1 + + # Point the edge agent at the central MQTT broker + mqttBrokerUrl: "ws://edgekit-server.edgekit.svc.cluster.local:9001" + topicPrefix: "edgekit" + publishIntervalMs: 5000 + + podAnnotations: + edgekit/role: edge-thin + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Thin client: deploy the single-container EdgeX image +edgexThinClient: + enabled: true + image: + repository: edgekit-thin-client + tag: latest + pullPolicy: IfNotPresent + + # Resource limits for a constrained edge device + resources: + limits: + memory: 512Mi + cpu: "1000m" + requests: + memory: 256Mi + cpu: "250m" + + # Persistent volume for PostgreSQL data + persistence: + enabled: true + storageClass: "" + size: 2Gi + accessMode: ReadWriteOnce + + # Expose EdgeX REST API ports inside the cluster only (ClusterIP) + service: + type: ClusterIP + ports: + coreKeeper: 59890 + coreData: 59880 + coreMetadata: 59881 + coreCommand: 59882 + supportNotifications: 59860 + supportScheduler: 59863 + deviceModbus: 59901 + + # Hardening: run as root only for entrypoint.sh chown; supervisord drops privileges + securityContext: + runAsUser: 0 + readOnlyRootFilesystem: true + + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/scripts/start-edgex-thin-client.sh b/scripts/start-edgex-thin-client.sh new file mode 100755 index 0000000..69a869d --- /dev/null +++ b/scripts/start-edgex-thin-client.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# ============================================================================= +# scripts/start-edgex-thin-client.sh +# +# Builds (if needed) and starts the EdgeKit thin EdgeX client – a single +# hardened container that bundles all EdgeX Core Services, Supporting Services, +# device-modbus, PostgreSQL, and Mosquitto. +# +# Usage: +# ./scripts/start-edgex-thin-client.sh # build + start +# ./scripts/start-edgex-thin-client.sh --no-build # start without rebuilding +# ============================================================================= +set -e + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="docker-compose.edgex-thin-client.yml" +IMAGE_NAME="edgekit-thin-client" + +cd "$REPO_ROOT" + +if [ "$1" != "--no-build" ]; then + echo "Building ${IMAGE_NAME} ..." + docker build -t "${IMAGE_NAME}:latest" edgex-thin-client/ +fi + +echo "Starting EdgeKit thin EdgeX client ..." +docker compose -f "${COMPOSE_FILE}" up -d + +echo "" +echo "Thin EdgeX client started. Waiting for services to initialise (~30s) ..." +sleep 30 + +echo "" +echo "Smoke tests (expect HTTP 200):" +for url in \ + "http://localhost:59890/api/v3/ping" \ + "http://localhost:59880/api/v3/ping" \ + "http://localhost:59881/api/v3/ping" \ + "http://localhost:59882/api/v3/ping" \ + "http://localhost:59860/api/v3/ping" \ + "http://localhost:59863/api/v3/ping" \ + "http://localhost:59901/api/v3/ping"; do + status=$(curl -s -o /dev/null -w "%{http_code}" "${url}" || echo "FAIL") + printf " %-50s %s\n" "${url}" "${status}" +done diff --git a/scripts/stop-edgex-thin-client.sh b/scripts/stop-edgex-thin-client.sh new file mode 100755 index 0000000..d2e5ccd --- /dev/null +++ b/scripts/stop-edgex-thin-client.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# ============================================================================= +# scripts/stop-edgex-thin-client.sh +# +# Stops the EdgeKit thin EdgeX client container. +# +# Usage: +# ./scripts/stop-edgex-thin-client.sh # stop; keep volume data +# ./scripts/stop-edgex-thin-client.sh --purge # stop + remove volumes +# ============================================================================= +set -e + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="docker-compose.edgex-thin-client.yml" + +cd "$REPO_ROOT" + +if [ "$1" = "--purge" ]; then + echo "Stopping EdgeKit thin EdgeX client and removing volumes ..." + docker compose -f "${COMPOSE_FILE}" down --volumes +else + echo "Stopping EdgeKit thin EdgeX client ..." + docker compose -f "${COMPOSE_FILE}" down +fi + +echo "Done." From 2ed9717fa04751fffd5230aeb5fa6b22c84e2ed1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 16:21:17 +0000 Subject: [PATCH 4/4] fix: run EdgeX services rootless in thin client; add registry/db to server; align proxy routes - supervisord.conf: add user=edgex to all EdgeX programs and user=mosquitto to mosquitto; supervisord continues to start as root (needed for entrypoint chown) so privilege-drop directives take effect - docker-compose.edgex-thin-client.yml: update comment to match the now-correct per-program user-drop behaviour - docker-compose.edgex-server.yml: add mqtt-broker, database (postgres), and core-keeper services required by the stage-gate scripts and security-proxy-auth; also add db-data volume and restrict EDGEX_ADD_PROXY_ROUTE to only core-keeper (the sole EdgeX service actually present in this stack) --- docker-compose.edgex-server.yml | 101 +++++++++++++++++++++++++-- docker-compose.edgex-thin-client.yml | 4 +- edgex-thin-client/supervisord.conf | 9 +++ 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/docker-compose.edgex-server.yml b/docker-compose.edgex-server.yml index 3b6f1ff..f9e0620 100644 --- a/docker-compose.edgex-server.yml +++ b/docker-compose.edgex-server.yml @@ -2,6 +2,9 @@ # EdgeKit – EdgeX Foundry 4.0 (Odessa) – Server / Central node security stack # # Runs the EdgeX Security Services on the central server: +# • mqtt-broker – internal message bus (required by core-keeper) +# • database – PostgreSQL (required by core-keeper) +# • core-keeper – service registry (required by security services) # • secret-store – OpenBao (secret store, open-source Vault fork) # • security-bootstrapper – orchestrates the secure startup sequence # • security-secretstore-setup – initialises OpenBao and mints service tokens @@ -42,6 +45,93 @@ x-stagegate-env: &stagegate-env services: + # --------------------------------------------------------------------------- + # mqtt-broker – internal EdgeX message bus (required by core-keeper) + # --------------------------------------------------------------------------- + mqtt-broker: + image: eclipse-mosquitto:2.0.21 + container_name: edgex-mqtt-broker + hostname: edgex-mqtt-broker + command: + - /usr/sbin/mosquitto + - -c + - /mosquitto-no-auth.conf + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + + # --------------------------------------------------------------------------- + # database – PostgreSQL data store for core-keeper + # --------------------------------------------------------------------------- + database: + image: postgres:16.3-alpine3.20 + container_name: edgex-postgres + hostname: edgex-postgres + environment: + POSTGRES_DB: edgex_db + POSTGRES_PASSWORD: postgres + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + tmpfs: + - /run + volumes: + - /etc/localtime:/etc/localtime:ro + - db-data:/var/lib/postgresql/data + + # --------------------------------------------------------------------------- + # core-keeper – service registry; required by security-proxy-auth and the + # stage-gate wait scripts + # --------------------------------------------------------------------------- + core-keeper: + image: edgexfoundry/core-keeper:4.0.0 + container_name: edgex-core-keeper + hostname: edgex-core-keeper + entrypoint: + - /bin/sh + - /edgex-init/ready_to_run_wait_install.sh + command: + - entrypoint.sh + - /core-keeper + depends_on: + database: + condition: service_started + mqtt-broker: + condition: service_started + security-secretstore-setup: + condition: service_started + environment: + <<: *stagegate-env + EDGEX_SECURITY_SECRET_STORE: "true" + SERVICE_HOST: edgex-core-keeper + DATABASE_HOST: edgex-postgres + MESSAGEBUS_TYPE: mqtt + MESSAGEBUS_PROTOCOL: tcp + MESSAGEBUS_HOST: edgex-mqtt-broker + MESSAGEBUS_PORT: "1883" + MESSAGEBUS_AUTHMODE: none + MESSAGEBUS_SECRETNAME: "" + ports: + - "127.0.0.1:59890:59890" + networks: + - edgex-network + read_only: true + restart: always + security_opt: + - no-new-privileges:true + user: "2002:2001" + volumes: + - /etc/localtime:/etc/localtime:ro + - edgex-init:/edgex-init:ro + - /tmp/edgex/secrets/core-keeper:/tmp/edgex/secrets/core-keeper:ro,z + # --------------------------------------------------------------------------- # security-bootstrapper – creates /edgex-init volume, seeds network keys, # and coordinates the multi-stage secure startup sequence @@ -192,14 +282,9 @@ services: environment: <<: *stagegate-env EDGEX_SECURITY_SECRET_STORE: "true" - # Add routes for the EdgeX core services exposed by this proxy + # Route only the services present in this server stack through the proxy EDGEX_ADD_PROXY_ROUTE: >- - core-data.http://edgex-core-data:59880, - core-metadata.http://edgex-core-metadata:59881, - core-command.http://edgex-core-command:59882, - support-notifications.http://edgex-support-notifications:59860, - support-scheduler.http://edgex-support-scheduler:59863, - device-modbus.http://edgex-device-modbus:59901 + core-keeper.http://edgex-core-keeper:59890 networks: - edgex-network read_only: true @@ -260,6 +345,8 @@ networks: volumes: edgex-init: name: edgex_edgex-init + db-data: + name: edgex_db-data secret-store-file: name: edgex_secret-store-file secret-store-logs: diff --git a/docker-compose.edgex-thin-client.yml b/docker-compose.edgex-thin-client.yml index 3e2f271..9e3d195 100644 --- a/docker-compose.edgex-thin-client.yml +++ b/docker-compose.edgex-thin-client.yml @@ -35,8 +35,8 @@ services: restart: unless-stopped # Run as root so entrypoint.sh can chown the PostgreSQL data directory; - # supervisord then drops individual services to the edgex user (uid 2002) - # or postgres user as appropriate. + # supervisord drops EdgeX services to the edgex user (uid 2002), + # mosquitto to the mosquitto user, and PostgreSQL to the postgres user. user: "0:0" # Hardening diff --git a/edgex-thin-client/supervisord.conf b/edgex-thin-client/supervisord.conf index ea501e8..908b8bd 100644 --- a/edgex-thin-client/supervisord.conf +++ b/edgex-thin-client/supervisord.conf @@ -54,6 +54,7 @@ stderr_logfile_maxbytes=0 [program:mosquitto] ; Embedded MQTT broker – internal EdgeX message bus (localhost only). command=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf +user=mosquitto priority=10 autostart=true autorestart=true @@ -69,6 +70,7 @@ stderr_logfile_maxbytes=0 [program:core-keeper] command=/edgex/bin/core-keeper directory=/edgex/services/core-keeper +user=edgex priority=20 autostart=true autorestart=true @@ -96,6 +98,7 @@ command=/edgex/bin/core-common-config-bootstrapper --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/core-ccb +user=edgex priority=30 autostart=true ; One-shot service: do not restart after clean exit. @@ -122,6 +125,7 @@ command=/edgex/bin/core-data --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/core-data +user=edgex priority=40 autostart=true autorestart=true @@ -140,6 +144,7 @@ command=/edgex/bin/core-metadata --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/core-metadata +user=edgex priority=40 autostart=true autorestart=true @@ -158,6 +163,7 @@ command=/edgex/bin/core-command --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/core-command +user=edgex priority=50 autostart=true autorestart=true @@ -179,6 +185,7 @@ command=/edgex/bin/support-notifications --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/support-notifications +user=edgex priority=60 autostart=true autorestart=true @@ -197,6 +204,7 @@ command=/edgex/bin/support-scheduler --registry -cp=keeper.http://localhost:59890 directory=/edgex/services/support-scheduler +user=edgex priority=60 autostart=true autorestart=true @@ -217,6 +225,7 @@ command=/edgex/bin/device-modbus -cp=keeper.http://localhost:59890 --registry directory=/edgex/services/device-modbus +user=edgex priority=70 autostart=true autorestart=true