The previous commit (961e094) gave each site cluster its own database
(OtOpcUa_SiteA / OtOpcUa_SiteB). That fights the architecture — ConfigDb
is multi-tenant by design: one schema with a ServerCluster table whose
rows scope the rest of the configuration via ClusterId. Per-cluster
databases would split the schema and force every singleton/coordinator
to point at a different connection string.
Correct model: one ConfigDb, three ServerCluster rows (MAIN / SITE-A /
SITE-B), each Akka cluster's ClusterNode rows pointing back at the
matching ClusterId. Akka mesh isolation is still enforced by the
disjoint seed-node lists (unchanged from the previous commit).
Compose: all eight host nodes now point at Server=sql,1433;Database=OtOpcUa
and the README documents the post-boot ServerCluster + ClusterNode rows
operators need to create via /clusters and /hosts before the runtime can
resolve its scope.
255 lines
10 KiB
YAML
255 lines
10 KiB
YAML
# docker-dev/ — Mac-friendly multi-cluster fleet for v2 development + manual UI exercise.
|
|
#
|
|
# Stack (3 separate Akka clusters — all share the single `OtOpcUa` ConfigDb):
|
|
# sql SQL Server 2022 — hosts the one ConfigDb that all three clusters use
|
|
# ldap OpenLDAP with the dev users from C:\publish\glauth\auth.md mirrored in
|
|
#
|
|
# Main cluster (existing — split-role admin / driver pair on a single Akka mesh):
|
|
# admin-a OtOpcUa.Host with OTOPCUA_ROLES=admin (seed)
|
|
# admin-b OtOpcUa.Host with OTOPCUA_ROLES=admin (joins admin-a)
|
|
# driver-a OtOpcUa.Host with OTOPCUA_ROLES=driver (joins via admin-a)
|
|
# driver-b OtOpcUa.Host with OTOPCUA_ROLES=driver (joins via admin-a)
|
|
#
|
|
# Site A cluster (2-node fused admin+driver):
|
|
# site-a-1, site-a-2 OTOPCUA_ROLES=admin,driver, seed = site-a-1
|
|
#
|
|
# Site B cluster (2-node fused admin+driver):
|
|
# site-b-1, site-b-2 OTOPCUA_ROLES=admin,driver, seed = site-b-1
|
|
#
|
|
# traefik PathPrefix → main cluster admin-a/admin-b; Host(`site-a.localhost`) →
|
|
# site-a-*; Host(`site-b.localhost`) → site-b-*. Add the two site hosts to
|
|
# your /etc/hosts (or rely on macOS `.localhost` auto-resolution).
|
|
#
|
|
# Multi-tenancy: ConfigDb is one schema with a `ServerCluster` table; each Akka cluster
|
|
# corresponds to a row in it (ClusterId = "MAIN" / "SITE-A" / "SITE-B"), and each node's
|
|
# `ClusterNode.NodeId` points back at the row that owns it. After first boot, sign in to
|
|
# any cluster's Admin UI and create the matching ServerCluster + ClusterNode rows via
|
|
# /clusters and /hosts so the runtime knows what configuration scope applies.
|
|
#
|
|
# Akka mesh isolation: same system name "otopcua" + same remoting port 4053 inside each
|
|
# container's own network namespace, but with disjoint seed-node lists — gossip never
|
|
# crosses between the three meshes.
|
|
#
|
|
# Usage:
|
|
# docker compose -f docker-dev/docker-compose.yml up -d --build
|
|
# open http://localhost # main cluster Blazor admin UI
|
|
# open http://site-a.localhost # site A admin UI
|
|
# open http://site-b.localhost # site B admin UI
|
|
# open http://localhost:8080 # Traefik dashboard
|
|
#
|
|
# Tear-down: docker compose -f docker-dev/docker-compose.yml down -v
|
|
|
|
name: otopcua-dev
|
|
|
|
services:
|
|
|
|
sql:
|
|
image: mcr.microsoft.com/mssql/server:2022-latest
|
|
environment:
|
|
ACCEPT_EULA: "Y"
|
|
SA_PASSWORD: "OtOpcUa!Dev123"
|
|
MSSQL_PID: Developer
|
|
ports:
|
|
- "14330:1433"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'OtOpcUa!Dev123' -No -Q 'SELECT 1' || exit 1"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 20
|
|
|
|
ldap:
|
|
image: bitnami/openldap:2.6
|
|
environment:
|
|
LDAP_ROOT: "dc=lmxopcua,dc=local"
|
|
LDAP_ADMIN_USERNAME: "admin"
|
|
LDAP_ADMIN_PASSWORD: "ldapadmin"
|
|
LDAP_USERS: "alice,bob"
|
|
LDAP_PASSWORDS: "alice123,bob123"
|
|
LDAP_USER_DC: "ou=FleetAdmin"
|
|
ports:
|
|
- "3893:1389"
|
|
|
|
admin-a: &otopcua-host
|
|
build:
|
|
context: ..
|
|
dockerfile: docker-dev/Dockerfile
|
|
image: otopcua-host:dev
|
|
depends_on:
|
|
sql: { condition: service_healthy }
|
|
environment:
|
|
OTOPCUA_ROLES: "admin"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "admin-a"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
|
|
admin-b:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "admin"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "admin-b"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
|
|
driver-a:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "driver"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "driver-a"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
|
|
Cluster__Roles__0: "driver"
|
|
ports:
|
|
- "4840:4840"
|
|
|
|
driver-b:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "driver"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "driver-b"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
|
|
Cluster__Roles__0: "driver"
|
|
ports:
|
|
- "4841:4840"
|
|
|
|
# ── Site A cluster (2-node fused admin+driver) ──────────────────────────────
|
|
# Shares the OtOpcUa ConfigDb with the main + site-b clusters; multi-tenancy is
|
|
# enforced by ServerCluster.ClusterId rows (configure via /clusters after boot).
|
|
# Akka isolation comes from the disjoint seed list (seed = site-a-1).
|
|
|
|
site-a-1:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "admin,driver"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "site-a-1"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@site-a-1:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Cluster__Roles__1: "driver"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
ports:
|
|
- "4842:4840"
|
|
|
|
site-a-2:
|
|
<<: *otopcua-host
|
|
depends_on:
|
|
sql: { condition: service_healthy }
|
|
site-a-1: { condition: service_started }
|
|
environment:
|
|
OTOPCUA_ROLES: "admin,driver"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "site-a-2"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@site-a-1:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Cluster__Roles__1: "driver"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
ports:
|
|
- "4843:4840"
|
|
|
|
# ── Site B cluster (2-node fused admin+driver) ──────────────────────────────
|
|
|
|
site-b-1:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "admin,driver"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "site-b-1"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@site-b-1:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Cluster__Roles__1: "driver"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
ports:
|
|
- "4844:4840"
|
|
|
|
site-b-2:
|
|
<<: *otopcua-host
|
|
depends_on:
|
|
sql: { condition: service_healthy }
|
|
site-b-1: { condition: service_started }
|
|
environment:
|
|
OTOPCUA_ROLES: "admin,driver"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
|
|
Cluster__Hostname: "0.0.0.0"
|
|
Cluster__Port: "4053"
|
|
Cluster__PublicHostname: "site-b-2"
|
|
Cluster__SeedNodes__0: "akka.tcp://otopcua@site-b-1:4053"
|
|
Cluster__Roles__0: "admin"
|
|
Cluster__Roles__1: "driver"
|
|
Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
|
|
Security__Jwt__Issuer: "otopcua-dev"
|
|
Security__Jwt__Audience: "otopcua-dev"
|
|
Authentication__Ldap__Server: "ldap"
|
|
Authentication__Ldap__Port: "1389"
|
|
Authentication__Ldap__AllowInsecureLdap: "true"
|
|
ports:
|
|
- "4845:4840"
|
|
|
|
traefik:
|
|
image: traefik:v3.1
|
|
command:
|
|
- --entrypoints.web.address=:80
|
|
- --providers.file.filename=/etc/traefik/dynamic.yml
|
|
- --providers.file.watch=true
|
|
- --api.insecure=true
|
|
ports:
|
|
- "80:80"
|
|
- "8080:8080"
|
|
volumes:
|
|
- ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
|
|
depends_on:
|
|
- admin-a
|
|
- admin-b
|
|
- site-a-1
|
|
- site-a-2
|
|
- site-b-1
|
|
- site-b-2
|