Multi-Master Cluster

Deploy a 3-node multi-master OpenLDAP cluster with automatic replication. Write to any node, read from any node — changes propagate to the entire cluster automatically.

Architecture

Multi-Master SyncRepl Architecture

Write to any node — changes replicate to all peers via OpenLDAP syncRepl

Writes: 0SyncRepl ops: 0In-flight: 0
syncReplsyncReplsyncReplNode 1:392Node 2:393Node 3:394
Node 1:392

No entries yet

Node 2:393

No entries yet

Node 3:394

No entries yet

syncRepl consumer/providerWrite to any nodeBidirectional replication

Project Files

openldap-multinode
Explorer
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
services:
  openldap-node1:
    image: ${LDAP_IMAGE:-openldap:local}
    container_name: openldap-oiocloud-node1
    hostname: openldap-oiocloud-node1
    env_file:
      - .env.node1
    ports:
      - "392:389"
      - "639:636"
    volumes:
      - ldap-data-node1:/var/lib/ldap
      - ldap-config-node1:/etc/openldap/slapd.d
      - ./logs/node1:/logs
      - ./custom-schema:/custom-schema:ro
      - ./sample/oiocloud_data.ldif:/data/oiocloud_data.ldif:ro
      - ./init/init-data.sh:/docker-entrypoint-initdb.d/init-data.sh:ro
    
    # Security: Drop all capabilities and add only required ones
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
      - DAC_OVERRIDE
      - NET_BIND_SERVICE
      - SETUID
      - SETGID
      - CHOWN
    
    # Grace period for clean shutdown
    stop_grace_period: 30s
    
    restart: unless-stopped
    
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 128M
    
    # Log rotation
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    
    networks:
      - ldap-shared-network

  openldap-node2:
    image: ${LDAP_IMAGE:-openldap:local}
    container_name: openldap-oiocloud-node2
    hostname: openldap-oiocloud-node2
    env_file:
      - .env.node2
    ports:
      - "393:389"
      - "640:636"
    volumes:
      - ldap-data-node2:/var/lib/ldap
      - ldap-config-node2:/etc/openldap/slapd.d
      - ./logs/node2:/logs
      - ./custom-schema:/custom-schema:ro
    
    # Security: Drop all capabilities and add only required ones
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
      - DAC_OVERRIDE
      - NET_BIND_SERVICE
      - SETUID
      - SETGID
      - CHOWN
    
    # Grace period for clean shutdown
    stop_grace_period: 30s
    
    restart: unless-stopped
    
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 128M
    
    # Log rotation
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    
    networks:
      - ldap-shared-network
    depends_on:
      - openldap-node1

  openldap-node3:
    image: ${LDAP_IMAGE:-openldap:local}
    container_name: openldap-oiocloud-node3
    hostname: openldap-oiocloud-node3
    env_file:
      - .env.node3
    ports:
      - "394:389"
      - "641:636"
    volumes:
      - ldap-data-node3:/var/lib/ldap
      - ldap-config-node3:/etc/openldap/slapd.d
      - ./logs/node3:/logs
      - ./custom-schema:/custom-schema:ro
    
    # Security: Drop all capabilities and add only required ones
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
      - DAC_OVERRIDE
      - NET_BIND_SERVICE
      - SETUID
      - SETGID
      - CHOWN
    
    # Grace period for clean shutdown
    stop_grace_period: 30s
    
    restart: unless-stopped
    
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
        reservations:
          memory: 128M
    
    # Log rotation
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    
    networks:
      - ldap-shared-network
    depends_on:
      - openldap-node1

volumes:
  ldap-data-node1:
  ldap-config-node1:
  ldap-data-node2:
  ldap-config-node2:
  ldap-data-node3:
  ldap-config-node3:

networks:
  ldap-shared-network:
    external: true
YAMLUTF-8
Ln 1757 files

Start the Cluster

# Create shared network
docker network create ldap-shared-network 2>/dev/null || true

# Start all 3 nodes
docker compose up -d

# Wait for initialization and replication sync
sleep 60

Verify Replication

Data loads on node 1 and replicates to node 2 and node 3:

# Count employees on all 3 nodes (expect 30 each)
echo "=== Node 1 (port 392) ===" && \
ldapsearch -x -H ldap://localhost:392 \
  -b "dc=oiocloud,dc=com" \
  -D "cn=Manager,dc=oiocloud,dc=com" -w changeme \
  "(objectClass=oioCloudEmployee)" dn | grep -c "^dn:"

echo "=== Node 2 (port 393) ===" && \
ldapsearch -x -H ldap://localhost:393 \
  -b "dc=oiocloud,dc=com" \
  -D "cn=Manager,dc=oiocloud,dc=com" -w changeme \
  "(objectClass=oioCloudEmployee)" dn | grep -c "^dn:"

echo "=== Node 3 (port 394) ===" && \
ldapsearch -x -H ldap://localhost:394 \
  -b "dc=oiocloud,dc=com" \
  -D "cn=Manager,dc=oiocloud,dc=com" -w changeme \
  "(objectClass=oioCloudEmployee)" dn | grep -c "^dn:"

Test Write Replication

Write on node 2, verify it appears on node 1 and node 3:

# Add a test employee on node 2
echo "dn: employeeID=TEST001,ou=People,dc=oiocloud,dc=com
objectClass: oioCloudEmployee
objectClass: posixAccount
employeeID: TEST001
uid: testuser
cn: Test User
sn: User
mail: test.user@oiocloud.com
department: Engineering
jobTitle: Test Engineer
location: Test City
telephoneNumber: +1-555-9999
uidNumber: 9001
gidNumber: 200
homeDirectory: /home/testuser
userPassword: test123" | docker exec -i openldap-oiocloud-node2 \
  ldapadd -x -D "cn=Manager,dc=oiocloud,dc=com" -w changeme

# Verify on node 1
ldapsearch -x -H ldap://localhost:392 \
  -D "cn=Manager,dc=oiocloud,dc=com" -w changeme \
  -b "dc=oiocloud,dc=com" "(employeeID=TEST001)" dn

# Verify on node 3
ldapsearch -x -H ldap://localhost:394 \
  -D "cn=Manager,dc=oiocloud,dc=com" -w changeme \
  -b "dc=oiocloud,dc=com" "(employeeID=TEST001)" dn

Employee Data

30 employees across 3 cloud departments:

DepartmentCountRoles
Cloud Infrastructure10VP, Architects, Engineers
Cloud Security10CISO, Security Engineers, Analysts
Cloud Operations10VP, Operations Engineers, Managers
3 groups are created automatically: CloudInfrastructure, CloudSecurity, CloudOperations.

Connection Details

NodeLDAP PortLDAPS PortContainer
Node 1392639openldap-oiocloud-node1
Node 2393640openldap-oiocloud-node2
Node 3394641openldap-oiocloud-node3
All nodes share:

SettingValue
Bind DNcn=Manager,dc=oiocloud,dc=com
Base DNdc=oiocloud,dc=com
Passwordchangeme

Environment Configuration

Each node has its own env file with unique settings:

VariableNode 1Node 2Node 3
SERVER_ID123
REPLICATION_PEERSnode2,node3node1,node3node1,node2
REPLICATION_RIDS101,102201,203301,302
All other environment variables (domain, password, schemas) must be identical across all nodes.

High Availability

This setup provides:
  • Read/write on all nodes — any node can handle modifications
  • Automatic sync — changes propagate to all nodes in near real-time
  • Fault tolerance — the cluster continues operating if 1–2 nodes fail
  • Load balancing — distribute read operations across nodes

Monitoring

# Check replication status
docker exec openldap-oiocloud-node1 ldapsearch -Y EXTERNAL -H ldapi:/// \
  -b "cn=config" "(olcSyncRepl=*)" olcSyncRepl

# View logs per node
docker logs openldap-oiocloud-node1
docker logs openldap-oiocloud-node2
docker logs openldap-oiocloud-node3

Cleanup

docker compose down -v