This article will cover the installation of two OpenLDAP instances on different nodes. OpenLDAP will be configured to provide multi-master replication. Writes should still be sent to a single server, but both can be used for directory reads. In the event of failure of the write node, a load balancer or similar solution could fail writes over to the other node. OpenLDAP multi-master replication to me doesn’t seem like true multi-master replication, rather an active-active read LDAP database with hot standby of a write database. In this author’s opinion, (Oracle) Sun JDS and Forgerock OpenDJ offer far superior replication abilities.
This article will not cover OpenLDAP basics which you can already find within the administration guide available from OpenLDAP. Review the documentation available there before proceeding with this tutorial.
Servers being used, both running CentOS 6.3 x86_64:
|
1 2 |
172.16.18.169 dolan 172.16.18.172 gooby |
Prerequisite Tasks
Unless noted, perform all steps on both nodes. Add to /etc/hosts in case there is a failure in DNS name resolution:
|
1 2 3 |
# vi /etc/hosts 172.16.18.169 dolan 172.16.18.172 gooby |
Install prerequisite packages:
|
1 2 3 4 5 |
# yum install -y libacl-devel libblkid-devel gnutls-devel \ > readline-devel python-devel autoconf gcc-c++ gcc glibc-devel \ > glibc-headers kernel-headers libgomp libstdc++-devel \ > openssl-devel e2fsprogs-devel keyutils-libs-devel \ > krb5-devel libselinux-devel libsepol-devel libtool-ltdl-devel |
Software Compilation and Installation
Presuming all source has been copied to each server and is available under /usr/local/src, compile and install the latest version of (Oracle) Berkeley DB (available from Oracle):
|
1 2 3 4 5 6 7 |
# cd /usr/local/src # tar xzf db-5.3.21.tar.gz # cd db-5.3.21 # cd build_unix # ../dist/configure # make # make install |
By default, this will install to /usr/local/BerkeleyDB.5.3 which is sufficient for our needs and standards of installing to /usr/local/<package>-<version>. Create a symlink to assist in upgrading the software, and to save keystrokes whilst administering the system.
|
1 |
# ln -s /usr/local/BerkeleyDB.5.3 /usr/local/db5 |
Build OpenLDAP. 2.4.33 (again, available from OpenLDAP) was the latest available stable version at time of writing.
|
1 2 3 4 5 6 7 8 9 10 11 |
# cd /usr/local/src # tar xzf openldap-2.4.33.tgz # vi /etc/ld.so.conf.d/db5.conf /usr/local/db5/lib # ldconfig # LDFLAGS="-L/usr/local/db5/lib" CPPFLAGS="-I/usr/local/db5/include" \ > ./configure --prefix=/usr/local/openldap-2.4.33 # make depend # make # make install # ln -s /usr/local/openldap-2.4.33 /usr/local/openldap |
Run a quick test to ensure that the installation has been successful using the supplied default OpenLDAP configuration:
|
1 2 3 4 |
# cd /usr/local/openldap/libexec # ./slapd # ps -ef | grep '[s]lapd' # pkill -f slapd |
Our OpenLDAP installation is now complete on both nodes. We can now continue on to our configuration.
OpenLDAP Configuration
Modify ldap.conf as follows. This file can be used to set appropriate OpenLDAP environment variables:
|
1 2 3 4 5 |
# cd /usr/local/openldap/etc/openldap # vi ldap.conf <append_to_file> BASE dc=tokiwinter,dc=com URI ldap://dolan ldap://gooby |
Now, modify slapd.conf as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# vi slapd.conf <change pidfile and argsfile to> pidfile /var/run/slapd.pid argsfile /var/run/slapd.args <add in the Load dynamic backend modules section> moduleload syncprov.la moduleload accesslog.la moduleload back_bdb.la <on dolan, add before BDB database definitions> serverID 1 <on gooby, add before BDB database definitions> serverID 2 |
In the previous section, we defined the location of OpenLDAP’s PID file and argument file and loaded in the following modules:
syncprov.la- For synchronisation/replicationaccesslog.la- So we can easily configure the overlay for access logging should we choose toback_bdb.la- The BDB backend
Configure the BDB database definition section of the file as follows:
|
1 2 3 4 5 |
database bdb suffix "dc=tokiwinter,dc=com" rootdn "cn=ldapadmin,dc=tokiwinter,dc=com" rootpw s3cr3tp@ss directory /var/local/ldap |
This is self-explanatory. Choose a sensible location for your LDAP data directory.
Before the indices section add:
|
1 2 |
checkpoint 10240 720 cachesize 50000 |
This configuration will cause a BDB recovery checkpoint to be made after 10MB written to the directory, or 720 minutes, whichever occurs first. These values can be tailored to your specific recovery conditions. cachesize defined here will keep 50,000 directory entries cached in memory - again, adjust this for your site.
Next, configure BDB via the dbconfig mechanism. Add the following entries to slapd.conf noting that we are creating a 256MB in memory cache for BDB, and defining some sensible values around locks and log buffers, etc:
|
1 2 3 4 5 6 |
dbconfig set_cachesize 0 268435456 1 dbconfig set_lk_max_locks 3000 dbconfig set_lk_max_objects 1500 dbconfig set_lk_max_lockers 1500 dbconfig set_lg_regionmax 262144 dbconfig set_lg_bsize 2097152 |
Now configure replication. It is important that the refreshAndPersist replication type is selected. This means that after the period specified in interval (which is when the initial replication connection will be made) the connection will persist and data will be refreshed almost immediately. As there is still some delay and fluidity in when the replication will occur, you should still only write to a single “preferred” master.
|
1 2 3 4 5 6 7 8 9 10 |
syncrepl rid=001 provider=ldap://gooby:389 binddn="cn=ldapadmin,dc=tokiwinter,dc=com" bindmethod=simple credentials=s3cr3tp@ss searchbase="dc=tokiwinter,dc=com" type=refreshAndPersist interval=00:00:00:10 retry="5 5 300 5" timeout=1 |
Let’s examine the above syncrepl configuration in a little more detail. Our rid is our replication ID which should be configured to the same value as the serverID specified previously. The LDAP provider is then defined (as we’re building a 2 node cluster, this should point at the other node). The binddn is specified, as well as the method and credentials to be used for the bind. type was covered above, but to reiterate: ensure you use refreshAndPersist for this. An initial replication interval of 10 seconds is specified. retry is set to 5 x 5 second retries. If those fail, 5 more retries 300 seconds apart are used. If they fail too, then syncrepl will stop retrying. A timeout period of 1 second is defined.
At the end of the file after index definitions, add the following configuration:
|
1 2 3 4 5 |
mirrormode TRUE overlay syncprov syncprov-nopresent TRUE syncprov-reloadhint TRUE syncprov-checkpoint 1000 60 |
Note that we enable mirrormode by setting its value to TRUE, and the syncprov extension by way of the overlay directive. The syncprov-checkpoint here will update the contextCSN in the database after either 1000 successful write operations, or 60 minutes since the last time the contextCSN was written to the database. syncprov-nopresent is set to TRUE meaning that the consumer requesting the replication operation to indicate that it wishes to force a complete transfer of the DIT irrespective of any other settings or values - such as the Sync Cookie.
The configuration on gooby is identical, except a rid of 2 is specified, and the provider in gooby‘s slapd.conf is dolan.
As our credentials are in plaintext (we can fix this - view the manual pages for slapd.conf and slappasswd for details which are beyond the scope of this tutorial) so ensure that appropriate restrictive permissions are set on slapd.conf:
|
1 |
# chmod 600 slapd.conf |
Create the directory for our database:
|
1 2 |
# mkdir /var/local/ldap # chmod 700 /var/local/ldap |
Start slapd in debug mode and ensure there are no errors:
|
1 |
# /usr/local/openldap/libexec/slapd -d 1 |
If both OpenLDAP instances start correctly in the foreground, press ^C to shut them down.
Testing Multi-master Replication
Start OpenLDAP in normal daemonised mode:
|
1 |
# /usr/local/openldap/libexec/slapd |
Test by loading some LDIF data onto dolan. For now, we’ll just create an Organisational Unit (OU) and our main dc=tokiwinter,dc=com DN. Create the LDIF:
|
1 2 3 4 5 6 7 8 |
# vi /var/tmp/test.ldif dn: dc=tokiwinter,dc=com dc: tokiwinter objectClass: dcObject objectClass: domain dn: ou=people,dc=tokiwinter,dc=com ou: people objectClass: organizationalUnit |
And load it:
|
1 2 |
# /usr/local/openldap/bin/ldapadd -h dolan -p 389 \ > -f /var/tmp/test.ldif -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W |
Let’s make sure dolan has the entries loaded:
|
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 |
# /usr/local/openldap/bin/ldapsearch -h dolan -p 389 \ > -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W '*' Enter LDAP Password: # extended LDIF # # LDAPv3 # base <dc=tokiwinter,dc=com> (default) with scope subtree # filter: (objectclass=*) # requesting: * # # tokiwinter.com dn: dc=tokiwinter,dc=com dc: tokiwinter objectClass: dcObject objectClass: domain # people, tokiwinter.com dn: ou=people,dc=tokiwinter,dc=com ou: people objectClass: organizationalUnit # search result search: 2 result: 0 Success # numResponses: 3 # numEntries: 2 |
Let’s check that replication has occurred and that gooby also has the entries.
|
1 2 |
# /usr/local/openldap/bin/ldapsearch -h gooby -p 389 \ > -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W '*' |
You may find that no entries are returned. This is because the base DN did not exist on gooby. Let’s add the base DN onto gooby, and restart slapd:
|
1 2 3 4 5 6 7 8 9 |
# vi /var/tmp/base.ldif dn: dc=tokiwinter,dc=com dc: tokiwinter objectClass: dcObject objectClass: domain # /usr/local/openldap/bin/ldapadd -h gooby -p 389 \ > -f /var/tmp/base.ldif -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W # pkill -f slapd # /usr/local/openldap/libexec/slapd |
Now run the search again, and you should see the OU (which we didn’t manually add to gooby) correctly replicated:
|
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 |
# /usr/local/openldap/bin/ldapsearch -h gooby -p 389 \ > -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W '*' Enter LDAP Password: # extended LDIF # # LDAPv3 # base <dc=tokiwinter,dc=com> (default) with scope subtree # filter: (objectclass=*) # requesting: * # # tokiwinter.com dn: dc=tokiwinter,dc=com dc: tokiwinter objectClass: dcObject objectClass: domain # people, tokiwinter.com dn: ou=people,dc=tokiwinter,dc=com ou: people objectClass: organizationalUnit # search result search: 2 result: 0 Success # numResponses: 3 # numEntries: 2 |
Let’s now add another domain entry onto dolan and check for correct replication:
|
1 2 3 4 5 6 7 |
# vi /var/tmp/domain.ldif dn: dc=testdomain,dc=tokiwinter,dc=com dc: testdomain objectClass: dcObject objectClass: domain # /usr/local/openldap/bin/ldapadd -h dolan -p 389 \ > -f /var/tmp/domain.ldif -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W |
And check the status on gooby:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# /usr/local/openldap/bin/ldapsearch -h gooby -p 389 \ > -D "cn=ldapadmin,dc=tokiwinter,dc=com" -x -W 'dc=testdomain' Enter LDAP Password: # extended LDIF # # LDAPv3 # base <dc=tokiwinter,dc=com> (default) with scope subtree # filter: dc=testdomain # requesting: ALL # # testdomain.tokiwinter.com dn: dc=testdomain,dc=tokiwinter,dc=com dc: testdomain objectClass: dcObject objectClass: domain # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1 |
Perfect - the new domain entry has been successfully replicated! So - ensure your base DNs are created ideally before enabling replication for the first time to avoid this little hiccup.
To finish up, create an init.d script for slapd and use chkconfig to set it to start up when the system changes runlevel.
Conclusion
This article has shown how to build BerkeleyDB and OpenLDAP from source, and how to configure two OpenLDAP nodes to provide multi-master mirrormode replication between each other.
This configuration has many practical uses, and is widely used where simultaneous reads occur, and a node is specified as a writable master. The master can easily be failed over to the second node via a load-balancer or DNS when a failure occurs.