© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
N. TolaramSoftware Development with Gohttps://doi.org/10.1007/978-1-4842-8731-6_17

17. systemd

Nanik Tolaram1  
(1)
Sydney, NSW, Australia
 

In this chapter, you will look at systemd, what it is, and how to write Go applications to interact with it. systemd is an important piece of software inside the Linux system, and it is too big to be covered entirely in this chapter.

You will look at an open source systemd Go library that is available and how to use it to access systemd. In this chapter, you will do the following:
  • Learn what systemd provides

  • Interact with systemd using systemctl and journalctl

  • Use the go-systemd library to write code

  • Write log messages to a systemd journal

  • Query systemd to get a list of registered services

Source Code

The source code for this chapter is available from the https://github.com/Apress/Software-Development-Go repository.

systemd

systemd is a suite of applications that are used in Linux systems to get them up and running. It provides more than just starting the core Linux systems run; it also starts a number of programs such as network stack, user logins, the logging server, and more. It uses socket and D-Bus activation to start up services, on-demand starting of background applications, and more.

D-Bus stands for Desktop Bus. It is a specification that is used for an inter-process communication mechanism, allowing different processes to communicate with one another on the same machine. Implementation of D-Bus consists of server components and a client library. For systemd, the implementation is known as sd-bus, and in a later section, you will look at using the D-Bus client library to communicate with the server component.

Socket activation is a mechanism in systemd to listen to a network port or Unix socket. When connected from an external source, it will trigger the running of a server application. This is useful in situations when a resource-hungry application needs to run only when it is needed and not during the time when the Linux system is started up.

systemd Units

Files that are used for systemd are called units. It is a standard way to represent resources managed by systemd. System-related systemd unit files can be found inside /lib/systemd/system, which looks this:
...
-rw-r--r--  1 root root   389 Nov 18  2021  apt-daily-upgrade.service
-rw-r--r--  1 root root   184 Nov 18  2021  apt-daily-upgrade.timer
lrwxrwxrwx  1 root root    14 Apr 25 23:23  [email protected] -> [email protected]
-rw-r--r--  1 root root  1044 Jul  7  2021  avahi-daemon.service
-rw-r--r--  1 root root   870 Jul  7  2021  avahi-daemon.socket
-rw-r--r--  1 root root   927 Apr 25 23:23  basic.target
-rw-r--r--  1 root root  1159 Apr 18  2020  binfmt-support.service
-rw-r--r--  1 root root   380 Oct  6  2021  blk-availability.service
-rw-r--r--  1 root root   449 Apr 25 23:23  [email protected]
-rw-r--r--  1 root root   419 Feb  1 11:49  bluetooth.service
...
-rw-r--r--  1 root root   758 Apr 25 23:23  dev-hugepages.mount
-rw-r--r--  1 root root   701 Apr 25 23:23  dev-mqueue.mount
...
-rw-r--r--  1 root root   251 Aug 31  2021  e2scrub_all.timer
-rw-r--r--  1 root root   245 Aug 31  2021  [email protected]
-rw-r--r--  1 root root   550 Aug 31  2021  e2scrub_reap.service
...
-rw-r--r--  1 root root   444 Apr 25 23:23  remote-fs-pre.target
-rw-r--r--  1 root root   530 Apr 25 23:23  remote-fs.target
The other location for unit files is inside the /etc/systemd/system directory. Unit files in this directory take precedence over any other unit files found on the filesystem, The following shows a snippet of the unit files in a local machine inside the /etc/system/system directory:
...
lrwxrwxrwx  1 root root   40 Mar  4 04:53 dbus-org.freedesktop.ModemManager1.service -> /lib/systemd/system/ModemManager.service
lrwxrwxrwx  1 root root   53 Mar  4 04:51 dbus-org.freedesktop.nm-dispatcher.service -> /lib/systemd/system/NetworkManager-dispatcher.service
lrwxrwxrwx  1 root root   44 Aug  7  2021 dbus-org.freedesktop.resolve1.service -> /lib/systemd/system/systemd-resolved.service
lrwxrwxrwx  1 root root   36 Mar  4 04:53 dbus-org.freedesktop.thermald.service -> /lib/systemd/system/thermald.service
lrwxrwxrwx  1 root root   45 Aug  7  2021 dbus-org.freedesktop.timesync1.service -> /lib/systemd/system/systemd-timesyncd.service
...
drwxr-xr-x  2 root root 4096 Mar  4 04:53 graphical.target.wants/
drwxr-xr-x  2 root root 4096 Mar  4 04:51 mdmonitor.service.wants/
drwxr-xr-x  2 root root 4096 May 27 10:10 multi-user.target.wants/
...
lrwxrwxrwx  1 root root   31 Apr  6 17:55 sshd.service -> /lib/systemd/system/ssh.service
...
The following is a non-exhaustive list of the different unit files:
  • .service: Describe a service or application and how to start or stop the service

  • .socket: Describes a network or Unix socket used for socket-based activation

  • .device: Describes a device exposed in the sysfs/udev device tree

  • .timer: Defines a timer that will be managed by systemd

You looked at systemd and what it is used for. In the next section, you will look at using the provided tools to look at the services provided by systemd using systemctl.

systemctl

systemctl is the main tool used to communicate with systemd that is running in your local machine. Type in the following command in your terminal:
systemctl
Without any parameter, it will list all the services that are currently registered with the system, as shown in Figure 17-1.

A screenshot of the output lists the services and their status as loaded, active, and exited.

Figure 17-1

Registered service in systemd

Let’s take a peek at the services that are currently running on local machines. You will look at the systemd-journal.service, which is running a systemd logging service. Open your terminal and use the following command:
systemctl status systemd-journald.service
You will see output that looks like the following:
systemd-journald.service - Journal Service
         Loaded: loaded (/lib/systemd/system/systemd-journald.service; static)
         Active: active (running) since Thu 2022-06-16 23:21:09 AEST; 1 week 0 days ago
TriggeredBy: • systemd-journald-dev-log.socket
             • systemd-journald-audit.socket
             • systemd-journald.socket
       Docs: man:systemd-journald.service(8)
             man:journald.conf(5)
   Main PID: 370 (systemd-journal)
     Status: "Processing requests..."
      Tasks: 1 (limit: 9294)
     Memory: 54.2M
        CPU: 3.263s
     CGroup: /system.slice/systemd-journald.service
             └─370 /lib/systemd/systemd-journald
Jun 16 23:21:09 nanik systemd-journald[370]: Journal started
...
Jun 16 23:21:09 nanik systemd-journald[370]: System Journal

The output shows information about the service such as the amount of memory the service is using, the process ID (PID), location of the .service file, and whether the service is active or not.

To stop a service, use the command systemctl stop. As an example, let’s try to stop cups.service (a service used to provide printing services in Linux). Use the following command in your terminal to check the status:
systemctl status cups.service
You will see output like the following:
• cups.service - CUPS Scheduler
     Loaded: loaded (/lib/systemd/system/cups.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-06-24 00:00:35 AEST; 22h ago
TriggeredBy: • cups.socket
             • cups.path
       Docs: man:cupsd(8)
   Main PID: 39757 (cupsd)
     Status: "Scheduler is running..."
      Tasks: 1 (limit: 9294)
     Memory: 2.8M
        CPU: 51ms
     CGroup: /system.slice/cups.service
             └─39757 /usr/sbin/cupsd -l
Jun 24 00:00:35 nanik systemd[1]: Starting CUPS Scheduler...
Jun 24 00:00:35 nanik systemd[1]: Started CUPS Scheduler.
To stop the service, use the following command in your terminal:
sudo systemctl stop  cups.service
If you check the status again using the same systemctl status cups.service command, you will see output that looks like the following:
○ cups.service - CUPS Scheduler
     Loaded: loaded (/lib/systemd/system/cups.service; enabled; vendor preset: enabled)
     Active: inactive (dead) since Fri 2022-06-24 22:47:52 AEST; 1s ago
TriggeredBy: ○ cups.socket
             ○ cups.path
       Docs: man:cupsd(8)
    Process: 39757 ExecStart=/usr/sbin/cupsd -l (code=exited, status=0/SUCCESS)
   Main PID: 39757 (code=exited, status=0/SUCCESS)
     Status: "Scheduler is running..."
        CPU: 54ms
Jun 24 00:00:35 nanik systemd[1]: Starting CUPS Scheduler...
...
Jun 24 22:47:52 nanik systemd[1]: Stopped CUPS Scheduler.

Using systemctl allows you to take a look at the status of the registered service in systemd. In the next section, you will write a simple server application and control it using systemctl.

Hello Server systemd

In this section, you will look at the sample code that can be found inside the chapter17/httpservice directory. The application is a simple HTTP server listening on port 8111. Let’s run the application normally first using the following command in your terminal:
go run main.go
You will see output like following:
2022/06/24 22:55:40 Server running - port 8111
Open your browser and access it using http://localhost:8111. You will see output that looks like the following:

A screenshot of the output screen reads Hello you are getting a response from an app running via systems.

Figure 17-2

HTTP server output

The application is working now. Let's create the executable file that you will use to run it as a systemd service. Compile the application using the following command. Make sure you are in the chapter17/httpservice directory.
go build -o httpservice
Your application is now ready to be installed as a systemd service. Follow the steps below to do the installation.
  1. 1.

    Copy the httpservice executable file into the /usr/local/bin directory in your terminal, as shown:

     
sudo cp ./httpservice /usr/local/bin
  1. 2.

    Copy the httpservice.service file into the /etc/systemd/system directory in your terminal, as shown:

     
sudo cp ./httpservice.service /etc/systemd/system
  1. 3.

    Take a look at the status of your newly created service using the following command:

     
sudo systemctl status  httpservice.service
You will see output as follows:
○ httpservice.service - HTTP Server Application
     Loaded: loaded (/etc/systemd/system/httpservice.service; disabled; vendor preset: enabled)
     Active: inactive (dead)
The service is recognized by systemd but it is not enabled or dead.
  1. 4.

    Start/enable the service using the following command:

     
sudo systemctl start  httpservice.service
  1. 5.

    If you run the same status command (step 3), you will get the following output:

     
● httpservice.service - HTTP Server Application
     Loaded: loaded (/etc/systemd/system/httpservice.service; disabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-06-24 23:09:34 AEST; 39s ago
   Main PID: 44068 (httpservice)
      Tasks: 5 (limit: 9294)
     Memory: 1.0M
        CPU: 3ms
     CGroup: /system.slice/httpservice.service
             └─44068 /usr/local/bin/httpservice
Jun 24 23:09:34 nanik systemd[1]: Started HTTP Server Application.
Jun 24 23:09:34 nanik httpservice[44068]: 2022/06/24 23:09:34 Server running - port 8111
  1. 6.

    To ensure that the service starts up when you reboot your machine, use the following command:

     
sudo systemctl enable  httpservice.service

Now you can access the application by pointing your browser to http://localhost:8111.

You have successfully deployed your sample app. It is configured to start up when you boot up your machine. In next section, you will look at using a Go library to write a system application.

go-systemd Library

You learned early in this chapter that the D-Bus specification contains a client library. The client library allows applications to interact with system. The client library that you are going to take a look at is for a Go application called go-systemd. The library can be found at http:/github.com/coreos/go-systemd.

The library provides the following features:
  • Using socket activation

  • Notifying systemd of service status changes

  • Starting, stopping, and inspecting services and units

  • Registering machines or containers

  • …and many more

For this chapter, you will look at code samples using the library to write to journal logs, list services available on local machines, and query machines.

Querying Services

The sample code for this section can be found inside the chapter17/listservices directory. The sample code queryies from systemd all the services that are registered, similar to how systemctl list-units works.

Open your terminal and make sure you are inside the chapter17/listservices directory. Build the application as follows:
go build -o listservices
Once compiled, run the executable as root.
sudo ./listservices
You will see output of the services registered in systemd.
...
Name : sys-module-fuse.device, LoadState : loaded, ActiveState : active, Substate : plugged
Name : gdm.service, LoadState : loaded, ActiveState : active, Substate : running
Name : sysinit.target, LoadState : loaded, ActiveState : active, Substate : active
Name : graphical.target, LoadState : loaded, ActiveState : active, Substate : active
Name : veritysetup.target, LoadState : loaded, ActiveState : active, Substate : active
...
Name : remote-fs-pre.target, LoadState : loaded, ActiveState : inactive, Substate : dead
Name : apt-daily-upgrade.timer, LoadState : loaded, ActiveState : active, Substate : waiting
Name : ssh.service, LoadState : loaded, ActiveState : active, Substate : running
Name : system.slice, LoadState : loaded, ActiveState : active, Substate : active
Name : systemd-ask-password-plymouth.path, LoadState : loaded, ActiveState : active, Substate : waiting
Name : dev-ttyS15.device, LoadState : loaded, ActiveState : active, Substate : plugged
...
Let’s walk through the code to understand how the app uses the library. The following snippet shows that it is using the NewSystemdConnectionContext function to connect to the systemd server:
import (
...
)
func main() {
  ...
  c, err := d.NewSystemdConnectionContext(ctx)
  ...
}
Once it successfully connects to systemd, it sends a request to get the list of units and print it out to the console.
import (
...
)
func main() {
  ...
  js, err := c.ListUnitsContext(ctx)
  ...
  for _, j := range js {
     fmt.Println(fmt.Sprintf("Name : %s, LoadState : %s, ActiveState : %s, Substate : %s", j.Name, j.LoadState, j.ActiveState, j.SubState))
  }
  c.Close()
}

The library takes care of all the heavy lifting of connecting to systemd, sending requests, and converting requests to a format that it passes to the application.

Journal

Another example you will look at is using the library to write log messages to the journal that provides a logging service. To access the logging service, you can use the journalctl command line.
journalctl -r
The output looks like following on my local machine (it will look different in your machine):
...
Jun 25 00:06:43 nanik sshd[2567]: pam_unix(sshd:session): session opened for user nanik(uid=1000) by (uid=0)
...
Jun 25 00:00:32 nanik kernel: audit: type=1400 audit(1656079232.440:30): apparmor="DENIED" operation="capable" profile="/usr/sbin/cups-browsed" pid=2527 comm="cups-browsed" capability=23  c>
Jun 25 00:00:32 nanik audit[2527]: AVC apparmor="DENIED" operation="capable" profile="/usr/sbin/cups-browsed" pid=2527 comm="cups-browsed" capability=23  capname="sys_nice"
Jun 25 00:00:32 nanik systemd[1]: Finished Rotate log files.
...

The parameter -r shows the latest log message on the top. Now you know how to look at the journal logging service. Let's run your sample application to write log messages into it.

Open terminal and make sure you are inside the chapter17/journal directory. Run the sample using the following command:
go run main.go
Open another terminal and run the same journalctl -r command. You will see the log message from the sample application in the output, as shown:
Jun 25 00:06:48 nanik journal[2591]: This log message is from Go application
Jun 25 00:06:44 nanik systemd[908]: Started Tracker metadata extractor.
Jun 25 00:06:44 nanik systemd[908]: Starting Tracker metadata extractor...
Jun 25 00:06:44 nanik systemd-logind[778]: Removed session 6.
Jun 25 00:06:44 nanik systemd[1]: session-6.scope: Deactivated successfully.
...
The code to write to the journal is very simple.
package main
import (
  j "github.com/coreos/go-systemd/v22/journal"
)
func main() {
  j.Print(j.PriErr, "This log message is from Go application")
}
The Print(..) function prints the message This log message is from Go application with the error priority. This is normally printed in red when you view it using journalctl. The following is a list of the different priorities available from the library:
const (
  PriEmerg Priority = iota
  PriAlert
  PriCrit
  PriErr
  PriWarning
  PriNotice
  PriInfo
  PriDebug
)

The following priorities are assigned the red color: PriErr, PriCrit, PriAlert, and PriEmerg. PriNotice and PriWarning are highlighted, and PriDebug is in lighter grey. One of the interesting priorities is PriEmerg, which broadcasts the log message to all open terminals in the local machine.

In the next section, you will look at an advanced feature of systemd, which is registering and running a machine or container.

Machines

One advanced feature that systemd provides is the ability to run virtual machines or containers in local machines. This feature does not come by default; there is extra installation of services and steps performed in order to use this feature. This feature is made available by installing a package called systemd-container. Let’s understand what this package is all about.

The systemd-container package contains a number of tools, particularly the tool called systemd-nspawn. This tool is similar to chroot (which I discussed in Chapter 4) but provides more advanced features such as virtualizing the file system hierarchy, process tree, and various IPC subsystems. Basically, it allows you to run a lightweight container with its own rootfs.

The following steps will walk you through in installing the package and configuring it.
  1. 1.

    Copy the file systemd-machined.service from the chapter17/machine directory to /usr/lib/systemd/user.

     
sudo cp systemd-machined.service /usr/lib/systemd/user
  1. 2.

    Install the systemd-container package using the following command:

     
sudo apt install systemd-container
  1. 3.

    Start the service using the following command:

     
sudo systemctl start systemd-machined.service
  1. 4.

    Check the status using the command:

     
sudo systemctl status  systemd-machined.service
You will see output like the following:
• systemd-machined.service - Virtual Machine and Container Registration Service
     Loaded: loaded (/lib/systemd/system/systemd-machined.service; static)
     Active: active (running) since Sat 2022-06-25 00:51:10 AEST; 21h ago
       Docs: man:systemd-machined.service(8)
             man:org.freedesktop.machine1(5)
   Main PID: 2744 (systemd-machine)
     Status: "Processing requests..."
      Tasks: 1 (limit: 9294)
     Memory: 1.2M
        CPU: 220ms
     CGroup: /system.slice/systemd-machined.service
             └─2744 /lib/systemd/systemd-machined
Jun 25 00:51:10 nanik systemd[1]: Starting Virtual Machine and Container Registration Service...
Jun 25 00:51:10 nanik systemd[1]: Started Virtual Machine and Container Registration Service.

Use the machinectl command-line tool to interact with the new machine service that you just installed. Use the tool to download and run Ubuntu operating system images locally as a container.

Use the following command to download the Ubuntu image:
machinectl pull-tar https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.gz trusty-server
If this way does not work for your Linux system, use the following command:
wget https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.gz
machinectl import-tar trusty-server-cloudimg-amd64-root.tar.gz
Let’s check to make sure that the image has been downloaded successfully by using the following command:
machinectl list-images
You will get output like following:
NAME                               TYPE      RO   USAGE CREATED MODIFIED
trusty-server-cloudimg-amd64-root  directory no   n/a   Sat 2022-06-25 23:16:16 AEST n/a
1 images listed.
The image download is stored inside the /var/lib/machines folder, as shown:
nanik@nanik:~/Downloads/alpine-container$ sudo ls -la  /var/lib/machines
total 24
drwx------  6 root root 4096 Jun 25 23:16 .
drwxr-xr-x 69 root root 4096 May 25 16:19 ..
drwxr-xr-x 22 root root 4096 Jun 25 23:16 trusty-server-cloudimg-amd64-root
Looking inside the trusty-server-cloudimg-amd64-root directory, you will see the rootfs directory structure.
drwx------  6 root root 4096 Jun 25 23:16 ..
drwxr-xr-x  2 root root 4096 Nov  8  2019 bin
drwxr-xr-x  3 root root 4096 Nov  8  2019 boot
drwxr-xr-x  4 root root 4096 Nov  8  2019 dev
...
drwxr-xr-x  2 root root 4096 Nov  8  2019 sbin
drwxr-xr-x  2 root root 4096 Nov  8  2019 srv
drwxr-xr-x  2 root root 4096 Mar 13  2014 sys
...
drwxr-xr-x 10 root root 4096 Nov  8  2019 usr
drwxr-xr-x 12 root root 4096 Nov  8  2019 var
Finally, now that you have the image downloaded and stored locally, you can run it using the following command:
sudo systemd-nspawn -M trusty-server-cloudimg-amd64-root
You will see output like the following:
nanik@nanik:~/Downloads/alpine-container$ sudo systemd-nspawn-M trusty-server-cloudimg-amd64-root
Spawning container trusty-server-cloudimg-amd64-root on /var/lib/machines/trusty-server-cloudimg-amd64-root.
Press ^] three times within 1s to kill container.
root@trusty-server-cloudimg-amd64-root:~#
Let’s take a look at the sample application inside the chapter17/machine folder. The sample uses a go-systemd library to query for the images that are stored locally. Change the directory to chapter17/machine and run the sample as follows:
go run main.go
You will get output that look like the following:
2022/06/25 23:22:19 image - .host directory
2022/06/25 23:22:19 image - trusty-server-cloudimg-amd64-root directory
The sample uses the machine1 package of the go-systemd library and it calls the New() function to establish a connection to the systemd system. Once connected, it uses the ListImages() function to retrieve the available images and print them out in the console.
package main
import (
  m "github.com/coreos/go-systemd/v22/machine1"
  ...
)
func main() {
  conn, err := m.New()
  ...
  s, err := conn.ListImages()
  ...
  for _, img := range s {
     log.Println("image - "+img.Name, img.ImageType)
  }
}

Summary

In this chapter, you learned about systemd and its functions in the Linux operating system. You explored the different tools that are available to allow you to interact with systemd. You looked at Go code samples that show how to interact with systemd using the go-systemd library.

go-systemd provides a different capability to interact with system. One of the advanced features you looked at was interacting with the systemd-machine service that provides virtual machine and container registration capability.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.211.70