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
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:
| Department | Count | Roles |
|---|---|---|
| Cloud Infrastructure | 10 | VP, Architects, Engineers |
| Cloud Security | 10 | CISO, Security Engineers, Analysts |
| Cloud Operations | 10 | VP, Operations Engineers, Managers |
CloudInfrastructure, CloudSecurity, CloudOperations.
Connection Details
| Node | LDAP Port | LDAPS Port | Container |
|---|---|---|---|
| Node 1 | 392 | 639 | openldap-oiocloud-node1 |
| Node 2 | 393 | 640 | openldap-oiocloud-node2 |
| Node 3 | 394 | 641 | openldap-oiocloud-node3 |
| Setting | Value |
|---|---|
| Bind DN | cn=Manager,dc=oiocloud,dc=com |
| Base DN | dc=oiocloud,dc=com |
| Password | changeme |
Environment Configuration
Each node has its own env file with unique settings:
| Variable | Node 1 | Node 2 | Node 3 |
|---|---|---|---|
SERVER_ID | 1 | 2 | 3 |
REPLICATION_PEERS | node2,node3 | node1,node3 | node1,node2 |
REPLICATION_RIDS | 101,102 | 201,203 | 301,302 |
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