Если вы видите что-то необычное, просто сообщите мне.

Запуск различных программ через systemd

Запуск java как сервиса через systemd

Предоставим у вас есть jar файл и вам нужно его запустить как сервис. Так же есть необходимость запускать его автоматически когда система перезагружается.

Убунту имеет встроенный механизм для создания сервисом в ручном режиме, запуск во время загрузи системы и останавливать и запускать как сервис. В этой статье мы сделаем простую сервис обертку для вашего jar файла, который можно будет запускать как сервис. Начнем.

1) Создадим сервис

sudo vim /etc/systemd/system/my-webapp.service

Копируем следующее содержание в файл /etc/systemd/system/my-webapp.service:

[Unit]
Description=My Webapp Java REST Service
[Service]
User=ubuntu
# Конфигурационный файл приложения application.properties должен быть тут: 

# Замените на вашу рабочую папку
WorkingDirectory=/home/ubuntu/workspace 

# Путь к экзешнику. 
# Экзешник это bash скрипт, который вызывает jar файл
ExecStart=/home/ubuntu/workspace/my-webapp 

SuccessExitStatus=143
TimeoutStopSec=10
Restart=on-failure
RestartSec=5 

[Install]
WantedBy=multi-user.target

2) Создаём Bash скрипт для вызова сервисов.

Это bash скрипт, который вызывает JAR файл: my-webapp

#!/bin/sh
sudo /usr/bin/java -jar my-webapp-1.0-SNAPSHOT.jar server config.yml

Не забудем выдать скрипту права на исполнение:

sudo od u+x my-webapp 

3) Запуск сервиса

sudo systemctl daemon-reload
sudo systemctl enable my-webapp.service
sudo systemctl start my-webapp
sudo systemctl status my-webapp

4) Настройка логирования

Первый запуск:

sudo journalctl --unit=my-webapp

Чтобы увидеть логи в реальном времени используйте опцию -f.

Если вы хотите обрезать логи, используйте -n <# количество линий>, чтобы увидеть нужное количество строк лога:

sudo journalctl -f -n 1000 -u my-webapp

Для отображения части логов используйте ключ -f:

sudo journalctl -f -u my-webapp

Остановите сервис с помощью команды:

sudo systemctl stop my-webapp

Deploying a service using ansible and systemd

You may be a sole developer or member of a small development team with no dedicated ops people. You will probably have a handful of small-ish services, perhaps a few cronjobs and a couple of VPSs to run them on. Or you may have one or more servers at home and would like to automate the deployment of custom or open source tools and services. What are your options?

At one end of the spectrum, there's the current kubernetes zeitgeist as recommended™ by the internetz. However, it may be that you can't pay the price (i.e. time) or simply do not have the desire to ride the steep learning curve that this path entails. On the other end of the spectrum, there's always rsync/scp and bash scripts but you'd like something better than that (including process management, logs, infrastructure as code checked into a git repo etc.). So, is there anything worthwile in between these two extremes?

This article is about how to deploy and run a service in a remote server using ansible and systemd. All the "configuration" that is neccessary to do that will be checked into a git repo and will be easily reproducible on an arbitrary set of servers (including your localhost) without the need to log into the servers and do any manual work (apart from setting up passwordless ssh access - but you already have that, right?). Now, a few words about the components that we are going to use.

Ansible is a tool for automating task execution in remote servers. It runs locally on your development machine and can connect to a specified set of servers via ssh in order to execute a series of tasks without the need of an "agent" process on the server(s). There's a wide variety of modules that can accomplish common tasks such as creating users and groups, installing dependencies, copying files and many more. We will focus on the absolutely necessary in this guide, but for those who would like to do more there are these nice tutorials as well as ansible's official documentation.

systemd is the basic foundation of most linux systems nowadays as the replacement of sysvinit and has a wide variety of features including managing processes and services (the feature that we'll be using for this article).

For our demonstration, we will be using a simple custom service written in Go, which very nicely and conveniently consists of a single statically-linked binary, but the concepts are the same for anything that can be executed on the remote server (this includes programs writen in ruby/python/java/dotnet etc.). So, let's start!

Prerequisites

We will be needing the following on our local (development) machine:

All the work that follows is checked into this repo which can be cloned using git clone https://github.com/kkentzo/deployment-ansible-systemd-demo.git. The repo contains the following components:

The Guide

Writing the service

Our service is a very simple one: it accepts http requests and responds with a greeting to the client based on the contents of the url path. The code is dead simple: package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        var name string
        if name = r.URL.Path[1:]; name == "" {
            name = "stranger"
        }
        fmt.Fprintf(w, "hello %s!", name)
    })
    log.Fatal(http.ListenAndServe(":9999", nil))
}

The code above starts an http server that listens on port 9999. If the url path is the root path ("/") then the service greets the "stranger", otherwise it greets whoever is mentioned in the url path (e.g. GET /world will return "hello world!").

This file is placed under cmd/demo as main.go in our working directory and can be built in executable form (under bin/) as follows:

$ go build -o ./bin/demo ./cmd/demo/...

OK, so now we have our service - how about we deploy it?

Deploying the service

We will use ansible to deploy our service to our remote server as a systemd service unit. As mentioned before, the remote server can be any linux system with ssh and systemd. If you don't have access to such a system, you can use a tool such as virtual box in order to setup a debian buster system.

We will specify our remote server in our inventory (file hosts) for use by ansible:

[myservers]
harpo

As you can see, this file can declare multiple named server groups (names in [] brackets can be referenced in other ansible files). We have specified the section myservers which contains the name of our single server harpo. In this case, harpo is an alias defined in our .ssh/config file as follows:

Host harpo
     HostName 12.34.56.789
     User USERNAME
     IdentityFile ~/.ssh/harpo

This configuration facilitates ansible's access to the remote server (as mentioned before) and assumes that we have correctly set up access for user USERNAME in the server located in the address 12.34.56.789 (replace this with your own server's IP).

Now that we have specified our remote server, we need to define a role (workbook in ansible terminology) for our server as follows:

$ mkdir roles
$ cd roles
$ ansible-galaxy init demo

The above command will generate a file/directory structure under roles/demo of which the following are relevant to our guide:

[Unit]
Description=Demo service

[Service]
User=demo
Group=demo
ExecStart=/usr/local/bin/demo

[Install]
WantedBy=multi-user.target

As you can see, systemd units are defined simply using a declarative language. In our case, we declare our service executable (ExecStart) that will run under user demo. The [Install] section specifies that our service requires a system state in which network is up and the system accepts logins.

Now, that we have our systemd unit, let's define our ansible playbook, starting from file roles/demo/tasks/main.yml:

---
- name: create demo group
  group:
    name: demo
    state: present

- name: create demo user
  user:
    name: demo
    groups: demo
    shell: /sbin/nologin
    append: yes
    state: present
    create_home: no

- name: Copy systemd service file to server
  copy:
    src: demo.service
    dest: /etc/systemd/system
    owner: root
    group: root
  notify:
    - Start demo

- name: Copy binary to server
  copy:
    src: demo
    dest: /usr/local/bin
    mode: 0755
    owner: root
    group: root
  notify:
    - Start demo

The task file is mostly self-explanatory but a few items need clarifications:

each task has a name and references an ansible module that accepts parameters

---
- name: Start demo
  systemd:
    name: demo
    state: started
    enabled: yes

This notification uses ansible's systemd module and requires the service to be started and enabled (i.e. started every time the remote server boots).

Finally, we complete our ansible configuration by combining our inventory and roles in file demo.yml:

---
- hosts: myservers
  become: yes
  become_user: root
  roles:
    - demo

Here, we declare that we would like to apply the role demo that we just defined to the specified host group (myservers as specified in our inventory file).

Wrap up

We're almost there! Let's wrap up the whole thing in a Makefile that contains the two targets of interest, build and deploy our service, as follows:

.PHONY: build
build:
    env GOOS=linux go build -o ./bin/demo ./cmd/demo/...

.PHONY: deploy
deploy: build
    cp ./bin/demo ./roles/demo/files/demo
    ansible-playbook -i hosts demo.yml

The build action compiles our service (for linux) and outputs the executable under bin/. The deploy target first builds the service, then copies the executable under the demo role's files and executes the entire ansible playbook by using the demo.yml spec.

Now, we can deploy our service by issuing:

$ make deploy

The output of this command on my machine was as follows:

make deploy
env GOOS=linux go build -o ./bin/demo ./cmd/demo/...
cp ./bin/demo ./roles/demo/files/demo
ansible-playbook -i hosts demo.yml

PLAY [home] ********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [harpo]

TASK [demo : create demo group] ************************************************
changed: [harpo]

TASK [demo : create demo user] *************************************************
changed: [harpo]

TASK [demo : Copy systemd service file to server] ******************************
changed: [harpo]

TASK [demo : Copy binary to server] ********************************************
changed: [harpo]

RUNNING HANDLER [demo : Start demo] ********************************************
changed: [harpo]

PLAY RECAP *********************************************************************
harpo                      : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

We can now test our service using curl:

$ curl 12.34.56.789:9999/world

where 12.34.56.789 needs to be replaced by your remote server's actual IP. If you see the output "hello world!", then you made it!

Status & Monitoring

We can also have a look on how the demo process is doing on our remote server by logging in (via ssh) and using the systemd commands systemctl (control and status) and journalctl (logs) as follows:

# check the status of our service
$ sudo systemctl status demo
# tail our service's logs
$ sudo journalctl -f -u demo

Further Work

This approach can be used to do pretty much anything on one or more remote servers in a consistent and robust manner. Beyond process management, systemd can also be used to schedule events (ala cronjobs) using timer units and manage logs using its own binary journal files and syslog.

Ansible's apt, shell and copy modules also facilitate the automated installation and configuration of standard software packages, even on the local machine using the "[local]" group name in the inventory file:

[local]
127.0.0.1

and executing any playbook using ansible-playbook's --connection=local command argument.

Epilogue

ansible and systemd are two fantastic tools that allow one to build automated, simple and reproducible operational pipelines quickly and efficiently.

All the contents of the service and the deployment code are in this repo.

I hope that you enjoyed this guide and found it useful! Please feel free to leave your comments or ask your questions.

docker-compose как сервис

Создаем папку c docker-compose в дирректории /projectdirectory.

Создаем файл /etc/systemd/system/НАЗВАНИЕСЕРВИСА.service:

[Unit]
Description=Docker Compose Service
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/projectdirectory
ExecStart=/usr/local/bin/docker-compose up -d --remove-orphans
ExecStop=/usr/local/bin/docker-compose down
[Install]
WantedBy=multi-user.target

Используем systemctl для управления:

Запуск/остановка:

systemctl start/stop НАЗВАНИЕСЕРВИСА

Включение/отключение автозапуска:

systemctl enable/disable НАЗВАНИЕСЕРВИСА

Run a Java Application as a Service on Linux

Введение

Любое Java приложение с точки зрения системы это просто объект JVM. В этом коротком руководстве мы увидим, как мы можем сделать наше приложение сервисом.

Мы будем истользовать удобства пакета system, systemd это сервис управления системой в современных дистрибутивах Linux.

Здесь вы найдёте две реализации: одна для простого случая, другая - расширенная.

Простой сервис

В мире systemd, для создания системного сервиса, нам нужно подготовить файл и зарегистрировать его определённым способом. Начнём с содержания файла:

[Unit]
Description=My Java driven simple service
After=syslog.target network.target

[Service]
SuccessExitStatus=143

User=appuser
Group=appgroup

Type=simple

Environment="JAVA_HOME=/path/to/jvmdir"
WorkingDirectory=/path/to/app/workdir
ExecStart=${JAVA_HOME}/bin/java -jar javaapp.jar
ExecStop=/bin/kill -15 $MAINPID

[Install]
WantedBy=multi-user.target

Мы узнали тип сервиса простым потому что система начинает JVM процесс напрямую без создания дочернего процесса.

ExecStop указывает команду завершения, и systemd достаточно умен чтобы выяснить PID начального процесса. он автоматически создаёт MAINPID переменные окружения.

После, мы указываем systemd посылать 15 (SIGTERM) системный сигнал, чтобы завершить процесс.

Java создатели спроектировали его таким образом, чтобы он возвращал не нулевой код в случае если он завершён системным сигналом. Так как сигнал не нулевой, то ответ будет 128 + числовое значение сигнала.

У казывая SuccessExitStatus как 143, мы говорим systemd отловить это значение(128+15) как нормальное завершение

Форкаем сервис

Простой файл описания сервиса выше может быть довольно эффективным для простого приложения. Однако, во многих практических случаях, будут возможно включены дополнительные настройки.

Это может быть JVM параметры так же как любой другой параметр приложения, для примера, файл конфигурации или данных. Это модно свести к написанию обёртки для shell скрипта, где мы можем настроить все требуемые параметры прежде чем запустить JVM.

Предоставим, мы уже имеем обертку для скрипта, и теперь просто хотим включить это в сервис систем:

#!/bin/bash

JAVA_HOME=/path/to/jvmdir
WORKDIR=/path/to/app/workdir
JAVA_OPTIONS=" -Xms256m -Xmx512m -server "
APP_OPTIONS=" -c /path/to/app.config -d /path/to/datadir "

cd $WORKDIR
"${JAVA_HOME}/bin/java" $JAVA_OPTIONS -jar javaapp.jar $APP_OPTIONS

Так как мы используем shell скрипт чтобы запустить сервис, JVM будет запущена с помощью shell скрипта. Эта операция известна как fork, и поэтому мы указали тип как forking.

Перенесём опеределения переменных в тело скрипта:

[Unit]
Description=My Java forking service
After=syslog.target network.target
[Service]
SuccessExitStatus=143
User=appuser
Group=appgroup

Type=forking

ExecStart=/path/to/wrapper
ExecStop=/bin/kill -15 $MAINPID

[Install]
WantedBy=multi-user.target

Регистрируем и запускаем сервис

Не важно какой тип сервиса выбран, для выполнения задачи, мы должны знать, как настроить и запустить сам systemd сервис.

First, we need to name the unit file after the service name we want to have. In our examples, that could be javasimple.service or javaforking.service.

Then, we put the unit file under one of the locations where systemd can find it. For an arbitrary service, /etc/systemd/system is a good choice.

The full path to our system units, in that case, will be:

/etc/systemd/system/javasimple.service
/etc/systemd/system/javaforking.service

Another possible path to place system units is /usr/lib/systemd/system. This is typically the location used by the system installation packages.

However, we should consider it more appropriate when we develop our own .rpm or .deb installation packages containing system services.

In either case, we’ll control the service using the systemctl utility and pass either the start, stop, or status command.

Before that, however, we should notify systemd that it has to rebuild its internal service database. Doing this will make it aware of the new system unit we introduced. We can do this by passing the daemon-reload command to systemctl.

Now, we’re ready to run all the commands we mentioned:

sudo systemctl daemon-reload

sudo systemctl start javasimple.service
sudo systemctl status javasimple.service

● javasimple.service - My Java driven simple service
Loaded: loaded (/etc/systemd/system/javasimple.service; disabled; vendor preset: disabled)
Active: active (running) since Sun 2021-01-17 20:10:19 CET; 8s ago
Main PID: 8124 (java)
CGroup: /system.slice/javasimple.service
└─8124 /path/to/jvmdir/bin/java -jar javaapp.jar

We’ll need to run the daemon-reload command each time we modify the unit file.

Next, we notice the system reports our service running but disabled. Disabled services will not start automatically when the system boots.

Of course, we can configure it to start up automatically along with the system. This is where we use another systemctl command — enable:

sudo systemctl enable javasimple.service
Created symlink from /etc/systemd/system/multi-user.target.wants/javasimple.service to /etc/systemd/system/javasimple.service

Now, we can see that it’s enabled:

sudo systemctl status javasimple.service
● javasimple.service - My Java driven simple service
Loaded: loaded (/etc/systemd/system/javasimple.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2021-01-17 20:10:19 CET; 14min ago
Main PID: 8124 (java)
....
  1. Conclusion

In this article, we looked at two possible ways of turning Java applications into system service by means of systemd.

Java is still one of the most popular programming languages. A lot of Java applications are designed to run non-interactively for a variety of tasks, such as processing data, providing an API, monitoring events, and so on. Thus, they all are good candidates to become system services.

Simple vs Oneshot - Выбираем тип systemd сервиса

Этот пост довольно подробный, но если вы просто ищите общую информацию когда и какие типы сервсов использовать, читайте под катом.

Когда вы создаете свой systemd сервис, выбор типа сервиса может быть довольно сложен. Есть множество доступных и полезных типов сервисов, но этот пост сконцентрирован вокруг разниц между oneshot и simple простого сервисов. Возможно вас смущает какой из них использоваться.

Время запуска последующей единицы

Это наибольшая разница между oneshot и simple сервисами, когда стартует слудующая единица. Как указано в man: следующая единица простого сервиса стартует сразу же. На картинке ниже можете посмотреть:

Простой сервис и следующие за ним

Simple service diagram

Напротив же в oneshot сервисе, все последующие единицы дождутся заверешения сервиса прежде чем они стартанут.

Oneshot сервис и следующие за ним

Oneshot service diagram

Давайте рассмотрим простой пример сервиса и последующего за ним:

simple-test.service

[Unit]
Description=Simple service test

[Service]
Type=simple
ExecStart=/bin/bash -c "echo Simple service - start && sleep 60 && echo Simple service - end"

И зависимый сервис:

dep-simple-test.service

[Unit]
Description=Dependent service
After=simple-test.service
Requires=simple-test.service

[Service]
ExecStart=/bin/bash -c "echo Dependent service - running"

Запуск зависимого сервиса dep-simple-test.service запустит simple-test.service сначала(из-за After/Requires директив), а логи выведут следующее:

Jun 19 20:28:16 thstring20200619162314 systemd[1]: Started Simple service test.
Jun 19 20:28:16 thstring20200619162314 systemd[1]: Started Dependent service.
Jun 19 20:28:16 thstring20200619162314 bash[1238]: Simple service - start
Jun 19 20:28:16 thstring20200619162314 bash[1239]: Dependent service - running
Jun 19 20:28:16 thstring20200619162314 systemd[1]: dep-simple-test.service: Succeeded.
Jun 19 20:29:16 thstring20200619162314 bash[1238]: Simple service - end
Jun 19 20:29:16 thstring20200619162314 systemd[1]: simple-test.service: Succeeded.'

Простой пример(как и множество дальше) просто используют sleep для имитации работы сервиса. Так как simple-test.service это просто сервис, сразу за ним следует запуск dep-simple-test.service, и можно увидеть как оба сервиса стартуют в одно и то же время.

Но если мы сделаем тоже самое для oneshot сервиса, давайте посмотрим как различия выглядят.

oneshot-test.service

[Unit]
Description=Oneshot service test

[Service]
Type=oneshot
ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

dep-oneshot-test.service

[Unit]
Description=Dependent service
After=oneshot-test.service
Requires=oneshot-test.service

[Service]
ExecStart=/bin/bash -c "echo Dependent service - running"

Логирование для этих двух единиц(после запуска dep-oneshot-test.service) показывает разницу:

Jun 19 20:31:46 thstring20200619162314 systemd[1]: Starting Oneshot service test...
Jun 19 20:31:46 thstring20200619162314 bash[1420]: Oneshot service - start
Jun 19 20:32:46 thstring20200619162314 bash[1420]: Oneshot service - end
Jun 19 20:32:46 thstring20200619162314 systemd[1]: oneshot-test.service: Succeeded.
Jun 19 20:32:46 thstring20200619162314 systemd[1]: Started Oneshot service test.
Jun 19 20:32:46 thstring20200619162314 systemd[1]: Started Dependent service.
Jun 19 20:32:46 thstring20200619162314 bash[1440]: Dependent service - running
Jun 19 20:32:46 thstring20200619162314 systemd[1]: dep-oneshot-test.service: Succeeded.

Вы можете видеть как зависимый сервис не запускается пока oneshot сервис не завершится.

Состояния активации

Состояния активации различных типов сервисов управляют множеством взаимодействия с другими единицами.

Тип До Во время После
Simple inactive (dead) active (running) inactive (dead)
Oneshot inactive (dead) activating (start) inactive (dead)
Oneshot (RemainAfterExit) inactive (dead) activating (start) active (exited)

Состояние Во время различного состояния между simple и oneshot это причина почему следующая единица ждет завершения oneshot сервиса и почему не ждет завершения simple сервиса.

RemainAfterExit (oneshot)

Вы можете заметить такую директиву выше, RemainAfterExit меняет поведение oneshot сервиса не много. Это просто способ сказать systemd что после того как он выходит, он долже держать активное состояние. Для понимания, рассмотрим пример:

oneshot-remainafterexit.service

[Unit]
Description=Oneshot service test with RemainAfterExit

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

Запустив systemctl status для этого сервиса во время работы, мы можем увидеть различия:

● oneshot-remainafterexit.service - Oneshot service test with RemainAfterExit
   Loaded: loaded (/etc/systemd/system/oneshot-remainafterexit.service; static; vendor preset: enabled)
   Active: active (exited) since Fri 2020-06-19 20:55:14 UTC; 7s ago
  Process: 1174 ExecStart=/bin/bash -c echo Oneshot service - start && sleep 60 && echo Oneshot service - end (code=exited, status=0/SUCCESS)
 Main PID: 1174 (code=exited, status=0/SUCCESS)

Jun 19 20:54:14 thstring20200619162314 systemd[1]: Starting Oneshot service test with RemainAfterExit...
Jun 19 20:54:14 thstring20200619162314 bash[1174]: Oneshot service - start
Jun 19 20:55:14 thstring20200619162314 bash[1174]: Oneshot service - end
Jun 19 20:55:14 thstring20200619162314 systemd[1]: Started Oneshot service test with RemainAfterExit.

Заметим что сервисв в active(exited) состоянии. вместо inactive(dead)(который должен быть, в случае если RemainAfterExit был отключен). Но если мы это хотим сохранить, что он делает на самом деле? Давайте посмотрим на пример, который использует ExecStop директиву. ExecStop запустится когда сервис остановится.

oneshot-execstop.service

[Unit]
Description=Oneshot service test with ExecStop

[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"
ExecStop=/bin/bash -c "echo Oneshot service - stop"

В этом сервисе RemainAfterExit отключен(это по-умолчанию, но добвлен для наглядности)

● oneshot-execstop.service - Oneshot service test with ExecStop
   Loaded: loaded (/etc/systemd/system/oneshot-execstop.service; static; vendor preset: enabled)
   Active: inactive (dead)

Jun 19 21:04:10 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop...
Jun 19 21:04:10 thstring20200619162314 bash[1480]: Oneshot service - start
Jun 19 21:05:10 thstring20200619162314 bash[1480]: Oneshot service - end
Jun 19 21:05:10 thstring20200619162314 bash[1604]: Oneshot service - stop
Jun 19 21:05:10 thstring20200619162314 systemd[1]: oneshot-execstop.service: Succeeded.
Jun 19 21:05:10 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop.

Теперь видно, что ExecStop запускается сразу когду ExecStart выполнился, так как сервис перешел в состояние inactive(dead). Теперь взглянем что случится с установленным RemainAfterExit:set:

oneshot-execstop-remainafterexit.service

[Unit]
Description=Oneshot service test with ExecStop and RemainAfterExit

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"
ExecStop=/bin/bash -c "echo Oneshot service - stop"

Вывод systemctl будет таков:

● oneshot-execstop-remainafterexit.service - Oneshot service test with ExecStop and RemainAfterExit
   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit.service; static; vendor preset: enabled)
   Active: active (exited) since Fri 2020-06-19 21:07:54 UTC; 8s ago
  Process: 1708 ExecStart=/bin/bash -c echo Oneshot service - start && sleep 60 && echo Oneshot service - end (code=exited, status=0/SUCCESS)
 Main PID: 1708 (code=exited, status=0/SUCCESS)

Jun 19 21:06:54 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:06:54 thstring20200619162314 bash[1708]: Oneshot service - start
Jun 19 21:07:54 thstring20200619162314 bash[1708]: Oneshot service - end
Jun 19 21:07:54 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

Так как сервис до сих пор активен(даже не смотря на то завершился его ExecStart), ExecStop до сих пор не запущен. Теперь если вы запустите systemctl stop oneshot-execstop-remainafterexit.service, посмотрим на вывод:

● oneshot-execstop-remainafterexit.service - Oneshot service test with ExecStop and RemainAfterExit
   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit.service; static; vendor preset: enabled)
   Active: inactive (dead)

Jun 19 21:06:54 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:06:54 thstring20200619162314 bash[1708]: Oneshot service - start
Jun 19 21:07:54 thstring20200619162314 bash[1708]: Oneshot service - end
Jun 19 21:07:54 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.
Jun 19 21:08:58 thstring20200619162314 systemd[1]: Stopping Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:08:58 thstring20200619162314 bash[1900]: Oneshot service - stop
Jun 19 21:08:58 thstring20200619162314 systemd[1]: oneshot-execstop-remainafterexit.service: Succeeded.
Jun 19 21:08:58 thstring20200619162314 systemd[1]: Stopped Oneshot service test with ExecStop and RemainAfterExit.

Теперь видно, что ExecStop запущен так как сервис теперь неактивен. Это все, конечно, интересно, но systemctl не часто останавливает сервис. Вопрос, когда это будет полезно? Смотрим ниже...

Запуск сервиса при выключении

Создавая oneshot сервис c ExecStop и RemainAfterExit, это лучший способ для того, чтобы запустить что-то при включении. Посмотрим как выглядит на практике:

oneshot-execstop-remainafterexit-install.service

[Unit]
Description=Oneshot service test with ExecStop and RemainAfterExit

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"
ExecStop=/bin/bash -c "echo Oneshot service - stop"

[Install]
WantedBy=multi-user.target

Затем запускаем systemctl enable чтобы включить сервис. Если мы запустим сервис, или перезагрузимся, то увидим:

● oneshot-execstop-remainafterexit-install.service - Oneshot service test with ExecStop and RemainAfterExit
   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit-install.service; enabled; vendor preset: enabled)
   Active: active (exited) since Fri 2020-06-19 21:14:02 UTC; 5s ago
 Main PID: 366 (code=exited, status=0/SUCCESS)
    Tasks: 0 (limit: 4087)
   Memory: 0B
   CGroup: /system.slice/oneshot-execstop-remainafterexit-install.service

Jun 19 21:13:02 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:13:02 thstring20200619162314 bash[366]: Oneshot service - start
Jun 19 21:14:02 thstring20200619162314 bash[366]: Oneshot service - end
Jun 19 21:14:02 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

Как указано выше, наш ExecStop не запущен. Теперь перезапускаемся и сморим на логи:

-- Logs begin at Fri 2020-06-19 21:14:50 UTC, end at Fri 2020-06-19 21:18:47 UTC. --
Jun 19 21:14:51 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:14:51 thstring20200619162314 bash[337]: Oneshot service - start
Jun 19 21:15:51 thstring20200619162314 bash[337]: Oneshot service - end
Jun 19 21:15:51 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.
Jun 19 21:17:48 thstring20200619162314 systemd[1]: Stopping Oneshot service test with ExecStop and RemainAfterExit...
Jun 19 21:17:48 thstring20200619162314 bash[681]: Oneshot service - stop
Jun 19 21:17:49 thstring20200619162314 systemd[1]: oneshot-execstop-remainafterexit-install.service: Succeeded.
Jun 19 21:17:49 thstring20200619162314 systemd[1]: Stopped Oneshot service test with ExecStop and RemainAfterExit.

Что будет если, машина была выключена в 9 часов вечера, и это приведет к остановке сервиса, который заставит выключиться машину чуть позже из-за запуска команды из ExecStop. Это довольно простой способ для запуска чего-то во время выключения(например процесса очистки). А что еще лучше, это то что у вас нет ExecStart с oneshot сервисом. Дальше больше.

Множественные ExecStarts

Простой сервис, может только иметь один Execstart директиву. Но oneshot сервис может иметь один или больше, или вообще не иметь ExecStart. Если у вас нет ExecStart, тогда необходимо обязательно указать ExecStop(так же указать RemainAfterExit). Это будет сервис который запускается при выключении, и ни в какое другое время. Он напоминает oneshot-execstop-remainaftgerexit-install.service но с удаленным ExecStart.

Как сказано выше, oneshot сервис может иметь множество ExecStarts. Выглядить это буде так:

oneshot-multiple-execstart.service

[Unit]
Description=Oneshot service test with multiple ExecStart

[Service]
Type=oneshot
ExecStart=/bin/bash -c "echo First"
ExecStart=/bin/bash -c "echo Second"
ExecStart=/bin/bash -c "echo Third"

Как ожидали, лог будет следующим:

-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:33:16 UTC. --
Jun 22 13:33:02 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart...
Jun 22 13:33:02 thstring20200622092223 bash[1316]: First
Jun 22 13:33:02 thstring20200622092223 bash[1317]: Second
Jun 22 13:33:02 thstring20200622092223 bash[1318]: Third
Jun 22 13:33:02 thstring20200622092223 systemd[1]: oneshot-multiple-execstart.service: Succeeded.
Jun 22 13:33:02 thstring20200622092223 systemd[1]: Started Oneshot service test with multiple ExecStart.

Объединим цепочку в Execstart действия, позволит нам создать мощный рабочий процесс прям внутри systemd единицы. Но что будет, если упадет один из ExecStarts?

oneshot-multiple-execstart-failure.service

[Unit]
Description=Oneshot service test with multiple ExecStart and failure

[Service]
Type=oneshot
ExecStart=/bin/bash -c "echo First"
ExecStart=/bin/bash -c "false && echo Second"
ExecStart=/bin/bash -c "echo Third"

Пытаясь запустить этот сервис, мы получим следующую ошибку:

$ sudo systemctl start oneshot-multiple-execstart-failure.service
Job for oneshot-multiple-execstart-failure.service failed because the control process exited with error code.
See "systemctl status oneshot-multiple-execstart-failure.service" and "journalctl -xe" for details.

$ sudo journalctl -u oneshot-multiple-execstart-failure.service
-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:37:16 UTC. --
Jun 22 13:36:53 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart and failure...
Jun 22 13:36:53 thstring20200622092223 bash[1441]: First
Jun 22 13:36:53 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure.service: Main process exited, code=exited, status=1/FAILURE
Jun 22 13:36:53 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure.service: Failed with result 'exit-code'.
Jun 22 13:36:53 thstring20200622092223 systemd[1]: Failed to start Oneshot service test with multiple ExecStart and failure.

Сервис падает и прерывает выполнение. Но, что если вы не хотите чтобы падение остановило сервис на середине? Мы можете добавить - символ перед выполнением команды

oneshot-multiple-execstart-failure-success.service

[Unit]
Description=Oneshot service test with multiple ExecStart and failure

[Service]
Type=oneshot
ExecStart=/bin/bash -c "echo First"
ExecStart=-/bin/bash -c "false && echo Second"
ExecStart=/bin/bash -c "echo Third"

Это не очевидно, но отметим, во втором Exectart что перед /bin/bash стоит -. Теперь посмотрим на вывод:

-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:39:04 UTC. --
Jun 22 13:38:59 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart and failure...
Jun 22 13:38:59 thstring20200622092223 bash[1553]: First
Jun 22 13:38:59 thstring20200622092223 bash[1555]: Third
Jun 22 13:38:59 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure-success.service: Succeeded.
Jun 22 13:38:59 thstring20200622092223 systemd[1]: Started Oneshot service test with multiple ExecStart and failure.

Второй ExecStart упал как и ожидали, но это не уронило в целом сервис или остановило выполнение третей стадии.

Podman systemd

podman generate systemd --new --files --name pod-name
systemctl --user enable container-pod-name.service
systemctl --user  daemon-reload
systemctl --user start container-pod-name.service
systemctl --user status container-pod-name.service