This is no post on “best practices” on how to build or deploy customised versions of docker images. I don’t even know if this technique is advisable or not. Nevertheless, I came up with this when I was in dire need and out of time (so normal case).
The Problem
I faced the issue of having to set up an elasticsearch cluster along with all bells and whistles needed for an effective nolow-ops approach. I went with docker images which I built based on the officially provided ones. As I’m totally wild for automation, I made sure I had code for just about anything and my favourite tool to do so is gradle (of course).
At one point I decided to include extra “client” nodes (which hold no data and aren’t supposed to be ranked to master ever). As I didn’t want to spoil the nice gradle build with specialties while also abhorring anything unDRY I had to come up with some niftiness.
The Situation
The typical elasticsearch deployment comes also with related technologies to complement the use case. Normally there is logstash instances and a kibana host, monitoring stuff and backup, maybe an extra database or two. To ease the setup I put all resources for a specific docker image into dedicated sub folders, which would also determine the image tag.
So, having a folder like:
$base/awesome/elk/logstash/
would result in a docker image tagged like:
$registry/awesome/elk/logstash:$version
All those images would later be bound into a specific docker-compose based deployment.
All was well. Until I needed variations on that.
The Way Out
Build Configuration. That’s how I call it momentarily. I know.
Basically, its just a bunch of properties under an extra build (name) space. I’m bad at explaining. Maybe I come up with something better eventually.
Nevertheless, the existence of a specifically named property file in the current build context defines a variant of the very same build. So every resource is copied and filtered over using the key-value-pairs provided for the current variant.
Here is a build script dealing with that:
task build_elk(description: 'builds the ELK stack docker images') { doFirst { file("${projectDir}/etc/docker/elk").eachDir { File dir -> // iterate over build configs def cfgs = dir.listFiles().findAll {it.name =~ /build\.config/} ?: [null] cfgs.each { File cfg -> def cprops = new Properties() if (cfg) { println ">>> Building config ${cfg.name - 'build.config.' - '.properties'}" cfg?.withInputStream {cprops.load(it)} } def tag = "${registry}/${dir.name}${cprops['image_path'] ?: ''}:${AUX_IMG_VERSION}" copy { from dir into "${buildDir}/elk/$tag" exclude 'build.config.**' filter(ReplaceTokens, tokens: cprops) } exec { workingDir "${buildDir}/elk/$tag" commandLine 'docker', 'build', '--rm','--force-rm', '--no-cache', '-t', tag, '.' } } } } }
The Way Forward
Lot’s of generification. Leveling this into the fabric of gradle itself. Maybe make a plugin. Maybe.