FreeBSD Cloud and DevOps 3

By now you know how to manage jails with Makefile. It's nice, but I took it a bit further this past few months. I realized that Makefile can effectively replace Vagrant, so I created program called Reggae: REGister Globaly Access Everywhere. In short it consists of few scripts that (de)register jail in/from Consul. As Consul acts as DNS, too, that means that when your jail is up, other jails using Consul as DNS will know about it. Also, Consul can be used for other things, but it's a different topic.

From the last blog post in this series you know how to use Makefile for these tasks, but I'll run through some of the Makefiles from my project. There are 3 .mk files in Reggae:

So let me explain how it works on the example of the first project I used it with: mail server. If you look at the mail as a project, it consists of few services like ldap, webmail, mail (dovecot + postfix) and so on. I have mail/Makefile which looks like this:

REGGAE_PATH = /usr/local/share/reggae
SERVICES = letsencrypt https://github.com/mekanix/jail-letsencrypt \
           ldap https://github.com/mekanix/jail-ldap \
           mail https://github.com/mekanix/jail-mail \
           jabber https://github.com/mekanix/jail-jabber \
           webmail https://github.com/mekanix/jail-webmail \
           web https://github.com/mekanix/jail-web \
           webconsul https://github.com/mekanix/jail-webconsul
DOMAIN=lust4trust.com

.include <${REGGAE_PATH}/mk/project.mk>

Yes, that's the whole file! The core of the Reggae is SERVICES in project.mk, so let's see how it deals with it:

up: fetch setup
.if defined(service)
    @echo "=== ${service} ==="
    @${MAKE} ${MAKEFLAGS} -C services/${service} up
.else
.for service url in ${SERVICES}
    @echo "=== ${service} ==="
    @${MAKE} ${MAKEFLAGS} -C services/${service} up
.endfor
.endif

Lets break it down. First, up target depends on fetch and setup. Once everything needed is downloaded and initialized, one of the two if branches will be triggered. You can run it with make up or make service=ldap up. Former runs up on all services (or jails in our case) and later get's only ldap jail up. So the if is there to see if service=<something> is present on the command line. If it's not, biggest problem for Reggae starts. That for loop is where I lost most time figuring out how to have something I would call "list of tuples" in Python. After a lot of experimenting, I realized that if I use service and url as indexes in the same loop, it will do what I want. With down target you have to do it in reverse, as some jails might depend on other jails (for nullfs mount, perheps?). As SERVICES is array, not array of pairs, you have to reverse the indexes, too: url and service in for loop.

down: setup
.if defined(service)
    @${MAKE} ${MAKEFLAGS} -C services/${service} down
.else
.for url service in ${SERVICES:[-1..1]}
    @${MAKE} ${MAKEFLAGS} -C services/${service} down
.endfor
.endif

Service uses all the same Makefile tricks, so let me just show how I provision the jails. I implemented ansible.mk as an example, but Reggae is not Ansible centric. First thing is to mark the default target to run:

.MAIN: up

This way it doesn't matter which target is first, up will be triggered if you just type make. This also solves the problem of adding targets wherever you like thus extending what can be done with your project. So let's look at how provisioning works.

provision:
    @touch .provisioned
.if target(do_provision)
    @${MAKE} ${MAKEFLAGS} do_provision
.endif

This is in service.mk in Reggae. If you defined do_provision or included ansible.mk from Reggae, provision will run it. As a matter of fact, this is how ldap service Makefile looks like:

SERVICE = ldap
REGGAE_PATH = /usr/local/share/reggae
CUSTOM_TEMPLATES = templates

.include <${REGGAE_PATH}/mk/ansible.mk>
.include <${REGGAE_PATH}/mk/service.mk>

Including ansible.mk before service.mk ensures that do_provision is defined when provision target from service.mk is parsed. Also, .MAIN will ensure that running just make doesn't run the first target from ansible.mk.

If you need to mount something extra in your jail, you can define EXTRA_FSTAB with the value of path to fstab containing extra mounts. Also, in order for provision to work, some files had to be generated from templates, so this is the directory hierarchy you need in your service repo:

You should know what those are if you ever used Ansible. Also, playbook directories are the ones where Reggae will either generate some files (that should be in .gitignore) or expect other files to be.

The last piece is registering with Consul. So, this is how I configured my /etc/rc.conf.d/consul:

consul_enable="YES"
consul_args="-bind=127.0.2.1 -client=127.0.2.1 -recursor=8.8.8.8 -ui -server -bootstrap"

You can run Consul in jail, too. As a matter of fact, I do and it's IP is special: 127.0.2.1. If you do that, Reggae will just work with Consul. In some of the future posts I'll explain how you can use Ansible with Consul to provision your jails. If you're inpatient, you can check out my mail project.