问题起源

systemd中使用脚本配置一个自有java程序的自启动时,发现一个自定义的环境变量无法在systemd中读取,在shell中手动启动就可以正常工作。

上网查了一下systemd读取环境变量的相关知识,最终选择在脚本中source了对应的环境变量文件,解决了问题。

systemd读取环境变量的机制和几种方法整理罗列如下,以作备忘。

systemd 读取环境变量的机制

根据systemd/User中的说明如下:

Environment variables

The user instance of systemd does not inherit any of the environment variables set in places like .bashrc etc. There are several ways to set environment variables for the systemd user instance:

  • For users with a $HOME directory, create a .conf file in the ~/.config/environment.d/ directory with lines of the form NAME=VAL. Affects only that user’s user unit. See environment.d(5) for more information.
  • Use the DefaultEnvironment option in /etc/systemd/user.conf file. Affects all user units.
  • Add a drop-in configuration file in /etc/systemd/system/user@.service.d/. Affects all user units; see #Service example
  • At any time, use systemctl --user set-environment or systemctl --user import-environment. Affects all user units started after setting the environment variables, but not the units that were already running.
  • Using the dbus-update-activation-environment --systemd --all command provided by dbus. Has the same effect as systemctl --user import-environment, but also affects the D-Bus session. You can add this to the end of your shell initialization file.
  • For “global” environment variables for the user environment you can use the environment.d directories which are parsed by some generators. See environment.d(5) and systemd.generator(7) for more information.
  • You can also write a systemd.environment-generator(7) script which can produce environment variables that vary from user to user, this is probably the best way if you need per-user environments (this is the case for XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, etc).

One variable you may want to set is PATH.

After configuration, the command systemctl --user show-environment can be used to verify that the values are correct.

搜刮网上的一些说法后,总结如下:

  • /etc/profile或者/etc/security/limit.d这些文件中配置的环境变量仅对通过pam登录的用户生效,而systemd是不读这些配置的,所以这就造成登录到终端时查看环境变量和手动启动应用都一切正常,但是systemd无法正常启动应用
  • 如果需要给systemd配置默认参数,全局的配置在/etc/systemd/system.conf/etc/systemd/user.conf中。同时还会加载两个配置文件对应的目录中所有的.conf配置文件/etc/systemd/system.conf.d/*.conf/etc/systemd/user.conf.d/*.conf,一般的服务单元使用system.conf即可。加载优先级system.conf最低,所以system.conf.d目录中的配置会覆盖system.conf的配置
  • 目前已知的是更改system.conf配置,需要重启系统才能生效,还没找到如何重新加载此配置

虚拟机实验

实验目的: 看通过systemd运行的程序可以读取到哪些环境变量。

实验方法: 配置同一个脚本,脚本输出env的内容,比较shell下和systemd下启动该脚本的输出内容的区别。

试验文件准备

创建环境变量文件/etc/profile.d/env.sh,在其中设置环境变量MY_ENV="helloworld"

1
2
3
# cat /etc/profile.d/env.sh
export MY_ENV="helloworld"
#

创建service使用的脚本/usr/local/bin/systemctl_env_test.sh

1
2
3
4
5
#!/bin/bash
LOG_FILE="/tmp/systemctl_env_test.log"
echo "========================================================" > $LOG_FILE
env | sort >> $LOG_FILE
echo "========================================================" >> $LOG_FILE
1
2
3
4
5
6
7
8
#!/bin/bash
LOG_FILE="/tmp/systemctl_env_test.log"
echo "Start...." > $LOG_FILE
echo "======= Before load /etc/profile.d/env.sh" >> $LOG_FILE
env >> $LOG_FILE
source /etc/profile.d/env.sh >> $LOG_FILE
echo "======= After load /etc/profile.d/env.sh" >> $LOG_FILE
env >> $LOG_FILE

添加权限sudo chmod a+x /usr/local/bin/systemctl_env_test.sh

创建system service文件,/usr/lib/systemd/system/systemctl_env_test.service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=systemctl_env_test service
After=syslog.target network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/systemctl_env_test.sh
PrivateTmp=false
[Install]
WantedBy=multi-user.target

导入service: systemctl daemon-reload

启动service命令: systemctl start systemctl_env_test.service

执行输出

当在shell下直接运行/usr/local/bin/systemctl_env_test.sh时,日志文件systemctl_env_test.log的输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# bash /usr/local/bin/systemctl_env_test.sh
#
# cat /tmp/systemctl_env_test.log
========================================================
HISTCONTROL=ignoredups
HISTSIZE=1000
HOME=/root
HOSTNAME=jk-server
LANG=zh_CN.UTF-8
LESSOPEN=||/usr/bin/lesspipe.sh %s
LOGNAME=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
MY_ENV=helloworld
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
PWD=/root
SELINUX_LEVEL_REQUESTED=
SELINUX_ROLE_REQUESTED=
SELINUX_USE_CURRENT_RANGE=
SHELL=/bin/bash
SHLVL=2
SSH_CLIENT=192.168.187.1 54467 22
SSH_CONNECTION=192.168.187.1 54467 192.168.187.81 22
SSH_TTY=/dev/pts/0
TERM=xterm-color
USER=root
_=/usr/bin/env
XDG_RUNTIME_DIR=/run/user/0
XDG_SESSION_ID=77
========================================================
#

可以看到/etc/profile.d/env.sh中设置的MY_ENV=helloworld能够被脚本/usr/local/bin/systemctl_env_test.sh读取到。

使用systemctl运行systemctl_env_test.service来执行脚本/usr/local/bin/systemctl_env_test.sh时,输出如下:

1
2
3
4
5
6
7
8
9
10
11
# systemctl start systemctl_env_test.service
#
# cat /tmp/systemctl_env_test.log
========================================================
LANG=zh_CN.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
PWD=/
SHLVL=1
_=/usr/bin/env
========================================================
#

可以看到除了/etc/profile.d/env.sh中设置的MY_ENV=helloworld没有被读取外,连基础的HOSTNAME,SHELL等环境变量都没有。

结论

  • 可以看到shell中运行脚本可以读取到全部正常的环境变量

  • systemd中运行脚本,只读取到最最基础的LANG,PATHPWD几个环境变量, /etc/profile下的环境变量并没有读取。

systemd添加使用环境变量的方法

使用Environment设置环境变量

可以在systemdservice中使用Environment来设置环境变量。

修改原文件/usr/lib/systemd/system/systemctl_env_test.service,使用Environment关键字来添加如下环境变量:

1
2
3
Environment="One=1" "Three=3"
Environment="Two=2"
Environment="Four=4"

此时/usr/lib/systemd/system/systemctl_env_test.service变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=systemctl_env_test service
After=syslog.target network.target
[Service]
Type=forking
Environment="One=1" "Three=3"
Environment="Two=2"
Environment="Four=4"
ExecStart=/usr/local/bin/systemctl_env_test.sh
PrivateTmp=false
[Install]
WantedBy=multi-user.target

执行systemctl start后查看脚本env的输出日志,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# systemctl start systemctl_env_test.service && cat /tmp/systemctl_env_test.log
========================================================
Four=4
LANG=zh_CN.UTF-8
One=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
PWD=/
SHLVL=1
Three=3
Two=2
_=/usr/bin/env
========================================================
#

可以看出systemdservice文件中设置的环境变量,可以被ExecStart指定的脚本读取到。

使用EnvironmentFile导入环境变量文件

使用Environment导入少数固定的环境变量是可行的,但是如果需要导入大量的,或者时常要变动的环境变量,那么使用EnvironmentFile 关键字通过导入文件的方式会更合适。

试验方法如下:

创建测试用环境变量文件/usr/local/etc/environment_file_test/load.conf, 内容如下:

1
2
MY_ENV="helloworld"
GLOBAL_ENV="nihaoshijie"

修改原文件/usr/lib/systemd/system/systemctl_env_test.service,使用EnvironmentFile关键字来导入文件/usr/local/etc/environment_file_test/load.conf

1
EnvironmentFile=/usr/local/etc/environment_file_test/load.conf

此时/usr/lib/systemd/system/systemctl_env_test.service变为:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=systemctl_env_test service
After=syslog.target network.target
[Service]
Type=forking
EnvironmentFile=/usr/local/etc/environment_file_test/load.conf
ExecStart=/usr/local/bin/systemctl_env_test.sh
PrivateTmp=false
[Install]
WantedBy=multi-user.target

执行systemctl start后查看脚本env的输出日志,结果如下:

1
2
3
4
5
6
7
8
9
10
11
# systemctl start systemctl_env_test.service && cat /tmp/systemctl_env_test.log
========================================================
GLOBAL_ENV=nihaoshijie
LANG=zh_CN.UTF-8
MY_ENV=helloworld
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
PWD=/
SHLVL=1
_=/usr/bin/env
========================================================
#

可以看出systemdservice文件中设置的环境变量,可以被ExecStart指定的脚本读取到。

注意/usr/local/etc/environment_file_test/load.conf文件是配置文件,就是key=value的格式,和/etc/profile.d/中导入的shell文件是不同的,需要加以区别。所以load.conf配置文件中,不能使用export xx=yy的格式

在执行命令或脚本中设置或者source环境变量文件

除了在systemdservice文件中指定EnvironmentEnvironmentFile外,还有一个方法就是在ExecStartExecStop等执行的脚本中直接指定环境变量或者source环境变量文件。

在上面的几个例子中,也可以不在/usr/lib/systemd/system/systemctl_env_test.service文件中使用EnvironmentEnvironmentFile,而是直接在执行的shell脚本/usr/local/bin/systemctl_env_test.shsource /etc/profile.d/environment_file_test.sh

试验方法如下:

修改ExecStart执行的shell脚本/usr/local/bin/systemctl_env_test.sh, 在里面添加source /etc/profile.d/env.sh , 添加后的文件内容为:

1
2
3
4
5
6
#!/bin/bash
LOG_FILE="/tmp/systemctl_env_test.log"
echo "========================================================" > $LOG_FILE
source /etc/profile.d/env.sh
env | sort >> $LOG_FILE
echo "========================================================" >> $LOG_FILE

其中/etc/profile.d/env.sh文件的内容如下:

1
export MY_ENV="helloworld"

还原文件/usr/lib/systemd/system/systemctl_env_test.service去除EnvironmentEnvironmentFile的设置,变为:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=systemctl_env_test service
After=syslog.target network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/systemctl_env_test.sh
PrivateTmp=false
[Install]
WantedBy=multi-user.target

执行systemctl start后查看脚本env的输出日志,结果如下:

1
2
3
4
5
6
7
8
9
10
# systemctl start systemctl_env_test.service && cat /tmp/systemctl_env_test.log
========================================================
LANG=zh_CN.UTF-8
MY_ENV=helloworld
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
PWD=/
SHLVL=1
_=/usr/bin/env
========================================================
#

可以看出也能加载出MY_ENV这个环境变量。

注意这边是shell脚本中source /etc/profile.d/env.sh, 所以文件中需要加上export,父shell才能读取对应的内容。

Reference

留言