Automating OpenDJ backups on Kubernetes
Kubernetes StatefulSets are designed to run "pet" like services such as databases. ForgeRock's OpenDJ LDAP server is an excellent fit for StatefulSets as it requires stable network identity and persistent storage.
The ForgeOps project contains a Kubernetes Helm chart to deploy DJ to a Kubernetes cluster. Using a StatefulSet, the cluster will auto-provision persistent storage for our pod. We configure OpenDJ to place its backend database on this storage volume.
This gives us persistence that survives container restarts, or even restarts of the cluster. As long as we don't delete the underlying persistent volume, our data is safe.
Persistent storage is quite reliable, but we typically want additional offline backups.
The high level approach to accomplish this is as follows:
- Configure the OpenDJ container to support scheduled backups to a volume.
- Configure a Kubernetes volume to store the backups.
- Create a sidecar container that archives the backups. In this example we will use Google Cloud Storage.
Here are the steps in more detail:
Scheduled Backups:
OpenDJ has a built in task scheduler that can periodically run backups using a crontab(5) format. We update the Dockerfile for OpenDJ with environment variables that control when backups run:
# The default backup directory. Only relevant if backups have been scheduled.
ENV BACKUP_DIRECTORY /opt/opendj/backup
# Optional full backup schedule in cron (5) format.
ENV BACKUP_SCHEDULE_FULL "0 2 * * *"
# Optional incremental backup schedule in cron(5) format.
ENV BACKUP_SCHEDULE_INCREMENTAL "15 * * * *"
# The hostname to run the backups on. If this hostname does not match the container hostname, the backups will *not* be scheduled.
# The default value below means backups will not be scheduled automatically. Set this environment variable if you want backups.
ENV BACKUP_HOST dont-run-backups
To enable backup support, the OpenDJ container runs a script on first time setup that configures the backup schedule. A snippet from that script looks like this:
if [ -n "$BACKUP_SCHEDULE_FULL" ];
then
echo "Scheduling full backup with cron schedule ${BACKUP_SCHEDULE_FULL}"
bin/backup --backupDirectory ${BACKUP_DIRECTORY} -p 4444 -D "cn=Directory Manager" \
-j ${DIR_MANAGER_PW_FILE} --trustAll --backupAll \
--recurringTask "${BACKUP_SCHEDULE_FULL}"
fi
Update the Helm Chart to support backup
Next we update the OpenDJ Helm chart to mount a volume. We use a ConfigMap to pass the relevant environment variables to the OpenDJ container: apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "fullname" . }}
data:
BASE_DN: {{ .Values.baseDN }}
BACKUP_HOST: {{ .Values.backupHost }}
BACKUP_SCHEDULE_FULL: {{ .Values.backupScheduleFull }}
BACKUP_SCHEDULE_INCREMENTAL: {{ .Values.backupScheduleIncremental }}
The funny looking expressions in the curly braces are Helm template expressions. These variables are expanded when the object is sent to Kubernetes.Next we configure the container with a volume to hold the backups:
volumeMounts:
- name: data
mountPath: /opt/opendj/data
- name: dj-backup
mountPath: /opt/opendj/backup
This can be any volume type supported by your Kubernetes cluster. We will use an "emptyDir" for now - which is a dynamic volume that Kubernetes creates and mounts on the container.
Configuring a sidecar backup container
Now for the pièce de résistance. We have our scheduled backups going to a Kubernetes volume. How do we send those files to offline storage?One approach would be to modify our OpenDJ Dockerfile to support offline storage. We could, for example, include commands to write backups to Amazon S3 or Google Cloud storage. This works, but it would specialize our container image to a unique environment. Where practical, we want our images to be flexible so they can be reused in different contexts.
This is where sidecar containers come into play. The sidecar container holds the specialized logic for archiving files. In general, it is a good idea to design containers that have a single responsibility. Using sidecars helps to enable this kind of design.
If you are running on Google Cloud Engine, there is a ready made container that bundles the "gcloud" SDK, including the "gsutil" utility for cloud storage. We update our Helm chart to include this container as a sidecar that shares the backup volume with the OpenDJ container:
{{- if .Values.enableGcloudBackups }}
# An example of enabling backup to google cloud storage.
# The bucket must exist, and the cluster needs --scopes storage-full when it is created.
# This runs the gsutil command periodically to rsync the contents of the /backup folder (shared with the DJ container) to cloud storage.
- name: backup
image: gcr.io/cloud-builders/gcloud
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "while true; do gsutil -m rsync -r /backup {{ .Values.gsBucket }} ; sleep 600; done"]
volumeMounts:
- name: dj-backup
mountPath: /backup
{{- end }}
The above container runs in a loop that periodically rsyncs the contents of the backup volume to cloud storage. You could of course replace this sidecar with another that sends storage to a different location (say an Amazon S3 bucket).
If you enable this feature and browse to your cloud storage bucket, you should see your backed up data:
To wrap it all up, here is the final helm command that will deploy a highly available, replicated two node OpenDJ cluster, and schedule backups on the second node:
helm install -f custom-gke.yaml \
--set djInstance=userstore \
--set numberSampleUsers=1000,backupHost=userstore-1,replicaCount=2 helm/opendj
Now we just need to demonstrate that we can restore our data. Stay tuned!
Comments