SpringBoot服务无法读取系统变量,我重新认识了profile和bashrc

Posted by Night Field's Blog on March 7, 2020

背景

CentOS服务器上,我们用Systemd部署了一个SpringBoot服务。关于如何部署,可以参考这篇文章。这个SpringBoot服务会用ProcessBuilder去调用机器上一个C++的可执行文件。

问题描述

SpringBoot程序跑得很正常,但是我们发现C++程序却没有log输出,也就是说它从没被执行过。 查看了ProcessBuilder的返回值,是127127的意思是系统找不到对应的命令。于是我们把C++程序对应的目录(里面包括第三方动态链接库)添加到了LD_LIBRARY_PATH。怎么添加的呢?我们在~/.bash_profile脚本中添加了命令:

1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/cppFoler/

经过测试,SpringBoot服务依然无法成功调用C++程序,依然返回127。但奇怪的是,当我们直接使用java -jar命令,并且用nohup的方式令SpringBoot程序跑在后台,可以正常调用C++程序:

1
nohup java -jar mySpringBoot.jar

问题分析

这里就不得不说Linuxnohupservice的区别了。

  • nohup: 在linux中,我们通过终端(shell)登陆机器,会有一个进程与shell关联。当我们退出的时候(如Ctrl+C),会向该进程发送一个SIGHUP信号,进程收到信号便会自动被销毁。nohup命令的作用,是将shell中产生的输出(stdout)和错误(stderr)重定向到文件nohup.out中。当我们退出shell的时候,nohup会忽略SIGHUP信号,让相关联的程序继续运行。由此看出,nohup进程是一次性的,其本质就是让程序在原先的shell进程中继续运行
  • service: 服务是一种后台守护进程,不与任何终端(shell)关联。它是系统的一部分,可以被servicesystemctl命令方便的管理。service的具体的启动过程可以参考这篇文章

回到我们的问题,nohup的方式可以工作,而service不行,说明前面设置的LD_LIBRARY_PATH参数只对nohup生效,对service无效。这让我去重新学习了CentOS~/.bash_profile脚本,以及相关的/etc/profile~/.bashrc/etc/bashrc

  • /etc/profile: 脚本设置了一些系统级别的环境变量,它会在有用户通过终端登陆的时候被执行,做一些初始化操作。初始化时,它会去/etc/profile.d/目录下,去逐个执行.sh文件。所以,与其直接修改/etc/profile文件,不如将自定义脚本放在/etc/profile.d/下面。 该脚本只会在交互式登陆终端(interactive - login shell)启动的时候运行,也就是它只在用户登陆的时候运行一次。以下摘自文件的注释

    System wide environment and startup programs, for login setup. Functions and aliases go in /etc/bashrc. It’s NOT a good idea to change this file unless you know what you are doing. It’s much better to create a custom.sh shell script in /etc/profile.d/ to make custom changes to your environment, as this will prevent the need for merging in future updates.

  • /etc/bashrc: 脚本和/etc/profile类似。区别是,/etc/bashrc更侧重的是系统级别的别名(alias)。而且,该脚本只会在交互式非登陆终端(interactive - non-login shell)启动的时候运行,即在每次新打开一个shell的时候,都会执行一遍。以下摘自文件的注释

    System wide functions and aliases, Environment stuff goes in /etc/profile.

  • ~/.bash_profile: 作用和/etc/profile一样,只不过作用范围只是当前用户
  • ~/.bashrc: 作用和/etc/bashrc一样,只不过作用范围只是当前用户

到这里,问题已经明朗了,我们在~/.bash_profile中设置的环境变量,只对shell所对应的进程生效,而对后台的service无效。当用nohup方式执行程序的时候,因为还是在shell对应的线程中,所以可以正常读到设置的环境变量。

问题解决

解决办法是,给service独立设置环境变量。这里有两种方式。

1. Systemd Environment

Systemd Service的配置文件中,显示指定Environment参数,来设置service运行时的环境变量,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=My SpringBoot Service
After=syslog.target

[Service]
User=nightfield
ExecStart=/path/to/mySpringBoot.jar
SuccessExitStatus=143
Restart=always
RestartSec=5
Environment="LD_LIBRARY_PATH=/path/to/cppFoler/"

[Install]
WantedBy=multi-user.target

这样,service运行的时候,就可以正确读到对应的变量。

2. ProcessBuild中指定environment()

ProcessBuild可以通过environment()方法,设置process运行的环境参数:

1
2
3
4
ProcessBuilder pb = new ProcessBuilder("java -version");
Map<String, String> env = pb.environment();
env.put("LD_LIBRARY_PATH", "/path/to/cppFoler/");
Process p = pb.start();

总结

通过这个问题,更深刻地学习了nohupservice的区别,profilebashrc的适用场景。