Many services on a Linux system use a *.d
directory approach to make
their configuration easily configurable by other services. This is a
remarkably simple yet efficient method for exposing services towards
other applications. Let's look into how this .d
approach works.
Take a look at the /etc/pam.d
structure: services that are PAM-aware
can place their PAM configuration files in this location, without
needing any additional configuration steps or registration. Same with
/etc/cron.d
: applications that need specific cronjobs do not need to
edit /etc/crontab
directly (with the problem of concurrent access,
overwriting changes, etc.) but instead can place their definitions in
the cron.d
directory.
This approach is getting more traction, as can be seen from the available "dot-d" directories on a system:
$ ls -d /etc/*.d
/etc/bash_completion.d /etc/ld.so.conf.d /etc/pam.d /etc/sysctl.d
/etc/conf.d /etc/local.d /etc/profile.d /etc/wgetpaste.d
/etc/dracut.conf.d /etc/logrotate.d /etc/request-key.d /etc/xinetd.d
/etc/env.d /etc/makedev.d /etc/sandbox.d /etc/cron.d
/etc/init.d /etc/modprobe.d /etc/sudoers.d
An application can place its configuration files in these directories, automatically "plugging" it in into the operating system and the services that it provides. And the more services adopt this approach, the easier it is for applications to be pluggable within the operating system. Even complex systems such as database systems can easily configure themselves this way. And for larger organizations, this is a very interesting approach.
Consider the need to deploy a database server on a Linux system in a
larger organization. Each organization has its standards for file system
locations, policies for log file management, etc. With the *.d
approach, these organizations only need to put files on the file system
(a rather primitive feature that every organization supports) and manage
these files instead of using specific, proprietary interfaces to
configure the environment. But to properly control this flexibility, a
few attention points need to be taken into account.
The first is to use a proper naming convention. If the organization
has a data management structure, it might have specific names for
services. These names are then used throughout the organization to
properly identify owners or responsibilities. When using the *.d
directories, these naming conventions also allow administrators to
easily know who to contact if a malfunctioning definition is placed. For
instance, if a log rotation definition has a wrong entry, a file called
mylogrotation
does not reveal much information. However,
CDBM-postgres-querylogs
might reveal that the file is placed there by
the customer database management team for a postgresql database. And it
isn't only about knowing who to contact (because that could easily be
done by comments as well), but also to ensure no conflicts occur. On a
shared database system, it is much more likely that two different teams
place a postgresql
file (which would overwrite the file already there)
unless they use a proper naming convention.
The second is to use something identifying where the file comes from. A best practice when using Puppet for instance is to add in a comment to the file such as the following:
# This file is managed by Puppet through the org-pgsl-def module
# Please do not modify manually
This informs the administrator how the file is put there; you might even want to include version information.
A third one is when the order of configuration entries is important.
Most *.d
supporting tools do not really care about ordering, but some,
like udev, do. When that is the case, the common consensus is to use
numbers in the beginning of the file name. The numbers then provide a
good ordering of the files.
Not all services already offer *.d
functionality, although it isn't
that difficult to provide it as well. Consider the Linux audit daemon,
whose rules are managed in the /etc/audit/audit.rules
file. Not that
flexible, isn't it? But one can create a /etc/audit/audit.rules.d
location and have the audit init script read these files (in
alphanumeric order), creating the same functionality.
Given enough service adoption, software distribution can be sufficient
to configure an application completely and integrate it with all
services used by the operating system. And even services that do not
support *.d
directories can still be easily wrapped around so that
their configuration file itself is generated based on the information in
such directories. Consider a hypothetical
AIDE configuration, where the
aide.conf
is generated based on the aide.conf.head
, aide.d/*
and
aide.conf.tail
files (similar to how resolv.conf
is sometimes
managed). The generation is triggered right before aide itself is
called (perhaps all in a single script).
Such an approach allows full integration:
- A PAM configuration file is placed, allowing the service authentication to be easily managed by administrators. Changes on the authentication (for instance, switch to an LDAP authentication or introduce some trust relation) is done by placing an updated file.
- A log rotation configuration file is placed, making sure that the log files for the service do not eventually fill the partitions
- A syslog configuration is provided, allowing for some events to be sent to a different server instead of keeping it local - or perhaps both
- A cron configuration is stored so that statistics and other house-cleaning jobs for the service can run at night
- An audit configuration snippet is added to ensure critical commands and configuration files are properly checked
- Intrusion detection rules are added when needed
- Monitoring information is placed on the file system, causing additional monitoring metrics to be automatically picked up
- Firewall definitions are extended based on the snippets placed on the system
etc. And all this by only placing files on the file system. Keep It Simple, and efficient ;-)