markround.com

DevOps, Sound Engineering and other things of interest...

Tiller Project and Docker Container Configuration

| Comments

After several days of hacking on my Runner.rb project, I’m pleased to say that I’ve completed a much more polished and complete solution to shipping multiple configuration files in a single Docker container. The project is now known as Tiller and has been published as a Ruby gem so you can just run gem install tiller to install it. You can find a good overview of it, and how it works over at the Github README.md, but it’s still essentially the same approach :

  • Provide templates of your configuration files as ERB templates
  • Provide a source for each “environment” containing the values that should be placed in those templates, and where you want them installed. This is usually done with a YAML configuration file, but you can now use other sources for this information (see later in this blog post).

The “TL;DR” pitch for Tiller is that it’s a tool that normally runs as the CMD or EXEC inside a Docker container; it generates dynamic configuration files for your application/services and then runs the application as a replacement process. It has a pluggable architecture and comes bundled with many plugins, so you can get values for your configuration files from environment variables, a consul cluster, JSON files, and many other things. It was created to provide a simple, generic way of building flexible “parameterized containers” that can grab their configuration at run-time – think of a service that needs a different DB connection string in a production environment, or maybe you want to pass in secrets such as passwords at run-time instead of baking them in your container.

And, as the end result is a set of plain-text files, your application doesn’t need to know how to directly talk to any of these services, it still only needs to know how to load a configuration file.

A very quick example of a Tiller configuration file which generates a single file and provides different values for it depending on the environment would be:

/etc/tiller/common.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
data_sources: [ "file" ]
template_sources: [ "file" ]
exec: [ "/usr/sbin/my_service" ]
environments:

  development:
    example.erb:
      target: /tmp/example.txt
      config:
        welcome_text: "This is the development environment"

  production:
    example.erb:
      target: /tmp/example.txt
      config:
        welcome_text: "This is the production environment"

And then the template :

/etc/tiller/templates/example.erb
1
2
<h1>This is generated from example.erb</h1>
<%= welcome_text %>

There’s a lot more to Tiller and you can set defaults for environments, specify the priority of plugins and much more. This means you can do things like :

  • Provide defaults for some values
  • Install a standard config file template in all environments
  • Hook up to a K/V store such as Consul or Zookeeper to retrieve templates and values
  • Pass “secrets” such as passwords into your application from encrypted files or Hashicorp’s Vault service
  • Finally allow users to configure some parameters through environment variables

Writing your own plugins

If Tiller doesn’t do exactly what you want, you can now write your own plugins (and new ones are being added all the time) to do things like pull templates from a remote HTTP server, populate the values with custom/dynamic variables such as FQDN, IP address of the host, or even fetch them from a LDAP server instead of pulling them off a YAML file on disk.

As a very simple example of a custom “global data source”, suppose your Docker containers all use the name.site.example.com FQDN structure (e.g. example01.london.example.com), and you wanted to extract the site part to use in a configuration file template. You could write a file called /usr/local/lib/tiller/data/example.rb :

/usr/local/lib/tiller/data/example.rb
1
2
3
4
5
6
7
8
class ExampleDataSource < Tiller::DataSource

  def global_values
    # site: Just the second part of the FQDN
    # This assumes DNS is working correctly!
    { 'site' =>  Socket.gethostbyname(Socket.gethostname).first.split('.')[1] }
  end
end

And then load it in /etc/tiller/common.yaml along with the default file data source :

/etc/tiller/common.yaml
1
2
3
data_sources:
  - file
  - example

And now all your templates can use this by referencing <%= site %>.

There’s much more you can do with this, including defining values for specific templates, and writing TemplateSources to provide the templates themselves, but that’s a bit too much detail for this introductory blog post. Go and check out the documentation at the Github page, browse through some of the examples, join the Gitter chatroom or check this blog for more updates & examples.

Comments