Extends the docker-dev compose with two additional, fully-isolated Akka clusters representing distinct sites. Each site is a 2-node fused admin+driver cluster (OTOPCUA_ROLES=admin,driver on both nodes), backed by its own ConfigDb database so configuration state stays separate from the main cluster and from the other site. Cluster isolation: the three meshes share the same Akka system name "otopcua" and remoting port 4053 (inside each container's own network namespace), but their seed-node lists are disjoint — main seeds at admin-a, site-a seeds at site-a-1, site-b seeds at site-b-1 — so gossip doesn't cross between them. Layout: Main cluster ConfigDb=OtOpcUa admin-a, admin-b, driver-a, driver-b Site A ConfigDb=OtOpcUa_SiteA site-a-1, site-a-2 (fused admin+driver) Site B ConfigDb=OtOpcUa_SiteB site-b-1, site-b-2 (fused admin+driver) OPC UA endpoints exposed on host ports 4840-4845. Admin UIs reachable through Traefik via Host-header routing: http://localhost → main cluster (PathPrefix default) http://site-a.localhost → site A http://site-b.localhost → site B `*.localhost` auto-resolves on macOS; Linux users add the two hosts to /etc/hosts (or rely on the resolver's RFC 6761 behaviour).
250 lines
9.6 KiB
YAML
250 lines
9.6 KiB
YAML
# docker-dev/ — Mac-friendly multi-cluster fleet for v2 development + manual UI exercise.
|
|
#
|
|
# Stack (3 separate Akka clusters sharing the same SQL + LDAP):
|
|
# sql SQL Server 2022 (per-cluster ConfigDb databases)
|
|
# 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)
|
|
# ConfigDb: OtOpcUa
|
|
#
|
|
# Site A cluster (2-node fused admin+driver — its own ConfigDb + seed):
|
|
# site-a-1, site-a-2 OTOPCUA_ROLES=admin,driver, seed = site-a-1
|
|
# ConfigDb: OtOpcUa_SiteA
|
|
#
|
|
# Site B cluster (2-node fused admin+driver — its own ConfigDb + seed):
|
|
# site-b-1, site-b-2 OTOPCUA_ROLES=admin,driver, seed = site-b-1
|
|
# ConfigDb: OtOpcUa_SiteB
|
|
#
|
|
# 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).
|
|
#
|
|
# Cluster isolation is enforced by disjoint seed-node lists — Akka.Cluster gossip won't
|
|
# cross between the three meshes even though they share the same system name "otopcua".
|
|
#
|
|
# 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) ──────────────────────────────
|
|
# Its own ConfigDb (OtOpcUa_SiteA) + its own seed (site-a-1) → Akka isolation
|
|
# from the main cluster and from site B. Both nodes carry both roles.
|
|
|
|
site-a-1:
|
|
<<: *otopcua-host
|
|
environment:
|
|
OTOPCUA_ROLES: "admin,driver"
|
|
ASPNETCORE_URLS: "http://+:9000"
|
|
ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa_SiteA;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_SiteA;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_SiteB;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_SiteB;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
|