Managing Systemd with Converge
Converge 0.6.0 adds support for the systemd.unit.state
resource, which will
allow you to control systemd services from converge. In this article, we’ll
talk about what capabilities have been added to converge for controlling systemd
processes.
Platform Requirements
Converge only supports systemd on Linux systems. In particular, only systems with systemd: installed, running, and accessible via dbus are supported. Some examples of systemd enabled distributions include: Ubuntu 16+, RHEL/Centos 7+, Debian 8+, and Container Linux. Docker images will not typically support systemd, so if you plan to use the systemd resource to configure a container, it’s important to first verify the availablity of systemd.
An Overview of systemd
Before we begin looking at how converge works with systemd, it’s helpful to take a quick look over the parts of systemd that will be relevant to our article. Feel free to Skip Ahead if you’re already familiar with how systemd works.
Systemd Units
In systemd each individual configurable task is called a unit
. Unit files can
be started, stopped, and restarted. Each unit will have some general information
about their current state and configuration. Different tasks require different
types of unit files. You can find the full list of the unit file types and
their
descriptions
here. In
our example we’ll be using a service
unit and a socket
unit.
Systemd Unit States
Unit files have both an active state, and a load state. The load state
determines whether the unit file will be available through calls like
ListUnits
. A file that is available in the normal systemd unit file locations
will automatically be loaded when some operation is performed on it.
The active state specifies whether the unit file is running, not running, or in
the middle of a state transition. Converge’s systemd resource does not expose
state transitions, but they may be visible through manual commands such as
systemctl
.
Installing A Service
In the following examples we’ll be using an example systemd service. If you are following along with this guide you can download the service and sample files.
Installing a systemd service requires writing the systemd unit files to a
directory in the systemd unit file search path. In this example we’re writing
to /lib/systemd/system/
. Systemd unit files have their own format which is
discussed
in the documentation.
The file.content
resource can be used for writing out systemd unit files. In
the example below we’ll write out two unit files, one for the service and
another for its socket. We’ll also use task.query
to request that systemd
reload its service daemon; reloading the daemon as a last step will prevent
systemd from warning that the unit files have changed (in this case, been
created).
param "logfile" {
default = "/var/log/example-service.log"
}
file.content "example.service" {
destination = "/lib/systemd/system/example.service"
content = <<EOF
[Unit]
Description=A well behaved daemon that does nothing
Requires=example.socket
After=network.target auditd.service
[Service]
ExecStart=/usr/sbin/example-service --log-to {{param `logfile`}} --foreground
ExecReload=/usr/bin/example-service --reload
PIDFile=/var/run/example-service.pid
KillSignal=SIGINT
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
[Install]
WantedBy=multi-user.target
EOF
}
file.content "example.socket" {
destination = "/lib/systemd/system/example.socket"
content = <<EOF
[Unit]
Description=Example service local unix socket
[Socket]
ListenDatagram=/tmp/example-service.socket
SocketMode=0622
[Install]
WantedBy=sockets.target
EOF
}
task "install-binary" {
interpreter = "/bin/bash"
check = "[[ -f /usr/sbin/example-service ]]"
apply = "cp service /usr/sbin/example-service"
}
task.query "reload-systemd" {
query = "systemctl daemon-reload"
}
Enabling and Disabling Services
The systemd.unit.state
resources does not (yet) offer first-class support for
enabling and disabling services, but you can easily do set your services to
start automatically at boot time using task
. In the example below we call
systemctl
directly to see if our service is enabled; if not we enable it.
task "enable-example-service" {
interpreter = "/bin/bash"
check = "systemctl is-enabled example.service"
apply = "systemctl enable example.service"
depends = ["file.content.example.service"]
}
Controlling Processes
The systemd.unit.state
resource allows you to control the state of a loaded
systemd unit. In the example below we ensure that the ssh service is running:
param "state" {
default = "running"
}
systemd.unit.state "example.service" {
unit = "example.service"
state = "running"
depends = ["file.content.example.service","file.content.example.socket","task.install-binary","task.query.reload-systemd"]
}
In this example we specify the name of the service: unit = "example.service"
and
the desired state based on the provided param (default: state = "running"
).
The unit states that you can set through converge are:
running
: indicates that the unit should be activestopped
: indicates that a unit should be inactiverestarted
: indicates that a unit should be restarted. If the unit is inactive, then this is equivalent torunning
.
The specific meaning of these states is dependent on the underlying unit type. For a service, it means that the process was successfully started and is active when converge checks it’s state. For a socket it means that systemd has created the socket and will buffer data until the socket is handed over to the owning process.
Reloading Service Configurations
The systemd.unit.state
resource supports systemd’s service reload
capabilities. This can be used with units that support the systemd reload
capability to instruct a service daemon to reload it’s configuration. In the
example below we disable password authentication for the SSH daemon, then
reload it’s configuration.
param "sshd_config" {
default = "/etc/ssh/sshd_config"
}
task "disable-password-authentication" {
check = "grep '^PasswordAuthentication no' {{param `sshd_config`}}"
apply = "sed -i -e 's/^#PasswordAuthentication no/PasswordAuthentication no/' {{param `sshd_config`}}"
}
systemd.unit.state "sshd" {
unit = "sshd.service"
state = "running"
reload = true
depends = ["task.disable-password-authentication"]
}
Sending Signals
The systemd.unit.state
resource supports sending arbitrary unix signals to a
running process. Signals may be sent via signal name or the signal number. In
the examples below we’ll send the SIGUSR1 signal to our example process process
using the name and number:
systemd.unit.state "hup-process" {
unit = "example.service"
signal_number = 10
}
systemd.unit.state "hup-process" {
unit = "bad.service"
signal_number = "sigusr1"
}
Unix signals can also be used to reload a configuration file if the underlying process does not support the systemd reload command. Consult the documentation for the individual services or your distribution documentation.
Unit Inspection
The systemd resource exports a great deal of information about the units that
are running on the system. It’s possible to get information about a process
without making any changes by creating a systemd.unit.state
resource and
specifying only the unit name. In the example below we’re specifying a service
unit, then looking up the name of the user that the daemon is running under.
systemd.unit.state "example" {
unit = "example.service"
}
file.content "service-info" {
destination = "out.txt"
content = "Process is running under: {{lookup `systemd.unit.state.example.service_properties.User`}}"
}
There are many different global and unit-type specific properties that are
exported by the systemd.unit.state
resource. See
the
full resource documentation for
a list of the exported fields.
Conclusion
Converge offers a convenient mechanism for both controlling and inspecting
processes that are governed by systemd. Try using the systemd.resource.state
resource next time you are making configuration changes to processes so that you
can seamlessly reload configurations and control running process state.