https://access.redhat.com/solutions/3116611
SOLUTION 已验证 - 已更新 2017年八月2日11:42 -
环境
- Red Hat Enterprise Linux 7
- systemd
问题
- Deciding what dependencies systemd service unit definition should have can be a reasonably complex issue and it can be difficult to decide what they should be. This solution article tries to explain some of the issues.
决议
Background:
Unlike RHEL 6 where startup and shutdown scripts were executed sequentially according to start/stop symlinks on RHEL 7, many services can start in parallel according to dependencies they have explicitly defined.
That raises the important idea that with accurate dependency information defined in one of:
After=
Before=
WantedBy=
Your services may not start in the way that you expect (the use of other dependency keywords such as Requisite= BindsTo= Conflicts= PartOf= Wants= Requires=
etc are not covered here please read the system.unit manual page or 9.6. Creating and Modifying systemd Unit Files in System Administration guide for additional information).
Detailed information:
After=/Before=
Definitions that are defined After= has the inverse meaning of Before=
when giving a unit as After=
there is an automatic assumption of equalence for the other unit. That is if a.service
contains After=b.service
the effect should be the same as if b.service
defined Before=a.service
.
That means that a.service
cannot start until b.service
has finished. These dependencies are applied in an inverse way during shutdown. That is a.service
must be stopped (if it is running) before b.service
can be stopped.
If both a.service
and b.service
instead both defined just Before=multi-user.target
, they would both start and must complete before multi-user.target
could be reached - however they may be started concurrently because they would no longer have any explicit dependency on each other.
It is important to understand that After=/Before=
make no distinction between a service failing or starting successfully. The only meaning After=/Before=
convey is about the order for stopping or starting a service. A discussion of other configuration that would prevent certain units from starting if dependencies fail is outside the scope of this discussion (some of the keywords mentioned above help with starting services only if something else started successfully).
WantedBy=
WantedBy=
may be used more than once, or a space-separated list of unit names may be given. A symbolic link is created in the .wants/
directory of each of the listed units when this unit is installed by systemctl enable
command. This has the effect that a dependency of type Wants=
is added from the listed unit to the current unit. The primary result is that the current unit will be started when the listed unit is started.
Unless you have very specific requirements to start a service very early in the boot you should always put the following in the [Install]
section of the service definition:
WantedBy=multi-user.target
For example, if a unit file of a.service
has that option and a.service
is enabled, multi-user.target
will gain dependencies Wants=a.service
and After=a.serivce
. It means that option, WantedBy=multi-user.target
, will ensure that a unit file which contains that opiton gets started (if not disabled or masked) before multi-user.target
is reached.
Specific recommendations:
-
Keep your ordering as simple as possible. If you have 3 services and they need to be started sequentially then create them as follows:
a.service (Before=b.service) b.service (After=a.service and Before=c.service) c.service (After=b.service)
Adding
Before=/After=
as appropriate keeps the service unit definitions self contained and looking at one you know what its order will be. For example, inb.service
you know it would be delayed from starting until aftera.service
andc.service
will be delayed from starting untilb.service
has started. AgainAfter=
(during startup) provide ordering only they do not have any effect on if something will start if something it needs to have started does not do so.
Let's use following examples to understand the dependency. These service unit files exist under/etc/systemd/system
directory.-
a.service
[Unit] Description=Test "a" Service Before=b.service [Service] ExecStart=/bin/true Type=oneshot [Install] WantedBy=multi-user.target
-
b.service
[Unit] Description=Test "b" Service After=a.service Before=c.service [Service] ExecStart=/bin/true Type=oneshot [Install] WantedBy=multi-user.target
-
c.service
[Unit] Description=Test "c" Service After=b.service [Service] ExecStart=/bin/true Type=oneshot [Install] WantedBy=multi-user.target
Obviously these services do nothing and are simply used to illustrate the following commands. Now we can see what needs to be started before b.service can start and what gets started after:
# systemctl list-dependencies --after b.service b.service ├─a.service ├─system.slice ├─systemd-journald.socket └─basic.target ├─rhel-import-state.service ├─systemd-ask-password-plymouth.path ├─paths.target │ ├─brandbot.path │ ├─systemd-ask-password-console.path │ └─systemd-ask-password-wall.path ├─slices.target
The
--after
option tosystemctl list-dependencies
shows what is listed as a dependency on the service given to the command. That is what has anBefore=
dependency on b.service - it does not show what will be started afterb.service
. We do expect to seea.service
listed because it has an explicitBefore=
on b.service. The other dependencies are automatic ones.Conversely with the
--before
option we can see what has aAfter=
dependency onb.service
.# systemctl list-dependencies --before b.service b.service ├─c.service ├─multi-user.target │ ├─systemd-readahead-done.service │ ├─systemd-readahead-done.timer │ ├─systemd-update-utmp-runlevel.service │ └─graphical.target │ └─systemd-update-utmp-runlevel.service └─shutdown.target ├─systemd-reboot.service └─final.target └─systemd-reboot.service
This command can help you understand the ordering of services in systemd. If a service did not have a dependency on another service it would be started in parallel at before the appropriate target was reached as defined by either,
Before= After= or WantedBy=
. Note that theshutdown.target
is added automatically as the service listed must be stopped beforeshutdown.target
can be reached. -
-
Using
After=network-online.target
How do I ensure that my service won't start until after networking is up and available? You need an
After=network-online.target
- See Running Services After the Network is up for additional information. In a lot of cases this should not be required unless your startup script requires active networking to be available (waiting onnetwork-online.target
may cause the boot to take longer). The URL provide gives information on how to create network services that can dynamically adapt to networking changes and do not require a specific interface to be up or a specific IP address to be available to use (at the bottom).Note that during shutdown there is no
network-online.target
. To be called for the shutdown before networking is torn down during a shutdown you must also addnetwork.target
to theAfter=
definition. That assumes that the service needs networking to be up in order for it to be able to shutdown.
Specific issues created by ordering choices:
-
Using
After=something.target
(where something would be replaced by a valid target name). An example some commands may not function reliably until aftersysinit.target
has been reached. An example is the rpm command which may work reliably until aftersysinit.target
has been reached (see The rpm command may core dump with SIGBUS during and after system startup. for more information). Inman 7 bootup
, the overview of bootup sequence in systemd is described as below (Please be noticed that the quote loses formatting information in the man page. You should refer the original one on the machine with the man command also). For more details of each special target in the overview below, you should refer toman 7 systemd.special
.SYSTEM MANAGER BOOTUP At boot, the system manager on the OS image is responsible for initializing the required file systems, services and drivers that are necessary for operation of the system. On systemd(1) systems, this process is split up in various discrete steps which are exposed as target units. (See systemd.target(5) for detailed information about target units.) The boot-up process is highly parallelized so that the order in which specific target units are reached is not deterministic, but still adheres to a limited amount of ordering structure. When systemd starts up the system, it will activate all units that are dependencies of default.target (as well as recursively all dependencies of these dependencies). Usually, default.target is simply an alias of graphical.target or multi-user.target, depending on whether the system is configured for a graphical UI or only for a text console. To enforce minimal ordering between the units pulled in, a number of well-known target units are available, as listed on systemd.special(7). The following chart is a structural overview of these well-known units and their position in the boot-up logic. The arrows describe which units are pulled in and ordered before which other units. Units near the top are started before units nearer to the bottom of the chart. local-fs-pre.target | v (various mounts and (various swap (various cryptsetup fsck services...) devices...) devices...) (various low-level (various low-level | | | services: udevd, API VFS mounts: v v v tmpfiles, random mqueue, configfs, local-fs.target swap.target cryptsetup.target seed, sysctl, ...) debugfs, ...) | | | | | \__________________|_________________ | ___________________|____________________/ \|/ v sysinit.target | ____________________________________/|\________________________________________ / | | | \ | | | | | v v | v v (various (various | (various rescue.service timers...) paths...) | sockets...) | | | | | v v v | v rescue.target timers.target paths.target | sockets.target | | | | v \_________________ | ___________________/ \|/ v basic.target | ____________________________________/| emergency.service / | | | | | | v v v v emergency.target display- (various system (various system manager.service services services) | required for | | graphical UIs) v | | multi-user.target | | | \_________________ | _________________/ \|/ v graphical.target Target units that are commonly used as boot targets are emphasized. These units are good choices as goal targets, for example by passing them to the systemd.unit= kernel command line option (see systemd(1)) or by symlinking default.target to them. timers.target is pulled-in by basic.target asynchronously. This allows timers units to depend on services which become only available later in boot.