jmtd → log → Puppet and filesystem mounts
Well, not long after writing my last post I've found some time to write up some of my puppet adventures, sooner than I imagined...
Outside work, I sys-admin a VPS instance that is shared by a few friends. We recently embarked in a project to migrate to a different VPS instance and I took the opportunity to revisit how we managed home directories.
I've got all the disk space allocated to the VM set up as LVM physical volumes.
This has proven very useful for later expansion: we can do it all live. Each
user on the VM may have one or more UNIX accounts that they use. Therefore, in
the old scheme, for the jon
user, we mounted an allocation of disk space at
/home/jons
, put the account home directories under it at e.g.
/home/jons/jon
, symlinked /home/jon -> /home/jons/jon
for brevity, and set
that as the home field in the passwd entry. This worked surprisingly well, but
I was always uncomfortable with having a symlink in the home path (and some
software was, too.)
For the new machine, I decided to try bind mounts. Short story: they just work.
However, the mtab
(and df
output) can look a little cluttered, and mount
order becomes quite important. To manage the set-up, I wrote a few puppet snippets.
First, a convenience definition to make the actual bind-mounts a little less
verbose.
define bindmount($device) {
mount { $name:
device => $device,
ensure => mounted,
fstype => 'none',
options => 'bind',
dump => 0,
pass => 2,
require => File[$device],
}
}
Once that was in place, we then needed to ensure that the directories to which the LV were to be mounted, and to where the user's home would be bind-mounted, actually exist; we also need to mount the underlying LV and set up the bind mount. The dependency chain is actually a graph, but with the majority of dependencies quite linear:
define bindmounthome() {
file { ["/home/${name}s", "/home/${name}"]:
ensure => directory,
} -> # depended upon by
mount { "/home/${name}s":
device => "LABEL=${name}",
ensure => mounted,
fstype => 'ext4',
options => 'defaults',
dump => 0,
pass => 2,
} -> # depended upon by
bindmount { "/home/${name}":
device => "/home/${name}s/${name}",
}
file { "/home/${name}s/${name}":
ensure => directory,
owner => $name,
group => $name,
mode => 0701, # 0701/drwx-----x
require => [User[$name], Group[$name], Mount["/home/${name}s"]],
}
}
That covers the underlying mounts and the "primary" accounts. However, the
point of this exercise was to support the secondary accounts for each user.
There's a bit of repetition here, and with some refactoring both this and
the preceding bindmounthome
definition could be a bit shorter, but I'm not
sure whether that would be at the expense of legibility:
define seconduser($parent) {
file { "/home/${name}":
ensure => directory,
} -> # depended upon by
bindmount { "/home/${name}":
device => "/home/${parent}s/${name}",
}
file { "/home/${parent}s/${name}":
ensure => directory,
owner => $name,
group => $name,
mode => 0701, # 0701/drwx-----x
require => [User[$name], Group[$name], Mount["/home/${parent}s"]],
}
}
I had to re-read the above a couple of times just now to convince myself that I
hadn't missed the dependencies between the mount
invocations towards the
bottom, but they're there: so, puppet will always run the mount for
/home/jons
before /home/jons/jon
. Since puppet is writing to the fstab
,
this means that the ordering is correct and a sequential start-up will work.
If you want anything cleverer than serialised, one-at-a-time mounting at boot,
I think one would have to use something other than trusty-old fstab
for the
job. I'm planning to look at Systemd's mount unit
type, but
there's no rush as this particular host is still running sysvinit
for the
time being.
Comments