• Servers and Tools

Writing Unit Configuration Files

From the class:  Managing Processes with systemd

Systemd knows about our services and other types of units because we create a unit configuration file, and those files go in the lib directory under systemd, and then typically under the system subfolder.

So if we take a look at all the files in this folder, notice there's just a ton of them. These are all configuration files. We installed Mongo earlier in the class, and you can see that that configuration file is down here in the left.

So if we take a look at the contents of that file, what you'll see is a bunch of configuration statements telling systemd how to deal with it with this particular service.

So we'll just open up this one quickly. And notice that it looks sort of like a configuration file. So what we need to do is create one of these unit configuration files for our custom service that I showed you around in the last video.

In the project directory, we'll create a file called hello.service. This is going to be our configuration file for the unit. And it's a unit of type service, and so we give it the extension service.

I'll open it up in Vim. And then on the right, I'm going to use the man pages as a reference. This will tell me what the different options are that go into this unit configuration file. So I can say man systemd.unit.

And if you hit Control-D a bunch of times-- you can read this description if you like-- but if you hit Control-D and go all the way down, it will give you the different sections and what their options are.

So a configuration file consists of different sections. The ones you're going to see most often are the unit section, and then we'll have the service section, and then optionally we'll have this install section.

The service section is where we tell systemd how to deal with this service. So this is going to be the only one that's really required. And unit and install are optional.

So in the unit section, we tell systemd about this unit. And so typically you'll see a description. And this option just takes some human readable text that describes what it is that this service does. I'm going to say it just says, hello world every 5 seconds. And that'll be the description that shows up in the list when we say list units.

I can provide some other options in the unit section like the documentation, a link to the documentation. And I can also declare dependencies, and that's one of the most important aspects of the unit section.

In this case, I'm going to say that this service requires another service before it starts. Or we could even say that it requires another target, another state of the system. So in this case, I'm going to say that this service requires the So we want our network services to be up and running before this service runs.

So when we reboot our system, and systemd is automatically starting up our services, it will make sure that the network services are running before this service gets started.

One potential gotcha here is if you're requiring another service-- like, for example, let's say we were depending on Postgres database or on Mongo. This will actually make sure that both services are started simultaneously.

If you need there to be a specific ordering, like this service needs to start after Postgres or after this target, you need to also provide an after option and assign it to the same thing. So we can say we want this explicitly to start after the target. So this declares the dependency and this declares the order. So a little bit of confusing nuance there.

There's a bunch of other options that we're going to ignore. But if you use the J key to scroll down in the man pages, you'll see a bunch of these other options.

I'm going to search next for the install section. So I'll use the forward slash key in the man pages and type install, and then Enter. And that brings me to the install section.

So the install section describes what happens when we enable this service. And what we'd most often put in this section is a wantedby option. So we'll say wantedby, and then provide the target that wants this service to be running.

So usually you'll see something like And this says that once the system is in a state of the multi-user target, that's when this service will start up automatically by systemd. So this governs the behavior of what happens when we enable this service. You're going to see that when we do it in a little bit.

Next up we're going to fill out the service section. And for that, we need to go to a different man page. We're going to say man systemd.service, and that's going to tell us the specific options that are available to us for this section.

The first one that we need to set is the type. There's a bunch of different types. For example, if our service or if our code forks, and we know that it's going to fork, we can say the type is forking. If it just does something very simple and then exits, we can say it's a one shot.

In this case, we're going to say our service is a simple service. So we're going to run it like we would run it in the foreground, and then let systemd daemonize it and put it in the background.

In the man pages I hit Control-D a few more times until I got down to the ExecStart option. And that's the only other one that is required. And so that tells systemd how to start up our service.

So I'm going to say ExecStart=. What we need to do is to provide a full path to a command. And in this case, we're going to provide the full path to the Ruby interpreter, which is located under the USR, or user directory, bin, Ruby. And then we'll tell Ruby to run the program which is in the Vagrant folder. And the file is hello.rb.

So when we use the start command of system control, this line here will get executed and our program will run. Scrolling down a bit more in the man pages, you can see two more options that we're going to use all the time.

ExecReload, this says which command should we run in order to reload the service. And ExecStop, which is which command should we run to actually stop the service.

Now ExecStop, by default, will just send a term kill signal, or will use kill to send a term signal to the service. And so we don't actually have to provide this option unless we need to do something custom. But I do want to provide an ExecReload option because there is no default for that. So we need to specify that specifically.

So coming back to our configuration file, I'll say ExecReload=. And we're going to follow the advice in the man page over on the right by using the kill command to send a signal, a custom signal, to our process.

And the way we can get the process ID for the process-- remember last time, we just kind of got it manually. But now we're not going to be able to do that. So we can use this special environment variable called MAINPID , and that will give us the value of the currently running process.

So in ExecReload, we're going to use the bin kill command, and we're going to send the USR1 signal. Remember, that's the one that we're handling in our code. And we're just kind of simulating what a program like NGINX or Mongo would do in order to reload its configuration. And we're going to send that to the MAINPID.

I'd like to provide a few more options. First, I'm going to tell systemd that I want this system or this process to be run as a particular user. And that user is going to be vagrant.

Next, I'm going to set the working directory. This is going to set the current working directory, the present directory, for the code when the code starts running. So it will believe that we're in this directory. We're going to set that to /vagrant.

And finally, I'm going to tell systemd that if anything causes our process to fail for whatever reason, I want it to restart the process automatically, and to do that always.

So for instance, if we send a kill signal, or somehow there's an error in the program, we just want systemd to automatically restart the program. So we'll restart it always. And this should be all we need in our unit configuration file to get our service working with systemd.